From 9986b12ce7daea408a75add0bde0aee7695b85cd Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 18:21:47 -0400 Subject: [PATCH 01/13] feat: Add Oracle Cloud Infrastructure (OCI) Generative AI client support Adds OciClient (V1 API) and OciClientV2 (V2 API) for the OCI Generative AI service, following the BedrockClient pattern with httpx event hooks. Authentication: config file, custom profiles, session tokens, direct credentials, instance principal, resource principal. API coverage: embed (all models), chat with streaming (OciClient for Command R family, OciClientV2 for Command A). Lazy-loads oci SDK as an optional dependency; install with `pip install cohere[oci]`. --- .fernignore | 1 + pyproject.toml | 4 + src/cohere/__init__.py | 4 + .../manually_maintained/lazy_oci_deps.py | 30 + src/cohere/manually_maintained/streaming.py | 15 + src/cohere/oci_client.py | 1157 +++++++++++++++++ 6 files changed, 1211 insertions(+) create mode 100644 src/cohere/manually_maintained/lazy_oci_deps.py create mode 100644 src/cohere/manually_maintained/streaming.py create mode 100644 src/cohere/oci_client.py diff --git a/.fernignore b/.fernignore index 16abecd79..8fcc59fed 100644 --- a/.fernignore +++ b/.fernignore @@ -15,6 +15,7 @@ src/cohere/manually_maintained/__init__.py src/cohere/bedrock_client.py src/cohere/aws_client.py src/cohere/sagemaker_client.py +src/cohere/oci_client.py src/cohere/client_v2.py mypy.ini src/cohere/aliases.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8abcedd7f..a55466399 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,10 @@ requests = "^2.0.0" tokenizers = ">=0.15,<1" types-requests = "^2.0.0" typing_extensions = ">= 4.0.0" +oci = { version = "^2.165.0", optional = true } + +[tool.poetry.extras] +oci = ["oci"] [tool.poetry.group.dev.dependencies] mypy = "==1.13.0" diff --git a/src/cohere/__init__.py b/src/cohere/__init__.py index fbadbfd07..6048798d1 100644 --- a/src/cohere/__init__.py +++ b/src/cohere/__init__.py @@ -518,6 +518,8 @@ "NotFoundError": ".errors", "NotImplementedError": ".errors", "OAuthAuthorizeResponse": ".types", + "OciClient": ".oci_client", + "OciClientV2": ".oci_client", "ParseInfo": ".types", "RerankDocument": ".types", "RerankRequestDocumentsItem": ".types", @@ -852,6 +854,8 @@ def __dir__(): "NotFoundError", "NotImplementedError", "OAuthAuthorizeResponse", + "OciClient", + "OciClientV2", "ParseInfo", "RerankDocument", "RerankRequestDocumentsItem", diff --git a/src/cohere/manually_maintained/lazy_oci_deps.py b/src/cohere/manually_maintained/lazy_oci_deps.py new file mode 100644 index 000000000..072d028b8 --- /dev/null +++ b/src/cohere/manually_maintained/lazy_oci_deps.py @@ -0,0 +1,30 @@ +"""Lazy loading for optional OCI SDK dependency.""" + +from typing import Any + +OCI_INSTALLATION_MESSAGE = """ +The OCI SDK is required to use OciClient or OciClientV2. + +Install it with: + pip install oci + +Or with the optional dependency group: + pip install cohere[oci] +""" + + +def lazy_oci() -> Any: + """ + Lazily import the OCI SDK. + + Returns: + The oci module + + Raises: + ImportError: If the OCI SDK is not installed + """ + try: + import oci + return oci + except ImportError: + raise ImportError(OCI_INSTALLATION_MESSAGE) diff --git a/src/cohere/manually_maintained/streaming.py b/src/cohere/manually_maintained/streaming.py new file mode 100644 index 000000000..88e513a34 --- /dev/null +++ b/src/cohere/manually_maintained/streaming.py @@ -0,0 +1,15 @@ +import typing + +from httpx import SyncByteStream + + +class Streamer(SyncByteStream): + """Wrap an iterator of bytes for httpx streaming responses.""" + + lines: typing.Iterator[bytes] + + def __init__(self, lines: typing.Iterator[bytes]): + self.lines = lines + + def __iter__(self) -> typing.Iterator[bytes]: + return self.lines diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py new file mode 100644 index 000000000..4f98e473a --- /dev/null +++ b/src/cohere/oci_client.py @@ -0,0 +1,1157 @@ +"""Oracle Cloud Infrastructure (OCI) client for Cohere API.""" + +import configparser +import email.utils +import json +import os +import typing +import uuid + +import httpx +import requests +from .client import Client, ClientEnvironment +from .client_v2 import ClientV2 +from .manually_maintained.lazy_oci_deps import lazy_oci +from .manually_maintained.streaming import Streamer +from httpx import URL, ByteStream + + +class OciClient(Client): + """ + Cohere V1 API client for Oracle Cloud Infrastructure (OCI) Generative AI service. + + Use this client for V1 API models (Command R family) and embeddings. + For V2 API models (Command A family), use OciClientV2 instead. + + Supported APIs on OCI: + - embed(): Full support for all embedding models + - chat(): Full support with Command-R models + - chat_stream(): Streaming chat support + + Supports all authentication methods: + - Config file (default): Uses ~/.oci/config + - Session-based: Uses OCI CLI session tokens + - Direct credentials: Pass OCI credentials directly + - Instance principal: For OCI compute instances + - Resource principal: For OCI functions + + Example: + ```python + import cohere + + client = cohere.OciClient( + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", + ) + + response = client.chat( + model="command-r-08-2024", + message="Hello!", + ) + print(response.text) + ``` + """ + + def __init__( + self, + *, + oci_config_path: typing.Optional[str] = None, + oci_profile: typing.Optional[str] = None, + oci_user_id: typing.Optional[str] = None, + oci_fingerprint: typing.Optional[str] = None, + oci_tenancy_id: typing.Optional[str] = None, + oci_private_key_path: typing.Optional[str] = None, + oci_private_key_content: typing.Optional[str] = None, + auth_type: typing.Literal["api_key", "instance_principal", "resource_principal"] = "api_key", + oci_region: typing.Optional[str] = None, + oci_compartment_id: str, + timeout: typing.Optional[float] = None, + ): + oci_config = _load_oci_config( + auth_type=auth_type, + config_path=oci_config_path, + profile=oci_profile, + user_id=oci_user_id, + fingerprint=oci_fingerprint, + tenancy_id=oci_tenancy_id, + private_key_path=oci_private_key_path, + private_key_content=oci_private_key_content, + ) + + if oci_region is None: + oci_region = oci_config.get("region") + if oci_region is None: + raise ValueError("oci_region must be provided either directly or in OCI config file") + + Client.__init__( + self, + base_url="https://api.cohere.com", + environment=ClientEnvironment.PRODUCTION, + client_name="n/a", + timeout=timeout, + api_key="n/a", + httpx_client=httpx.Client( + event_hooks=get_event_hooks( + oci_config=oci_config, + oci_region=oci_region, + oci_compartment_id=oci_compartment_id, + is_v2_client=False, + ), + timeout=timeout, + ), + ) + + +class OciClientV2(ClientV2): + """ + Cohere V2 API client for Oracle Cloud Infrastructure (OCI) Generative AI service. + + Supported APIs on OCI: + - embed(): Full support for all embedding models (returns embeddings as dict) + - chat(): Full support with Command-A models (command-a-03-2025) + - chat_stream(): Streaming chat with proper V2 event format + + Note: rerank() requires fine-tuned models deployed to dedicated endpoints. + OCI on-demand inference does not support the rerank API. + + Supports all authentication methods: + - Config file (default): Uses ~/.oci/config + - Session-based: Uses OCI CLI session tokens + - Direct credentials: Pass OCI credentials directly + - Instance principal: For OCI compute instances + - Resource principal: For OCI functions + + Example using config file: + ```python + import cohere + + client = cohere.OciClientV2( + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", + ) + + response = client.embed( + model="embed-english-v3.0", + texts=["Hello world"], + input_type="search_document", + ) + print(response.embeddings.float_) + + response = client.chat( + model="command-a-03-2025", + messages=[{"role": "user", "content": "Hello!"}], + ) + print(response.message) + ``` + + Example using direct credentials: + ```python + client = cohere.OciClientV2( + oci_user_id="ocid1.user.oc1...", + oci_fingerprint="xx:xx:xx:...", + oci_tenancy_id="ocid1.tenancy.oc1...", + oci_private_key_path="~/.oci/key.pem", + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", + ) + ``` + + Example using instance principal: + ```python + client = cohere.OciClientV2( + auth_type="instance_principal", + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", + ) + ``` + """ + + def __init__( + self, + *, + # Authentication - Config file (default) + oci_config_path: typing.Optional[str] = None, + oci_profile: typing.Optional[str] = None, + # Authentication - Direct credentials + oci_user_id: typing.Optional[str] = None, + oci_fingerprint: typing.Optional[str] = None, + oci_tenancy_id: typing.Optional[str] = None, + oci_private_key_path: typing.Optional[str] = None, + oci_private_key_content: typing.Optional[str] = None, + # Authentication - Instance principal + auth_type: typing.Literal["api_key", "instance_principal", "resource_principal"] = "api_key", + # Required for OCI Generative AI + oci_region: typing.Optional[str] = None, + oci_compartment_id: str, + # Standard parameters + timeout: typing.Optional[float] = None, + ): + # Load OCI config based on auth_type + oci_config = _load_oci_config( + auth_type=auth_type, + config_path=oci_config_path, + profile=oci_profile, + user_id=oci_user_id, + fingerprint=oci_fingerprint, + tenancy_id=oci_tenancy_id, + private_key_path=oci_private_key_path, + private_key_content=oci_private_key_content, + ) + + # Get region from config if not provided + if oci_region is None: + oci_region = oci_config.get("region") + if oci_region is None: + raise ValueError("oci_region must be provided either directly or in OCI config file") + + # Create httpx client with OCI event hooks + ClientV2.__init__( + self, + base_url="https://api.cohere.com", # Unused, OCI URL set in hooks + environment=ClientEnvironment.PRODUCTION, + client_name="n/a", + timeout=timeout, + api_key="n/a", + httpx_client=httpx.Client( + event_hooks=get_event_hooks( + oci_config=oci_config, + oci_region=oci_region, + oci_compartment_id=oci_compartment_id, + is_v2_client=True, + ), + timeout=timeout, + ), + ) + + +EventHook = typing.Callable[..., typing.Any] + + +def _load_oci_config( + auth_type: str, + config_path: typing.Optional[str], + profile: typing.Optional[str], + **kwargs: typing.Any, +) -> typing.Dict[str, typing.Any]: + """ + Load OCI configuration based on authentication type. + + Args: + auth_type: Authentication method (api_key, instance_principal, resource_principal) + config_path: Path to OCI config file (for api_key auth) + profile: Profile name in config file (for api_key auth) + **kwargs: Direct credentials (user_id, fingerprint, etc.) + + Returns: + Dictionary containing OCI configuration + """ + oci = lazy_oci() + + if auth_type == "instance_principal": + signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner() + return {"signer": signer, "auth_type": "instance_principal"} + + elif auth_type == "resource_principal": + signer = oci.auth.signers.get_resource_principals_signer() + return {"signer": signer, "auth_type": "resource_principal"} + + elif kwargs.get("user_id"): + # Direct credentials provided - validate required fields + required_fields = ["fingerprint", "tenancy_id"] + missing = [f for f in required_fields if not kwargs.get(f)] + if missing: + raise ValueError( + f"When providing oci_user_id, you must also provide: {', '.join('oci_' + f for f in missing)}" + ) + if not kwargs.get("private_key_path") and not kwargs.get("private_key_content"): + raise ValueError( + "When providing oci_user_id, you must also provide either " + "oci_private_key_path or oci_private_key_content" + ) + config = { + "user": kwargs["user_id"], + "fingerprint": kwargs["fingerprint"], + "tenancy": kwargs["tenancy_id"], + } + if kwargs.get("private_key_path"): + config["key_file"] = kwargs["private_key_path"] + if kwargs.get("private_key_content"): + config["key_content"] = kwargs["private_key_content"] + return config + + else: + # Load from config file + oci_config = oci.config.from_file( + file_location=config_path or "~/.oci/config", profile_name=profile or "DEFAULT" + ) + _remove_inherited_session_auth(oci_config, config_path=config_path, profile=profile) + return oci_config + + +def _remove_inherited_session_auth( + oci_config: typing.Dict[str, typing.Any], + *, + config_path: typing.Optional[str], + profile: typing.Optional[str], +) -> None: + """Drop session auth fields inherited from the OCI config DEFAULT section.""" + profile_name = profile or "DEFAULT" + if profile_name == "DEFAULT" or "security_token_file" not in oci_config: + return + + config_file = os.path.expanduser(config_path or "~/.oci/config") + parser = configparser.ConfigParser(interpolation=None) + if not parser.read(config_file): + return + + if not parser.has_section(profile_name): + oci_config.pop("security_token_file", None) + return + + explicit_security_token = False + current_section: typing.Optional[str] = None + with open(config_file, encoding="utf-8") as handle: + for raw_line in handle: + line = raw_line.strip() + if not line or line.startswith(("#", ";")): + continue + if line.startswith("[") and line.endswith("]"): + current_section = line[1:-1].strip() + continue + if current_section == profile_name and line.split("=", 1)[0].strip() == "security_token_file": + explicit_security_token = True + break + + if not explicit_security_token: + oci_config.pop("security_token_file", None) + + +def _usage_from_oci(usage_data: typing.Optional[typing.Dict[str, typing.Any]]) -> typing.Dict[str, typing.Any]: + usage_data = usage_data or {} + input_tokens = usage_data.get("inputTokens", 0) + output_tokens = usage_data.get("completionTokens", usage_data.get("outputTokens", 0)) + + return { + "tokens": { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + }, + "billed_units": { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + } + } + + +def get_event_hooks( + oci_config: typing.Dict[str, typing.Any], + oci_region: str, + oci_compartment_id: str, + is_v2_client: bool = False, +) -> typing.Dict[str, typing.List[EventHook]]: + """ + Create httpx event hooks for OCI request/response transformation. + + Args: + oci_config: OCI configuration dictionary + oci_region: OCI region (e.g., "us-chicago-1") + oci_compartment_id: OCI compartment OCID + is_v2_client: Whether this is for OciClientV2 (True) or OciClient (False) + + Returns: + Dictionary of event hooks for httpx + """ + return { + "request": [ + map_request_to_oci( + oci_config=oci_config, + oci_region=oci_region, + oci_compartment_id=oci_compartment_id, + is_v2_client=is_v2_client, + ), + ], + "response": [map_response_from_oci()], + } + + +def map_request_to_oci( + oci_config: typing.Dict[str, typing.Any], + oci_region: str, + oci_compartment_id: str, + is_v2_client: bool = False, +) -> EventHook: + """ + Create event hook that transforms Cohere requests to OCI format and signs them. + + Args: + oci_config: OCI configuration dictionary + oci_region: OCI region + oci_compartment_id: OCI compartment OCID + is_v2_client: Whether this is for OciClientV2 (True) or OciClient (False) + + Returns: + Event hook function for httpx + """ + oci = lazy_oci() + + # Create OCI signer based on config type + # Priority order: instance/resource principal > session-based auth > API key auth + if "signer" in oci_config: + signer = oci_config["signer"] # Instance/resource principal + elif "security_token_file" in oci_config: + # Session-based authentication with security token (fallback if no user field) + token_file_path = os.path.expanduser(oci_config["security_token_file"]) + with open(token_file_path, "r") as f: + security_token = f.read().strip() + + # Load private key using OCI's utility function + key_file = oci_config.get("key_file") + if not key_file: + raise ValueError( + "OCI config profile is missing 'key_file'. " + "Session-based auth requires a key_file entry in your OCI config profile." + ) + private_key = oci.signer.load_private_key_from_file(os.path.expanduser(key_file)) + + signer = oci.auth.signers.SecurityTokenSigner( + token=security_token, + private_key=private_key, + ) + elif "user" in oci_config: + signer = oci.signer.Signer( + tenancy=oci_config["tenancy"], + user=oci_config["user"], + fingerprint=oci_config["fingerprint"], + private_key_file_location=oci_config.get("key_file"), + private_key_content=oci_config.get("key_content"), + ) + else: + # Config doesn't have user or security token - unsupported + raise ValueError( + "OCI config is missing 'user' field and no security_token_file found. " + "Please use a profile with standard API key authentication, " + "session-based authentication, or provide direct credentials via oci_user_id parameter." + ) + + def _event_hook(request: httpx.Request) -> None: + # Extract Cohere API details + path_parts = request.url.path.split("/") + endpoint = path_parts[-1] + body = json.loads(request.read()) + + # Build OCI URL + url = get_oci_url( + region=oci_region, + endpoint=endpoint, + ) + + # Transform request body to OCI format + oci_body = transform_request_to_oci( + endpoint=endpoint, + cohere_body=body, + compartment_id=oci_compartment_id, + is_v2=is_v2_client, + ) + + # Prepare request for signing + oci_body_bytes = json.dumps(oci_body).encode("utf-8") + + # Build headers for signing + headers = { + "content-type": "application/json", + "date": email.utils.formatdate(usegmt=True), + } + + # Create a requests.PreparedRequest for OCI signing + oci_request = requests.Request( + method=request.method, + url=url, + headers=headers, + data=oci_body_bytes, + ) + prepped_request = oci_request.prepare() + + # Sign the request using OCI signer (modifies headers in place) + signer.do_request_sign(prepped_request) + + # Update httpx request with signed headers + request.url = URL(url) + request.headers = httpx.Headers(prepped_request.headers) + request.stream = ByteStream(oci_body_bytes) + request._content = oci_body_bytes + request.extensions["endpoint"] = endpoint + request.extensions["is_stream"] = "stream" in endpoint or body.get("stream", False) + request.extensions["is_v2"] = is_v2_client + + return _event_hook + + +def map_response_from_oci() -> EventHook: + """ + Create event hook that transforms OCI responses to Cohere format. + + Returns: + Event hook function for httpx + """ + + def _hook(response: httpx.Response) -> None: + endpoint = response.request.extensions["endpoint"] + is_stream = response.request.extensions.get("is_stream", False) + is_v2 = response.request.extensions.get("is_v2", False) + + output: typing.Iterator[bytes] + + # Only transform successful responses (200-299) + # Let error responses pass through unchanged so SDK error handling works + if not (200 <= response.status_code < 300): + return + + # For streaming responses, wrap the stream with a transformer + if is_stream: + original_stream = response.stream + transformed_stream = transform_oci_stream_wrapper(original_stream, endpoint, is_v2) + response.stream = Streamer(transformed_stream) + # Reset consumption flags + if hasattr(response, "_content"): + del response._content + response.is_stream_consumed = False + response.is_closed = False + return + + # Handle non-streaming responses + oci_response = json.loads(response.read()) + cohere_response = transform_oci_response_to_cohere(endpoint, oci_response, is_v2) + output = iter([json.dumps(cohere_response).encode("utf-8")]) + + response.stream = Streamer(output) + + # Reset response for re-reading + if hasattr(response, "_content"): + del response._content + response.is_stream_consumed = False + response.is_closed = False + + return _hook + + +def get_oci_url( + region: str, + endpoint: str, +) -> str: + """ + Map Cohere endpoints to OCI Generative AI endpoints. + + Args: + region: OCI region (e.g., "us-chicago-1") + endpoint: Cohere endpoint name + Returns: + Full OCI Generative AI endpoint URL + """ + base = f"https://inference.generativeai.{region}.oci.oraclecloud.com" + api_version = "20231130" + + # Map Cohere endpoints to OCI actions + action_map = { + "embed": "embedText", + "chat": "chat", + "chat_stream": "chat", + } + + action = action_map.get(endpoint) + if action is None: + raise ValueError( + f"Endpoint '{endpoint}' is not supported by OCI Generative AI. " + f"Supported endpoints: {list(action_map.keys())}" + ) + return f"{base}/{api_version}/actions/{action}" + + +def normalize_model_for_oci(model: str) -> str: + """ + Normalize model name for OCI. + + OCI accepts model names in the format "cohere.model-name" or full OCIDs. + This function ensures proper formatting for all regions. + + Args: + model: Model name (e.g., "command-r-08-2024") or full OCID + + Returns: + Normalized model identifier (e.g., "cohere.command-r-08-2024" or OCID) + + Examples: + >>> normalize_model_for_oci("command-a-03-2025") + "cohere.command-a-03-2025" + >>> normalize_model_for_oci("cohere.embed-english-v3.0") + "cohere.embed-english-v3.0" + >>> normalize_model_for_oci("ocid1.generativeaimodel.oc1...") + "ocid1.generativeaimodel.oc1..." + """ + if not model: + raise ValueError("OCI requests require a non-empty model name") + + # If it's already an OCID, return as-is (works across all regions) + if model.startswith("ocid1."): + return model + + # Add "cohere." prefix if not present + if not model.startswith("cohere."): + return f"cohere.{model}" + + return model + + +def transform_request_to_oci( + endpoint: str, + cohere_body: typing.Dict[str, typing.Any], + compartment_id: str, + is_v2: bool = False, +) -> typing.Dict[str, typing.Any]: + """ + Transform Cohere request body to OCI format. + + Args: + endpoint: Cohere endpoint name + cohere_body: Original Cohere request body + compartment_id: OCI compartment OCID + is_v2: Whether this request comes from OciClientV2 (True) or OciClient (False) + + Returns: + Transformed request body in OCI format + """ + model = normalize_model_for_oci(cohere_body.get("model")) + + if endpoint == "embed": + if "texts" in cohere_body: + inputs = cohere_body["texts"] + elif "inputs" in cohere_body: + inputs = cohere_body["inputs"] + elif "images" in cohere_body: + raise ValueError("OCI embed does not support the top-level 'images' parameter; use 'inputs' instead") + else: + raise ValueError("OCI embed requires either 'texts' or 'inputs'") + + oci_body = { + "inputs": inputs, + "servingMode": { + "servingType": "ON_DEMAND", + "modelId": model, + }, + "compartmentId": compartment_id, + } + + # Add optional fields only if provided + if "input_type" in cohere_body: + oci_body["inputType"] = cohere_body["input_type"].upper() + + if "truncate" in cohere_body: + oci_body["truncate"] = cohere_body["truncate"].upper() + + if "embedding_types" in cohere_body: + oci_body["embeddingTypes"] = [et.upper() for et in cohere_body["embedding_types"]] + if "max_tokens" in cohere_body: + oci_body["maxTokens"] = cohere_body["max_tokens"] + if "output_dimension" in cohere_body: + oci_body["outputDimension"] = cohere_body["output_dimension"] + if "priority" in cohere_body: + oci_body["priority"] = cohere_body["priority"] + + return oci_body + + elif endpoint in ["chat", "chat_stream"]: + # Validate that the request body matches the client type + has_messages = "messages" in cohere_body + has_message = "message" in cohere_body + if is_v2 and not has_messages: + raise ValueError( + "OciClientV2 requires the V2 API format ('messages' array). " + "Got a V1-style request with 'message' string. " + "Use OciClient for V1 models like Command R, " + "or switch to the V2 messages format." + ) + if not is_v2 and has_messages and not has_message: + raise ValueError( + "OciClient uses the V1 API format (single 'message' string). " + "Got a V2-style request with 'messages' array. " + "Use OciClientV2 for V2 models like Command A." + ) + + chat_request: typing.Dict[str, typing.Any] = { + "apiFormat": "COHEREV2" if is_v2 else "COHERE", + } + + if is_v2: + # V2: Transform Cohere V2 messages to OCI V2 format + # Cohere sends: [{"role": "user", "content": "text"}] + # OCI expects: [{"role": "USER", "content": [{"type": "TEXT", "text": "..."}]}] + oci_messages = [] + for msg in cohere_body["messages"]: + oci_msg: typing.Dict[str, typing.Any] = { + "role": msg["role"].upper(), + } + + # Transform content + if isinstance(msg.get("content"), str): + oci_msg["content"] = [{"type": "TEXT", "text": msg["content"]}] + elif isinstance(msg.get("content"), list): + transformed_content = [] + for item in msg["content"]: + if isinstance(item, dict) and "type" in item: + transformed_item = item.copy() + transformed_item["type"] = item["type"].upper() + transformed_content.append(transformed_item) + else: + transformed_content.append(item) + oci_msg["content"] = transformed_content + else: + oci_msg["content"] = msg.get("content", []) + + if "tool_calls" in msg: + oci_msg["toolCalls"] = msg["tool_calls"] + if "tool_call_id" in msg: + oci_msg["toolCallId"] = msg["tool_call_id"] + if "tool_plan" in msg: + oci_msg["toolPlan"] = msg["tool_plan"] + + oci_messages.append(oci_msg) + + chat_request["messages"] = oci_messages + + # V2 optional parameters + if "max_tokens" in cohere_body: + chat_request["maxTokens"] = cohere_body["max_tokens"] + if "temperature" in cohere_body: + chat_request["temperature"] = cohere_body["temperature"] + if "k" in cohere_body: + chat_request["topK"] = cohere_body["k"] + if "p" in cohere_body: + chat_request["topP"] = cohere_body["p"] + if "seed" in cohere_body: + chat_request["seed"] = cohere_body["seed"] + if "frequency_penalty" in cohere_body: + chat_request["frequencyPenalty"] = cohere_body["frequency_penalty"] + if "presence_penalty" in cohere_body: + chat_request["presencePenalty"] = cohere_body["presence_penalty"] + if "stop_sequences" in cohere_body: + chat_request["stopSequences"] = cohere_body["stop_sequences"] + if "tools" in cohere_body: + chat_request["tools"] = cohere_body["tools"] + if "strict_tools" in cohere_body: + chat_request["strictTools"] = cohere_body["strict_tools"] + if "documents" in cohere_body: + chat_request["documents"] = cohere_body["documents"] + if "citation_options" in cohere_body: + chat_request["citationOptions"] = cohere_body["citation_options"] + if "response_format" in cohere_body: + chat_request["responseFormat"] = cohere_body["response_format"] + if "safety_mode" in cohere_body: + chat_request["safetyMode"] = cohere_body["safety_mode"] + if "logprobs" in cohere_body: + chat_request["logprobs"] = cohere_body["logprobs"] + if "tool_choice" in cohere_body: + chat_request["toolChoice"] = cohere_body["tool_choice"] + if "priority" in cohere_body: + chat_request["priority"] = cohere_body["priority"] + # Thinking parameter for Command A Reasoning models + if "thinking" in cohere_body and cohere_body["thinking"] is not None: + thinking = cohere_body["thinking"] + oci_thinking: typing.Dict[str, typing.Any] = {} + if "type" in thinking: + oci_thinking["type"] = thinking["type"].upper() + if "token_budget" in thinking and thinking["token_budget"] is not None: + oci_thinking["tokenBudget"] = thinking["token_budget"] + if oci_thinking: + chat_request["thinking"] = oci_thinking + else: + # V1: single message string + chat_request["message"] = cohere_body["message"] + + if "temperature" in cohere_body: + chat_request["temperature"] = cohere_body["temperature"] + if "max_tokens" in cohere_body: + chat_request["maxTokens"] = cohere_body["max_tokens"] + if "k" in cohere_body: + chat_request["topK"] = cohere_body["k"] + if "p" in cohere_body: + chat_request["topP"] = cohere_body["p"] + if "seed" in cohere_body: + chat_request["seed"] = cohere_body["seed"] + if "stop_sequences" in cohere_body: + chat_request["stopSequences"] = cohere_body["stop_sequences"] + if "frequency_penalty" in cohere_body: + chat_request["frequencyPenalty"] = cohere_body["frequency_penalty"] + if "presence_penalty" in cohere_body: + chat_request["presencePenalty"] = cohere_body["presence_penalty"] + if "preamble" in cohere_body: + chat_request["preambleOverride"] = cohere_body["preamble"] + if "chat_history" in cohere_body: + chat_request["chatHistory"] = cohere_body["chat_history"] + if "documents" in cohere_body: + chat_request["documents"] = cohere_body["documents"] + if "tools" in cohere_body: + chat_request["tools"] = cohere_body["tools"] + if "tool_results" in cohere_body: + chat_request["toolResults"] = cohere_body["tool_results"] + if "response_format" in cohere_body: + chat_request["responseFormat"] = cohere_body["response_format"] + if "safety_mode" in cohere_body: + chat_request["safetyMode"] = cohere_body["safety_mode"] + if "priority" in cohere_body: + chat_request["priority"] = cohere_body["priority"] + + # Handle streaming for both versions + if "stream" in endpoint or cohere_body.get("stream"): + chat_request["isStream"] = True + + # Top level OCI request structure + oci_body = { + "servingMode": { + "servingType": "ON_DEMAND", + "modelId": model, + }, + "compartmentId": compartment_id, + "chatRequest": chat_request, + } + + return oci_body + + return cohere_body + + +def transform_oci_response_to_cohere( + endpoint: str, oci_response: typing.Dict[str, typing.Any], is_v2: bool = False, +) -> typing.Dict[str, typing.Any]: + """ + Transform OCI response to Cohere format. + + Args: + endpoint: Cohere endpoint name + oci_response: OCI response body + is_v2: Whether this is a V2 API response + + Returns: + Transformed response in Cohere format + """ + if endpoint == "embed": + embeddings_data = oci_response.get("embeddings", {}) + + if isinstance(embeddings_data, dict): + normalized_embeddings = {str(key).lower(): value for key, value in embeddings_data.items()} + else: + normalized_embeddings = {"float": embeddings_data} + + if is_v2: + embeddings = normalized_embeddings + else: + embeddings = normalized_embeddings.get("float", []) + + meta = { + "api_version": {"version": "1"}, + } + usage = _usage_from_oci(oci_response.get("usage")) + if "tokens" in usage: + meta["tokens"] = usage["tokens"] + if "billed_units" in usage: + meta["billed_units"] = usage["billed_units"] + + return { + "id": oci_response.get("id", str(uuid.uuid4())), + "embeddings": embeddings, + "texts": [], + "meta": meta, + } + + elif endpoint == "chat" or endpoint == "chat_stream": + chat_response = oci_response.get("chatResponse", {}) + + if is_v2: + usage = _usage_from_oci(chat_response.get("usage")) + message = chat_response.get("message", {}) + + if "role" in message: + message = {**message, "role": message["role"].lower()} + + if "content" in message and isinstance(message["content"], list): + transformed_content = [] + for item in message["content"]: + if isinstance(item, dict): + transformed_item = item.copy() + if "type" in transformed_item: + transformed_item["type"] = transformed_item["type"].lower() + transformed_content.append(transformed_item) + else: + transformed_content.append(item) + message = {**message, "content": transformed_content} + + if "toolCalls" in message: + tool_calls = message["toolCalls"] + message = {k: v for k, v in message.items() if k != "toolCalls"} + message["tool_calls"] = tool_calls + if "toolPlan" in message: + tool_plan = message["toolPlan"] + message = {k: v for k, v in message.items() if k != "toolPlan"} + message["tool_plan"] = tool_plan + + return { + "id": chat_response.get("id", str(uuid.uuid4())), + "message": message, + "finish_reason": chat_response.get("finishReason", "COMPLETE"), + "usage": usage, + } + + # V1 response + meta = { + "api_version": {"version": "1"}, + } + usage = _usage_from_oci(chat_response.get("usage")) + if "tokens" in usage: + meta["tokens"] = usage["tokens"] + if "billed_units" in usage: + meta["billed_units"] = usage["billed_units"] + + return { + "text": chat_response.get("text", ""), + "generation_id": str(uuid.uuid4()), + "chat_history": chat_response.get("chatHistory", []), + "finish_reason": chat_response.get("finishReason", "COMPLETE"), + "citations": chat_response.get("citations", []), + "documents": chat_response.get("documents", []), + "search_queries": chat_response.get("searchQueries", []), + "meta": meta, + } + + return oci_response + + +def transform_oci_stream_wrapper( + stream: typing.Iterator[bytes], endpoint: str, is_v2: bool = False, +) -> typing.Iterator[bytes]: + """ + Wrap OCI stream and transform events to Cohere format. + + Args: + stream: Original OCI stream iterator + endpoint: Cohere endpoint name + is_v2: Whether this is a V2 API stream + + Yields: + Bytes of transformed streaming events + """ + generation_id = str(uuid.uuid4()) + emitted_start = False + emitted_content_end = False + current_content_type: typing.Optional[str] = None + current_content_index = 0 + final_finish_reason = "COMPLETE" + final_usage: typing.Optional[typing.Dict[str, typing.Any]] = None + full_v1_text = "" + final_v1_finish_reason = "COMPLETE" + buffer = b"" + + def _emit_v2_event(event: typing.Dict[str, typing.Any]) -> bytes: + return b"data: " + json.dumps(event).encode("utf-8") + b"\n\n" + + def _emit_v1_event(event: typing.Dict[str, typing.Any]) -> bytes: + return json.dumps(event).encode("utf-8") + b"\n" + + def _current_content_type(oci_event: typing.Dict[str, typing.Any]) -> str: + message = oci_event.get("message") + if isinstance(message, dict): + content_list = message.get("content") + if content_list and isinstance(content_list, list) and len(content_list) > 0: + oci_type = content_list[0].get("type", "TEXT").upper() + if oci_type == "THINKING": + return "thinking" + return "text" + + def _transform_v2_event(oci_event: typing.Dict[str, typing.Any]) -> typing.Iterator[bytes]: + nonlocal emitted_start, emitted_content_end, current_content_type, current_content_index + nonlocal final_finish_reason, final_usage + + event_content_type = _current_content_type(oci_event) + + if not emitted_start: + yield _emit_v2_event( + { + "type": "message-start", + "id": generation_id, + "delta": {"message": {"role": "assistant"}}, + } + ) + yield _emit_v2_event( + { + "type": "content-start", + "index": current_content_index, + "delta": {"message": {"content": {"type": event_content_type}}}, + } + ) + emitted_start = True + current_content_type = event_content_type + elif current_content_type != event_content_type: + yield _emit_v2_event({"type": "content-end", "index": current_content_index}) + current_content_index += 1 + yield _emit_v2_event( + { + "type": "content-start", + "index": current_content_index, + "delta": {"message": {"content": {"type": event_content_type}}}, + } + ) + current_content_type = event_content_type + emitted_content_end = False + + for cohere_event in typing.cast( + typing.List[typing.Dict[str, typing.Any]], transform_stream_event(endpoint, oci_event, is_v2=True) + ): + if "index" in cohere_event: + cohere_event = {**cohere_event, "index": current_content_index} + if cohere_event["type"] == "content-end": + emitted_content_end = True + final_finish_reason = oci_event.get("finishReason", final_finish_reason) + final_usage = _usage_from_oci(oci_event.get("usage")) + yield _emit_v2_event(cohere_event) + + def _transform_v1_event(oci_event: typing.Dict[str, typing.Any]) -> bytes: + nonlocal full_v1_text, final_v1_finish_reason + event = transform_stream_event(endpoint, oci_event, is_v2=False) + if isinstance(event, dict): + if event.get("event_type") == "text-generation" and event.get("text"): + full_v1_text += typing.cast(str, event["text"]) + if "finishReason" in oci_event: + final_v1_finish_reason = oci_event.get("finishReason", final_v1_finish_reason) + return _emit_v1_event(event) + return b"" + + def _process_line(line: str) -> typing.Iterator[bytes]: + if not line.startswith("data: "): + return + + data_str = line[6:] + if data_str.strip() == "[DONE]": + if is_v2: + if emitted_start: + if not emitted_content_end: + yield _emit_v2_event({"type": "content-end", "index": current_content_index}) + message_end_event: typing.Dict[str, typing.Any] = { + "type": "message-end", + "id": generation_id, + "delta": {"finish_reason": final_finish_reason}, + } + if final_usage: + message_end_event["delta"]["usage"] = final_usage + yield _emit_v2_event(message_end_event) + else: + yield _emit_v1_event( + { + "event_type": "stream-end", + "finish_reason": final_v1_finish_reason, + "response": { + "text": full_v1_text, + "generation_id": generation_id, + "finish_reason": final_v1_finish_reason, + }, + } + ) + return + + try: + oci_event = json.loads(data_str) + except json.JSONDecodeError: + return + + try: + if is_v2: + for event_bytes in _transform_v2_event(oci_event): + yield event_bytes + else: + yield _transform_v1_event(oci_event) + except Exception as exc: + raise RuntimeError(f"OCI stream event transformation failed for endpoint '{endpoint}': {exc}") from exc + + for chunk in stream: + buffer += chunk + while b"\n" in buffer: + line_bytes, buffer = buffer.split(b"\n", 1) + line = line_bytes.decode("utf-8").strip() + for event_bytes in _process_line(line): + yield event_bytes + + if buffer.strip(): + line = buffer.decode("utf-8").strip() + for event_bytes in _process_line(line): + yield event_bytes + + +def transform_stream_event( + endpoint: str, oci_event: typing.Dict[str, typing.Any], is_v2: bool = False, +) -> typing.Union[typing.Dict[str, typing.Any], typing.List[typing.Dict[str, typing.Any]]]: + """ + Transform individual OCI stream event to Cohere format. + + Args: + endpoint: Cohere endpoint name + oci_event: OCI stream event + is_v2: Whether this is a V2 API stream + + Returns: + V2: List of transformed events. V1: Single transformed event dict. + """ + if endpoint in ["chat_stream", "chat"]: + if is_v2: + content_type = "text" + content_value = "" + message = oci_event.get("message") + + if "message" in oci_event and not isinstance(message, dict): + raise TypeError("OCI V2 stream event message must be an object") + + if isinstance(message, dict) and "content" in message: + content_list = message["content"] + if content_list and isinstance(content_list, list) and len(content_list) > 0: + first_content = content_list[0] + oci_type = first_content.get("type", "TEXT").upper() + if oci_type == "THINKING": + content_type = "thinking" + content_value = first_content.get("thinking", "") + else: + content_type = "text" + content_value = first_content.get("text", "") + + events: typing.List[typing.Dict[str, typing.Any]] = [] + if content_value: + delta_content: typing.Dict[str, typing.Any] = {} + if content_type == "thinking": + delta_content["thinking"] = content_value + else: + delta_content["text"] = content_value + + events.append( + { + "type": "content-delta", + "index": 0, + "delta": { + "message": { + "content": delta_content, + } + }, + } + ) + + if "finishReason" in oci_event: + events.append( + { + "type": "content-end", + "index": 0, + } + ) + + return events + + # V1 stream event + return { + "event_type": "text-generation", + "text": oci_event.get("text", ""), + "is_finished": oci_event.get("isFinished", False), + } + + return [] if is_v2 else {} From 636e76e4e10442424f4efc2fcdb92f1ebecc8890 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 18:21:51 -0400 Subject: [PATCH 02/13] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20remove=20stale=20model=20names=20and=20fix=20test?= =?UTF-8?q?=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: remove specific model names from Supported APIs and Model Availability sections (per mkozakov review — will go out of date) - tests: default OCI_PROFILE to DEFAULT instead of API_KEY_AUTH --- README.md | 105 ++++ tests/test_oci_client.py | 1031 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1136 insertions(+) create mode 100644 tests/test_oci_client.py diff --git a/README.md b/README.md index c474bb632..7df774b8f 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,111 @@ for event in response: print(event.delta.message.content.text, end="") ``` +## Oracle Cloud Infrastructure (OCI) + +The SDK supports Oracle Cloud Infrastructure (OCI) Generative AI service. First, install the OCI SDK: + +``` +pip install 'cohere[oci]' +``` + +Then use the `OciClient` or `OciClientV2`: + +```Python +import cohere + +# Using OCI config file authentication (default: ~/.oci/config) +co = cohere.OciClient( + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", +) + +response = co.embed( + model="embed-english-v3.0", + texts=["Hello world"], + input_type="search_document", +) + +print(response.embeddings) +``` + +### OCI Authentication Methods + +**1. Config File (Default)** +```Python +co = cohere.OciClient( + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", + # Uses ~/.oci/config with DEFAULT profile +) +``` + +**2. Custom Profile** +```Python +co = cohere.OciClient( + oci_profile="MY_PROFILE", + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", +) +``` + +**3. Session-based Authentication (Security Token)** +```Python +# Works with OCI CLI session tokens +co = cohere.OciClient( + oci_profile="MY_SESSION_PROFILE", # Profile with security_token_file + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", +) +``` + +**4. Direct Credentials** +```Python +co = cohere.OciClient( + oci_user_id="ocid1.user.oc1...", + oci_fingerprint="xx:xx:xx:...", + oci_tenancy_id="ocid1.tenancy.oc1...", + oci_private_key_path="~/.oci/key.pem", + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", +) +``` + +**5. Instance Principal (for OCI Compute instances)** +```Python +co = cohere.OciClient( + auth_type="instance_principal", + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1...", +) +``` + +### Supported OCI APIs + +The OCI client supports the following Cohere APIs: +- **Embed**: Full support for all embedding models +- **Chat**: Full support with both V1 (`OciClient`) and V2 (`OciClientV2`) APIs + - Streaming available via `chat_stream()` + - Supports Command-R and Command-A model families + +### OCI Model Availability and Limitations + +**Available on OCI On-Demand Inference:** +- ✅ **Embed models**: available on OCI Generative AI +- ✅ **Chat models**: available via `OciClient` (V1) and `OciClientV2` (V2) + +**Not Available on OCI On-Demand Inference:** +- ❌ **Generate API**: OCI TEXT_GENERATION models are base models that require fine-tuning before deployment +- ❌ **Rerank API**: OCI TEXT_RERANK models are base models that require fine-tuning before deployment +- ❌ **Multiple Embedding Types**: OCI on-demand models only support single embedding type per request (cannot request both `float` and `int8` simultaneously) + +**Note**: To use Generate or Rerank models on OCI, you need to: +1. Fine-tune the base model using OCI's fine-tuning service +2. Deploy the fine-tuned model to a dedicated endpoint +3. Update your code to use the deployed model endpoint + +For the latest model availability, see the [OCI Generative AI documentation](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm). + ## Contributing While we value open-source contributions to this SDK, the code is generated programmatically. Additions made directly would have to be moved over to our generation code, otherwise they would be overwritten upon the next generated release. Feel free to open a PR as a proof of concept, but know that we will not be able to merge it as-is. We suggest opening an issue first to discuss with us! diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py new file mode 100644 index 000000000..7798921c9 --- /dev/null +++ b/tests/test_oci_client.py @@ -0,0 +1,1031 @@ +"""Integration and unit tests for OCI Generative AI client. + +All integration tests are validated against the live OCI Generative AI inference +layer (us-chicago-1). The OciClientV2 uses the V2 Cohere API format (COHEREV2) +and communicates with the OCI inference endpoint at: + https://inference.generativeai.{region}.oci.oraclecloud.com + +Integration test coverage: + + V1 API (OciClient — Command R family): + Test Model What it proves + ------------------------------- -------------------------- ------------------------------------------ + test_embed embed-english-v3.0 V1 embed returns 2x 1024-dim float vectors + test_chat command-r-08-2024 V1 chat returns text with COHERE apiFormat + test_chat_stream command-r-08-2024 V1 streaming with text-generation events + + V2 API (OciClientV2 — Command A family): + Test Model What it proves + ------------------------------- -------------------------- ------------------------------------------ + test_embed_v2 embed-english-v3.0 V2 embed returns dict with float_ key + test_embed_with_model_prefix_v2 cohere.embed-english-v3.0 Model normalization works + test_chat_v2 command-a-03-2025 V2 chat returns message with COHEREV2 format + test_chat_stream_v2 command-a-03-2025 V2 SSE streaming with content-delta events + test_command_a_chat command-a-03-2025 Command A chat via V2 + + Cross-cutting: + Test Model What it proves + ------------------------------- -------------------------- ------------------------------------------ + test_config_file_auth embed-english-v3.0 API key auth from config file + test_custom_profile_auth embed-english-v3.0 Custom OCI profile auth + test_embed_english_v3 embed-english-v3.0 1024-dim embeddings + test_embed_multilingual_v3 embed-multilingual-v3.0 Multilingual model works + test_invalid_model invalid-model-name Error handling works + test_missing_compartment_id -- Raises TypeError + +Requirements: +1. OCI SDK installed: pip install oci +2. OCI credentials configured in ~/.oci/config +3. TEST_OCI environment variable set to run +4. OCI_COMPARTMENT_ID environment variable with valid OCI compartment OCID +5. OCI_REGION environment variable (optional, defaults to us-chicago-1) + +Run with: + TEST_OCI=1 OCI_COMPARTMENT_ID=ocid1.compartment.oc1... pytest tests/test_oci_client.py +""" + +import os +import sys +import tempfile +import types +import unittest +from unittest.mock import MagicMock, mock_open, patch + +import cohere + +if "tokenizers" not in sys.modules: + tokenizers_stub = types.ModuleType("tokenizers") + tokenizers_stub.Tokenizer = object + sys.modules["tokenizers"] = tokenizers_stub + +if "fastavro" not in sys.modules: + fastavro_stub = types.ModuleType("fastavro") + fastavro_stub.parse_schema = lambda schema: schema + fastavro_stub.reader = lambda *args, **kwargs: iter(()) + fastavro_stub.writer = lambda *args, **kwargs: None + sys.modules["fastavro"] = fastavro_stub + +if "httpx_sse" not in sys.modules: + httpx_sse_stub = types.ModuleType("httpx_sse") + httpx_sse_stub.connect_sse = lambda *args, **kwargs: None + sys.modules["httpx_sse"] = httpx_sse_stub + + +@unittest.skipIf(os.getenv("TEST_OCI") is None, "TEST_OCI not set") +class TestOciClient(unittest.TestCase): + """Test OciClient (V1 API) with OCI Generative AI.""" + + def setUp(self): + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + region = os.getenv("OCI_REGION", "us-chicago-1") + profile = os.getenv("OCI_PROFILE", "DEFAULT") + + self.client = cohere.OciClient( + oci_region=region, + oci_compartment_id=compartment_id, + oci_profile=profile, + ) + + def test_embed(self): + """Test embedding with V1 client.""" + response = self.client.embed( + model="embed-english-v3.0", + texts=["Hello world", "Cohere on OCI"], + input_type="search_document", + ) + self.assertIsNotNone(response) + self.assertIsNotNone(response.embeddings) + self.assertEqual(len(response.embeddings), 2) + self.assertEqual(len(response.embeddings[0]), 1024) + + def test_chat(self): + """Test V1 chat with Command R.""" + response = self.client.chat( + model="command-r-08-2024", + message="What is 2+2? Answer with just the number.", + ) + self.assertIsNotNone(response) + self.assertIsNotNone(response.text) + self.assertIn("4", response.text) + + def test_chat_stream(self): + """Test V1 streaming chat.""" + events = [] + for event in self.client.chat_stream( + model="command-r-08-2024", + message="Count from 1 to 3.", + ): + events.append(event) + + self.assertTrue(len(events) > 0) + text_events = [e for e in events if hasattr(e, "text") and e.text] + self.assertTrue(len(text_events) > 0) + + +@unittest.skipIf(os.getenv("TEST_OCI") is None, "TEST_OCI not set") +class TestOciClientV2(unittest.TestCase): + """Test OciClientV2 (v2 API) with OCI Generative AI.""" + + def setUp(self): + """Set up OCI v2 client for each test.""" + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + region = os.getenv("OCI_REGION", "us-chicago-1") + profile = os.getenv("OCI_PROFILE", "DEFAULT") + + self.client = cohere.OciClientV2( + oci_region=region, + oci_compartment_id=compartment_id, + oci_profile=profile, + ) + + def test_embed_v2(self): + """Test embedding with v2 client.""" + response = self.client.embed( + model="embed-english-v3.0", + texts=["Hello from v2", "Second text"], + input_type="search_document", + ) + + self.assertIsNotNone(response) + self.assertIsNotNone(response.embeddings) + # V2 returns embeddings as a dict with "float" key + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_), 2) + # Verify embedding dimensions (1024 for embed-english-v3.0) + self.assertEqual(len(response.embeddings.float_[0]), 1024) + + def test_embed_with_model_prefix_v2(self): + """Test embedding with 'cohere.' model prefix on v2 client.""" + response = self.client.embed( + model="cohere.embed-english-v3.0", + texts=["Test with prefix"], + input_type="search_document", + ) + + self.assertIsNotNone(response) + self.assertIsNotNone(response.embeddings) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_), 1) + + def test_chat_v2(self): + """Test chat with v2 client.""" + response = self.client.chat( + model="command-a-03-2025", + messages=[{"role": "user", "content": "Say hello"}], + ) + + self.assertIsNotNone(response) + self.assertIsNotNone(response.message) + + def test_chat_stream_v2(self): + """Test streaming chat with v2 client.""" + events = [] + for event in self.client.chat_stream( + model="command-a-03-2025", + messages=[{"role": "user", "content": "Count from 1 to 3"}], + ): + events.append(event) + + self.assertTrue(len(events) > 0) + # Verify we received content-delta events with text + content_delta_events = [e for e in events if hasattr(e, "type") and e.type == "content-delta"] + self.assertTrue(len(content_delta_events) > 0) + + # Verify we can extract text from events + full_text = "" + for event in events: + if ( + hasattr(event, "delta") + and event.delta + and hasattr(event.delta, "message") + and event.delta.message + and hasattr(event.delta.message, "content") + and event.delta.message.content + and hasattr(event.delta.message.content, "text") + and event.delta.message.content.text is not None + ): + full_text += event.delta.message.content.text + + # Should have received some text + self.assertTrue(len(full_text) > 0) + +@unittest.skipIf(os.getenv("TEST_OCI") is None, "TEST_OCI not set") +class TestOciClientAuthentication(unittest.TestCase): + """Test different OCI authentication methods.""" + + def test_config_file_auth(self): + """Test authentication using OCI config file.""" + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + profile = os.getenv("OCI_PROFILE", "DEFAULT") + client = cohere.OciClientV2( + oci_region="us-chicago-1", + oci_compartment_id=compartment_id, + oci_profile=profile, + ) + + # Test with a simple embed call + response = client.embed( + model="embed-english-v3.0", + texts=["Auth test"], + input_type="search_document", + ) + + self.assertIsNotNone(response) + self.assertIsNotNone(response.embeddings) + + def test_custom_profile_auth(self): + """Test authentication using custom OCI profile.""" + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + profile = os.getenv("OCI_PROFILE", "DEFAULT") + + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + client = cohere.OciClientV2( + oci_profile=profile, + oci_region="us-chicago-1", + oci_compartment_id=compartment_id, + ) + + response = client.embed( + model="embed-english-v3.0", + texts=["Profile auth test"], + input_type="search_document", + ) + + self.assertIsNotNone(response) + + +@unittest.skipIf(os.getenv("TEST_OCI") is None, "TEST_OCI not set") +class TestOciClientErrors(unittest.TestCase): + """Test error handling in OCI client.""" + + def test_missing_compartment_id(self): + """Test error when compartment ID is missing.""" + with self.assertRaises(TypeError): + cohere.OciClientV2( + oci_region="us-chicago-1", + # Missing oci_compartment_id + ) + + def test_invalid_model(self): + """Test error handling with invalid model.""" + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + profile = os.getenv("OCI_PROFILE", "DEFAULT") + client = cohere.OciClientV2( + oci_region="us-chicago-1", + oci_compartment_id=compartment_id, + oci_profile=profile, + ) + + # OCI should return an error for invalid model + with self.assertRaises(Exception): + client.embed( + model="invalid-model-name", + texts=["Test"], + input_type="search_document", + ) + + +@unittest.skipIf(os.getenv("TEST_OCI") is None, "TEST_OCI not set") +class TestOciClientModels(unittest.TestCase): + """Test different Cohere models on OCI.""" + + def setUp(self): + """Set up OCI client for each test.""" + compartment_id = os.getenv("OCI_COMPARTMENT_ID") + if not compartment_id: + self.skipTest("OCI_COMPARTMENT_ID not set") + + region = os.getenv("OCI_REGION", "us-chicago-1") + profile = os.getenv("OCI_PROFILE", "DEFAULT") + + self.client = cohere.OciClientV2( + oci_region=region, + oci_compartment_id=compartment_id, + oci_profile=profile, + ) + + def test_embed_english_v3(self): + """Test embed-english-v3.0 model.""" + response = self.client.embed( + model="embed-english-v3.0", + texts=["Test"], + input_type="search_document", + ) + self.assertIsNotNone(response.embeddings) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_[0]), 1024) + + def test_embed_multilingual_v3(self): + """Test embed-multilingual-v3.0 model.""" + response = self.client.embed( + model="embed-multilingual-v3.0", + texts=["Test"], + input_type="search_document", + ) + self.assertIsNotNone(response.embeddings) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_[0]), 1024) + + def test_command_a_chat(self): + """Test command-a-03-2025 model for chat.""" + response = self.client.chat( + model="command-a-03-2025", + messages=[{"role": "user", "content": "Hello"}], + ) + self.assertIsNotNone(response.message) + +class TestOciClientTransformations(unittest.TestCase): + """Unit tests for OCI request/response transformations (no OCI credentials required).""" + + def test_thinking_parameter_transformation(self): + """Test that thinking parameter is correctly transformed to OCI format.""" + from cohere.oci_client import transform_request_to_oci + + cohere_body = { + "model": "command-a-reasoning-08-2025", + "messages": [{"role": "user", "content": "What is 2+2?"}], + "thinking": { + "type": "enabled", + "token_budget": 10000, + }, + } + + result = transform_request_to_oci("chat", cohere_body, "compartment-123", is_v2=True) + + # Verify thinking parameter is transformed with camelCase for OCI API + chat_request = result["chatRequest"] + self.assertIn("thinking", chat_request) + self.assertEqual(chat_request["thinking"]["type"], "ENABLED") + self.assertEqual(chat_request["thinking"]["tokenBudget"], 10000) # camelCase for OCI + + def test_thinking_parameter_disabled(self): + """Test that disabled thinking is correctly transformed.""" + from cohere.oci_client import transform_request_to_oci + + cohere_body = { + "model": "command-a-reasoning-08-2025", + "messages": [{"role": "user", "content": "Hello"}], + "thinking": { + "type": "disabled", + }, + } + + result = transform_request_to_oci("chat", cohere_body, "compartment-123", is_v2=True) + + chat_request = result["chatRequest"] + self.assertIn("thinking", chat_request) + self.assertEqual(chat_request["thinking"]["type"], "DISABLED") + self.assertNotIn("token_budget", chat_request["thinking"]) + + def test_thinking_response_transformation(self): + """Test that thinking content in response is correctly transformed.""" + from cohere.oci_client import transform_oci_response_to_cohere + + oci_response = { + "chatResponse": { + "id": "test-id", + "message": { + "role": "ASSISTANT", + "content": [ + {"type": "THINKING", "thinking": "Let me think about this..."}, + {"type": "TEXT", "text": "The answer is 4."}, + ], + }, + "finishReason": "COMPLETE", + "usage": {"inputTokens": 10, "completionTokens": 20}, + } + } + + result = transform_oci_response_to_cohere("chat", oci_response, is_v2=True) + + # Verify content types are lowercased + self.assertEqual(result["message"]["content"][0]["type"], "thinking") + self.assertEqual(result["message"]["content"][1]["type"], "text") + + def test_stream_event_thinking_transformation(self): + """Test that thinking content in stream events is correctly transformed.""" + from cohere.oci_client import transform_stream_event + + # OCI thinking event + oci_event = { + "message": { + "content": [{"type": "THINKING", "thinking": "Reasoning step..."}] + } + } + + result = transform_stream_event("chat", oci_event, is_v2=True) + + self.assertEqual(result[0]["type"], "content-delta") + self.assertIn("thinking", result[0]["delta"]["message"]["content"]) + self.assertEqual(result[0]["delta"]["message"]["content"]["thinking"], "Reasoning step...") + + def test_stream_event_text_transformation(self): + """Test that text content in stream events is correctly transformed.""" + from cohere.oci_client import transform_stream_event + + # OCI text event + oci_event = { + "message": { + "content": [{"type": "TEXT", "text": "The answer is..."}] + } + } + + result = transform_stream_event("chat", oci_event, is_v2=True) + + self.assertEqual(result[0]["type"], "content-delta") + self.assertIn("text", result[0]["delta"]["message"]["content"]) + self.assertEqual(result[0]["delta"]["message"]["content"]["text"], "The answer is...") + + def test_thinking_parameter_none(self): + """Test that thinking=None does not crash (issue: null guard).""" + from cohere.oci_client import transform_request_to_oci + + cohere_body = { + "model": "command-a-03-2025", + "messages": [{"role": "user", "content": "Hello"}], + "thinking": None, # Explicitly set to None + } + + # Should not crash with TypeError + result = transform_request_to_oci("chat", cohere_body, "compartment-123", is_v2=True) + + chat_request = result["chatRequest"] + # thinking should not be in request when None + self.assertNotIn("thinking", chat_request) + + def test_v2_response_role_lowercased(self): + """Test that V2 response message role is lowercased.""" + from cohere.oci_client import transform_oci_response_to_cohere + + oci_response = { + "chatResponse": { + "id": "test-id", + "message": { + "role": "ASSISTANT", + "content": [{"type": "TEXT", "text": "Hello"}], + }, + "finishReason": "COMPLETE", + "usage": {"inputTokens": 10, "completionTokens": 20}, + } + } + + result = transform_oci_response_to_cohere("chat", oci_response, is_v2=True) + + # Role should be lowercased + self.assertEqual(result["message"]["role"], "assistant") + + def test_v2_response_finish_reason_uppercase(self): + """Test that V2 response finish_reason stays uppercase.""" + from cohere.oci_client import transform_oci_response_to_cohere + + oci_response = { + "chatResponse": { + "id": "test-id", + "message": { + "role": "ASSISTANT", + "content": [{"type": "TEXT", "text": "Hello"}], + }, + "finishReason": "MAX_TOKENS", + "usage": {"inputTokens": 10, "completionTokens": 20}, + } + } + + result = transform_oci_response_to_cohere("chat", oci_response, is_v2=True) + + # V2 finish_reason should stay uppercase + self.assertEqual(result["finish_reason"], "MAX_TOKENS") + + def test_v2_response_tool_calls_conversion(self): + """Test that V2 response converts toolCalls to tool_calls.""" + from cohere.oci_client import transform_oci_response_to_cohere + + oci_response = { + "chatResponse": { + "id": "test-id", + "message": { + "role": "ASSISTANT", + "content": [{"type": "TEXT", "text": "I'll help with that."}], + "toolCalls": [ + { + "id": "call_123", + "type": "function", + "function": {"name": "get_weather", "arguments": '{"city": "London"}'}, + } + ], + }, + "finishReason": "TOOL_CALL", + "usage": {"inputTokens": 10, "completionTokens": 20}, + } + } + + result = transform_oci_response_to_cohere("chat", oci_response, is_v2=True) + + # toolCalls should be converted to tool_calls + self.assertIn("tool_calls", result["message"]) + self.assertNotIn("toolCalls", result["message"]) + self.assertEqual(len(result["message"]["tool_calls"]), 1) + self.assertEqual(result["message"]["tool_calls"][0]["id"], "call_123") + + def test_normalize_model_for_oci(self): + """Test model name normalization for OCI.""" + from cohere.oci_client import normalize_model_for_oci + + # Plain model name gets cohere. prefix + self.assertEqual(normalize_model_for_oci("command-a-03-2025"), "cohere.command-a-03-2025") + # Already prefixed passes through + self.assertEqual(normalize_model_for_oci("cohere.embed-english-v3.0"), "cohere.embed-english-v3.0") + # OCID passes through + self.assertEqual( + normalize_model_for_oci("ocid1.generativeaimodel.oc1.us-chicago-1.abc"), + "ocid1.generativeaimodel.oc1.us-chicago-1.abc", + ) + + def test_transform_embed_request(self): + """Test embed request transformation to OCI format.""" + from cohere.oci_client import transform_request_to_oci + + body = { + "model": "embed-english-v3.0", + "texts": ["hello", "world"], + "input_type": "search_document", + "truncate": "end", + "embedding_types": ["float", "int8"], + } + result = transform_request_to_oci("embed", body, "compartment-123") + + self.assertEqual(result["inputs"], ["hello", "world"]) + self.assertEqual(result["inputType"], "SEARCH_DOCUMENT") + self.assertEqual(result["truncate"], "END") + self.assertEqual(result["embeddingTypes"], ["FLOAT", "INT8"]) + self.assertEqual(result["compartmentId"], "compartment-123") + self.assertEqual(result["servingMode"]["modelId"], "cohere.embed-english-v3.0") + + def test_transform_embed_request_with_optional_params(self): + """Test embed request forwards optional params.""" + from cohere.oci_client import transform_request_to_oci + + body = { + "model": "embed-english-v3.0", + "inputs": [{"content": [{"type": "text", "text": "hello"}]}], + "input_type": "classification", + "max_tokens": 256, + "output_dimension": 512, + "priority": 42, + } + result = transform_request_to_oci("embed", body, "compartment-123") + + self.assertEqual(result["inputs"], body["inputs"]) + self.assertEqual(result["maxTokens"], 256) + self.assertEqual(result["outputDimension"], 512) + self.assertEqual(result["priority"], 42) + + def test_transform_embed_request_rejects_images(self): + """Test embed request fails clearly for unsupported top-level images.""" + from cohere.oci_client import transform_request_to_oci + + with self.assertRaises(ValueError) as ctx: + transform_request_to_oci( + "embed", + { + "model": "embed-english-v3.0", + "images": ["data:image/png;base64,abc"], + "input_type": "classification", + }, + "compartment-123", + ) + + self.assertIn("top-level 'images' parameter", str(ctx.exception)) + + def test_transform_chat_request_optional_params(self): + """Test chat request transformation includes optional params.""" + from cohere.oci_client import transform_request_to_oci + + body = { + "model": "command-a-03-2025", + "messages": [{"role": "user", "content": "Hi"}], + "max_tokens": 100, + "temperature": 0.7, + "stop_sequences": ["END"], + "frequency_penalty": 0.5, + "strict_tools": True, + "response_format": {"type": "json_object"}, + "logprobs": True, + "tool_choice": "REQUIRED", + "priority": 7, + } + result = transform_request_to_oci("chat", body, "compartment-123", is_v2=True) + + chat_req = result["chatRequest"] + self.assertEqual(chat_req["maxTokens"], 100) + self.assertEqual(chat_req["temperature"], 0.7) + self.assertEqual(chat_req["stopSequences"], ["END"]) + self.assertEqual(chat_req["frequencyPenalty"], 0.5) + self.assertTrue(chat_req["strictTools"]) + self.assertEqual(chat_req["responseFormat"], {"type": "json_object"}) + self.assertTrue(chat_req["logprobs"]) + self.assertEqual(chat_req["toolChoice"], "REQUIRED") + self.assertEqual(chat_req["priority"], 7) + + def test_v2_client_rejects_v1_request(self): + """Test OciClientV2 fails when given V1-style 'message' string.""" + from cohere.oci_client import transform_request_to_oci + + with self.assertRaises(ValueError) as ctx: + transform_request_to_oci( + "chat", + {"model": "command-a-03-2025", "message": "Hello"}, + "compartment-123", + is_v2=True, + ) + self.assertIn("OciClientV2", str(ctx.exception)) + + def test_v1_client_rejects_v2_request(self): + """Test OciClient fails when given V2-style 'messages' array.""" + from cohere.oci_client import transform_request_to_oci + + with self.assertRaises(ValueError) as ctx: + transform_request_to_oci( + "chat", + {"model": "command-r-08-2024", "messages": [{"role": "user", "content": "Hi"}]}, + "compartment-123", + is_v2=False, + ) + self.assertIn("OciClient ", str(ctx.exception)) + + def test_v1_chat_request_optional_params(self): + """Test V1 chat request forwards supported optional params.""" + from cohere.oci_client import transform_request_to_oci + + body = { + "model": "command-r-08-2024", + "message": "Hi", + "max_tokens": 100, + "temperature": 0.7, + "k": 10, + "p": 0.8, + "seed": 123, + "stop_sequences": ["END"], + "frequency_penalty": 0.5, + "presence_penalty": 0.2, + "documents": [{"title": "Doc", "text": "Body"}], + "tools": [{"name": "lookup"}], + "tool_results": [{"call": {"name": "lookup"}}], + "response_format": {"type": "json_object"}, + "safety_mode": "NONE", + "priority": 4, + } + result = transform_request_to_oci("chat", body, "compartment-123", is_v2=False) + + chat_req = result["chatRequest"] + self.assertEqual(chat_req["apiFormat"], "COHERE") + self.assertEqual(chat_req["message"], "Hi") + self.assertEqual(chat_req["maxTokens"], 100) + self.assertEqual(chat_req["temperature"], 0.7) + self.assertEqual(chat_req["topK"], 10) + self.assertEqual(chat_req["topP"], 0.8) + self.assertEqual(chat_req["seed"], 123) + self.assertEqual(chat_req["frequencyPenalty"], 0.5) + self.assertEqual(chat_req["presencePenalty"], 0.2) + self.assertEqual(chat_req["priority"], 4) + + def test_v1_stream_wrapper_preserves_finish_reason(self): + """Test V1 stream-end uses the OCI finish reason from the final event.""" + import json + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: {"text": "Hello", "isFinished": false}\n', + b'data: {"text": " world", "isFinished": true, "finishReason": "MAX_TOKENS"}\n', + b"data: [DONE]\n", + ] + + events = [ + json.loads(raw.decode("utf-8")) + for raw in transform_oci_stream_wrapper(iter(chunks), "chat_stream", is_v2=False) + ] + + self.assertEqual(events[2]["event_type"], "stream-end") + self.assertEqual(events[2]["finish_reason"], "MAX_TOKENS") + self.assertEqual(events[2]["response"]["text"], "Hello world") + + def test_transform_chat_request_tool_message_fields(self): + """Test tool message fields are converted to OCI names.""" + from cohere.oci_client import transform_request_to_oci + + body = { + "model": "command-a-03-2025", + "messages": [ + { + "role": "assistant", + "content": [{"type": "text", "text": "Use tool"}], + "tool_calls": [{"id": "call_1"}], + "tool_plan": "Plan", + }, + { + "role": "tool", + "tool_call_id": "call_1", + "content": [{"type": "text", "text": "Result"}], + }, + ], + } + + result = transform_request_to_oci("chat", body, "compartment-123", is_v2=True) + assistant_message, tool_message = result["chatRequest"]["messages"] + self.assertEqual(assistant_message["toolCalls"], [{"id": "call_1"}]) + self.assertEqual(assistant_message["toolPlan"], "Plan") + self.assertEqual(tool_message["toolCallId"], "call_1") + + def test_get_oci_url_known_endpoints(self): + """Test URL generation for known endpoints.""" + from cohere.oci_client import get_oci_url + + url = get_oci_url("us-chicago-1", "embed") + self.assertIn("/actions/embedText", url) + + url = get_oci_url("us-chicago-1", "chat") + self.assertIn("/actions/chat", url) + + url = get_oci_url("us-chicago-1", "chat_stream") + self.assertIn("/actions/chat", url) + + def test_get_oci_url_unknown_endpoint_raises(self): + """Test that unknown endpoints raise ValueError instead of producing bad URLs.""" + from cohere.oci_client import get_oci_url + + with self.assertRaises(ValueError) as ctx: + get_oci_url("us-chicago-1", "unknown_endpoint") + self.assertIn("not supported", str(ctx.exception)) + + def test_load_oci_config_missing_private_key_raises(self): + """Test that direct credentials without private key raises clear error.""" + from cohere.oci_client import _load_oci_config + + with patch("cohere.oci_client.lazy_oci", return_value=MagicMock()): + with self.assertRaises(ValueError) as ctx: + _load_oci_config( + auth_type="api_key", + config_path=None, + profile=None, + user_id="ocid1.user.oc1...", + fingerprint="xx:xx:xx", + tenancy_id="ocid1.tenancy.oc1...", + # No private_key_path or private_key_content + ) + self.assertIn("oci_private_key_path", str(ctx.exception)) + + def test_load_oci_config_ignores_inherited_session_auth(self): + """Test that named API-key profiles do not inherit DEFAULT session auth fields.""" + from cohere.oci_client import _load_oci_config + + config_text = """ +[DEFAULT] +security_token_file=/tmp/default-token + +[API_KEY_AUTH] +user=ocid1.user.oc1..test +fingerprint=aa:bb +key_file=/tmp/test.pem +tenancy=ocid1.tenancy.oc1..test +region=us-chicago-1 +""".strip() + + with tempfile.NamedTemporaryFile("w", delete=False) as config_file: + config_file.write(config_text) + config_path = config_file.name + + try: + mock_oci = MagicMock() + mock_oci.config.from_file.return_value = { + "user": "ocid1.user.oc1..test", + "fingerprint": "aa:bb", + "key_file": "/tmp/test.pem", + "tenancy": "ocid1.tenancy.oc1..test", + "region": "us-chicago-1", + "security_token_file": "/tmp/default-token", + } + + with patch("cohere.oci_client.lazy_oci", return_value=mock_oci): + config = _load_oci_config( + auth_type="api_key", + config_path=config_path, + profile="API_KEY_AUTH", + ) + finally: + os.unlink(config_path) + + self.assertNotIn("security_token_file", config) + + def test_session_auth_prefers_security_token_signer(self): + """Test session-based auth uses SecurityTokenSigner before API key signer.""" + from cohere.oci_client import map_request_to_oci + + mock_oci = MagicMock() + mock_security_signer = MagicMock() + mock_oci.signer.load_private_key_from_file.return_value = "private-key" + mock_oci.auth.signers.SecurityTokenSigner.return_value = mock_security_signer + + with patch("cohere.oci_client.lazy_oci", return_value=mock_oci), patch( + "builtins.open", mock_open(read_data="session-token") + ): + hook = map_request_to_oci( + oci_config={ + "user": "ocid1.user.oc1..example", + "fingerprint": "xx:xx", + "tenancy": "ocid1.tenancy.oc1..example", + "security_token_file": "~/.oci/token", + "key_file": "~/.oci/key.pem", + }, + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1..example", + ) + + request = MagicMock() + request.url.path = "/v2/embed" + request.read.return_value = b'{"model":"embed-english-v3.0","texts":["hello"]}' + request.method = "POST" + request.extensions = {} + + hook(request) + + mock_oci.auth.signers.SecurityTokenSigner.assert_called_once_with( + token="session-token", + private_key="private-key", + ) + mock_oci.signer.Signer.assert_not_called() + + def test_embed_response_lowercases_embedding_keys(self): + """Test embed response uses lowercase keys expected by the SDK model.""" + from cohere.oci_client import transform_oci_response_to_cohere + + result = transform_oci_response_to_cohere( + "embed", + { + "id": "embed-id", + "embeddings": {"FLOAT": [[0.1, 0.2]], "INT8": [[1, 2]]}, + "usage": {"inputTokens": 3, "completionTokens": 7}, + }, + is_v2=True, + ) + + self.assertIn("float", result["embeddings"]) + self.assertIn("int8", result["embeddings"]) + self.assertNotIn("FLOAT", result["embeddings"]) + self.assertEqual(result["meta"]["tokens"]["output_tokens"], 7) + + def test_normalize_model_for_oci_rejects_empty_model(self): + """Test model normalization fails clearly for empty model names.""" + from cohere.oci_client import normalize_model_for_oci + + with self.assertRaises(ValueError) as ctx: + normalize_model_for_oci("") + self.assertIn("non-empty model", str(ctx.exception)) + + def test_stream_wrapper_emits_full_event_lifecycle(self): + """Test that stream emits message-start, content-start, content-delta, content-end, message-end.""" + import json + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: {"message": {"content": [{"type": "TEXT", "text": "Hello"}]}}\n', + b'data: {"message": {"content": [{"type": "TEXT", "text": " world"}]}, "finishReason": "COMPLETE"}\n', + b'data: [DONE]\n', + ] + + events = [] + for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True): + line = raw.decode("utf-8").strip() + if line.startswith("data: "): + events.append(json.loads(line[6:])) + + event_types = [e["type"] for e in events] + self.assertEqual(event_types[0], "message-start") + self.assertEqual(event_types[1], "content-start") + self.assertEqual(event_types[2], "content-delta") + self.assertEqual(event_types[3], "content-delta") + self.assertEqual(event_types[4], "content-end") + self.assertEqual(event_types[5], "message-end") + + # Verify message-start has id and role + self.assertIn("id", events[0]) + self.assertEqual(events[0]["delta"]["message"]["role"], "assistant") + + # Verify content-start has index and type + self.assertEqual(events[1]["index"], 0) + self.assertEqual(events[1]["delta"]["message"]["content"]["type"], "text") + self.assertEqual(events[5]["delta"]["finish_reason"], "COMPLETE") + + def test_stream_wrapper_emits_new_content_block_on_thinking_transition(self): + """Test streams emit a new content block when transitioning from thinking to text.""" + import json + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: {"message": {"content": [{"type": "THINKING", "thinking": "Reasoning..."}]}}\n', + b'data: {"message": {"content": [{"type": "TEXT", "text": "Answer"}]}, "finishReason": "COMPLETE"}\n', + b"data: [DONE]\n", + ] + + events = [] + for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True): + line = raw.decode("utf-8").strip() + if line.startswith("data: "): + events.append(json.loads(line[6:])) + + self.assertEqual(events[1]["type"], "content-start") + self.assertEqual(events[1]["delta"]["message"]["content"]["type"], "thinking") + self.assertEqual(events[2]["type"], "content-delta") + self.assertEqual(events[2]["index"], 0) + self.assertEqual(events[3], {"type": "content-end", "index": 0}) + self.assertEqual(events[4]["type"], "content-start") + self.assertEqual(events[4]["index"], 1) + self.assertEqual(events[4]["delta"]["message"]["content"]["type"], "text") + self.assertEqual(events[5]["type"], "content-delta") + self.assertEqual(events[5]["index"], 1) + + def test_stream_wrapper_skips_malformed_json_with_warning(self): + """Test that malformed JSON in SSE stream is skipped.""" + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: not-valid-json\n', + b'data: {"message": {"content": [{"type": "TEXT", "text": "hello"}]}}\n', + b'data: [DONE]\n', + ] + events = list(transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True)) + # Should get message-start + content-start + content-delta + content-end + message-end. + self.assertEqual(len(events), 5) + + def test_stream_wrapper_skips_message_end_for_empty_stream(self): + """Test empty streams do not emit message-end without a preceding message-start.""" + from cohere.oci_client import transform_oci_stream_wrapper + + events = list(transform_oci_stream_wrapper(iter([b"data: [DONE]\n"]), "chat", is_v2=True)) + + self.assertEqual(events, []) + + def test_stream_wrapper_done_uses_current_content_index_after_transition(self): + """Test fallback content-end uses the latest content index after type transitions.""" + import json + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: {"message": {"content": [{"type": "THINKING", "thinking": "Reasoning..."}]}}\n', + b'data: {"message": {"content": [{"type": "TEXT", "text": "Answer"}]}}\n', + b"data: [DONE]\n", + ] + + events = [] + for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True): + line = raw.decode("utf-8").strip() + if line.startswith("data: "): + events.append(json.loads(line[6:])) + + self.assertEqual(events[-2], {"type": "content-end", "index": 1}) + self.assertEqual(events[-1]["type"], "message-end") + + def test_stream_wrapper_raises_on_transform_error(self): + """Test that transform errors in stream produce OCI-specific error.""" + from cohere.oci_client import transform_oci_stream_wrapper + + # Event with structure that will cause transform_stream_event to fail + # (message is None, causing TypeError on "content" in None) + chunks = [ + b'data: {"message": null}\n', + ] + with self.assertRaises(RuntimeError) as ctx: + list(transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True)) + self.assertIn("OCI stream event transformation failed", str(ctx.exception)) + + def test_stream_event_finish_reason_keeps_final_text(self): + """Test finish events keep final text before content-end.""" + from cohere.oci_client import transform_stream_event + + events = transform_stream_event( + "chat", + { + "message": {"content": [{"type": "TEXT", "text": " world"}]}, + "finishReason": "COMPLETE", + }, + is_v2=True, + ) + + self.assertEqual(events[0]["type"], "content-delta") + self.assertEqual(events[0]["delta"]["message"]["content"]["text"], " world") + self.assertEqual(events[1]["type"], "content-end") + +if __name__ == "__main__": + unittest.main() From b644eb3a24d23d315d48c214efa3a48e3a6b8a46 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 18:48:01 -0400 Subject: [PATCH 03/13] fix: remove dead chat_stream endpoint and body-based stream detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "stream" in endpoint check was dead code — both V1 and V2 SDK always route through endpoint "chat" (v1/chat and v2/chat paths). Streaming is reliably signalled via body["stream"], which the SDK always sets. - Drop "stream" in endpoint guard on is_stream and isStream detection - Remove "chat_stream" from action_map, transform, and response branches - Update unit tests to use "chat" endpoint (the only real one) --- src/cohere/oci_client.py | 11 +++++------ tests/test_oci_client.py | 4 +--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 4f98e473a..66a4fcf41 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -480,7 +480,7 @@ def _event_hook(request: httpx.Request) -> None: request.stream = ByteStream(oci_body_bytes) request._content = oci_body_bytes request.extensions["endpoint"] = endpoint - request.extensions["is_stream"] = "stream" in endpoint or body.get("stream", False) + request.extensions["is_stream"] = body.get("stream", False) request.extensions["is_v2"] = is_v2_client return _event_hook @@ -554,7 +554,6 @@ def get_oci_url( action_map = { "embed": "embedText", "chat": "chat", - "chat_stream": "chat", } action = action_map.get(endpoint) @@ -658,7 +657,7 @@ def transform_request_to_oci( return oci_body - elif endpoint in ["chat", "chat_stream"]: + elif endpoint == "chat": # Validate that the request body matches the client type has_messages = "messages" in cohere_body has_message = "message" in cohere_body @@ -800,7 +799,7 @@ def transform_request_to_oci( chat_request["priority"] = cohere_body["priority"] # Handle streaming for both versions - if "stream" in endpoint or cohere_body.get("stream"): + if cohere_body.get("stream"): chat_request["isStream"] = True # Top level OCI request structure @@ -861,7 +860,7 @@ def transform_oci_response_to_cohere( "meta": meta, } - elif endpoint == "chat" or endpoint == "chat_stream": + elif endpoint == "chat": chat_response = oci_response.get("chatResponse", {}) if is_v2: @@ -1096,7 +1095,7 @@ def transform_stream_event( Returns: V2: List of transformed events. V1: Single transformed event dict. """ - if endpoint in ["chat_stream", "chat"]: + if endpoint == "chat": if is_v2: content_type = "text" content_value = "" diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 7798921c9..251d67d41 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -715,7 +715,7 @@ def test_v1_stream_wrapper_preserves_finish_reason(self): events = [ json.loads(raw.decode("utf-8")) - for raw in transform_oci_stream_wrapper(iter(chunks), "chat_stream", is_v2=False) + for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=False) ] self.assertEqual(events[2]["event_type"], "stream-end") @@ -759,8 +759,6 @@ def test_get_oci_url_known_endpoints(self): url = get_oci_url("us-chicago-1", "chat") self.assertIn("/actions/chat", url) - url = get_oci_url("us-chicago-1", "chat_stream") - self.assertIn("/actions/chat", url) def test_get_oci_url_unknown_endpoint_raises(self): """Test that unknown endpoints raise ValueError instead of producing bad URLs.""" From bac195c28c79af2231012b4155aa440947973ebd Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 18:56:25 -0400 Subject: [PATCH 04/13] fix: don't trigger content-type transition on finish-only stream events _current_content_type now returns None for events with no message content (e.g. {"finishReason": "COMPLETE"}). The transition branch in _transform_v2_event is skipped when event_content_type is None, so a finish-only event after a thinking block no longer opens a spurious empty text block before emitting content-end. --- src/cohere/oci_client.py | 14 +++++++------- tests/test_oci_client.py | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 66a4fcf41..24e81a2d9 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -953,21 +953,21 @@ def _emit_v2_event(event: typing.Dict[str, typing.Any]) -> bytes: def _emit_v1_event(event: typing.Dict[str, typing.Any]) -> bytes: return json.dumps(event).encode("utf-8") + b"\n" - def _current_content_type(oci_event: typing.Dict[str, typing.Any]) -> str: + def _current_content_type(oci_event: typing.Dict[str, typing.Any]) -> typing.Optional[str]: message = oci_event.get("message") if isinstance(message, dict): content_list = message.get("content") if content_list and isinstance(content_list, list) and len(content_list) > 0: oci_type = content_list[0].get("type", "TEXT").upper() - if oci_type == "THINKING": - return "thinking" - return "text" + return "thinking" if oci_type == "THINKING" else "text" + return None # finish-only or non-content event — don't trigger a type transition def _transform_v2_event(oci_event: typing.Dict[str, typing.Any]) -> typing.Iterator[bytes]: nonlocal emitted_start, emitted_content_end, current_content_type, current_content_index nonlocal final_finish_reason, final_usage event_content_type = _current_content_type(oci_event) + open_type = event_content_type or "text" if not emitted_start: yield _emit_v2_event( @@ -981,12 +981,12 @@ def _transform_v2_event(oci_event: typing.Dict[str, typing.Any]) -> typing.Itera { "type": "content-start", "index": current_content_index, - "delta": {"message": {"content": {"type": event_content_type}}}, + "delta": {"message": {"content": {"type": open_type}}}, } ) emitted_start = True - current_content_type = event_content_type - elif current_content_type != event_content_type: + current_content_type = open_type + elif event_content_type is not None and current_content_type != event_content_type: yield _emit_v2_event({"type": "content-end", "index": current_content_index}) current_content_index += 1 yield _emit_v2_event( diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 251d67d41..9f8819d5d 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -954,6 +954,32 @@ def test_stream_wrapper_emits_new_content_block_on_thinking_transition(self): self.assertEqual(events[5]["type"], "content-delta") self.assertEqual(events[5]["index"], 1) + def test_stream_wrapper_no_spurious_block_on_finish_only_event(self): + """Finish-only event after thinking block must not open a spurious empty text block.""" + import json + from cohere.oci_client import transform_oci_stream_wrapper + + chunks = [ + b'data: {"message": {"content": [{"type": "THINKING", "thinking": "Reasoning..."}]}}\n', + b'data: {"finishReason": "COMPLETE"}\n', + b"data: [DONE]\n", + ] + + events = [] + for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=True): + line = raw.decode("utf-8").strip() + if line.startswith("data: "): + events.append(json.loads(line[6:])) + + types = [e["type"] for e in events] + # Must not contain two content-start events + self.assertEqual(types.count("content-start"), 1) + # The single content block must be thinking + cs = next(e for e in events if e["type"] == "content-start") + self.assertEqual(cs["delta"]["message"]["content"]["type"], "thinking") + # Must end cleanly + self.assertEqual(events[-1]["type"], "message-end") + def test_stream_wrapper_skips_malformed_json_with_warning(self): """Test that malformed JSON in SSE stream is skipped.""" from cohere.oci_client import transform_oci_stream_wrapper From 81f159b8cbd635eee7b8474c3e18682dea38ed3e Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 19:01:31 -0400 Subject: [PATCH 05/13] test: add integration tests for light models, command-r-plus, multi-turn, and system message --- tests/test_oci_client.py | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 9f8819d5d..e25605fd8 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -348,6 +348,77 @@ def test_command_a_chat(self): ) self.assertIsNotNone(response.message) + def test_embed_english_light_v3(self): + """Test embed-english-light-v3.0 returns 384-dim vectors.""" + response = self.client.embed( + model="embed-english-light-v3.0", + texts=["Hello world"], + input_type="search_document", + ) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_[0]), 384) + + def test_embed_multilingual_light_v3(self): + """Test embed-multilingual-light-v3.0 returns 384-dim vectors.""" + response = self.client.embed( + model="embed-multilingual-light-v3.0", + texts=["Bonjour le monde"], + input_type="search_document", + ) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_[0]), 384) + + def test_embed_search_query_input_type(self): + """Test embed with search_query input_type (distinct from search_document).""" + response = self.client.embed( + model="embed-english-v3.0", + texts=["What is the capital of France?"], + input_type="search_query", + ) + self.assertIsNotNone(response.embeddings.float_) + self.assertEqual(len(response.embeddings.float_[0]), 1024) + + def test_command_r_plus_chat(self): + """Test command-r-plus-08-2024 via V1 client.""" + v1_client = cohere.OciClient( + oci_region=os.getenv("OCI_REGION", "us-chicago-1"), + oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"), + oci_profile=os.getenv("OCI_PROFILE", "DEFAULT"), + ) + response = v1_client.chat( + model="command-r-plus-08-2024", + message="What is 2+2? Answer with just the number.", + ) + self.assertIsNotNone(response.text) + self.assertIn("4", response.text) + + def test_v2_multi_turn_chat(self): + """Test V2 chat with conversation history (multi-turn).""" + response = self.client.chat( + model="command-a-03-2025", + messages=[ + {"role": "user", "content": "My name is Alice."}, + {"role": "assistant", "content": "Nice to meet you, Alice!"}, + {"role": "user", "content": "What is my name?"}, + ], + ) + self.assertIsNotNone(response.message) + content = response.message.content[0].text + self.assertIn("Alice", content) + + def test_v2_system_message(self): + """Test V2 chat with a system message.""" + response = self.client.chat( + model="command-a-03-2025", + messages=[ + {"role": "system", "content": "You are a helpful assistant. Always respond in exactly 3 words."}, + {"role": "user", "content": "Say hello."}, + ], + ) + self.assertIsNotNone(response.message) + self.assertIsNotNone(response.message.content[0].text) + + class TestOciClientTransformations(unittest.TestCase): """Unit tests for OCI request/response transformations (no OCI credentials required).""" From 2ac5ec4a9c74d86a75511d54229be30107c97a49 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 19:10:24 -0400 Subject: [PATCH 06/13] fix: use 'or []' to guard against explicit content=None in V2 messages --- src/cohere/oci_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 24e81a2d9..82b9de77c 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -703,7 +703,7 @@ def transform_request_to_oci( transformed_content.append(item) oci_msg["content"] = transformed_content else: - oci_msg["content"] = msg.get("content", []) + oci_msg["content"] = msg.get("content") or [] if "tool_calls" in msg: oci_msg["toolCalls"] = msg["tool_calls"] From 27abc5cd9449988a6162babe386e13c6ac7f1642 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 19:25:43 -0400 Subject: [PATCH 07/13] Address cursor review: raise on unsupported endpoint, refresh session token per-request - transform_request_to_oci now raises ValueError for endpoints other than 'embed' and 'chat' instead of silently returning the untransformed body - Session token auth uses a refreshing wrapper that re-reads the token file before each signing call, so OCI CLI token refreshes are picked up without restarting the client - Add test_unsupported_endpoint_raises to cover the new explicit error - Update test_session_auth_prefers_security_token_signer to expect multi-call behaviour from the refreshing signer --- src/cohere/oci_client.py | 47 ++++++++++++++++++++++++++++++---------- tests/test_oci_client.py | 14 +++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 82b9de77c..796223c52 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -399,24 +399,46 @@ def map_request_to_oci( if "signer" in oci_config: signer = oci_config["signer"] # Instance/resource principal elif "security_token_file" in oci_config: - # Session-based authentication with security token (fallback if no user field) - token_file_path = os.path.expanduser(oci_config["security_token_file"]) - with open(token_file_path, "r") as f: - security_token = f.read().strip() - - # Load private key using OCI's utility function + # Session-based authentication with security token. + # The token file is re-read on every request so that OCI CLI token refreshes + # (e.g. `oci session refresh`) are picked up without restarting the client. key_file = oci_config.get("key_file") if not key_file: raise ValueError( "OCI config profile is missing 'key_file'. " "Session-based auth requires a key_file entry in your OCI config profile." ) + token_file_path = os.path.expanduser(oci_config["security_token_file"]) private_key = oci.signer.load_private_key_from_file(os.path.expanduser(key_file)) - signer = oci.auth.signers.SecurityTokenSigner( - token=security_token, - private_key=private_key, - ) + class _RefreshingSecurityTokenSigner: + """Wraps SecurityTokenSigner and re-reads the token file before each signing call.""" + + def __init__(self) -> None: + self._token_file = token_file_path + self._private_key = private_key + self._refresh() + + def _refresh(self) -> None: + with open(self._token_file, "r") as _f: + _token = _f.read().strip() + self._signer = oci.auth.signers.SecurityTokenSigner( + token=_token, + private_key=self._private_key, + ) + + # Delegate all attribute access to the inner signer, refreshing first. + def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any: + self._refresh() + return self._signer(*args, **kwargs) + + def __getattr__(self, name: str) -> typing.Any: + if name.startswith("_"): + raise AttributeError(name) + self._refresh() + return getattr(self._signer, name) + + signer = _RefreshingSecurityTokenSigner() elif "user" in oci_config: signer = oci.signer.Signer( tenancy=oci_config["tenancy"], @@ -814,7 +836,10 @@ def transform_request_to_oci( return oci_body - return cohere_body + raise ValueError( + f"Endpoint '{endpoint}' is not supported by OCI Generative AI on-demand inference. " + "Supported endpoints: ['embed', 'chat']" + ) def transform_oci_response_to_cohere( diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index e25605fd8..3f3272aca 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -737,6 +737,15 @@ def test_v1_client_rejects_v2_request(self): ) self.assertIn("OciClient ", str(ctx.exception)) + def test_unsupported_endpoint_raises(self): + """Test that transform_request_to_oci raises for unsupported endpoints.""" + from cohere.oci_client import transform_request_to_oci + + with self.assertRaises(ValueError) as ctx: + transform_request_to_oci("rerank", {"model": "rerank-v3.5"}, "compartment-123") + self.assertIn("rerank", str(ctx.exception)) + self.assertIn("not supported", str(ctx.exception)) + def test_v1_chat_request_optional_params(self): """Test V1 chat request forwards supported optional params.""" from cohere.oci_client import transform_request_to_oci @@ -930,10 +939,13 @@ def test_session_auth_prefers_security_token_signer(self): hook(request) - mock_oci.auth.signers.SecurityTokenSigner.assert_called_once_with( + # SecurityTokenSigner is called at least once (init) and again per request + # (token file is re-read on each signing call to pick up refreshed tokens). + mock_oci.auth.signers.SecurityTokenSigner.assert_called_with( token="session-token", private_key="private-key", ) + self.assertGreaterEqual(mock_oci.auth.signers.SecurityTokenSigner.call_count, 1) mock_oci.signer.Signer.assert_not_called() def test_embed_response_lowercases_embedding_keys(self): From 15239e0eefad4064e75522f190e6c41c899077ef Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 2 Apr 2026 19:28:44 -0400 Subject: [PATCH 08/13] Add test proving session token is re-read on subsequent requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_session_token_refreshed_on_subsequent_requests writes a real token file, makes two requests with the file updated between them, and asserts that the second signing call uses the new token — verifying the refreshing signer works end-to-end. --- tests/test_oci_client.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 3f3272aca..45a682213 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -948,6 +948,62 @@ def test_session_auth_prefers_security_token_signer(self): self.assertGreaterEqual(mock_oci.auth.signers.SecurityTokenSigner.call_count, 1) mock_oci.signer.Signer.assert_not_called() + def test_session_token_refreshed_on_subsequent_requests(self): + """Verify the refreshing signer picks up a new token written to the token file.""" + import tempfile + import os + from cohere.oci_client import map_request_to_oci + + mock_oci = MagicMock() + mock_oci.signer.load_private_key_from_file.return_value = "private-key" + + # Write initial token to a real temp file so we can overwrite it later. + with tempfile.NamedTemporaryFile("w", suffix=".token", delete=False) as tf: + tf.write("token-v1") + token_path = tf.name + + try: + with patch("cohere.oci_client.lazy_oci", return_value=mock_oci): + hook = map_request_to_oci( + oci_config={ + "security_token_file": token_path, + "key_file": "/irrelevant.pem", + }, + oci_region="us-chicago-1", + oci_compartment_id="ocid1.compartment.oc1..example", + ) + + def _make_request(): + req = MagicMock() + req.url.path = "/v2/embed" + req.read.return_value = b'{"model":"embed-english-v3.0","texts":["hi"]}' + req.method = "POST" + req.extensions = {} + return req + + # First request uses token-v1 + hook(_make_request()) + calls_after_first = mock_oci.auth.signers.SecurityTokenSigner.call_count + + # Simulate token refresh by overwriting the file + with open(token_path, "w") as _f: + _f.write("token-v2") + + # Second request — should re-read and use token-v2 + hook(_make_request()) + self.assertGreater( + mock_oci.auth.signers.SecurityTokenSigner.call_count, + calls_after_first, + "SecurityTokenSigner should be re-instantiated after token file update", + ) + # Verify the latest call used the refreshed token + all_calls = mock_oci.auth.signers.SecurityTokenSigner.call_args_list + last_call = all_calls[-1] + last_token = last_call.kwargs.get("token") or (last_call.args[0] if last_call.args else None) + self.assertEqual(last_token, "token-v2", "Last signing call must use the refreshed token") + finally: + os.unlink(token_path) + def test_embed_response_lowercases_embedding_keys(self): """Test embed response uses lowercase keys expected by the SDK model.""" from cohere.oci_client import transform_oci_response_to_cohere From 0654585728e0a7e1de6e6e76f0780a0ab7ac670e Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 9 Apr 2026 15:16:20 -0400 Subject: [PATCH 09/13] fix(oci): resolve mypy type errors and add type-checking test gate The OCI client code introduced several mypy errors that went unnoticed because mypy was configured but never enforced in tests or CI. Type fixes: - lazy_oci_deps.py: suppress import-untyped for oci SDK (no type stubs) - oci_client.py: cast response.stream to Iterator[bytes] (httpx types it as SyncByteStream | AsyncByteStream but it's iterable at runtime) - oci_client.py: use .get("model", "") to satisfy str expectation - test_oci_client.py: suppress attr-defined on dynamic module stubs New test gate (tests/test_oci_mypy.py): - Runs mypy on OCI source and test files as part of pytest - Uses --follow-imports=silent to isolate from pre-existing AWS errors - Skips gracefully if mypy is not on PATH - Ensures future type regressions fail the test suite immediately --- poetry.lock | 896 ++++++++++++------ pyproject.toml | 4 +- .../manually_maintained/lazy_oci_deps.py | 2 +- src/cohere/oci_client.py | 4 +- tests/test_oci_client.py | 10 +- tests/test_oci_mypy.py | 65 ++ 6 files changed, 687 insertions(+), 294 deletions(-) create mode 100644 tests/test_oci_mypy.py diff --git a/poetry.lock b/poetry.lock index 6960d402b..91d0601b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,8 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -17,6 +19,8 @@ version = "3.13.5" description = "Async http client/server framework (asyncio)" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, @@ -151,7 +155,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -159,6 +163,8 @@ version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -174,6 +180,7 @@ version = "0.0.4" description = "Document parameters, class attributes, return types, and variables inline, with Annotated." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, @@ -185,6 +192,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -192,13 +200,14 @@ files = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.12.1" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, + {file = "anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c"}, + {file = "anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703"}, ] [package.dependencies] @@ -207,7 +216,7 @@ idna = ">=2.8" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.32.0)"] +trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""] [[package]] name = "async-timeout" @@ -215,6 +224,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"aiohttp\" and python_version == \"3.10\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -226,6 +237,8 @@ version = "26.1.0" description = "Classes Without Boilerplate" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -237,6 +250,8 @@ version = "1.2.0" description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." optional = false python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -244,162 +259,260 @@ files = [ [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and extra == \"oci\"" +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.7" +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"] files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, + {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 = "circuitbreaker" +version = "2.1.3" +description = "Python Circuit Breaker pattern implementation" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"oci\"" +files = [ + {file = "circuitbreaker-2.1.3-py3-none-any.whl", hash = "sha256:87ba6a3ed03fdc7032bc175561c2b04d52ade9d5faf94ca2b035fbdc5e6b1dd1"}, + {file = "circuitbreaker-2.1.3.tar.gz", hash = "sha256:1a4baee510f7bea3c91b194dcce7c07805fe96c4423ed5594b75af438531d084"}, ] [[package]] name = "click" -version = "8.3.2" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, - {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -411,10 +524,86 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} + +[[package]] +name = "cryptography" +version = "46.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = true +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main"] +markers = "extra == \"oci\"" +files = [ + {file = "cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e"}, + {file = "cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457"}, + {file = "cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b"}, + {file = "cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee"}, + {file = "cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298"}, + {file = "cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb"}, + {file = "cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e"}, + {file = "cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246"}, + {file = "cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4"}, + {file = "cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} +typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.7)", "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 = "exceptiongroup" @@ -422,6 +611,8 @@ version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -439,6 +630,7 @@ version = "2.1.2" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, @@ -453,6 +645,7 @@ version = "1.12.1" description = "Fast read/write of AVRO files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "fastavro-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:00650ca533907361edda22e6ffe8cf87ab2091c5d8aee5c8000b0f2dcdda7ed3"}, {file = "fastavro-1.12.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac76d6d95f909c72ee70d314b460b7e711d928845771531d823eb96a10952d26"}, @@ -510,13 +703,14 @@ zstandard = ["zstandard"] [[package]] name = "filelock" -version = "3.25.2" +version = "3.19.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70"}, - {file = "filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] [[package]] @@ -525,6 +719,8 @@ version = "1.8.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -660,13 +856,14 @@ files = [ [[package]] name = "fsspec" -version = "2026.3.0" +version = "2025.10.0" description = "File-system specification" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4"}, - {file = "fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41"}, + {file = "fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d"}, + {file = "fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59"}, ] [package.extras] @@ -677,9 +874,9 @@ dask = ["dask", "distributed"] dev = ["pre-commit", "ruff (>=0.5)"] doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs (>2024.2.0)", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs (>2024.2.0)", "smbprotocol", "tqdm"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] fuse = ["fusepy"] -gcs = ["gcsfs (>2024.2.0)"] +gcs = ["gcsfs"] git = ["pygit2"] github = ["requests"] gs = ["gcsfs"] @@ -688,13 +885,13 @@ hdfs = ["pyarrow (>=1)"] http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] libarchive = ["libarchive-c"] oci = ["ocifs"] -s3 = ["s3fs (>2024.2.0)"] +s3 = ["s3fs"] sftp = ["paramiko"] smb = ["smbprotocol"] ssh = ["paramiko"] test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] -test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas (<3.0.0)", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard ; python_version < \"3.14\""] tqdm = ["tqdm"] [[package]] @@ -703,6 +900,7 @@ 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"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -710,36 +908,35 @@ files = [ [[package]] name = "hf-xet" -version = "1.4.3" +version = "1.3.0" description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" files = [ - {file = "hf_xet-1.4.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7551659ba4f1e1074e9623996f28c3873682530aee0a846b7f2f066239228144"}, - {file = "hf_xet-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bee693ada985e7045997f05f081d0e12c4c08bd7626dc397f8a7c487e6c04f7f"}, - {file = "hf_xet-1.4.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21644b404bb0100fe3857892f752c4d09642586fd988e61501c95bbf44b393a3"}, - {file = "hf_xet-1.4.3-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:987f09cfe418237812896a6736b81b1af02a3a6dcb4b4944425c4c4fca7a7cf8"}, - {file = "hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:60cf7fc43a99da0a853345cf86d23738c03983ee5249613a6305d3e57a5dca74"}, - {file = "hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2815a49a7a59f3e2edf0cf113ae88e8cb2ca2a221bf353fb60c609584f4884d4"}, - {file = "hf_xet-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:42ee323265f1e6a81b0e11094564fb7f7e0ec75b5105ffd91ae63f403a11931b"}, - {file = "hf_xet-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:27c976ba60079fb8217f485b9c5c7fcd21c90b0367753805f87cb9f3cdc4418a"}, - {file = "hf_xet-1.4.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5251d5ece3a81815bae9abab41cf7ddb7bcb8f56411bce0827f4a3071c92fdc6"}, - {file = "hf_xet-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1feb0f3abeacee143367c326a128a2e2b60868ec12a36c225afb1d6c5a05e6d2"}, - {file = "hf_xet-1.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b301fc150290ca90b4fccd079829b84bb4786747584ae08b94b4577d82fb791"}, - {file = "hf_xet-1.4.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d972fbe95ddc0d3c0fc49b31a8a69f47db35c1e3699bf316421705741aab6653"}, - {file = "hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5b48db1ee344a805a1b9bd2cda9b6b65fe77ed3787bd6e87ad5521141d317cd"}, - {file = "hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22bdc1f5fb8b15bf2831440b91d1c9bbceeb7e10c81a12e8d75889996a5c9da8"}, - {file = "hf_xet-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0392c79b7cf48418cd61478c1a925246cf10639f4cd9d94368d8ca1e8df9ea07"}, - {file = "hf_xet-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:681c92a07796325778a79d76c67011764ecc9042a8c3579332b61b63ae512075"}, - {file = "hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025"}, - {file = "hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583"}, - {file = "hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08"}, - {file = "hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f"}, - {file = "hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac"}, - {file = "hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba"}, - {file = "hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021"}, - {file = "hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47"}, - {file = "hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113"}, + {file = "hf_xet-1.3.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:95bdeab4747cb45f855601e39b9e86ae92b4a114978ada6e0401961fcc5d2958"}, + {file = "hf_xet-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f99992583f27b139392601fe99e88df155dc4de7feba98ed27ce2d3e6b4a65bb"}, + {file = "hf_xet-1.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:687a71fc6d2eaa79d864da3aa13e5d887e124d357f5f306bfff6c385eea9d990"}, + {file = "hf_xet-1.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:75d19813ed0e24525409bc22566282ae9bc93e5d764b185565e863dc28280a45"}, + {file = "hf_xet-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:078af43569c2e05233137a93a33d2293f95c272745eaf030a9bb5f27bb0c9e9c"}, + {file = "hf_xet-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be8731e1620cc8549025c39ed3917c8fd125efaeae54ae679214a3d573e6c109"}, + {file = "hf_xet-1.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:1552616c0e0fa728a4ffdffa106e91faa0fd4edb44868e79b464fad00b2758ee"}, + {file = "hf_xet-1.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a61496eccf412d7c51a5613c31a2051d357ddea6be53a0672c7644cf39bfefe9"}, + {file = "hf_xet-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aba35218871cc438826076778958f7ab2a1f4f8d654e91c307073a815360558f"}, + {file = "hf_xet-1.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c444d8f657dedd7a72aa0ef0178fe01fe92b04b58014ee49e2b3b4985aea1529"}, + {file = "hf_xet-1.3.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6d1bbda7900d72bc591cd39a64e35ad07f89a24f90e3d7b7c692cb93a1926cde"}, + {file = "hf_xet-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:588f5df302e7dba5c3b60d4e5c683f95678526c29b9f64cbeb23e9f1889c6b83"}, + {file = "hf_xet-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:944ae454b296c42b18219c37f245c78d0e64a734057423e9309f4938faa85d7f"}, + {file = "hf_xet-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:34cdd5f10e61b7a1a7542672d20887c85debcfeb70a471ff1506f5a4c9441e42"}, + {file = "hf_xet-1.3.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:df4447f69086dcc6418583315eda6ed09033ac1fbbc784fedcbbbdf67bea1680"}, + {file = "hf_xet-1.3.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:39f4fe714628adc2214ab4a67391182ee751bc4db581868cb3204900817758a8"}, + {file = "hf_xet-1.3.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b16e53ed6b5c8197cefb3fd12047a430b7034428effed463c03cec68de7e9a3"}, + {file = "hf_xet-1.3.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:92051a1f73019489be77f6837671024ec785a3d1b888466b09d3a9ea15c4a1b5"}, + {file = "hf_xet-1.3.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:943046b160e7804a85e68a659d2eee1a83ce3661f72d1294d3cc5ece0f45a355"}, + {file = "hf_xet-1.3.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9b798a95d41b4f33b0b455c8aa76ff1fd26a587a4dd3bdec29f0a37c60b78a2f"}, + {file = "hf_xet-1.3.0-cp37-abi3-win_amd64.whl", hash = "sha256:227eee5b99d19b9f20c31d901a0c2373af610a24a34e6c2701072c9de48d6d95"}, + {file = "hf_xet-1.3.0.tar.gz", hash = "sha256:9c154ad63e17aca970987b2cf17dbd8a0c09bb18aeb246f637647a8058e4522b"}, ] [package.extras] @@ -751,6 +948,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -772,6 +970,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -784,7 +983,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -796,6 +995,8 @@ version = "0.1.8" description = "Aiohttp transport for HTTPX" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, @@ -807,36 +1008,37 @@ httpx = ">=0.27.0" [[package]] name = "huggingface-hub" -version = "1.9.2" +version = "1.4.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false -python-versions = ">=3.10.0" +python-versions = ">=3.9.0" +groups = ["main"] files = [ - {file = "huggingface_hub-1.9.2-py3-none-any.whl", hash = "sha256:e1e62ce237d4fbeca9f970aeb15176fbd503e04c25577bfd22f44aa7aa2b5243"}, - {file = "huggingface_hub-1.9.2.tar.gz", hash = "sha256:8d09d080a186bd950a361bfc04b862dfb04d6a2b41d48e9ba1b37507cfd3f1e1"}, + {file = "huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18"}, + {file = "huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5"}, ] [package.dependencies] -filelock = ">=3.10.0" +filelock = "*" fsspec = ">=2023.5.0" -hf-xet = {version = ">=1.4.3,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} +hf-xet = {version = ">=1.2.0,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} httpx = ">=0.23.0,<1" packaging = ">=20.9" pyyaml = ">=5.1" +shellingham = "*" tqdm = ">=4.42.1" -typer = "*" +typer-slim = "*" typing-extensions = ">=4.1.0" [package.extras] -all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -gradio = ["gradio (>=5.0.0)", "requests"] -hf-xet = ["hf-xet (>=1.4.3,<2.0.0)"] +hf-xet = ["hf-xet (>=1.2.0,<2.0.0)"] mcp = ["mcp (>=1.8.0)"] oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "ruff (>=0.9.0)", "ty"] -testing = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +testing = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] @@ -846,6 +1048,7 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -856,24 +1059,26 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "iniconfig" -version = "2.3.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.10" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false -python-versions = ">=3.10" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] @@ -881,12 +1086,13 @@ 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)"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins (>=0.5.0)"] +plugins = ["mdit-py-plugins"] 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"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" @@ -894,6 +1100,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -905,6 +1112,8 @@ version = "6.7.1" description = "multidict implementation" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -1063,6 +1272,7 @@ version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -1116,17 +1326,44 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "oci" +version = "2.170.0" +description = "Oracle Cloud Infrastructure Python SDK" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"oci\"" +files = [ + {file = "oci-2.170.0-py3-none-any.whl", hash = "sha256:49adf0ffaf754c07c43194d19d2e11f5e1b602a95bb365bb384d939ff365b1b4"}, + {file = "oci-2.170.0.tar.gz", hash = "sha256:1e205a395e856b6514501d34595dd1e8e261415aaaab2c97f77fd2fc9618dcc3"}, +] + +[package.dependencies] +certifi = "*" +circuitbreaker = {version = ">=1.3.1,<3.0.0", markers = "python_version >= \"3.7\""} +cryptography = ">=3.2.1,<47.0.0" +pyOpenSSL = ">=17.5.0,<27.0.0" +python-dateutil = ">=2.5.3,<3.0.0" +pytz = ">=2016.10" +urllib3 = {version = ">=2.6.3", markers = "python_version >= \"3.10.0\""} + +[package.extras] +adk = ["docstring-parser (>=0.16) ; python_version >= \"3.10\" and python_version < \"4\"", "mcp (>=1.6.0) ; python_version >= \"3.10\" and python_version < \"4\"", "pydantic (>=2.10.6) ; python_version >= \"3.10\" and python_version < \"4\"", "rich (>=13.9.4) ; python_version >= \"3.10\" and python_version < \"4\""] + [[package]] name = "packaging" version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -1138,6 +1375,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1153,6 +1391,8 @@ version = "0.4.1" description = "Accelerated property cache" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, @@ -1278,12 +1518,26 @@ files = [ {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, ] +[[package]] +name = "pycparser" +version = "3.0" +description = "C parser in Python" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\" and extra == \"oci\" and implementation_name != \"PyPy\"" +files = [ + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, +] + [[package]] name = "pydantic" version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, @@ -1297,7 +1551,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1305,6 +1559,7 @@ version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] 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"}, @@ -1434,24 +1689,47 @@ typing-extensions = ">=4.14.1" [[package]] name = "pygments" -version = "2.20.0" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, + {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 = "pyopenssl" +version = "26.0.0" +description = "Python wrapper module around the OpenSSL library" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"oci\"" +files = [ + {file = "pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81"}, + {file = "pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc"}, +] + +[package.dependencies] +cryptography = ">=46.0.0,<47" +typing-extensions = {version = ">=4.9", markers = "python_version < \"3.13\" and python_version >= \"3.8\""} + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"] +test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] + [[package]] name = "pytest" version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -1475,6 +1753,7 @@ version = "1.3.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, @@ -1495,6 +1774,7 @@ version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -1515,20 +1795,36 @@ 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", "dev"] 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"}, ] +markers = {main = "extra == \"oci\""} [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2026.1.post1" +description = "World timezone definitions, modern and historical" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"oci\"" +files = [ + {file = "pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a"}, + {file = "pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1"}, +] + [[package]] name = "pyyaml" version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -1607,24 +1903,25 @@ files = [ [[package]] name = "requests" -version = "2.33.1" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, - {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, + {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 = ">=2023.5.7" +certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.26,<3" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" @@ -1632,6 +1929,7 @@ version = "14.3.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"}, {file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"}, @@ -1650,6 +1948,7 @@ version = "0.11.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, @@ -1677,6 +1976,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -1688,10 +1988,12 @@ 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", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +markers = {main = "extra == \"oci\""} [[package]] name = "tokenizers" @@ -1699,6 +2001,7 @@ version = "0.22.2" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c"}, {file = "tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001"}, @@ -1736,58 +2039,60 @@ testing = ["datasets", "numpy", "pytest", "pytest-asyncio", "requests", "ruff", [[package]] name = "tomli" -version = "2.4.1" +version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] [[package]] @@ -1796,6 +2101,7 @@ version = "4.67.3" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, @@ -1813,41 +2119,59 @@ telegram = ["requests"] [[package]] name = "typer" -version = "0.24.1" +version = "0.23.2" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e"}, - {file = "typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45"}, + {file = "typer-0.23.2-py3-none-any.whl", hash = "sha256:e9c8dc380f82450b3c851a9b9d5a0edf95d1d6456ae70c517d8b06a50c7a9978"}, + {file = "typer-0.23.2.tar.gz", hash = "sha256:a99706a08e54f1aef8bb6a8611503808188a4092808e86addff1828a208af0de"}, ] [package.dependencies] annotated-doc = ">=0.0.2" -click = ">=8.2.1" +click = {version = ">=8.2.1", markers = "python_version >= \"3.10\""} rich = ">=12.3.0" shellingham = ">=1.3.0" +[[package]] +name = "typer-slim" +version = "0.23.2" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typer_slim-0.23.2-py3-none-any.whl", hash = "sha256:2bc3f67ac58fe40763b414c3c65f6fcc92c6b81393d0d89339663aa69252c688"}, + {file = "typer_slim-0.23.2.tar.gz", hash = "sha256:19714179f4717a891650d8d5d062e990a0a1ed23e8d6e0f502f5a800802b3cdf"}, +] + +[package.dependencies] +typer = ">=0.23.2" + [[package]] name = "types-python-dateutil" -version = "2.9.0.20260408" +version = "2.9.0.20260124" description = "Typing stubs for python-dateutil" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, + {file = "types_python_dateutil-2.9.0.20260124-py3-none-any.whl", hash = "sha256:f802977ae08bf2260142e7ca1ab9d4403772a254409f7bbdf652229997124951"}, + {file = "types_python_dateutil-2.9.0.20260124.tar.gz", hash = "sha256:7d2db9f860820c30e5b8152bfe78dbdf795f7d1c6176057424e8b3fdd1f581af"}, ] [[package]] name = "types-requests" -version = "2.33.0.20260408" +version = "2.32.4.20260107" description = "Typing stubs for requests" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"}, - {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"}, + {file = "types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d"}, + {file = "types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f"}, ] [package.dependencies] @@ -1859,6 +2183,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] 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"}, @@ -1870,6 +2195,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -1884,16 +2210,17 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "yarl" @@ -1901,6 +2228,8 @@ version = "1.23.0" description = "Yet another URL library" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" files = [ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, @@ -2039,8 +2368,9 @@ propcache = ">=0.2.1" [extras] aiohttp = ["aiohttp", "httpx-aiohttp"] +oci = ["oci"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "5d79733d0bf323183bb79b1ec5963366a21ebf672184d773df118d40c9e941a3" +content-hash = "fd5eef56a37c96cc23d0f3f8fa9719491b68c4dfac900cf4747108061dd2e219" diff --git a/pyproject.toml b/pyproject.toml index b70fc60ac..db964b4f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ oci = { version = "^2.165.0", optional = true } [tool.poetry.extras] oci = ["oci"] +aiohttp=["aiohttp", "httpx-aiohttp"] [tool.poetry.group.dev.dependencies] mypy = "==1.13.0" @@ -99,6 +100,3 @@ section-order = ["future", "standard-library", "third-party", "first-party"] [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" - -[tool.poetry.extras] -aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/src/cohere/manually_maintained/lazy_oci_deps.py b/src/cohere/manually_maintained/lazy_oci_deps.py index 072d028b8..430a1b8be 100644 --- a/src/cohere/manually_maintained/lazy_oci_deps.py +++ b/src/cohere/manually_maintained/lazy_oci_deps.py @@ -24,7 +24,7 @@ def lazy_oci() -> Any: ImportError: If the OCI SDK is not installed """ try: - import oci + import oci # type: ignore[import-untyped] return oci except ImportError: raise ImportError(OCI_INSTALLATION_MESSAGE) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 796223c52..6bac6ca2a 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -530,7 +530,7 @@ def _hook(response: httpx.Response) -> None: # For streaming responses, wrap the stream with a transformer if is_stream: - original_stream = response.stream + original_stream = typing.cast(typing.Iterator[bytes], response.stream) transformed_stream = transform_oci_stream_wrapper(original_stream, endpoint, is_v2) response.stream = Streamer(transformed_stream) # Reset consumption flags @@ -640,7 +640,7 @@ def transform_request_to_oci( Returns: Transformed request body in OCI format """ - model = normalize_model_for_oci(cohere_body.get("model")) + model = normalize_model_for_oci(cohere_body.get("model", "")) if endpoint == "embed": if "texts" in cohere_body: diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 45a682213..167c6b268 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -55,19 +55,19 @@ if "tokenizers" not in sys.modules: tokenizers_stub = types.ModuleType("tokenizers") - tokenizers_stub.Tokenizer = object + tokenizers_stub.Tokenizer = object # type: ignore[attr-defined] sys.modules["tokenizers"] = tokenizers_stub if "fastavro" not in sys.modules: fastavro_stub = types.ModuleType("fastavro") - fastavro_stub.parse_schema = lambda schema: schema - fastavro_stub.reader = lambda *args, **kwargs: iter(()) - fastavro_stub.writer = lambda *args, **kwargs: None + fastavro_stub.parse_schema = lambda schema: schema # type: ignore[attr-defined] + fastavro_stub.reader = lambda *args, **kwargs: iter(()) # type: ignore[attr-defined] + fastavro_stub.writer = lambda *args, **kwargs: None # type: ignore[attr-defined] sys.modules["fastavro"] = fastavro_stub if "httpx_sse" not in sys.modules: httpx_sse_stub = types.ModuleType("httpx_sse") - httpx_sse_stub.connect_sse = lambda *args, **kwargs: None + httpx_sse_stub.connect_sse = lambda *args, **kwargs: None # type: ignore[attr-defined] sys.modules["httpx_sse"] = httpx_sse_stub diff --git a/tests/test_oci_mypy.py b/tests/test_oci_mypy.py new file mode 100644 index 000000000..a91c7959d --- /dev/null +++ b/tests/test_oci_mypy.py @@ -0,0 +1,65 @@ +"""Mypy type-checking gate for OCI client code. + +Runs mypy on OCI source and test files and fails if any type errors are found. +This prevents type regressions from being introduced silently. + +Run with: + pytest tests/test_oci_mypy.py +""" + +import os +import shutil +import subprocess +import unittest + +MYPY_BIN = shutil.which("mypy") + +# Files that must stay mypy-clean +OCI_SOURCE_FILES = [ + "src/cohere/oci_client.py", + "src/cohere/manually_maintained/lazy_oci_deps.py", +] + +OCI_TEST_FILES = [ + "tests/test_oci_client.py", +] + +# --follow-imports=silent prevents mypy from crawling into transitive +# dependencies (e.g. the AWS client) that have pre-existing errors. +_MYPY_BASE = [ + "--config-file", "mypy.ini", + "--follow-imports=silent", +] + + +def _run_mypy(files: list[str], extra_env: dict[str, str] | None = None) -> tuple[int, str]: + """Run mypy on the given files and return (exit_code, output).""" + assert MYPY_BIN is not None + env = {**os.environ, **(extra_env or {})} + result = subprocess.run( + [MYPY_BIN, *_MYPY_BASE, *files], + capture_output=True, + text=True, + env=env, + ) + return result.returncode, (result.stdout + result.stderr).strip() + + +@unittest.skipIf(MYPY_BIN is None, "mypy not found on PATH") +class TestOciMypy(unittest.TestCase): + """Ensure OCI files pass mypy with no new errors.""" + + def test_oci_source_types(self): + """OCI source files must be free of mypy errors.""" + code, output = _run_mypy(OCI_SOURCE_FILES) + self.assertEqual(code, 0, f"mypy found type errors in OCI source:\n{output}") + + def test_oci_test_types(self): + """OCI test files must be free of mypy errors.""" + # PYTHONPATH=src so mypy can resolve `import cohere` + code, output = _run_mypy(OCI_TEST_FILES, extra_env={"PYTHONPATH": "src"}) + self.assertEqual(code, 0, f"mypy found type errors in OCI tests:\n{output}") + + +if __name__ == "__main__": + unittest.main() From bb9d3a4e08b33b0fc867b851d04a641c4ac3413c Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 9 Apr 2026 15:20:49 -0400 Subject: [PATCH 10/13] fix(oci): add response_type to embed response for SDK discriminated union The SDK's EmbedResponse is a discriminated union on response_type (embeddings_floats vs embeddings_by_type). The OCI embed response transformation was missing this field, causing pydantic to return None instead of an EmbedResponse object. This broke V1 embed when the SDK's merge_embed_responses tried to access .meta on None. V1 (flat float arrays) now returns response_type="embeddings_floats", V2 (typed dict) returns response_type="embeddings_by_type". --- src/cohere/oci_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index 6bac6ca2a..af2308001 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -878,7 +878,10 @@ def transform_oci_response_to_cohere( if "billed_units" in usage: meta["billed_units"] = usage["billed_units"] + response_type = "embeddings_by_type" if is_v2 else "embeddings_floats" + return { + "response_type": response_type, "id": oci_response.get("id", str(uuid.uuid4())), "embeddings": embeddings, "texts": [], From 49f4c34be7d29d8d70d206a4f841bf98806b27d7 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 9 Apr 2026 15:29:58 -0400 Subject: [PATCH 11/13] test(oci): assert response_type on embed responses (unit + integration) Adds unit tests for V1 (embeddings_floats) and V2 (embeddings_by_type) response_type presence, and adds assertions to the live integration embed tests. Ensures the discriminated union field can't be silently removed without test failure. --- tests/test_oci_client.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 167c6b268..3e7db92fa 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -100,6 +100,7 @@ def test_embed(self): self.assertIsNotNone(response.embeddings) self.assertEqual(len(response.embeddings), 2) self.assertEqual(len(response.embeddings[0]), 1024) + self.assertEqual(response.response_type, "embeddings_floats") def test_chat(self): """Test V1 chat with Command R.""" @@ -159,6 +160,7 @@ def test_embed_v2(self): self.assertEqual(len(response.embeddings.float_), 2) # Verify embedding dimensions (1024 for embed-english-v3.0) self.assertEqual(len(response.embeddings.float_[0]), 1024) + self.assertEqual(response.response_type, "embeddings_by_type") def test_embed_with_model_prefix_v2(self): """Test embedding with 'cohere.' model prefix on v2 client.""" @@ -1023,6 +1025,38 @@ def test_embed_response_lowercases_embedding_keys(self): self.assertNotIn("FLOAT", result["embeddings"]) self.assertEqual(result["meta"]["tokens"]["output_tokens"], 7) + def test_embed_response_includes_response_type_v1(self): + """Test V1 embed response includes response_type=embeddings_floats for SDK union.""" + from cohere.oci_client import transform_oci_response_to_cohere + + result = transform_oci_response_to_cohere( + "embed", + { + "id": "embed-id", + "embeddings": [[0.1, 0.2]], + "usage": {"inputTokens": 3, "completionTokens": 0}, + }, + is_v2=False, + ) + + self.assertEqual(result["response_type"], "embeddings_floats") + + def test_embed_response_includes_response_type_v2(self): + """Test V2 embed response includes response_type=embeddings_by_type for SDK union.""" + from cohere.oci_client import transform_oci_response_to_cohere + + result = transform_oci_response_to_cohere( + "embed", + { + "id": "embed-id", + "embeddings": {"FLOAT": [[0.1, 0.2]]}, + "usage": {"inputTokens": 3, "completionTokens": 0}, + }, + is_v2=True, + ) + + self.assertEqual(result["response_type"], "embeddings_by_type") + def test_normalize_model_for_oci_rejects_empty_model(self): """Test model normalization fails clearly for empty model names.""" from cohere.oci_client import normalize_model_for_oci From b1ed6d58a14023ceed91a44011c973bbe24140a9 Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 9 Apr 2026 15:33:44 -0400 Subject: [PATCH 12/13] =?UTF-8?q?fix(oci):=20address=20Bugbot=20review=20?= =?UTF-8?q?=E2=80=94=20deduplicate=20Streamer=20and=20add=20V1=20stream-st?= =?UTF-8?q?art?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove duplicate Streamer class (manually_maintained/streaming.py) and import from aws_client.py instead. Both were identical SyncByteStream wrappers. 2. Emit stream-start event with generation_id at the beginning of V1 streams, matching the standard Cohere V1 streaming chat format. Consumers relying on stream-start for state initialization will now receive it before text-generation events. Updated test_v1_stream_wrapper_preserves_finish_reason to verify stream-start is emitted first. --- src/cohere/manually_maintained/streaming.py | 15 --------------- src/cohere/oci_client.py | 19 +++++++++++++------ tests/test_oci_client.py | 10 +++++++--- 3 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 src/cohere/manually_maintained/streaming.py diff --git a/src/cohere/manually_maintained/streaming.py b/src/cohere/manually_maintained/streaming.py deleted file mode 100644 index 88e513a34..000000000 --- a/src/cohere/manually_maintained/streaming.py +++ /dev/null @@ -1,15 +0,0 @@ -import typing - -from httpx import SyncByteStream - - -class Streamer(SyncByteStream): - """Wrap an iterator of bytes for httpx streaming responses.""" - - lines: typing.Iterator[bytes] - - def __init__(self, lines: typing.Iterator[bytes]): - self.lines = lines - - def __iter__(self) -> typing.Iterator[bytes]: - return self.lines diff --git a/src/cohere/oci_client.py b/src/cohere/oci_client.py index af2308001..698145416 100644 --- a/src/cohere/oci_client.py +++ b/src/cohere/oci_client.py @@ -11,8 +11,8 @@ import requests from .client import Client, ClientEnvironment from .client_v2 import ClientV2 +from .aws_client import Streamer from .manually_maintained.lazy_oci_deps import lazy_oci -from .manually_maintained.streaming import Streamer from httpx import URL, ByteStream @@ -1038,16 +1038,22 @@ def _transform_v2_event(oci_event: typing.Dict[str, typing.Any]) -> typing.Itera final_usage = _usage_from_oci(oci_event.get("usage")) yield _emit_v2_event(cohere_event) - def _transform_v1_event(oci_event: typing.Dict[str, typing.Any]) -> bytes: - nonlocal full_v1_text, final_v1_finish_reason + def _transform_v1_event(oci_event: typing.Dict[str, typing.Any]) -> typing.Iterator[bytes]: + nonlocal emitted_start, full_v1_text, final_v1_finish_reason + if not emitted_start: + yield _emit_v1_event({ + "event_type": "stream-start", + "generation_id": generation_id, + "is_finished": False, + }) + emitted_start = True event = transform_stream_event(endpoint, oci_event, is_v2=False) if isinstance(event, dict): if event.get("event_type") == "text-generation" and event.get("text"): full_v1_text += typing.cast(str, event["text"]) if "finishReason" in oci_event: final_v1_finish_reason = oci_event.get("finishReason", final_v1_finish_reason) - return _emit_v1_event(event) - return b"" + yield _emit_v1_event(event) def _process_line(line: str) -> typing.Iterator[bytes]: if not line.startswith("data: "): @@ -1091,7 +1097,8 @@ def _process_line(line: str) -> typing.Iterator[bytes]: for event_bytes in _transform_v2_event(oci_event): yield event_bytes else: - yield _transform_v1_event(oci_event) + for event_bytes in _transform_v1_event(oci_event): + yield event_bytes except Exception as exc: raise RuntimeError(f"OCI stream event transformation failed for endpoint '{endpoint}': {exc}") from exc diff --git a/tests/test_oci_client.py b/tests/test_oci_client.py index 3e7db92fa..78b525732 100644 --- a/tests/test_oci_client.py +++ b/tests/test_oci_client.py @@ -800,9 +800,13 @@ def test_v1_stream_wrapper_preserves_finish_reason(self): for raw in transform_oci_stream_wrapper(iter(chunks), "chat", is_v2=False) ] - self.assertEqual(events[2]["event_type"], "stream-end") - self.assertEqual(events[2]["finish_reason"], "MAX_TOKENS") - self.assertEqual(events[2]["response"]["text"], "Hello world") + # First event should be stream-start with generation_id + self.assertEqual(events[0]["event_type"], "stream-start") + self.assertIn("generation_id", events[0]) + + self.assertEqual(events[3]["event_type"], "stream-end") + self.assertEqual(events[3]["finish_reason"], "MAX_TOKENS") + self.assertEqual(events[3]["response"]["text"], "Hello world") def test_transform_chat_request_tool_message_fields(self): """Test tool message fields are converted to OCI names.""" From 17d4647ac56b504b48ec8c0b64e96f64461423df Mon Sep 17 00:00:00 2001 From: Federico Kamelhar Date: Thu, 9 Apr 2026 15:51:02 -0400 Subject: [PATCH 13/13] fix(oci): cover both import-untyped and import-not-found for oci SDK When oci is installed but lacks stubs, mypy raises import-untyped. When oci is not installed (optional dep), mypy raises import-not-found. Cover both cases since cohere[oci] is optional. --- src/cohere/manually_maintained/lazy_oci_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cohere/manually_maintained/lazy_oci_deps.py b/src/cohere/manually_maintained/lazy_oci_deps.py index 430a1b8be..8517e61fa 100644 --- a/src/cohere/manually_maintained/lazy_oci_deps.py +++ b/src/cohere/manually_maintained/lazy_oci_deps.py @@ -24,7 +24,7 @@ def lazy_oci() -> Any: ImportError: If the OCI SDK is not installed """ try: - import oci # type: ignore[import-untyped] + import oci # type: ignore[import-untyped, import-not-found] return oci except ImportError: raise ImportError(OCI_INSTALLATION_MESSAGE)