From 8dc49a54a875cbf020d75133bb63d0dba7341203 Mon Sep 17 00:00:00 2001 From: eosho Date: Mon, 29 Dec 2025 18:35:30 -0500 Subject: [PATCH 1/3] (feat): support visualization via Azure Container Apps Dynamic Sessions --- .env.example | 7 + README.md | 3 + docs/CONFIGURATION.md | 21 ++ docs/VISUALIZATION.md | 226 +++++++++++ docs/data_agent_graph.png | Bin 19852 -> 33822 bytes pyproject.toml | 3 + src/data_agent/agent.py | 3 + src/data_agent/config.py | 41 ++ src/data_agent/config/amex.yaml | 9 +- .../config/schema/agent_config.schema.json | 19 + src/data_agent/config_loader.py | 4 + src/data_agent/executors/__init__.py | 59 +++ src/data_agent/executors/azure_sessions.py | 155 ++++++++ src/data_agent/executors/base.py | 86 +++++ src/data_agent/graph.py | 32 +- src/data_agent/models/outputs.py | 5 + src/data_agent/models/state.py | 14 + src/data_agent/nodes/data_nodes.py | 42 +-- src/data_agent/nodes/response.py | 14 +- src/data_agent/nodes/visualization.py | 153 ++++++++ src/data_agent/prompts/__init__.py | 15 + src/data_agent/prompts/defaults.py | 66 ++++ src/data_agent/ui/app.py | 46 ++- uv.lock | 353 ++++++++++++++++++ 24 files changed, 1336 insertions(+), 40 deletions(-) create mode 100644 docs/VISUALIZATION.md create mode 100644 src/data_agent/executors/__init__.py create mode 100644 src/data_agent/executors/azure_sessions.py create mode 100644 src/data_agent/executors/base.py create mode 100644 src/data_agent/nodes/visualization.py create mode 100644 src/data_agent/prompts/__init__.py create mode 100644 src/data_agent/prompts/defaults.py diff --git a/.env.example b/.env.example index a707a76..0987c7f 100644 --- a/.env.example +++ b/.env.example @@ -87,3 +87,10 @@ BIGQUERY_DATASET=your-dataset BIGQUERY_LOCATION=US GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json BIGQUERY_CREDENTIALS_JSON= + +# ============================================================================= +# Visualization (Optional) +# Requires Azure Container Apps Dynamic Sessions for secure code execution. +# See docs/CONFIGURATION.md for setup instructions. +# ============================================================================= +AZURE_SESSIONS_POOL_ENDPOINT=https://eastus.dynamicsessions.io/subscriptions/xxx/resourceGroups/xxx/sessionPools/xxx diff --git a/README.md b/README.md index 20b0995..3486cd6 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Built on top of LangChain's [`SQLDatabase`](https://docs.langchain.com/oss/pytho - **Intent Detection**: Automatically routes queries to the correct data agent based on question context - **Multi-Turn Conversations**: Follow-up questions with context awareness (e.g., "What's the average?" after a query) - **SQL Validation**: Safe query execution with sqlglot-based validation across all dialects +- **Data Visualization**: Generate charts and graphs from query results using natural language (e.g., "show me a bar chart") - **Configurable Agents**: YAML-based configuration for adding new data sources - **A2A Protocol**: Agent-to-Agent interoperability for integration with other A2A-compliant systems @@ -49,6 +50,7 @@ Generates, validates, and executes SQL queries with retry logic. - [Database Setup](docs/DATABASE_SETUP.md) - [Configuration](docs/CONFIGURATION.md) +- [Data Visualization](docs/VISUALIZATION.md) - [A2A Protocol](docs/A2A.md) ## Quick Start @@ -163,6 +165,7 @@ data-agent chat -c adventure_works 1. What are the total deposits by customer segment? 2. Show me all high-severity fraud alerts from the past week 3. Who are the top 5 customers by transaction volume? +4. Show me a bar chart of transactions by type ```bash data-agent query "What are the total deposits by customer segment?" -c amex diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 1bb8d81..aaa17ba 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -35,6 +35,8 @@ data_agents: blocked_functions: - pg_sleep - pg_read_file + code_interpreter: + enabled: true system_prompt: | You are an SQL assistant... {schema_context} @@ -54,6 +56,25 @@ data_agents: answer: "There are 1,234 users." ``` +## Code Interpreter (Data Visualization) + +Enable the code interpreter to generate charts and visualizations from query results. When enabled, the LLM can detect visualization intent (e.g., "show me a chart", "visualize", "plot") and generate matplotlib code to create charts. + +```yaml +code_interpreter: + enabled: true + azure_sessions_endpoint: ${AZURE_SESSIONS_POOL_ENDPOINT} +``` + +| Setting | Description | Default | +|---------|-------------|---------| +| `enabled` | Enable/disable visualization generation | `false` | +| `azure_sessions_endpoint` | Azure Container Apps session pool management endpoint URL | - | + +**Note:** Visualization requires Azure Container Apps Dynamic Sessions for secure, isolated code execution. + +See [VISUALIZATION.md](VISUALIZATION.md) for complete setup instructions, architecture details, and troubleshooting. + ## SQL Validation Each data agent can configure SQL validation settings to control query safety: diff --git a/docs/VISUALIZATION.md b/docs/VISUALIZATION.md new file mode 100644 index 0000000..0797446 --- /dev/null +++ b/docs/VISUALIZATION.md @@ -0,0 +1,226 @@ +# Data Visualization + +This guide covers the code interpreter feature for generating charts and visualizations from query results. + +## Overview + +When enabled, the data agent can detect visualization intent in user queries (e.g., "show me a chart", "plot the data") and generate matplotlib code to create charts. The code runs in a secure, isolated environment using Azure Container Apps Dynamic Sessions. + +**Key features:** +- Automatic detection of visualization requests +- LLM-generated matplotlib code +- Secure sandboxed execution with Hyper-V isolation +- Native image capture (no file storage) +- Support for bar charts, line charts, pie charts, scatter plots, and more + +## Requirements + +Visualization requires Azure Container Apps Dynamic Sessions. This provides: + +| Feature | Benefit | +|---------|---------| +| **Hyper-V isolation** | Each execution runs in a dedicated VM | +| **Pre-installed packages** | NumPy, Pandas, Matplotlib ready to use | +| **Native image capture** | `plt.show()` output captured automatically | +| **Automatic cleanup** | Sessions terminate after idle timeout | +| **No host access** | Code cannot access host filesystem or network | + +## Azure Setup + +### 1. Create a Container Apps Environment + +If you don't already have one: + +```bash +az containerapp env create \ + --name aca-env \ + --resource-group rg-data-agent \ + --location eastus +``` + +### 2. Create the Session Pool + +```bash +az containerapp sessionpool create \ + --name session-pool-viz \ + --resource-group rg-data-agent \ + --container-type PythonLTS \ + --max-sessions 100 \ + --cooldown-period 300 \ + --location eastus +``` + +**Parameters:** +- `--container-type PythonLTS`: Python runtime with common data science packages +- `--max-sessions`: Maximum concurrent sessions +- `--cooldown-period`: Seconds before idle session is terminated + +### 3. Get the Pool Management Endpoint + +```bash +az containerapp sessionpool show \ + --name session-pool-viz \ + --resource-group rg-data-agent \ + --query "properties.poolManagementEndpoint" -o tsv +``` + +This returns a URL like: +``` +https://eastus.dynamicsessions.io/subscriptions//resourceGroups//sessionPools/ +``` + +### 4. Assign the Executor Role + +Grant your identity permission to execute code in the session pool: + +```bash +# Get your user ID +USER_ID=$(az ad signed-in-user show --query id -o tsv) + +# Get the session pool resource ID +POOL_ID=$(az containerapp sessionpool show \ + --name session-pool-viz \ + --resource-group rg-data-agent \ + --query id -o tsv) + +# Assign the role +az role assignment create \ + --role "Azure ContainerApps Session Executor" \ + --assignee $USER_ID \ + --scope $POOL_ID +``` + +**Note:** For service principals or managed identities, replace `$USER_ID` with the appropriate object ID. + +### 5. Install the SDK + +```bash +pip install langchain-azure-dynamic-sessions +``` + +Or add to your `pyproject.toml`: +```toml +dependencies = [ + "langchain-azure-dynamic-sessions>=0.1.0", +] +``` + +## Configuration + +### Environment Variable + +Set the pool endpoint: + +```bash +export AZURE_SESSIONS_POOL_ENDPOINT="https://eastus.dynamicsessions.io/subscriptions/.../sessionPools/..." +``` + +Or in `.env`: +```bash +AZURE_SESSIONS_POOL_ENDPOINT=https://eastus.dynamicsessions.io/subscriptions/.../sessionPools/... +``` + +### YAML Configuration + +Enable visualization in your agent config: + +```yaml +data_agents: + - name: "sales_agent" + # ... other config ... + code_interpreter: + enabled: true + azure_sessions_endpoint: ${AZURE_SESSIONS_POOL_ENDPOINT} +``` + +| Setting | Description | Default | +|---------|-------------|---------| +| `enabled` | Enable/disable visualization | `false` | +| `azure_sessions_endpoint` | Session pool management endpoint URL | - | + +### System Prompt + +To enable visualization detection, include `visualization_requested` in your response format: + +```yaml +system_prompt: | + You are a SQL expert for the sales database. + + {schema_context} + + ## Response Format + + Provide your response as JSON with these fields: + - "thinking": Step-by-step reasoning about the query + - "sql_query": The generated SQL query + - "explanation": Brief explanation of what the query does + - "visualization_requested": Set to true if the user asks for a chart, graph, plot, or visualization +``` + +## How It Works + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ User Query │ +│ "Show me a bar chart of sales by region" │ +└─────────────────────────────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SQL Generation LLM │ +│ Generates SQL + sets visualization_requested: true │ +└─────────────────────────────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Database Query │ +│ Execute SQL, return result rows │ +└─────────────────────────────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Visualization LLM │ +│ Generates matplotlib code based on data + user question │ +└─────────────────────────────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Azure Container Apps Dynamic Sessions │ +│ • Code executed in Hyper-V isolated container │ +│ • plt.show() output captured automatically │ +│ • Image returned as base64 PNG │ +└─────────────────────────────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Response │ +│ Text explanation + embedded chart image │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Execution Flow + +1. **Intent Detection**: The SQL LLM sets `visualization_requested: true` when it detects chart/graph/plot intent +2. **SQL Execution**: Query runs against the database, returning structured data +3. **Code Generation**: A second LLM call generates matplotlib code tailored to the data and question +4. **Sandboxed Execution**: Code runs in Azure Sessions with automatic image capture +5. **Response Assembly**: Text response and chart image are combined for display + +## Example Queries + +These prompts trigger visualization: + +| Query | Chart Type | +|-------|------------| +| "Show me a bar chart of sales by region" | Bar chart | +| "Visualize the top 10 customers by revenue" | Horizontal bar | +| "Plot monthly revenue trends for 2024" | Line chart | +| "Create a pie chart of transaction types" | Pie chart | +| "Graph the distribution of order values" | Histogram | +| "Compare Q1 vs Q2 performance" | Grouped bar | + +## Further Reading + +- [Azure Container Apps Dynamic Sessions](https://learn.microsoft.com/azure/container-apps/sessions) +- [Session Pool Management](https://learn.microsoft.com/azure/container-apps/sessions-code-interpreter) +- [LangChain Azure Dynamic Sessions](https://python.langchain.com/docs/integrations/tools/azure_dynamic_sessions) diff --git a/docs/data_agent_graph.png b/docs/data_agent_graph.png index 30175b77edde2af060b8c5f4442ac6c5be4728f2..5ab8faee67a20bdc25d1a39fa21b9995d2ed8d03 100644 GIT binary patch literal 33822 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 z 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 rN9pj+d+c$|7cO4Ez%(=2.0.8", "google-cloud-bigquery-storage>=2.36.0", "psycopg2>=2.9.11", + "langchain-experimental>=0.4.1", + "langchain-azure-dynamic-sessions>=0.2.0", + "matplotlib>=3.10.8", ] [project.scripts] diff --git a/src/data_agent/agent.py b/src/data_agent/agent.py index a24d757..0b9fd02 100644 --- a/src/data_agent/agent.py +++ b/src/data_agent/agent.py @@ -561,6 +561,9 @@ async def run( datasource_name=result.get("datasource_name", ""), rewritten_question=result.get("rewritten_question", ""), messages=result.get("messages", []), + visualization_image=result.get("visualization_image"), + visualization_code=result.get("visualization_code"), + visualization_error=result.get("visualization_error"), ) def get_agent_names(self) -> list[str]: diff --git a/src/data_agent/config.py b/src/data_agent/config.py index ed90702..e411e25 100644 --- a/src/data_agent/config.py +++ b/src/data_agent/config.py @@ -322,6 +322,44 @@ def from_dict(cls, data: dict[str, Any]) -> "ValidationConfig": ) +@dataclass +class CodeInterpreterConfig: + """Configuration for code interpreter / visualization feature. + + Visualization requires deploying an Azure Container Apps session pool + for secure, isolated code execution. See docs/CONFIGURATION.md. + + Attributes: + enabled: Whether to enable the code interpreter feature. + azure_sessions_endpoint: Pool management endpoint for Azure Sessions. + Can also be set via AZURE_SESSIONS_POOL_ENDPOINT environment variable. + """ + + enabled: bool = False + azure_sessions_endpoint: str | None = None + + def __post_init__(self) -> None: + """Validate configuration.""" + import os + + if self.enabled: + endpoint = self.azure_sessions_endpoint or os.getenv( + "AZURE_SESSIONS_POOL_ENDPOINT" + ) + if not endpoint: + raise ValueError( + "azure_sessions_endpoint is required when code_interpreter is enabled. " + "Set in config or via AZURE_SESSIONS_POOL_ENDPOINT environment variable." + ) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "CodeInterpreterConfig": + return cls( + enabled=data.get("enabled", False), + azure_sessions_endpoint=data.get("azure_sessions_endpoint"), + ) + + @dataclass class DataAgentConfig: """Configuration for a single data agent.""" @@ -331,6 +369,9 @@ class DataAgentConfig: datasource: Datasource | None = None llm_config: LLMConfig = field(default_factory=LLMConfig) validation_config: ValidationConfig = field(default_factory=ValidationConfig) + code_interpreter: CodeInterpreterConfig = field( + default_factory=CodeInterpreterConfig + ) system_prompt: str = "" response_prompt: str = "" table_schemas: list[TableSchema] = field(default_factory=list) diff --git a/src/data_agent/config/amex.yaml b/src/data_agent/config/amex.yaml index dfa0f8b..f7650e1 100644 --- a/src/data_agent/config/amex.yaml +++ b/src/data_agent/config/amex.yaml @@ -42,6 +42,11 @@ data_agents: blocked_functions: - session_user - external_query + # Enable code interpreter for data visualization + # Requires Azure Container Apps Dynamic Sessions for secure execution + code_interpreter: + enabled: true + azure_sessions_endpoint: https://eastus.dynamicsessions.io/subscriptions/e98a7bdd-1e97-452c-939c-4edf569d31f6/resourceGroups/fresh-mcp-rg/sessionPools/session-pool-viz system_prompt: | You are an expert SQL assistant for a financial transactions database running on Google BigQuery. @@ -77,9 +82,11 @@ data_agents: ## Response Format - Provide your response as JSON with two fields: + Provide your response as JSON with these fields: + - "thinking": Step-by-step reasoning about how to construct the query - "sql_query": The generated BigQuery SQL query - "explanation": Brief explanation of what the query does + - "visualization_requested": Set to true if the user is asking for a chart, graph, plot, bar chart, pie chart, line chart, histogram, or any visual representation of the data. Set to false for plain data queries. response_prompt: | You are a helpful financial analyst for banking and transaction data. diff --git a/src/data_agent/config/schema/agent_config.schema.json b/src/data_agent/config/schema/agent_config.schema.json index 98e601f..21c6c0a 100644 --- a/src/data_agent/config/schema/agent_config.schema.json +++ b/src/data_agent/config/schema/agent_config.schema.json @@ -488,6 +488,22 @@ } } }, + "code_interpreter_config": { + "type": "object", + "description": "Code interpreter configuration for data visualization. Requires Azure Container Apps Dynamic Sessions for secure, isolated code execution.", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable code interpreter for visualization. Requires azure_sessions_endpoint to be configured.", + "default": false + }, + "azure_sessions_endpoint": { + "type": "string", + "description": "Azure Container Apps session pool management endpoint URL. Required when enabled. Can also be set via AZURE_SESSIONS_POOL_ENDPOINT environment variable." + } + } + }, "data_agent_config": { "type": "object", "description": "Configuration for a single data agent", @@ -510,6 +526,9 @@ "validation": { "$ref": "#/$defs/validation_config" }, + "code_interpreter": { + "$ref": "#/$defs/code_interpreter_config" + }, "system_prompt": { "type": "string", "description": "System prompt for SQL generation" diff --git a/src/data_agent/config_loader.py b/src/data_agent/config_loader.py index c109912..9e1a28e 100644 --- a/src/data_agent/config_loader.py +++ b/src/data_agent/config_loader.py @@ -16,6 +16,7 @@ CONFIG_DIR, DATASOURCE_TYPES, AgentConfig, + CodeInterpreterConfig, DataAgentConfig, Datasource, FewShotExample, @@ -166,6 +167,9 @@ def _parse_data_agent(cls, data: dict[str, Any]) -> DataAgentConfig: datasource=cls._parse_datasource(data.get("datasource")), llm_config=LLMConfig.from_dict(data.get("llm", {})), validation_config=ValidationConfig.from_dict(data.get("validation", {})), + code_interpreter=CodeInterpreterConfig.from_dict( + data.get("code_interpreter", {}) + ), system_prompt=data.get("system_prompt", ""), response_prompt=data.get("response_prompt", ""), table_schemas=[ diff --git a/src/data_agent/executors/__init__.py b/src/data_agent/executors/__init__.py new file mode 100644 index 0000000..ccf95c0 --- /dev/null +++ b/src/data_agent/executors/__init__.py @@ -0,0 +1,59 @@ +"""Code execution backends for sandboxed Python execution. + +This module provides the executor for running LLM-generated Python code +in Azure Container Apps Dynamic Sessions with Hyper-V isolation. + +Usage: + from data_agent.executors import create_executor + + # Create from configuration + executor = create_executor(config.code_interpreter) + + # Execute code + result = await executor.execute("print('Hello')") + if result.success: + print(result.output) + +Note: + Visualization support requires deploying an Azure Container Apps + session pool. See docs/CONFIGURATION.md for setup instructions. +""" + +from typing import TYPE_CHECKING + +from data_agent.executors.base import CodeExecutor, ExecutionResult, ExecutionStatus + +if TYPE_CHECKING: + from data_agent.config import CodeInterpreterConfig + +__all__ = [ + "CodeExecutor", + "ExecutionResult", + "ExecutionStatus", + "create_executor", +] + + +def create_executor(config: "CodeInterpreterConfig") -> CodeExecutor: + """Create a code executor based on configuration. + + Args: + config: CodeInterpreterConfig with endpoint settings. + + Returns: + Configured AzureSessionsExecutor instance. + + Raises: + ValueError: If azure_sessions_endpoint is not configured. + TypeError: If config is not a CodeInterpreterConfig. + """ + from data_agent.config import CodeInterpreterConfig + + if not isinstance(config, CodeInterpreterConfig): + raise TypeError(f"Expected CodeInterpreterConfig, got {type(config)}") + + from data_agent.executors.azure_sessions import AzureSessionsExecutor + + return AzureSessionsExecutor( + pool_management_endpoint=config.azure_sessions_endpoint, + ) diff --git a/src/data_agent/executors/azure_sessions.py b/src/data_agent/executors/azure_sessions.py new file mode 100644 index 0000000..e78da45 --- /dev/null +++ b/src/data_agent/executors/azure_sessions.py @@ -0,0 +1,155 @@ +"""Azure Container Apps Dynamic Sessions executor. + +This module provides secure code execution using Azure Container Apps +dynamic sessions with Hyper-V isolation. + +Requires: + pip install langchain-azure-dynamic-sessions + +Environment: + AZURE_SESSIONS_POOL_ENDPOINT: Session pool management endpoint URL +""" + +import logging +import os +from base64 import b64decode +from typing import Any + +from langchain_azure_dynamic_sessions import SessionsPythonREPLTool + +from data_agent.executors.base import CodeExecutor, ExecutionResult, ExecutionStatus + +logger = logging.getLogger(__name__) + + +class AzureSessionsExecutor(CodeExecutor): + """Execute code in Azure Container Apps dynamic sessions with Hyper-V isolation. + + The tool uses DefaultAzureCredential internally for authentication. + Ensure you have the "Azure ContainerApps Session Executor" role assigned. + + Configuration: + pool_management_endpoint: Session pool endpoint URL + Can be set via config or AZURE_SESSIONS_POOL_ENDPOINT env var + + Example: + executor = AzureSessionsExecutor( + pool_management_endpoint="https://eastus.dynamicsessions.io/..." + ) + result = await executor.execute("print('Hello from Azure!')") + """ + + def __init__( + self, + pool_management_endpoint: str | None = None, + session_id: str | None = None, + ) -> None: + """Initialize Azure Sessions executor. + + Args: + pool_management_endpoint: Session pool management endpoint URL. + Falls back to AZURE_SESSIONS_POOL_ENDPOINT environment variable. + session_id: Optional session ID for session reuse. If not provided, + the tool will manage session IDs automatically. + + Raises: + ValueError: If pool_management_endpoint is not provided and + AZURE_SESSIONS_POOL_ENDPOINT is not set. + """ + self._endpoint = pool_management_endpoint or os.getenv( + "AZURE_SESSIONS_POOL_ENDPOINT" + ) + if not self._endpoint: + raise ValueError( + "pool_management_endpoint is required. " + "Set via config or AZURE_SESSIONS_POOL_ENDPOINT environment variable." + ) + + self._session_id = session_id + self._tool: SessionsPythonREPLTool | None = None + + def _get_tool(self) -> SessionsPythonREPLTool: + """Get or create the SessionsPythonREPLTool instance. + + The tool uses DefaultAzureCredential internally for authentication. + """ + if self._tool is None: + # Per Microsoft docs, just pass the endpoint - the tool handles auth + # internally using DefaultAzureCredential + assert self._endpoint is not None # Validated in __init__ + self._tool = SessionsPythonREPLTool( + pool_management_endpoint=self._endpoint, + ) + endpoint_preview = ( + self._endpoint[:50] + "..." + if len(self._endpoint) > 50 + else self._endpoint + ) + logger.debug( + "Created Azure Sessions tool with endpoint: %s", + endpoint_preview, + ) + return self._tool + + async def execute(self, code: str, timeout: float = 30.0) -> ExecutionResult: + """Execute Python code in an Azure dynamic session. + + Args: + code: Python code to execute. + timeout: Maximum execution time (note: Azure has its own limits). + + Returns: + ExecutionResult with output and any generated files. + """ + try: + tool = self._get_tool() + + # Use execute() to get raw response with artifact support + response = tool.execute(code) + + stdout = response.get("stdout", "") + stderr = response.get("stderr", "") + result = response.get("result") + + output = stdout + if stderr: + output += f"\nSTDERR: {stderr}" + + logger.debug("Azure session response: %s", _preview(response)) + + # Check if result contains an image (native matplotlib capture) + files = None + if isinstance(result, dict) and result.get("type") == "image": + base64_data = result.get("base64_data") + if base64_data: + files = {"visualization.png": b64decode(base64_data)} + logger.debug("Captured visualization image from session") + + return ExecutionResult( + status=ExecutionStatus.SUCCESS, + output=output, + files=files, + metadata={"session_id": self._session_id, "result": result}, + ) + + except Exception as e: + logger.exception("Azure session execution failed") + return ExecutionResult( + status=ExecutionStatus.ERROR, + error=str(e), + metadata={"session_id": self._session_id}, + ) + + async def cleanup(self) -> None: + """Clean up the Azure session. + + Note: Azure automatically cleans up idle sessions after timeout. + """ + self._tool = None + logger.debug("Azure sessions executor cleaned up") + + +def _preview(obj: Any, max_len: int = 200) -> str: + """Create a preview string of an object for logging.""" + s = str(obj) + return s[:max_len] + "..." if len(s) > max_len else s diff --git a/src/data_agent/executors/base.py b/src/data_agent/executors/base.py new file mode 100644 index 0000000..947840a --- /dev/null +++ b/src/data_agent/executors/base.py @@ -0,0 +1,86 @@ +"""Base interface for sandboxed code execution. + +This module defines the abstract interface for code execution backends, +providing a consistent API for different isolation strategies. +""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from enum import Enum +from typing import Any + + +class ExecutionStatus(Enum): + """Status of code execution.""" + + SUCCESS = "success" + ERROR = "error" + TIMEOUT = "timeout" + + +@dataclass +class ExecutionResult: + """Result of code execution in a sandbox. + + Attributes: + status: Execution status (success, error, timeout). + output: Standard output from code execution. + error: Error message if execution failed. + files: Dictionary of generated files {filename: content_bytes}. + metadata: Additional execution metadata (timing, resource usage). + """ + + status: ExecutionStatus + output: str = "" + error: str | None = None + files: dict[str, bytes] | None = None + metadata: dict[str, Any] = field(default_factory=dict) + + @property + def success(self) -> bool: + """Check if execution was successful.""" + return self.status == ExecutionStatus.SUCCESS + + +class CodeExecutor(ABC): + """Abstract base class for code execution backends. + + Example: + executor = AzureSessionsExecutor(pool_management_endpoint="https://...") + result = await executor.execute("print('Hello')") + if result.success: + print(result.output) + """ + + @abstractmethod + async def execute(self, code: str, timeout: float = 30.0) -> ExecutionResult: + """Execute Python code in an isolated environment. + + Args: + code: Python code to execute. + timeout: Maximum execution time in seconds. + + Returns: + ExecutionResult with output, errors, and any generated files. + """ + pass + + async def cleanup(self) -> None: + """Clean up any resources (sessions, containers). + + Override in implementations that maintain state. + """ + pass + + async def __aenter__(self) -> "CodeExecutor": + """Async context manager entry.""" + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: Any, + ) -> None: + """Async context manager exit - cleanup resources.""" + await self.cleanup() diff --git a/src/data_agent/graph.py b/src/data_agent/graph.py index feffcfe..f9c050a 100644 --- a/src/data_agent/graph.py +++ b/src/data_agent/graph.py @@ -14,9 +14,11 @@ from langgraph.graph.state import CompiledStateGraph from data_agent.config import DataAgentConfig +from data_agent.executors import create_executor from data_agent.models.state import AgentState, InputState, OutputState from data_agent.nodes.data_nodes import DataAgentNodes from data_agent.nodes.response import ResponseNode +from data_agent.nodes.visualization import VisualizationNode if TYPE_CHECKING: from langchain_community.utilities.sql_database import SQLDatabase @@ -62,6 +64,12 @@ def __init__( self._nodes = DataAgentNodes(llm, datasource, config, max_retries) self._response_node = ResponseNode(llm, config) + # Initialize visualization node if code_interpreter is enabled + self._viz_node: VisualizationNode | None = None + if config.code_interpreter.enabled: + executor = create_executor(config.code_interpreter) + self._viz_node = VisualizationNode(llm, executor) + def _should_retry(self, state: AgentState) -> str: """Determine if SQL generation should be retried. @@ -80,16 +88,20 @@ def _should_retry(self, state: AgentState) -> str: return "end" return "retry" - def _check_error(self, state: AgentState) -> str: - """Check if there's an error after query execution. + def _route_after_execute(self, state: AgentState) -> str: + """Route after query execution based on error and visualization request. Args: state: Current agent state. Returns: - 'error' if execution failed, 'success' otherwise. + 'error' if execution failed, 'visualize' if visualization requested, 'respond' otherwise. """ - return "error" if state.get("error") else "success" + if state.get("error"): + return "error" + if self._viz_node and state.get("visualization_requested", False): + return "visualize" + return "respond" def build(self) -> StateGraph: """Build the state graph without compiling. @@ -106,6 +118,8 @@ def build(self) -> StateGraph: graph.add_node("retry_sql", self._nodes.retry_sql) graph.add_node("execute_query", self._nodes.execute_query) graph.add_node("generate_response", self._response_node.generate_response) + if self._viz_node: + graph.add_node("visualize_data", self._viz_node.generate_visualization) graph.set_entry_point("generate_sql") @@ -118,9 +132,15 @@ def build(self) -> StateGraph: graph.add_edge("retry_sql", "validate_sql") graph.add_conditional_edges( "execute_query", - self._check_error, - {"error": END, "success": "generate_response"}, + self._route_after_execute, + { + "error": END, + "visualize": "visualize_data", + "respond": "generate_response", + }, ) + if self._viz_node: + graph.add_edge("visualize_data", "generate_response") graph.add_edge("generate_response", END) return graph diff --git a/src/data_agent/models/outputs.py b/src/data_agent/models/outputs.py index 3930f3d..a74345a 100644 --- a/src/data_agent/models/outputs.py +++ b/src/data_agent/models/outputs.py @@ -14,6 +14,7 @@ class SQLGeneratorOutput(BaseModel): thinking: Step-by-step reasoning about how to answer the question. sql_query: The generated SQL query. explanation: Brief explanation of what the query does. + visualization_requested: Whether the user wants a chart/graph/visualization. """ thinking: str = Field( @@ -21,6 +22,10 @@ class SQLGeneratorOutput(BaseModel): ) sql_query: str = Field(description="The SQL query that answers the user's question") explanation: str = Field(description="Brief explanation of what the query does") + visualization_requested: bool = Field( + default=False, + description="Whether the user is asking for a chart, graph, plot, or visual representation of the data", + ) class SQLValidationOutput(BaseModel): diff --git a/src/data_agent/models/state.py b/src/data_agent/models/state.py index f355a33..52f05f9 100644 --- a/src/data_agent/models/state.py +++ b/src/data_agent/models/state.py @@ -31,6 +31,9 @@ class OutputState(TypedDict): datasource_name: Name of the agent/datasource that handled the query. rewritten_question: The question after query rewriting (if different from original). messages: Accumulated LLM conversation messages (for debugging). + visualization_image: Base64-encoded PNG image of chart (if visualization was requested). + visualization_code: Generated Python code for visualization. + visualization_error: Error message if visualization generation failed. """ generated_sql: str @@ -40,6 +43,9 @@ class OutputState(TypedDict): datasource_name: NotRequired[str] rewritten_question: NotRequired[str] messages: NotRequired[list[AnyMessage]] + visualization_image: NotRequired[str | None] + visualization_code: NotRequired[str | None] + visualization_error: NotRequired[str | None] class AgentState(TypedDict): @@ -61,6 +67,10 @@ class AgentState(TypedDict): retry_count: Current retry attempt number. rewritten_question: The question after query rewriting (if different from original). validation_result: SQL validation output with status, errors, and warnings. + visualization_requested: Whether the user requested a visualization (set by generate_sql). + visualization_image: Base64-encoded PNG image of generated chart. + visualization_code: Generated Python code for visualization. + visualization_error: Error message if visualization generation failed. """ question: str @@ -75,3 +85,7 @@ class AgentState(TypedDict): retry_count: NotRequired[int] rewritten_question: NotRequired[str] validation_result: NotRequired[SQLValidationOutput | None] + visualization_requested: NotRequired[bool] + visualization_image: NotRequired[str | None] + visualization_code: NotRequired[str | None] + visualization_error: NotRequired[str | None] diff --git a/src/data_agent/nodes/data_nodes.py b/src/data_agent/nodes/data_nodes.py index 82e9086..ff9a1ee 100644 --- a/src/data_agent/nodes/data_nodes.py +++ b/src/data_agent/nodes/data_nodes.py @@ -28,31 +28,9 @@ from data_agent.adapters.azure.cosmos import CosmosAdapter from data_agent.models.state import AgentState -logger = logging.getLogger(__name__) - -DEFAULT_SQL_PROMPT = """You are a SQL expert. Generate a syntactically correct SQL query. -Limit results to 10 unless specified. Only select relevant columns. - -## Conversation Context -If this is a follow-up question, use the conversation history to understand what the user is referring to: -- When in doubt, infer context from the most recent SQL query in the conversation - -IMPORTANT: Always generate a single, executable SQL query. Never include comments, explanations, or multiple query options. - -{schema_context} - -{few_shot_examples}""" +from data_agent.prompts import COSMOS_PROMPT_ADDENDUM, DEFAULT_SQL_PROMPT -COSMOS_PROMPT_ADDENDUM = """ -Key Cosmos DB constraints: -1. Queries operate on a SINGLE container - no cross-container or cross-document joins. -2. JOIN only works WITHIN documents (to traverse arrays), not across documents. -3. Always filter on partition key ({partition_key}) for performance - avoids fan-out queries. -4. DISTINCT inside aggregate functions (COUNT, SUM, AVG) is NOT supported. -5. Aggregates without partition key filter may timeout or consume high RUs. -6. SUM/AVG return undefined if any value is string, boolean, or null. -7. Max 4MB response per page; use continuation tokens for large results. -""" +logger = logging.getLogger(__name__) class DataAgentNodes: @@ -182,7 +160,7 @@ async def generate_sql(self, state: "AgentState") -> dict[str, Any]: state: Current agent state containing the question. Returns: - State update with generated SQL, dialect, and messages. + State update with generated SQL, dialect, visualization_requested, and messages. """ question = state["question"] logger.debug("Generating query for: %s", question[:100]) @@ -201,10 +179,22 @@ async def generate_sql(self, state: "AgentState") -> dict[str, Any]: ) cleaned = clean_sql_query(sql) - logger.debug("Generated SQL: %s", cleaned) + # Extract visualization intent from LLM output + visualization_requested = ( + result.visualization_requested + if isinstance(result, SQLGeneratorOutput) + else False + ) + + logger.debug( + "Generated SQL: %s, visualization_requested: %s", + cleaned, + visualization_requested, + ) return { "generated_sql": cleaned, "dialect": self._dialect, + "visualization_requested": visualization_requested, "messages": [ AIMessage(content=f"```sql\n{cleaned}\n```", name="sql_generator"), ], diff --git a/src/data_agent/nodes/response.py b/src/data_agent/nodes/response.py index 5ed3552..348a61f 100644 --- a/src/data_agent/nodes/response.py +++ b/src/data_agent/nodes/response.py @@ -17,13 +17,9 @@ if TYPE_CHECKING: from data_agent.models.state import AgentState -logger = logging.getLogger(__name__) - -DEFAULT_RESPONSE_PROMPT = """You are a helpful data analyst. Given the user's question, -the SQL query that was executed, and the results, provide a clear and concise natural -language response that answers the user's question. +from data_agent.prompts import DEFAULT_RESPONSE_PROMPT -Be conversational but precise. Include relevant numbers and insights from the data.""" +logger = logging.getLogger(__name__) class ResponseNode: @@ -68,6 +64,12 @@ def generate_response(self, state: "AgentState") -> dict[str, Any]: sql = state.get("generated_sql", "") result = state.get("result", {}) + # Check if visualization was generated - if so, modify the prompt + # to avoid generating ASCII charts or code snippets + visualization_image = state.get("visualization_image") + if visualization_image: + prompt += "\n\nIMPORTANT: A visualization/chart has already been generated and will be displayed separately. Do NOT create ASCII charts, text-based charts, or matplotlib code snippets. Focus only on providing insights and analysis of the data." + history = get_recent_history(state.get("messages", []), max_messages=4) messages = [ diff --git a/src/data_agent/nodes/visualization.py b/src/data_agent/nodes/visualization.py new file mode 100644 index 0000000..d96b253 --- /dev/null +++ b/src/data_agent/nodes/visualization.py @@ -0,0 +1,153 @@ +"""Data visualization node using Azure Container Apps Dynamic Sessions.""" + +import base64 +import logging +import re +from typing import TYPE_CHECKING, Any + +from langchain_core.language_models import BaseChatModel +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage + +from data_agent.executors import CodeExecutor, ExecutionStatus +from data_agent.prompts import VISUALIZATION_SYSTEM_PROMPT + +if TYPE_CHECKING: + from data_agent.models.state import AgentState + +logger = logging.getLogger(__name__) + + +class VisualizationNode: + """Node for generating data visualizations using sandboxed code execution.""" + + def __init__(self, llm: BaseChatModel, executor: CodeExecutor) -> None: + """Initialize the visualization node. + + Args: + llm: Language model for code generation. + executor: Code executor backend for running generated code. + """ + self._llm = llm + self._executor = executor + + async def generate_visualization(self, state: "AgentState") -> dict[str, Any]: + """Generate a visualization from query results using LLM + PythonREPL. + + Args: + state: Current agent state with query results and visualization_requested flag. + + Returns: + State update with visualization_image (base64 PNG) or visualization_error. + """ + result = state.get("result") + if not result: + logger.warning("Visualization requested but no query result available") + return {"visualization_error": "No data to visualize"} + + if isinstance(result, dict): + rows = result.get("rows", []) + columns = result.get("columns", []) + else: + rows = getattr(result, "rows", []) + columns = getattr(result, "columns", []) + + if not rows or not columns: + logger.warning("Visualization requested but result has no data") + return {"visualization_error": "No data to visualize"} + + data = [dict(zip(columns, row)) for row in rows] + + messages = [ + SystemMessage(content=VISUALIZATION_SYSTEM_PROMPT), + HumanMessage( + content=f"""User question: {state['question']} + +Data columns: {columns} +Data ({len(data)} rows): +data = {data} + +Generate Python code to create an appropriate visualization for this data and question. +""" + ), + ] + + try: + response = await self._llm.ainvoke(messages) + + content = response.content + if isinstance(content, list): + content = "".join( + part if isinstance(part, str) else part.get("text", "") + for part in content + ) + code = self._extract_code(content) + if not code: + logger.error("Failed to extract code from LLM response") + return {"visualization_error": "Failed to generate visualization code"} + + logger.debug("Generated visualization code:\n%s", code[:500]) + + # Execute code in sandbox + exec_result = await self._executor.execute(code) + + if exec_result.status == ExecutionStatus.SUCCESS: + # Check for image in files (native capture from Azure Sessions) + if exec_result.files and "visualization.png" in exec_result.files: + img_base64 = base64.b64encode( + exec_result.files["visualization.png"] + ).decode() + logger.debug("Successfully generated visualization") + return { + "visualization_image": img_base64, + "visualization_code": code, + "messages": [ + AIMessage( + content="Generated visualization", + name="visualizer", + ) + ], + } + else: + output_preview = ( + exec_result.output[:500] if exec_result.output else "" + ) + logger.error( + "Code executed but no image output: %s", output_preview + ) + return { + "visualization_error": f"Code executed but no image: {exec_result.output[:200]}" + } + else: + logger.error("Code execution failed: %s", exec_result.error) + return {"visualization_error": exec_result.error} + + except Exception as e: + logger.exception("Visualization code execution failed") + return {"visualization_error": str(e)} + + def _extract_code(self, content: str) -> str | None: + """Extract Python code from LLM response. + + Tries to find code in: + 1. ```python ... ``` blocks + 2. ``` ... ``` blocks + 3. Raw content if it looks like Python code + + Args: + content: LLM response content. + + Returns: + Extracted Python code or None if not found. + """ + code_match = re.search(r"```python\n(.*?)```", content, re.DOTALL) + if code_match: + return code_match.group(1) + + code_match = re.search(r"```\n(.*?)```", content, re.DOTALL) + if code_match: + return code_match.group(1) + + if "import" in content and ("plt" in content or "matplotlib" in content): + return content + + return None diff --git a/src/data_agent/prompts/__init__.py b/src/data_agent/prompts/__init__.py new file mode 100644 index 0000000..ce50e04 --- /dev/null +++ b/src/data_agent/prompts/__init__.py @@ -0,0 +1,15 @@ +"""Default prompts for the data agent.""" + +from data_agent.prompts.defaults import ( + COSMOS_PROMPT_ADDENDUM, + DEFAULT_RESPONSE_PROMPT, + DEFAULT_SQL_PROMPT, + VISUALIZATION_SYSTEM_PROMPT, +) + +__all__ = [ + "COSMOS_PROMPT_ADDENDUM", + "DEFAULT_RESPONSE_PROMPT", + "DEFAULT_SQL_PROMPT", + "VISUALIZATION_SYSTEM_PROMPT", +] diff --git a/src/data_agent/prompts/defaults.py b/src/data_agent/prompts/defaults.py new file mode 100644 index 0000000..9aaa6e1 --- /dev/null +++ b/src/data_agent/prompts/defaults.py @@ -0,0 +1,66 @@ +"""Default system prompts for data agent nodes.""" + +DEFAULT_SQL_PROMPT = """You are a SQL expert. Generate a syntactically correct SQL query. +Limit results to 10 unless specified. Only select relevant columns. + +## Conversation Context +If this is a follow-up question, use the conversation history to understand what the user is referring to: +- When in doubt, infer context from the most recent SQL query in the conversation + +IMPORTANT: Always generate a single, executable SQL query. Never include comments, explanations, or multiple query options. + +{schema_context} + +{few_shot_examples}""" + +COSMOS_PROMPT_ADDENDUM = """ +Key Cosmos DB constraints: +1. Queries operate on a SINGLE container - no cross-container or cross-document joins. +2. JOIN only works WITHIN documents (to traverse arrays), not across documents. +3. Always filter on partition key ({partition_key}) for performance - avoids fan-out queries. +4. DISTINCT inside aggregate functions (COUNT, SUM, AVG) is NOT supported. +5. Aggregates without partition key filter may timeout or consume high RUs. +6. SUM/AVG return undefined if any value is string, boolean, or null. +7. Max 4MB response per page; use continuation tokens for large results. +""" + +DEFAULT_RESPONSE_PROMPT = """You are a helpful data analyst. Given the user's question, +the SQL query that was executed, and the results, provide a clear and concise natural +language response that answers the user's question. + +Be conversational but precise. Include relevant numbers and insights from the data.""" + +VISUALIZATION_SYSTEM_PROMPT = """You are a data visualization expert. Generate Python code using matplotlib to create a chart. + +## Rules +1. Use matplotlib to create visualizations +2. DO NOT use plt.style.use() with seaborn styles - they are deprecated +3. End your code with plt.show() - the image will be captured automatically +4. Use this pattern: + +```python +import matplotlib.pyplot as plt + +# ... your chart code ... + +plt.tight_layout() +plt.show() +``` + +5. Choose chart type based on data: + - Bar chart: Comparing categories + - Line chart: Time series, trends + - Pie chart: Part-to-whole (limit to <7 categories) + - Scatter plot: Relationship between two numeric variables + - Histogram: Distribution of values + +6. Make charts visually appealing: + - Use descriptive titles and axis labels + - Use appropriate colors (e.g., plt.cm.Blues, tab10, etc.) + - Rotate x-axis labels if they overlap + - Add gridlines with plt.grid(True, alpha=0.3) + - Use figure size that fits the data well + +## Available Data +The query results are provided as a list of dictionaries. Parse and visualize them. +""" diff --git a/src/data_agent/ui/app.py b/src/data_agent/ui/app.py index 69b700b..c0c5795 100644 --- a/src/data_agent/ui/app.py +++ b/src/data_agent/ui/app.py @@ -1,5 +1,6 @@ """Chainlit UI application for Data Agent.""" +import base64 import logging import os from typing import cast @@ -183,16 +184,23 @@ async def on_message(message: cl.Message): query_result = result.get("result", {}) or {} final_response = result.get("final_response", "") or "" error = result.get("error") or None + visualization_image = result.get("visualization_image") or None + visualization_code = result.get("visualization_code") or None + visualization_error = result.get("visualization_error") or None else: datasource_name = getattr(result, "datasource_name", "") or "" generated_sql = getattr(result, "generated_sql", "") or "" query_result = getattr(result, "result", {}) or {} final_response = getattr(result, "final_response", "") or "" error = getattr(result, "error", None) + visualization_image = getattr(result, "visualization_image", None) + visualization_code = getattr(result, "visualization_code", None) + visualization_error = getattr(result, "visualization_error", None) logger.info( f"Parsed - datasource: {datasource_name}, sql: {bool(generated_sql)}, " - f"result: {bool(query_result)}, response: {bool(final_response)}, error: {error}" + f"result: {bool(query_result)}, response: {bool(final_response)}, " + f"viz: {bool(visualization_image)}, error: {error}" ) if datasource_name: @@ -245,6 +253,41 @@ async def on_message(message: cl.Message): content="Query completed but no response was generated. Please try rephrasing your question." ).send() + if visualization_code: + async with cl.Step( + name="🐍 Generated Visualization Code", type="tool", show_input=False + ) as code_step: + code_step.output = f"```python\n{visualization_code}\n```" + + if visualization_image: + try: + # Decode base64 to bytes + image_bytes = base64.b64decode(visualization_image) + + elements = [ + cl.Image( + name="chart", + content=image_bytes, + display="inline", + size="large", + ) + ] + await cl.Message( + content="📊 Visualization:", + elements=cast("list[Element]", elements), + ).send() + except Exception as e: + logger.error(f"Failed to render visualization: {e}") + await cl.Message( + content=f"⚠️ Failed to render visualization: {e}" + ).send() + + # Handle visualization errors + if visualization_error: + await cl.Message( + content=f"⚠️ Could not generate visualization: {visualization_error}" + ).send() + except Exception as e: logger.exception("Error processing message") await cl.Message(content=f"An error occurred: {e}").send() @@ -273,6 +316,7 @@ def main() -> None: "localhost", "--port", "8000", + "-w", ], check=True, ) diff --git a/uv.lock b/uv.lock index d702510..fa1a00e 100644 --- a/uv.lock +++ b/uv.lock @@ -868,6 +868,72 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + [[package]] name = "coverage" version = "7.13.0" @@ -983,6 +1049,15 @@ version = "0.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/55/ca/d323556e2bf9bfb63219fbb849ce61bb830cc42d1b25b91cde3815451b91/cuid-0.4.tar.gz", hash = "sha256:74eaba154916a2240405c3631acee708c263ef8fa05a86820b87d0f59f84e978", size = 4986, upload-time = "2023-03-06T00:41:12.708Z" } +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "data-agent" version = "0.5.0" @@ -1003,11 +1078,14 @@ dependencies = [ { name = "httpx" }, { name = "jsonschema" }, { name = "langchain" }, + { name = "langchain-azure-dynamic-sessions" }, { name = "langchain-community" }, + { name = "langchain-experimental" }, { name = "langchain-openai" }, { name = "langgraph" }, { name = "langgraph-api" }, { name = "langgraph-cli", extra = ["inmem"] }, + { name = "matplotlib" }, { name = "nest-asyncio" }, { name = "pandas" }, { name = "psycopg", extra = ["binary"] }, @@ -1079,11 +1157,14 @@ requires-dist = [ { name = "isort", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "jsonschema", specifier = ">=4.0.0" }, { name = "langchain", specifier = ">=1.2.0" }, + { name = "langchain-azure-dynamic-sessions", specifier = ">=0.2.0" }, { name = "langchain-community", specifier = ">=0.4.1" }, + { name = "langchain-experimental", specifier = ">=0.4.1" }, { name = "langchain-openai", specifier = ">=1.1.3" }, { name = "langgraph", specifier = ">=1.0.5" }, { name = "langgraph-api", specifier = ">=0.5.42" }, { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.10" }, + { name = "matplotlib", specifier = ">=3.10.8" }, { name = "nest-asyncio", specifier = ">=1.6.0" }, { name = "opentelemetry-api", marker = "extra == 'foundry'", specifier = ">=1.20.0" }, { name = "opentelemetry-sdk", marker = "extra == 'foundry'", specifier = ">=1.20.0" }, @@ -1291,6 +1372,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" }, ] +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + [[package]] name = "forbiddenfruit" version = "0.1.4" @@ -2009,6 +2131,78 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, +] + [[package]] name = "langchain" version = "1.2.0" @@ -2056,6 +2250,20 @@ opentelemetry = [ { name = "opentelemetry-semantic-conventions-ai" }, ] +[[package]] +name = "langchain-azure-dynamic-sessions" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-identity" }, + { name = "langchain-core" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/67/680dfb5e0d9d83bda2344912471ecac56878b61f19a0a70c0e591c290b7f/langchain_azure_dynamic_sessions-0.3.1.tar.gz", hash = "sha256:be3dc5b0bbe50319b017af79d52e9e35932fd8c8567d3aa98569261af8399233", size = 5865, upload-time = "2025-12-11T20:54:47.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/56/b903ff0a992565cd5cf2fa68060fe42746901585405f3d9b0c0312dc0c16/langchain_azure_dynamic_sessions-0.3.1-py3-none-any.whl", hash = "sha256:034bb46d71169d58cfa1c44da4eb2432915994fc3142912477a3dfd20e7c7529", size = 7457, upload-time = "2025-12-11T20:54:47.048Z" }, +] + [[package]] name = "langchain-classic" version = "1.0.0" @@ -2116,6 +2324,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/95/98c47dbb4b6098934ff70e0f52efef3a85505dbcccc9eb63587e21fde4c9/langchain_core-1.2.1-py3-none-any.whl", hash = "sha256:2f63859f85dc3d95f768e35fed605702e3ff5aa3e92c7b253103119613e79768", size = 475972, upload-time = "2025-12-15T14:32:49.698Z" }, ] +[[package]] +name = "langchain-experimental" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-community" }, + { name = "langchain-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/ec/6fe7b2e3c105b4f4fc6b943d8fc1b5b10f883429edc36c58a09fc2e28419/langchain_experimental-0.4.1.tar.gz", hash = "sha256:ab6b19a0b98fbc15225fbfcf096176fec339b7e3e930bcf328bb717985fc1da5", size = 170449, upload-time = "2025-12-11T05:30:48.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/fa/fb2c8b6418e1c9ef50c82b3b6e0184bce321582577240bb4b8ed3274a4aa/langchain_experimental-0.4.1-py3-none-any.whl", hash = "sha256:b6ee2f42b50aaadb45e581439ecf5ee50f3a6a0986d52e74d1e64721309e387d", size = 210096, upload-time = "2025-12-11T05:30:47.234Z" }, +] + [[package]] name = "langchain-openai" version = "1.1.3" @@ -2504,6 +2725,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, +] + [[package]] name = "mcp" version = "1.24.0" @@ -3772,6 +4047,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, +] + [[package]] name = "platformdirs" version = "4.5.1" @@ -4283,6 +4627,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/8f/d8889efd96bbe8e5d43ff9701f6b1565a8e09c3e1f58c388d550724f777b/pyodbc-5.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:13656184faa3f2d5c6f19b701b8f247342ed581484f58bf39af7315c054e69db", size = 70142, upload-time = "2025-10-17T18:03:55.551Z" }, ] +[[package]] +name = "pyparsing" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, +] + [[package]] name = "pytest" version = "9.0.2" From 6171aeb466f5e66881662573aa996f514e82324d Mon Sep 17 00:00:00 2001 From: eosho Date: Mon, 29 Dec 2025 18:38:10 -0500 Subject: [PATCH 2/3] refactor: remove unused method stubs in CodeExecutor class --- src/data_agent/executors/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/data_agent/executors/base.py b/src/data_agent/executors/base.py index 947840a..cfa7042 100644 --- a/src/data_agent/executors/base.py +++ b/src/data_agent/executors/base.py @@ -63,14 +63,12 @@ async def execute(self, code: str, timeout: float = 30.0) -> ExecutionResult: Returns: ExecutionResult with output, errors, and any generated files. """ - pass async def cleanup(self) -> None: """Clean up any resources (sessions, containers). Override in implementations that maintain state. """ - pass async def __aenter__(self) -> "CodeExecutor": """Async context manager entry.""" From fc34f577b1ac03f695f80362dcdee275457ddc2c Mon Sep 17 00:00:00 2001 From: eosho Date: Mon, 29 Dec 2025 18:45:04 -0500 Subject: [PATCH 3/3] chore: clean up dependencies in pyproject.toml --- pyproject.toml | 15 -- uv.lock | 522 +------------------------------------------------ 2 files changed, 1 insertion(+), 536 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ad91bbd..310fb04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,15 +22,11 @@ dependencies = [ "pyyaml>=6.0", "python-dotenv>=1.2.1", "databricks-sql-connector>=4.2.2", - "asyncpg>=0.29.0", "psycopg[binary]>=3.1.0", - "nest-asyncio>=1.6.0", "azure-cosmos>=4.7.0", - "aiohttp>=3.9.0", "structlog>=24.0.0", "typing-extensions>=4.12", "azure-identity>=1.25.1", - "azure-keyvault-secrets>=4.10.0", "langgraph-cli[inmem]>=0.4.10", "langgraph-api>=0.5.42", "pyodbc>=5.3.0", @@ -41,7 +37,6 @@ dependencies = [ "rich>=14.0.0", "chainlit>=2.0.0", "pandas>=2.0.0", - "tabulate>=0.9.0", "a2a-sdk[http-server]>=0.3.22", "httpx>=0.27.0", "sqlalchemy>=2.0.45", @@ -49,7 +44,6 @@ dependencies = [ "databricks-sqlalchemy>=2.0.8", "google-cloud-bigquery-storage>=2.36.0", "psycopg2>=2.9.11", - "langchain-experimental>=0.4.1", "langchain-azure-dynamic-sessions>=0.2.0", "matplotlib>=3.10.8", ] @@ -75,15 +69,6 @@ dev = [ "isort>=7.0.0", ] -# Azure AI Foundry hosting dependencies -foundry = [ - "azure-ai-agentserver-langgraph>=0.1.0", - "azure-ai-projects>=1.0.0", - "azure-identity>=1.25.1", - "opentelemetry-api>=1.20.0", - "opentelemetry-sdk>=1.20.0", -] - [tool.setuptools.packages.find] where = ["src"] include = ["data_agent*"] diff --git a/uv.lock b/uv.lock index fa1a00e..7adf29a 100644 --- a/uv.lock +++ b/uv.lock @@ -205,15 +205,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] -[[package]] -name = "asgiref" -version = "3.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, -] - [[package]] name = "asyncer" version = "0.0.11" @@ -227,46 +218,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/17/aae04a8e87b266581206af94159d5aa2899b006581033db7c64af8538403/asyncer-0.0.11-py3-none-any.whl", hash = "sha256:cdcda69773d042cc17bf45a69827b82fa89012def6ae24f8b3cd0ea70c44bb2d", size = 9531, upload-time = "2025-12-01T18:12:25.301Z" }, ] -[[package]] -name = "asyncpg" -version = "0.31.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, - { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, - { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, - { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, - { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, - { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, - { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, - { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, - { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, - { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, - { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, - { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, - { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, - { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, - { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, - { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, -] - [[package]] name = "attrs" version = "25.4.0" @@ -344,101 +295,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483, upload-time = "2024-03-13T03:41:26.969Z" }, ] -[[package]] -name = "azure-ai-agents" -version = "1.2.0b5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/57/8adeed578fa8984856c67b4229e93a58e3f6024417d448d0037aafa4ee9b/azure_ai_agents-1.2.0b5.tar.gz", hash = "sha256:1a16ef3f305898aac552269f01536c34a00473dedee0bca731a21fdb739ff9d5", size = 394876, upload-time = "2025-09-30T01:55:02.328Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/6d/15070d23d7a94833a210da09d5d7ed3c24838bb84f0463895e5d159f1695/azure_ai_agents-1.2.0b5-py3-none-any.whl", hash = "sha256:257d0d24a6bf13eed4819cfa5c12fb222e5908deafb3cbfd5711d3a511cc4e88", size = 217948, upload-time = "2025-09-30T01:55:04.155Z" }, -] - -[[package]] -name = "azure-ai-agentserver-core" -version = "1.0.0b7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "azure-ai-agents" }, - { name = "azure-ai-projects" }, - { name = "azure-core" }, - { name = "azure-identity" }, - { name = "azure-monitor-opentelemetry" }, - { name = "openai" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "starlette" }, - { name = "uvicorn" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/f7/6c08df3c503a16adef8c7bc1a4e4ceb5d27e31a172f0464a9720a9c0d565/azure_ai_agentserver_core-1.0.0b7.tar.gz", hash = "sha256:5652e732d0e299230de5d8ba474b6e3d5f6998155c8f0a3c984630447b90997c", size = 153982, upload-time = "2025-12-07T19:26:17.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/4e/08280801aefe89fcb730df83c85eb66033c396199a707c1826c60b526f05/azure_ai_agentserver_core-1.0.0b7-py3-none-any.whl", hash = "sha256:cfc1c20a6157a72809a5aad9ac33405171509462c4bfb5bb189ef92c3db5be91", size = 149985, upload-time = "2025-12-07T19:26:18.728Z" }, -] - -[[package]] -name = "azure-ai-agentserver-langgraph" -version = "1.0.0b7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-ai-agentserver-core" }, - { name = "langchain" }, - { name = "langchain-azure-ai", extra = ["opentelemetry"] }, - { name = "langchain-openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f0/ab/1373edd4edcb6a34c2edb78b7ad92d2874bdd3323d90d2b0bf8d319523bb/azure_ai_agentserver_langgraph-1.0.0b7.tar.gz", hash = "sha256:79f14e7321def8da735db218bfd14bb76ee279f2b603cccda5c651c0f16b3f86", size = 38971, upload-time = "2025-12-07T19:34:31.861Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/86/f283f510f97100b9fa5fd32a8b0e347947c6e388e7f9c3eb0b9daf41014b/azure_ai_agentserver_langgraph-1.0.0b7-py3-none-any.whl", hash = "sha256:1aebc26f86061ab74fd610f2d7f2d1871a4f71b97be9523cd756d361c40f47e6", size = 31396, upload-time = "2025-12-07T19:34:33.004Z" }, -] - -[[package]] -name = "azure-ai-inference" -version = "1.0.0b9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4e/6a/ed85592e5c64e08c291992f58b1a94dab6869f28fb0f40fd753dced73ba6/azure_ai_inference-1.0.0b9.tar.gz", hash = "sha256:1feb496bd84b01ee2691befc04358fa25d7c344d8288e99364438859ad7cd5a4", size = 182408, upload-time = "2025-02-15T00:37:28.464Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/0f/27520da74769db6e58327d96c98e7b9a07ce686dff582c9a5ec60b03f9dd/azure_ai_inference-1.0.0b9-py3-none-any.whl", hash = "sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990", size = 124885, upload-time = "2025-02-15T00:37:29.964Z" }, -] - -[package.optional-dependencies] -opentelemetry = [ - { name = "azure-core-tracing-opentelemetry" }, -] - -[[package]] -name = "azure-ai-projects" -version = "1.1.0b4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-ai-agents" }, - { name = "azure-core" }, - { name = "azure-storage-blob" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/16/7a7c978a79f545d62ab4327cd704c22b5d7ade8dcfb58ea193257aebabf9/azure_ai_projects-1.1.0b4.tar.gz", hash = "sha256:39e2f1396270b375069c2d9c82ccfe91c11384eca9f61d59adbc12fb6d6a32ca", size = 147568, upload-time = "2025-09-12T17:35:08.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/10/8b7bd070e3cc804343dab124ce66a3b7999a72d5be0e49232cbcd1d36e18/azure_ai_projects-1.1.0b4-py3-none-any.whl", hash = "sha256:d8aab84fd7cd7c5937e78141e37ca4473dc5ed6cce2c0490c634418abe14afea", size = 126670, upload-time = "2025-09-12T17:35:10.039Z" }, -] - -[[package]] -name = "azure-common" -version = "1.1.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/71/f6f71a276e2e69264a97ad39ef850dca0a04fce67b12570730cb38d0ccac/azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3", size = 20914, upload-time = "2022-02-03T19:39:44.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/55/7f118b9c1b23ec15ca05d15a578d8207aa1706bc6f7c87218efffbbf875d/azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad", size = 14462, upload-time = "2022-02-03T19:39:42.417Z" }, -] - [[package]] name = "azure-core" version = "1.37.0" @@ -452,19 +308,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, ] -[[package]] -name = "azure-core-tracing-opentelemetry" -version = "1.0.0b12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "opentelemetry-api" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/7f/5de13a331a5f2919417819cc37dcf7c897018f02f83aa82b733e6629a6a6/azure_core_tracing_opentelemetry-1.0.0b12.tar.gz", hash = "sha256:bb454142440bae11fd9d68c7c1d67ae38a1756ce808c5e4d736730a7b4b04144", size = 26010, upload-time = "2025-03-21T00:18:37.346Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/5e/97a471f66935e7f89f521d0e11ae49c7f0871ca38f5c319dccae2155c8d8/azure_core_tracing_opentelemetry-1.0.0b12-py3-none-any.whl", hash = "sha256:38fd42709f1cc4bbc4f2797008b1c30a6a01617e49910c05daa3a0d0c65053ac", size = 11962, upload-time = "2025-03-21T00:18:38.581Z" }, -] - [[package]] name = "azure-cosmos" version = "4.14.3" @@ -494,90 +337,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, ] -[[package]] -name = "azure-keyvault-secrets" -version = "4.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/97/e5/3074e581b6e8923c4a1f2e42192ea6f390bb52de3600c68baaaed529ef05/azure_keyvault_secrets-4.10.0.tar.gz", hash = "sha256:666fa42892f9cee749563e551a90f060435ab878977c95265173a8246d546a36", size = 129695, upload-time = "2025-06-16T22:52:20.986Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/94/7c902e966b28e7cb5080a8e0dd6bffc22ba44bc907f09c4c633d2b7c4f6a/azure_keyvault_secrets-4.10.0-py3-none-any.whl", hash = "sha256:9dbde256077a4ee1a847646671580692e3f9bea36bcfc189c3cf2b9a94eb38b9", size = 125237, upload-time = "2025-06-16T22:52:22.489Z" }, -] - -[[package]] -name = "azure-monitor-opentelemetry" -version = "1.8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "azure-core-tracing-opentelemetry" }, - { name = "azure-monitor-opentelemetry-exporter" }, - { name = "opentelemetry-instrumentation-django" }, - { name = "opentelemetry-instrumentation-fastapi" }, - { name = "opentelemetry-instrumentation-flask" }, - { name = "opentelemetry-instrumentation-psycopg2" }, - { name = "opentelemetry-instrumentation-requests" }, - { name = "opentelemetry-instrumentation-urllib" }, - { name = "opentelemetry-instrumentation-urllib3" }, - { name = "opentelemetry-resource-detector-azure" }, - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/62/6a7cc6579b62d06b1eac2156095eb32152c95bf0e2ba7971e2bb35f40b56/azure_monitor_opentelemetry-1.8.2.tar.gz", hash = "sha256:efd9ac523a1bba06770a02ea2f98a383efd7035ff1d47c28e31e6cc73c99aa6a", size = 55011, upload-time = "2025-11-14T01:30:23.842Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/58/3433abd77811237bc91a6118f659e13a557c6d97903acb47f56a8c141171/azure_monitor_opentelemetry-1.8.2-py3-none-any.whl", hash = "sha256:f7da4de1320486ce69a9e804fc5e5a5431643888afb1b7ded55d1fb80c89c9c0", size = 27671, upload-time = "2025-11-14T01:30:25.209Z" }, -] - -[[package]] -name = "azure-monitor-opentelemetry-exporter" -version = "1.0.0b45" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "azure-identity" }, - { name = "msrest" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-sdk" }, - { name = "psutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/02/a4f6d50ee43a25680b46f82d985c0920c5f8422b0fb9b408efc484b498f8/azure_monitor_opentelemetry_exporter-1.0.0b45.tar.gz", hash = "sha256:b2d495b318710c521d6611cfb9cb4b36d990e3d613cf10ebdaa957099ddbe351", size = 277588, upload-time = "2025-11-13T23:12:01.461Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/05/4007d55d7fa88992bf70cdfdf9e7c4ec0734fd853c4c548da9fd36623892/azure_monitor_opentelemetry_exporter-1.0.0b45-py2.py3-none-any.whl", hash = "sha256:10d6363ac971fb4530511df464898fe69c0499e85841febad800572d1e8e8ec6", size = 200419, upload-time = "2025-11-13T23:12:02.7Z" }, -] - -[[package]] -name = "azure-search-documents" -version = "11.7.0b2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-common" }, - { name = "azure-core" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f9/ba/bde0f03e0a742ba3bbcc929f91ed2f3b1420c2bb84c9a7f878f3b87ebfce/azure_search_documents-11.7.0b2.tar.gz", hash = "sha256:b6e039f8038ff2210d2057e704e867c6e29bb46bfcd400da4383e45e4b8bb189", size = 423956, upload-time = "2025-11-14T20:09:32.876Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/26/ed4498374f9088818278ac225f2bea688b4ec979d81bf83a5355c8c366af/azure_search_documents-11.7.0b2-py3-none-any.whl", hash = "sha256:f82117b321344a84474269ed26df194c24cca619adc024d981b1b86aee3c6f05", size = 432037, upload-time = "2025-11-14T20:09:34.347Z" }, -] - -[[package]] -name = "azure-storage-blob" -version = "12.28.0b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/46/3499d7946ac4ab822919619ba8333f34d49df5efc769ca0a22989814d8c6/azure_storage_blob-12.28.0b1.tar.gz", hash = "sha256:76fcb4e91c8f0f36678534b35c9b22795266f1426572307812d04d1abc868fd8", size = 604004, upload-time = "2025-12-04T21:13:58.152Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/b8/0ae85bc173147e73ef97099041d1a7de7d754993ec869fb948e4e6591d85/azure_storage_blob-12.28.0b1-py3-none-any.whl", hash = "sha256:113b1d90b234782be5bf44397a39f253f007918e414cbbeb6c7e513069bde736", size = 431438, upload-time = "2025-12-04T21:13:59.885Z" }, -] - [[package]] name = "bidict" version = "0.23.1" @@ -1064,11 +823,8 @@ version = "0.5.0" source = { editable = "." } dependencies = [ { name = "a2a-sdk", extra = ["http-server"] }, - { name = "aiohttp" }, - { name = "asyncpg" }, { name = "azure-cosmos" }, { name = "azure-identity" }, - { name = "azure-keyvault-secrets" }, { name = "chainlit" }, { name = "databricks-sql-connector" }, { name = "databricks-sqlalchemy" }, @@ -1080,13 +836,11 @@ dependencies = [ { name = "langchain" }, { name = "langchain-azure-dynamic-sessions" }, { name = "langchain-community" }, - { name = "langchain-experimental" }, { name = "langchain-openai" }, { name = "langgraph" }, { name = "langgraph-api" }, { name = "langgraph-cli", extra = ["inmem"] }, { name = "matplotlib" }, - { name = "nest-asyncio" }, { name = "pandas" }, { name = "psycopg", extra = ["binary"] }, { name = "psycopg2" }, @@ -1100,7 +854,6 @@ dependencies = [ { name = "sqlalchemy-bigquery" }, { name = "sqlglot", extra = ["rs"] }, { name = "structlog" }, - { name = "tabulate" }, { name = "typer" }, { name = "typing-extensions" }, { name = "uvicorn" }, @@ -1121,13 +874,6 @@ dev = [ { name = "ruff" }, { name = "skylos" }, ] -foundry = [ - { name = "azure-ai-agentserver-langgraph" }, - { name = "azure-ai-projects" }, - { name = "azure-identity" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-sdk" }, -] [package.dev-dependencies] dev = [ @@ -1137,15 +883,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", extras = ["http-server"], specifier = ">=0.3.22" }, - { name = "aiohttp", specifier = ">=3.9.0" }, - { name = "asyncpg", specifier = ">=0.29.0" }, { name = "autoflake", marker = "extra == 'dev'", specifier = ">=2.3.1" }, - { name = "azure-ai-agentserver-langgraph", marker = "extra == 'foundry'", specifier = ">=0.1.0" }, - { name = "azure-ai-projects", marker = "extra == 'foundry'", specifier = ">=1.0.0" }, { name = "azure-cosmos", specifier = ">=4.7.0" }, { name = "azure-identity", specifier = ">=1.25.1" }, - { name = "azure-identity", marker = "extra == 'foundry'", specifier = ">=1.25.1" }, - { name = "azure-keyvault-secrets", specifier = ">=4.10.0" }, { name = "black", marker = "extra == 'dev'" }, { name = "chainlit", specifier = ">=2.0.0" }, { name = "databricks-sql-connector", specifier = ">=4.2.2" }, @@ -1159,15 +899,11 @@ requires-dist = [ { name = "langchain", specifier = ">=1.2.0" }, { name = "langchain-azure-dynamic-sessions", specifier = ">=0.2.0" }, { name = "langchain-community", specifier = ">=0.4.1" }, - { name = "langchain-experimental", specifier = ">=0.4.1" }, { name = "langchain-openai", specifier = ">=1.1.3" }, { name = "langgraph", specifier = ">=1.0.5" }, { name = "langgraph-api", specifier = ">=0.5.42" }, { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.10" }, { name = "matplotlib", specifier = ">=3.10.8" }, - { name = "nest-asyncio", specifier = ">=1.6.0" }, - { name = "opentelemetry-api", marker = "extra == 'foundry'", specifier = ">=1.20.0" }, - { name = "opentelemetry-sdk", marker = "extra == 'foundry'", specifier = ">=1.20.0" }, { name = "pandas", specifier = ">=2.0.0" }, { name = "poethepoet", marker = "extra == 'dev'" }, { name = "pre-commit", marker = "extra == 'dev'" }, @@ -1190,12 +926,11 @@ requires-dist = [ { name = "sqlalchemy-bigquery", specifier = ">=1.16.0" }, { name = "sqlglot", extras = ["rs"], specifier = ">=26.0.0" }, { name = "structlog", specifier = ">=24.0.0" }, - { name = "tabulate", specifier = ">=0.9.0" }, { name = "typer", specifier = ">=0.15.0" }, { name = "typing-extensions", specifier = ">=4.12" }, { name = "uvicorn", specifier = ">=0.38.0" }, ] -provides-extras = ["dev", "foundry"] +provides-extras = ["dev"] [package.metadata.requires-dev] dev = [{ name = "vulture", specifier = ">=2.14" }] @@ -1942,15 +1677,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/fd/7c404169a3e04a908df0644893a331f253a7f221961f2b6c0cf44430ae5a/inquirer-3.4.1-py3-none-any.whl", hash = "sha256:717bf146d547b595d2495e7285fd55545cff85e5ce01decc7487d2ec6a605412", size = 18152, upload-time = "2025-08-02T18:36:26.753Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "isort" version = "7.0.0" @@ -2217,39 +1943,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/00/4e3fa0d90f5a5c376ccb8ca983d0f0f7287783dfac48702e18f01d24673b/langchain-1.2.0-py3-none-any.whl", hash = "sha256:82f0d17aa4fbb11560b30e1e7d4aeb75e3ad71ce09b85c90ab208b181a24ffac", size = 102828, upload-time = "2025-12-15T14:51:40.802Z" }, ] -[[package]] -name = "langchain-azure-ai" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "azure-ai-agents" }, - { name = "azure-ai-inference", extra = ["opentelemetry"] }, - { name = "azure-ai-projects" }, - { name = "azure-core" }, - { name = "azure-cosmos" }, - { name = "azure-identity" }, - { name = "azure-search-documents" }, - { name = "langchain" }, - { name = "langchain-openai" }, - { name = "numpy" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/2c/b58e890a0445c1d987570f56e19b077859e6019e44694e6847b3bd722113/langchain_azure_ai-1.0.4.tar.gz", hash = "sha256:0dffa882f103baa211380086cb35b9cac56eb99f4315067b5aa25c9ed426829d", size = 84372, upload-time = "2025-12-01T17:49:23.615Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/d0/b960ae9ac877cd601a3155767ebcad8a049c4cf500fed466e4c1e9917b1c/langchain_azure_ai-1.0.4-py3-none-any.whl", hash = "sha256:c2031172c4acf743a24d4cdb430078464ac3e8e0c1704ced3e719b566628d638", size = 100625, upload-time = "2025-12-01T17:49:22.392Z" }, -] - -[package.optional-dependencies] -opentelemetry = [ - { name = "azure-monitor-opentelemetry" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-threading" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-semantic-conventions-ai" }, -] - [[package]] name = "langchain-azure-dynamic-sessions" version = "0.3.1" @@ -2324,19 +2017,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/95/98c47dbb4b6098934ff70e0f52efef3a85505dbcccc9eb63587e21fde4c9/langchain_core-1.2.1-py3-none-any.whl", hash = "sha256:2f63859f85dc3d95f768e35fed605702e3ff5aa3e92c7b253103119613e79768", size = 475972, upload-time = "2025-12-15T14:32:49.698Z" }, ] -[[package]] -name = "langchain-experimental" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-community" }, - { name = "langchain-core" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/ec/6fe7b2e3c105b4f4fc6b943d8fc1b5b10f883429edc36c58a09fc2e28419/langchain_experimental-0.4.1.tar.gz", hash = "sha256:ab6b19a0b98fbc15225fbfcf096176fec339b7e3e930bcf328bb717985fc1da5", size = 170449, upload-time = "2025-12-11T05:30:48.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/fa/fb2c8b6418e1c9ef50c82b3b6e0184bce321582577240bb4b8ed3274a4aa/langchain_experimental-0.4.1-py3-none-any.whl", hash = "sha256:b6ee2f42b50aaadb45e581439ecf5ee50f3a6a0986d52e74d1e64721309e387d", size = 210096, upload-time = "2025-12-11T05:30:47.234Z" }, -] - [[package]] name = "langchain-openai" version = "1.1.3" @@ -2839,22 +2519,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, ] -[[package]] -name = "msrest" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "certifi" }, - { name = "isodate" }, - { name = "requests" }, - { name = "requests-oauthlib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332, upload-time = "2022-06-13T22:41:25.111Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384, upload-time = "2022-06-13T22:41:22.42Z" }, -] - [[package]] name = "multidict" version = "6.7.0" @@ -3205,22 +2869,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/31/5ff0ec511c2f98d3f983a3942bd71d2bfcc9b0e122c9327bdf20895b9edd/opentelemetry_instrumentation_anthropic-0.50.1-py3-none-any.whl", hash = "sha256:d5c12497c01a10ba34412805cb6d970fbdb5fc5d2f73f95ac387df99d2c9e813", size = 18458, upload-time = "2025-12-16T08:26:15.927Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-asgi" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asgiref" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/77/db/851fa88db7441da82d50bd80f2de5ee55213782e25dc858e04d0c9961d60/opentelemetry_instrumentation_asgi-0.60b1.tar.gz", hash = "sha256:16bfbe595cd24cda309a957456d0fc2523f41bc7b076d1f2d7e98a1ad9876d6f", size = 26107, upload-time = "2025-12-11T13:36:47.015Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/76/1fb94367cef64420d2171157a6b9509582873bd09a6afe08a78a8d1f59d9/opentelemetry_instrumentation_asgi-0.60b1-py3-none-any.whl", hash = "sha256:d48def2dbed10294c99cfcf41ebbd0c414d390a11773a41f472d20000fcddc25", size = 16933, upload-time = "2025-12-11T13:35:40.462Z" }, -] - [[package]] name = "opentelemetry-instrumentation-bedrock" version = "0.50.1" @@ -3283,70 +2931,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/4f/4a1da76a51c91093d003983fbe8cb218b9909f3fe4fd7f7753bf5d47a7c5/opentelemetry_instrumentation_crewai-0.50.1-py3-none-any.whl", hash = "sha256:e8d96c79ca498985ff72102bc106baec603bc4ee3915ec506a955f4a94b1d7c9", size = 6231, upload-time = "2025-12-16T08:26:21.507Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-dbapi" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/b5/1e1f0642892a2abb6e75b7009ccee946e801cded88caac3d803cf46c8c73/opentelemetry_instrumentation_dbapi-0.60b1.tar.gz", hash = "sha256:a239d328249b86fba5e42900b98bf31ee99c07092530feca18afde92c600f901", size = 16311, upload-time = "2025-12-11T13:36:55.654Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/08/d4c78b6e317d9975d473dd98f7854f5731ff4a1d470c65d2630fa68a1484/opentelemetry_instrumentation_dbapi-0.60b1-py3-none-any.whl", hash = "sha256:5577189f678de5b9828c930c8fb72e8f1999b304131777b60099e5c4b3948193", size = 13968, upload-time = "2025-12-11T13:35:56.316Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation-django" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-wsgi" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/dc/a42fb5ff5c4ea8128d7c61a322e0cfbeae0fd204fc63a679f73caeec266e/opentelemetry_instrumentation_django-0.60b1.tar.gz", hash = "sha256:765b69c7ccdea7e9ebfd0b9e68387956b8f74816f3e39775d5b06a20f16b0522", size = 26599, upload-time = "2025-12-11T13:36:56.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/05/6b348ea989f7a9e1e6311fa653e113bd39f4506771323e27a639c2a1ea54/opentelemetry_instrumentation_django-0.60b1-py3-none-any.whl", hash = "sha256:3f6b4ba201eee35406dab965b254eed0c64fa1ef42e4a7b0296ad1b30e8e3f81", size = 21172, upload-time = "2025-12-11T13:35:57.365Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation-fastapi" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-asgi" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/e7/e7e5e50218cf488377209d85666b182fa2d4928bf52389411ceeee1b2b60/opentelemetry_instrumentation_fastapi-0.60b1.tar.gz", hash = "sha256:de608955f7ff8eecf35d056578346a5365015fd7d8623df9b1f08d1c74769c01", size = 24958, upload-time = "2025-12-11T13:36:59.35Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/cc/6e808328ba54662e50babdcab21138eae4250bc0fddf67d55526a615a2ca/opentelemetry_instrumentation_fastapi-0.60b1-py3-none-any.whl", hash = "sha256:af94b7a239ad1085fc3a820ecf069f67f579d7faf4c085aaa7bd9b64eafc8eaf", size = 13478, upload-time = "2025-12-11T13:36:00.811Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation-flask" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-wsgi" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/da/ab70b6c22e7cbad165505ef4e53a125b68b273e8025ab913fad69e16a0dd/opentelemetry_instrumentation_flask-0.60b1.tar.gz", hash = "sha256:88cb0f6178d8a9b66f6aabb008e2735f28e3114def90b28004a2b295ca9b67e8", size = 20336, upload-time = "2025-12-11T13:37:00Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/68/7dd156ea341c73e73afc120f9c877bc827990d5ae8f8265d74d1b14608d6/opentelemetry_instrumentation_flask-0.60b1-py3-none-any.whl", hash = "sha256:070cb00f3f1214a4b680c7ab6ae1e5a14a54fd05ef163323500326002dfcfed4", size = 15188, upload-time = "2025-12-11T13:36:02.108Z" }, -] - [[package]] name = "opentelemetry-instrumentation-google-generativeai" version = "0.50.1" @@ -3571,20 +3155,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/f4/760326d1e27bb1ee2654eec4f63181b89841b72762a24f46c1bf7b1d096c/opentelemetry_instrumentation_pinecone-0.50.1-py3-none-any.whl", hash = "sha256:2a4fff70805d241ef713fb7dde46080b5f37cbb87a6919881b46f69c5e2811a1", size = 6359, upload-time = "2025-12-16T08:26:39.516Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-psycopg2" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-dbapi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/f2/e914cf7f3dd5fd84a2dd95be060ddd713791447216a8b99f036ad30c49b2/opentelemetry_instrumentation_psycopg2-0.60b1.tar.gz", hash = "sha256:46f46c47e11bf59b9746f35761995e4513fb985bab08d4e1f876a2c46ed4eeec", size = 11263, upload-time = "2025-12-11T13:37:07.462Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/02/e30d5aae987c99ad4a4b4f98e009bf7c4f010d888da641efb0428814a4a6/opentelemetry_instrumentation_psycopg2-0.60b1-py3-none-any.whl", hash = "sha256:f3841fe83400ee33c51ba5c38f4034534c3415075a4ebf01b97e49f8e0e5dd3e", size = 11283, upload-time = "2025-12-11T13:36:13.623Z" }, -] - [[package]] name = "opentelemetry-instrumentation-qdrant" version = "0.50.1" @@ -3720,21 +3290,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/3a/a939bfdc51fa070d9aafe11fe0bb43807448067dea953b2cb21e4ea03a13/opentelemetry_instrumentation_transformers-0.50.1-py3-none-any.whl", hash = "sha256:f1c1b6a5f8efbcb34119972277bc568362b5d3d437aefc83f4a30b90945bfe2b", size = 8284, upload-time = "2025-12-16T08:26:45.306Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-urllib" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/1d/b1ac334f36a3e7e5bf73cd26f5dde7b23909fe9a068316f9cd29388a245e/opentelemetry_instrumentation_urllib-0.60b1.tar.gz", hash = "sha256:7d6c56e45551bdbf21efc11bd463e10862e8fd04ed4a94b5695325a56440b13e", size = 13930, upload-time = "2025-12-11T13:37:18.507Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/70/b4c7b3bc17081725cf4bfa09286fd87e1d3a0d0589b12b35efba5db8da34/opentelemetry_instrumentation_urllib-0.60b1-py3-none-any.whl", hash = "sha256:bf36188d684ca6454b7162492a66749181955011e0cc47a2324cbe66e7f13e81", size = 12674, upload-time = "2025-12-11T13:36:31.33Z" }, -] - [[package]] name = "opentelemetry-instrumentation-urllib3" version = "0.60b1" @@ -3811,21 +3366,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/75/d1fc1d3805ea8e15aad764541158a4f8a849322794484cad8bde0f511361/opentelemetry_instrumentation_writer-0.50.1-py3-none-any.whl", hash = "sha256:d1090030c3164f430bf8d8470b6d6db6e969693867400c8b0e0f36c3154672be", size = 11553, upload-time = "2025-12-16T08:26:49.912Z" }, ] -[[package]] -name = "opentelemetry-instrumentation-wsgi" -version = "0.60b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/24/5632d31506a27650567fdff8f9be37fc4d98396b6331617be69bd332bf77/opentelemetry_instrumentation_wsgi-0.60b1.tar.gz", hash = "sha256:eb553eec7ebfcf2945cc10d787a265e7abadb9ed1d1ebce8b13988d44fa0cf45", size = 19167, upload-time = "2025-12-11T13:37:20.3Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/98/c637d9e5cab1355d6765de2304199a1d79a43aa94c33d8eddb475327d81a/opentelemetry_instrumentation_wsgi-0.60b1-py3-none-any.whl", hash = "sha256:5e7b432778ebf5a39af136227884a6ab2efc3c4e73e2dbb1d05ecf03ea196705", size = 14583, upload-time = "2025-12-11T13:36:33.164Z" }, -] - [[package]] name = "opentelemetry-proto" version = "1.39.1" @@ -3838,18 +3378,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, ] -[[package]] -name = "opentelemetry-resource-detector-azure" -version = "0.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/e4/0d359d48d03d447225b30c3dd889d5d454e3b413763ff721f9b0e4ac2e59/opentelemetry_resource_detector_azure-0.1.5.tar.gz", hash = "sha256:e0ba658a87c69eebc806e75398cd0e9f68a8898ea62de99bc1b7083136403710", size = 11503, upload-time = "2024-05-16T21:54:58.994Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/c26d8da88ba2e438e9653a408b0c2ad6f17267801250a8f3cc6405a93a72/opentelemetry_resource_detector_azure-0.1.5-py3-none-any.whl", hash = "sha256:4dcc5d54ab5c3b11226af39509bc98979a8b9e0f8a24c1b888783755d3bf00eb", size = 14252, upload-time = "2024-05-16T21:54:57.208Z" }, -] - [[package]] name = "opentelemetry-sdk" version = "1.39.1" @@ -4274,32 +3802,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, ] -[[package]] -name = "psutil" -version = "7.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, -] - [[package]] name = "psycopg" version = "3.3.2" @@ -4979,19 +4481,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, -] - [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -5357,15 +4846,6 @@ version = "2.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/dd/d4dd75843692690d81f0a4b929212a1614b25d4896aa7c72f4c3546c7e3d/syncer-2.0.3.tar.gz", hash = "sha256:4340eb54b54368724a78c5c0763824470201804fe9180129daf3635cb500550f", size = 11512, upload-time = "2023-05-08T07:50:17.963Z" } -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "tenacity" version = "9.1.2"