diff --git a/src/a2a_storage/context_store.py b/src/a2a_storage/context_store.py index 2a14deddc..392b6e4a2 100644 --- a/src/a2a_storage/context_store.py +++ b/src/a2a_storage/context_store.py @@ -1,6 +1,7 @@ """Abstract base class for A2A context-to-conversation mapping storage.""" from abc import ABC, abstractmethod +from typing import Optional class A2AContextStore(ABC): @@ -14,7 +15,7 @@ class A2AContextStore(ABC): """ @abstractmethod - async def get(self, context_id: str) -> str | None: + async def get(self, context_id: str) -> Optional[str]: """Retrieve the conversation ID for an A2A context. Args: diff --git a/src/a2a_storage/in_memory_context_store.py b/src/a2a_storage/in_memory_context_store.py index 0c0c33173..a505a2912 100644 --- a/src/a2a_storage/in_memory_context_store.py +++ b/src/a2a_storage/in_memory_context_store.py @@ -2,6 +2,7 @@ import asyncio import logging +from typing import Optional from a2a_storage.context_store import A2AContextStore @@ -26,7 +27,7 @@ def __init__(self) -> None: self._lock = asyncio.Lock() self._initialized = True - async def get(self, context_id: str) -> str | None: + async def get(self, context_id: str) -> Optional[str]: """Retrieve the conversation ID for an A2A context. Args: diff --git a/src/a2a_storage/postgres_context_store.py b/src/a2a_storage/postgres_context_store.py index b9ead766c..7c537cf46 100644 --- a/src/a2a_storage/postgres_context_store.py +++ b/src/a2a_storage/postgres_context_store.py @@ -1,6 +1,7 @@ """PostgreSQL implementation of A2A context store.""" import logging +from typing import Optional from sqlalchemy import Column, String, Table, MetaData, select, delete from sqlalchemy.dialects.postgresql import insert as pg_insert @@ -66,7 +67,7 @@ async def _ensure_initialized(self) -> None: if not self._initialized: await self.initialize() - async def get(self, context_id: str) -> str | None: + async def get(self, context_id: str) -> Optional[str]: """Retrieve the conversation ID for an A2A context. Args: diff --git a/src/a2a_storage/sqlite_context_store.py b/src/a2a_storage/sqlite_context_store.py index edf201e5d..229f31e94 100644 --- a/src/a2a_storage/sqlite_context_store.py +++ b/src/a2a_storage/sqlite_context_store.py @@ -1,6 +1,7 @@ """SQLite implementation of A2A context store.""" import logging +from typing import Optional from sqlalchemy import Column, String, Table, MetaData, select, delete from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker @@ -65,7 +66,7 @@ async def _ensure_initialized(self) -> None: if not self._initialized: await self.initialize() - async def get(self, context_id: str) -> str | None: + async def get(self, context_id: str) -> Optional[str]: """Retrieve the conversation ID for an A2A context. Args: diff --git a/src/a2a_storage/storage_factory.py b/src/a2a_storage/storage_factory.py index 59e91ec3f..b7596b2f3 100644 --- a/src/a2a_storage/storage_factory.py +++ b/src/a2a_storage/storage_factory.py @@ -2,6 +2,7 @@ import logging from urllib.parse import quote_plus +from typing import Optional from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine @@ -24,9 +25,9 @@ class A2AStorageFactory: creates database-backed stores that share state across workers. """ - _engine: AsyncEngine | None = None - _task_store: TaskStore | None = None - _context_store: A2AContextStore | None = None + _engine: Optional[AsyncEngine] = None + _task_store: Optional[TaskStore] = None + _context_store: Optional[A2AContextStore] = None @classmethod async def create_task_store(cls, config: A2AStateConfiguration) -> TaskStore: diff --git a/src/app/database.py b/src/app/database.py index 6d41416f0..86a5c8f2b 100644 --- a/src/app/database.py +++ b/src/app/database.py @@ -1,7 +1,7 @@ """Database engine management.""" from pathlib import Path -from typing import Any +from typing import Any, Optional from sqlalchemy import create_engine, text from sqlalchemy.engine.base import Engine @@ -14,8 +14,8 @@ logger = get_logger(__name__) # pylint: disable=invalid-name -engine: Engine | None = None -session_local: sessionmaker | None = None +engine: Optional[Engine] = None +session_local: Optional[sessionmaker] = None def get_engine() -> Engine: diff --git a/src/authorization/middleware.py b/src/authorization/middleware.py index 1ee36f667..76c9fb4ee 100644 --- a/src/authorization/middleware.py +++ b/src/authorization/middleware.py @@ -2,7 +2,7 @@ import logging from functools import lru_cache, wraps -from typing import Any, Callable, Tuple +from typing import Any, Callable, Optional, Tuple from fastapi import HTTPException from starlette.requests import Request @@ -107,7 +107,7 @@ async def _perform_authorization_check( authorized_actions = access_resolver.get_actions(user_roles) - req: Request | None = None + req: Optional[Request] = None if "request" in kwargs and isinstance(kwargs["request"], Request): req = kwargs["request"] else: diff --git a/src/configuration.py b/src/configuration.py index ac5806ebc..4c9fd48e6 100644 --- a/src/configuration.py +++ b/src/configuration.py @@ -324,7 +324,7 @@ def token_usage_history(self) -> Optional[TokenUsageHistory]: token history is disabled, returns None. Returns: - TokenUsageHistory | None: The cached TokenUsageHistory instance + Optional[TokenUsageHistory]: The cached TokenUsageHistory instance when enabled, otherwise `None`. Raises: diff --git a/src/models/cache_entry.py b/src/models/cache_entry.py index 116372bbb..637de38a9 100644 --- a/src/models/cache_entry.py +++ b/src/models/cache_entry.py @@ -1,5 +1,6 @@ """Model for conversation history cache entry.""" +from typing import Optional from pydantic import BaseModel from models.responses import ReferencedDocument @@ -21,4 +22,4 @@ class CacheEntry(BaseModel): model: str started_at: str completed_at: str - referenced_documents: list[ReferencedDocument] | None = None + referenced_documents: Optional[list[ReferencedDocument]] = None diff --git a/src/models/config.py b/src/models/config.py index afc258c1a..f573c0e16 100644 --- a/src/models/config.py +++ b/src/models/config.py @@ -1178,7 +1178,7 @@ def check_default_model_and_provider(self) -> Self: class ConversationHistoryConfiguration(ConfigurationBase): """Conversation history configuration.""" - type: Literal["noop", "memory", "sqlite", "postgres"] | None = Field( + type: Optional[Literal["noop", "memory", "sqlite", "postgres"]] = Field( None, title="Conversation history database type", description="Type of database where the conversation history is to be stored.", @@ -1298,7 +1298,7 @@ def storage_type(self) -> Literal["memory", "sqlite", "postgres"]: @property def config( self, - ) -> SQLiteDatabaseConfiguration | PostgreSQLDatabaseConfiguration | None: + ) -> Optional[SQLiteDatabaseConfiguration | PostgreSQLDatabaseConfiguration]: """Return the active storage configuration, or None for memory storage.""" if self.sqlite is not None: return self.sqlite diff --git a/src/models/requests.py b/src/models/requests.py index 261e2337f..e8d084ba2 100644 --- a/src/models/requests.py +++ b/src/models/requests.py @@ -205,15 +205,15 @@ class QueryRequest(BaseModel): @field_validator("conversation_id") @classmethod - def check_uuid(cls, value: str | None) -> str | None: + def check_uuid(cls, value: Optional[str]) -> Optional[str]: """ Validate that a conversation identifier matches the expected SUID format. Parameters: - value (str | None): Conversation identifier to validate; may be None. + value (Optional[str]): Conversation identifier to validate; may be None. Returns: - str | None: The original `value` if valid or `None` if not provided. + Optional[str]: The original `value` if valid or `None` if not provided. Raises: ValueError: If `value` is provided and does not conform to the diff --git a/src/models/responses.py b/src/models/responses.py index 9a90d4fc0..bf4da2698 100644 --- a/src/models/responses.py +++ b/src/models/responses.py @@ -30,7 +30,7 @@ # tool_name: str = Field(description="Name of the tool called") # arguments: dict[str, Any] = Field(description="Arguments passed to the tool") -# result: dict[str, Any] | None = Field(None, description="Result from the tool") +# result: Optional[dict[str, Any]] = Field(None, description="Result from the tool") # class ToolResult(BaseModel): @@ -321,7 +321,7 @@ class ConversationData(BaseModel): """ conversation_id: str - topic_summary: str | None + topic_summary: Optional[str] last_message_timestamp: float @@ -333,9 +333,13 @@ class ReferencedDocument(BaseModel): doc_title: Title of the referenced doc. """ - doc_url: AnyUrl | None = Field(None, description="URL of the referenced document") + doc_url: Optional[AnyUrl] = Field( + None, description="URL of the referenced document" + ) - doc_title: str | None = Field(None, description="Title of the referenced document") + doc_title: Optional[str] = Field( + None, description="Title of the referenced document" + ) class QueryResponse(AbstractSuccessfulResponse): @@ -353,7 +357,7 @@ class QueryResponse(AbstractSuccessfulResponse): available_quotas: Quota available as measured by all configured quota limiters. """ - conversation_id: str | None = Field( + conversation_id: Optional[str] = Field( None, description="The optional conversation ID (UUID)", examples=["c5260aec-4d82-4370-9fdf-05cf908b3f16"], @@ -404,12 +408,12 @@ class QueryResponse(AbstractSuccessfulResponse): examples=[{"daily": 1000, "monthly": 50000}], ) - tool_calls: list[ToolCallSummary] | None = Field( + tool_calls: Optional[list[ToolCallSummary]] = Field( None, description="List of tool calls made during response generation", ) - tool_results: list[ToolResultSummary] | None = Field( + tool_results: Optional[list[ToolResultSummary]] = Field( None, description="List of tool results", ) @@ -573,7 +577,7 @@ class ProviderHealthStatus(BaseModel): description="The health status", examples=["ok", "unhealthy", "not_implemented"], ) - message: str | None = Field( + message: Optional[str] = Field( None, description="Optional message about the health status", examples=["All systems operational", "Llama Stack is unavailable"], @@ -921,37 +925,37 @@ class ConversationDetails(BaseModel): examples=["c5260aec-4d82-4370-9fdf-05cf908b3f16"], ) - created_at: str | None = Field( + created_at: Optional[str] = Field( None, description="When the conversation was created", examples=["2024-01-01T01:00:00Z"], ) - last_message_at: str | None = Field( + last_message_at: Optional[str] = Field( None, description="When the last message was sent", examples=["2024-01-01T01:00:00Z"], ) - message_count: int | None = Field( + message_count: Optional[int] = Field( None, description="Number of user messages in the conversation", examples=[42], ) - last_used_model: str | None = Field( + last_used_model: Optional[str] = Field( None, description="Identification of the last model used for the conversation", examples=["gpt-4-turbo", "gpt-3.5-turbo-0125"], ) - last_used_provider: str | None = Field( + last_used_provider: Optional[str] = Field( None, description="Identification of the last provider used for the conversation", examples=["openai", "gemini"], ) - topic_summary: str | None = Field( + topic_summary: Optional[str] = Field( None, description="Topic summary for the conversation", examples=["Openshift Microservices Deployment Strategies"], diff --git a/src/models/rlsapi/responses.py b/src/models/rlsapi/responses.py index 8d7f13a74..e5e619a47 100644 --- a/src/models/rlsapi/responses.py +++ b/src/models/rlsapi/responses.py @@ -1,5 +1,6 @@ """Models for rlsapi v1 REST API responses.""" +from typing import Optional from pydantic import Field from models.config import ConfigurationBase @@ -19,7 +20,7 @@ class RlsapiV1InferData(ConfigurationBase): description="Generated response text", examples=["To list files in Linux, use the `ls` command."], ) - request_id: str | None = Field( + request_id: Optional[str] = Field( None, description="Unique request identifier", examples=["01JDKR8N7QW9ZMXVGK3PB5TQWZ"], diff --git a/src/quota/token_usage_history.py b/src/quota/token_usage_history.py index 591058fe5..db6134e14 100644 --- a/src/quota/token_usage_history.py +++ b/src/quota/token_usage_history.py @@ -7,7 +7,7 @@ import sqlite3 from datetime import datetime -from typing import Any +from typing import Any, Optional import psycopg2 @@ -50,13 +50,13 @@ def __init__(self, configuration: QuotaHandlersConfiguration) -> None: """ # store the configuration, it will be used # by reconnection logic later, if needed - self.sqlite_connection_config: SQLiteDatabaseConfiguration | None = ( + self.sqlite_connection_config: Optional[SQLiteDatabaseConfiguration] = ( configuration.sqlite ) - self.postgres_connection_config: PostgreSQLDatabaseConfiguration | None = ( + self.postgres_connection_config: Optional[PostgreSQLDatabaseConfiguration] = ( configuration.postgres ) - self.connection: Any | None = None + self.connection: Optional[Any] = None # initialize connection to DB self.connect() diff --git a/src/utils/checks.py b/src/utils/checks.py index f9c4d3f71..a6d852ecc 100644 --- a/src/utils/checks.py +++ b/src/utils/checks.py @@ -87,7 +87,7 @@ def directory_check( raise InvalidConfigurationError(f"{desc} '{path}' is not writable") -def import_python_module(profile_name: str, profile_path: str) -> ModuleType | None: +def import_python_module(profile_name: str, profile_path: str) -> Optional[ModuleType]: """ Import a Python module from a filesystem path and return the loaded module. @@ -96,7 +96,7 @@ def import_python_module(profile_name: str, profile_path: str) -> ModuleType | N profile_path (str): Filesystem path to the Python source file; must end with `.py`. Returns: - ModuleType | None: The loaded module on success; `None` if + Optional[ModuleType]: The loaded module on success; `None` if `profile_path` does not end with `.py`, if a module spec or loader cannot be created, or if importing/executing the module fails. """ diff --git a/src/utils/endpoints.py b/src/utils/endpoints.py index 372e3b4ea..2fb51871c 100644 --- a/src/utils/endpoints.py +++ b/src/utils/endpoints.py @@ -2,7 +2,7 @@ from contextlib import suppress from datetime import UTC, datetime -from typing import Any +from typing import Any, Optional from fastapi import HTTPException from llama_stack_client._client import AsyncLlamaStackClient @@ -55,14 +55,14 @@ def delete_conversation(conversation_id: str) -> bool: return False -def retrieve_conversation(conversation_id: str) -> UserConversation | None: +def retrieve_conversation(conversation_id: str) -> Optional[UserConversation]: """Retrieve a conversation from the database by its ID. Args: conversation_id (str): The unique identifier of the conversation to retrieve. Returns: - UserConversation | None: The conversation object if found, otherwise None. + Optional[UserConversation]: The conversation object if found, otherwise None. """ with get_session() as session: return session.query(UserConversation).filter_by(id=conversation_id).first() @@ -70,7 +70,7 @@ def retrieve_conversation(conversation_id: str) -> UserConversation | None: def validate_conversation_ownership( user_id: str, conversation_id: str, others_allowed: bool = False -) -> UserConversation | None: +) -> Optional[UserConversation]: """Validate that the conversation belongs to the user. Validates that the conversation with the given ID belongs to the user with the given ID. @@ -86,7 +86,7 @@ def validate_conversation_ownership( else conversation_query.filter_by(id=conversation_id, user_id=user_id) ) - conversation: UserConversation | None = filtered_conversation_query.first() + conversation: Optional[UserConversation] = filtered_conversation_query.first() return conversation @@ -251,7 +251,7 @@ def store_conversation_into_cache( conversation_id: str, cache_entry: CacheEntry, _skip_userid_check: bool, - topic_summary: str | None, + topic_summary: Optional[str], ) -> None: """ Store one part of conversation into conversation history cache. @@ -268,7 +268,7 @@ def store_conversation_into_cache( cache_entry (CacheEntry): Entry to insert or append to the conversation history. _skip_userid_check (bool): When true, bypasses enforcing that the cache operation must match the user id. - topic_summary (str | None): Optional topic summary to store alongside + topic_summary (Optional[str]): Optional topic summary to store alongside the conversation; ignored if None or empty. """ if config.conversation_cache_configuration.type is not None: @@ -292,7 +292,7 @@ async def get_agent( system_prompt: str, available_input_shields: list[str], available_output_shields: list[str], - conversation_id: str | None, + conversation_id: Optional[str], no_tools: bool = False, ) -> tuple[AsyncAgent, str, str]: """ @@ -317,7 +317,7 @@ async def get_agent( available_output_shields (list[str]): Output shields to apply to the agent; empty list used if None/empty. - conversation_id (str | None): If provided, attempt to reuse the agent + conversation_id (Optional[str]): If provided, attempt to reuse the agent for this conversation; otherwise a new conversation_id is created. no_tools (bool): When True, disables tool parsing for the agent (uses no tool parser). @@ -441,7 +441,7 @@ def create_rag_chunks_dict(summary: TurnSummary) -> list[dict[str, Any]]: def _process_http_source( src: str, doc_urls: set[str] -) -> tuple[AnyUrl | None, str] | None: +) -> Optional[tuple[Optional[AnyUrl], str]]: """ Process HTTP source and return (doc_url, doc_title) tuple. @@ -451,7 +451,7 @@ def _process_http_source( will add `src` to this set when it is new. Returns: - tuple[AnyUrl | None, str] | None: A tuple (validated_url, doc_title) + Optional[tuple[Optional[AnyUrl], str]]: A tuple (validated_url, doc_title) when `src` was not previously seen: - `validated_url`: an `AnyUrl` instance if `src` is a valid URL, or `None` if validation failed. @@ -477,8 +477,8 @@ def _process_document_id( doc_ids: set[str], doc_urls: set[str], metas_by_id: dict[str, dict[str, Any]], - metadata_map: dict[str, Any] | None, -) -> tuple[AnyUrl | None, str] | None: + metadata_map: Optional[dict[str, Any]], +) -> Optional[tuple[Optional[AnyUrl], str]]: """ Process document ID and return (doc_url, doc_title) tuple. @@ -491,13 +491,13 @@ def _process_document_id( metadata dicts that may contain `docs_url` and `title`. - metadata_map (dict[str, Any] | None): If provided (truthy), indicates + metadata_map (Optional[dict[str, Any]]): If provided (truthy), indicates metadata is available and enables metadata lookup; when falsy, metadata lookup is skipped. Returns: - tuple[AnyUrl | None, str] | None: `(validated_url, doc_title)` where + Optional[tuple[Optional[AnyUrl], str]]: `(validated_url, doc_title)` where `validated_url` is a validated `AnyUrl` or `None` and `doc_title` is the chosen title string; returns `None` if the `src` or its URL was already processed. @@ -535,9 +535,9 @@ def _process_document_id( def _add_additional_metadata_docs( doc_urls: set[str], metas_by_id: dict[str, dict[str, Any]], -) -> list[tuple[AnyUrl | None, str]]: +) -> list[tuple[Optional[AnyUrl], str]]: """Add additional referenced documents from metadata_map.""" - additional_entries: list[tuple[AnyUrl | None, str]] = [] + additional_entries: list[tuple[Optional[AnyUrl], str]] = [] for meta in metas_by_id.values(): doc_url = meta.get("docs_url") title = meta.get("title") # Note: must be "title", not "Title" @@ -562,8 +562,8 @@ def _add_additional_metadata_docs( def _process_rag_chunks_for_documents( rag_chunks: list, - metadata_map: dict[str, Any] | None = None, -) -> list[tuple[AnyUrl | None, str]]: + metadata_map: Optional[dict[str, Any]] = None, +) -> list[tuple[Optional[AnyUrl], str]]: """ Process RAG chunks and return a list of (doc_url, doc_title) tuples. @@ -572,11 +572,11 @@ def _process_rag_chunks_for_documents( Parameters: rag_chunks (list): Iterable of RAG chunk objects; each chunk must provide a `source` attribute (e.g., an HTTP URL or a document ID). - metadata_map (dict[str, Any] | None): Optional mapping of document IDs + metadata_map (Optional[dict[str, Any]]): Optional mapping of document IDs to metadata dictionaries used to resolve titles and document URLs. Returns: - list[tuple[AnyUrl | None, str]]: Ordered list of tuples where the first + list[tuple[Optional[AnyUrl], str]]: Ordered list of tuples where the first element is a validated URL object or `None` (if no URL is available) and the second element is the document title. """ @@ -588,7 +588,7 @@ def _process_rag_chunks_for_documents( if metadata_map: metas_by_id = {k: v for k, v in metadata_map.items() if isinstance(v, dict)} - document_entries: list[tuple[AnyUrl | None, str]] = [] + document_entries: list[tuple[Optional[AnyUrl], str]] = [] for chunk in rag_chunks: src = chunk.source @@ -616,9 +616,9 @@ def _process_rag_chunks_for_documents( def create_referenced_documents( rag_chunks: list, - metadata_map: dict[str, Any] | None = None, + metadata_map: Optional[dict[str, Any]] = None, return_dict_format: bool = False, -) -> list[ReferencedDocument] | list[dict[str, str | None]]: +) -> list[ReferencedDocument] | list[dict[str, Optional[str]]]: """ Create referenced documents from RAG chunks with optional metadata enrichment. @@ -721,7 +721,7 @@ async def cleanup_after_streaming( is_transcripts_enabled_func: Any, store_transcript_func: Any, persist_user_conversation_details_func: Any, - rag_chunks: list[dict[str, Any]] | None = None, + rag_chunks: Optional[list[dict[str, Any]]] = None, ) -> None: """ Perform cleanup tasks after streaming is complete. diff --git a/src/utils/transcripts.py b/src/utils/transcripts.py index 6b0d93c7d..6e159566d 100644 --- a/src/utils/transcripts.py +++ b/src/utils/transcripts.py @@ -10,6 +10,7 @@ import os from pathlib import Path import hashlib +from typing import Optional from configuration import configuration from models.requests import Attachment, QueryRequest @@ -57,7 +58,7 @@ def store_transcript( # pylint: disable=too-many-arguments,too-many-positional- user_id: str, conversation_id: str, model_id: str, - provider_id: str | None, + provider_id: Optional[str], query_is_valid: bool, query: str, query_request: QueryRequest, diff --git a/src/utils/types.py b/src/utils/types.py index 070869da5..b6280d13d 100644 --- a/src/utils/types.py +++ b/src/utils/types.py @@ -68,7 +68,7 @@ def get_tool_calls( Return the `tool_calls` list from a CompletionMessage, or an empty list if none are present. Parameters: - output_message (AgentCompletionMessage | None): Completion + output_message (Optional[AgentCompletionMessage]): Completion message potentially containing `tool_calls`. Returns: @@ -129,8 +129,8 @@ class RAGChunk(BaseModel): """Model representing a RAG chunk used in the response.""" content: str = Field(description="The content of the chunk") - source: str | None = Field(None, description="Source document or URL") - score: float | None = Field(None, description="Relevance score") + source: Optional[str] = Field(None, description="Source document or URL") + score: Optional[float] = Field(None, description="Relevance score") class TurnSummary(BaseModel):