ci: fix OCR lint and patch coverage

This commit is contained in:
Ishaan Jaff 2026-06-25 12:21:12 -07:00
parent 1bfa6256ec
commit 5581fc0946
No known key found for this signature in database
9 changed files with 114 additions and 88 deletions

View file

@ -3,6 +3,9 @@ codecov:
notify:
wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI
ignore:
- "litellm-rust/**"
# Uploads are flagged per workflow/shard (GHA) or "circleci". carryforward makes
# a re-upload of a flag replace its prior session instead of accumulating a
# conflicting one, and lets a commit reuse a flag from its parent when that flag

View file

@ -11,7 +11,7 @@ The operation location must be polled until the analysis completes.
import asyncio
import re
import time
from typing import Any, Dict, Optional
from typing import Any, Dict
from urllib.parse import quote
import httpx
@ -56,7 +56,7 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig):
def __init__(self) -> None:
super().__init__()
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
return AZURE_DOCUMENT_INTELLIGENCE_API_KEY_ENV_VAR
def get_supported_ocr_params(self, model: str) -> list:
@ -149,9 +149,9 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig):
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -187,10 +187,10 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig):
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -2,7 +2,7 @@
Azure AI OCR transformation implementation.
"""
from typing import Dict, Optional
from typing import Dict
from litellm._logging import verbose_logger
from litellm.litellm_core_utils.prompt_templates.image_handling import (
@ -32,16 +32,16 @@ class AzureAIOCRConfig(MistralOCRConfig):
def __init__(self) -> None:
super().__init__()
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
return AZURE_AI_OCR_API_KEY_ENV_VAR
def validate_environment(
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -77,10 +77,10 @@ class AzureAIOCRConfig(MistralOCRConfig):
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -2,7 +2,7 @@
Base OCR transformation configuration.
"""
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Dict, List, Union
import httpx
from pydantic import PrivateAttr
@ -25,16 +25,16 @@ DocumentType = Dict[str, str]
class OCRPageDimensions(LiteLLMPydanticObjectBase):
"""Page dimensions from OCR response."""
dpi: Optional[int] = None
height: Optional[int] = None
width: Optional[int] = None
dpi: int | None = None
height: int | None = None
width: int | None = None
class OCRPageImage(LiteLLMPydanticObjectBase):
"""Image extracted from OCR page."""
image_base64: Optional[str] = None
bbox: Optional[Dict[str, Any]] = None
image_base64: str | None = None
bbox: Dict[str, Any] | None = None
model_config = {"extra": "allow"}
@ -44,8 +44,8 @@ class OCRPage(LiteLLMPydanticObjectBase):
index: int
markdown: str
images: Optional[List[OCRPageImage]] = None
dimensions: Optional[OCRPageDimensions] = None
images: List[OCRPageImage] | None = None
dimensions: OCRPageDimensions | None = None
model_config = {"extra": "allow"}
@ -53,9 +53,9 @@ class OCRPage(LiteLLMPydanticObjectBase):
class OCRUsageInfo(LiteLLMPydanticObjectBase):
"""Usage information from OCR response."""
pages_processed: Optional[int] = None
credits: Optional[float] = None
doc_size_bytes: Optional[int] = None
pages_processed: int | None = None
credits: float | None = None
doc_size_bytes: int | None = None
model_config = {"extra": "allow"}
@ -68,8 +68,8 @@ class OCRResponse(LiteLLMPydanticObjectBase):
pages: List[OCRPage]
model: str
document_annotation: Optional[Any] = None
usage_info: Optional[OCRUsageInfo] = None
document_annotation: Any | None = None
usage_info: OCRUsageInfo | None = None
object: str = "ocr"
model_config = {"extra": "allow"}
@ -81,8 +81,8 @@ class OCRResponse(LiteLLMPydanticObjectBase):
class OCRRequestData(LiteLLMPydanticObjectBase):
"""OCR request data structure."""
data: Optional[Union[Dict, bytes]] = None
files: Optional[Dict[str, Any]] = None
data: Union[Dict, bytes] | None = None
files: Dict[str, Any] | None = None
class BaseOCRConfig:
@ -101,7 +101,7 @@ class BaseOCRConfig:
"""
return []
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
"""
Return the provider-specific API key environment variable name, if any.
"""
@ -120,9 +120,9 @@ class BaseOCRConfig:
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -133,10 +133,10 @@ class BaseOCRConfig:
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -2,7 +2,7 @@
Mistral OCR transformation implementation.
"""
from typing import Any, Dict, Optional
from typing import Any, Dict
import httpx
@ -61,7 +61,7 @@ class MistralOCRConfig(BaseOCRConfig):
"id",
]
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
return MISTRAL_OCR_API_KEY_ENV_VAR
def map_ocr_params(
@ -90,9 +90,9 @@ class MistralOCRConfig(BaseOCRConfig):
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -118,10 +118,10 @@ class MistralOCRConfig(BaseOCRConfig):
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -3,7 +3,7 @@ Vertex AI DeepSeek OCR transformation implementation.
"""
import json
from typing import TYPE_CHECKING, Any, Dict, Optional
from typing import TYPE_CHECKING, Any, Dict
import httpx
@ -38,16 +38,16 @@ class VertexAIDeepSeekOCRConfig(BaseOCRConfig):
super().__init__()
self.vertex_base = VertexBase()
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
return VERTEX_AI_DEEPSEEK_OCR_API_KEY_ENV_VAR
def validate_environment(
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -89,10 +89,10 @@ class VertexAIDeepSeekOCRConfig(BaseOCRConfig):
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -2,7 +2,7 @@
Vertex AI Mistral OCR transformation implementation.
"""
from typing import Dict, Optional
from typing import Dict
from litellm._logging import verbose_logger
from litellm.litellm_core_utils.prompt_templates.image_handling import (
@ -34,16 +34,16 @@ class VertexAIOCRConfig(MistralOCRConfig):
super().__init__()
self.vertex_base = VertexBase()
def get_api_key_env_var(self) -> Optional[str]:
def get_api_key_env_var(self) -> str | None:
return VERTEX_AI_OCR_API_KEY_ENV_VAR
def validate_environment(
self,
headers: Dict,
model: str,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
litellm_params: Optional[dict] = None,
api_key: str | None = None,
api_base: str | None = None,
litellm_params: dict | None = None,
**kwargs,
) -> Dict:
"""
@ -85,10 +85,10 @@ class VertexAIOCRConfig(MistralOCRConfig):
def get_complete_url(
self,
api_base: Optional[str],
api_base: str | None,
model: str,
optional_params: dict,
litellm_params: Optional[dict] = None,
litellm_params: dict | None = None,
**kwargs,
) -> str:
"""

View file

@ -9,7 +9,7 @@ import os
import re
from dataclasses import dataclass
from io import IOBase
from typing import Any, Callable, Coroutine, Optional, Union, cast
from typing import Any, Callable, Coroutine, Union, cast
import httpx
@ -38,10 +38,10 @@ base_llm_http_handler = BaseLLMHTTPHandler()
class _PreparedOCRRequest:
model: str
document: dict[str, Any]
api_key: Optional[str]
api_base: Optional[str]
api_key: str | None
api_base: str | None
custom_llm_provider: str
extra_headers: Optional[dict[str, object]]
extra_headers: dict[str, object] | None
provider_config: BaseOCRConfig
optional_params: dict[str, object]
litellm_params: dict[str, object]
@ -51,8 +51,8 @@ class _PreparedOCRRequest:
@dataclass
class _PreparedRustOCRCall:
api_key: Optional[str]
api_base: Optional[str]
api_key: str | None
api_base: str | None
headers: dict[str, object]
optional_params: dict[str, object]
@ -66,8 +66,8 @@ _RUST_OCR_PROVIDERS = {
def _timeout_to_seconds(
timeout: Optional[Union[float, httpx.Timeout]],
) -> Optional[float]:
timeout: Union[float, httpx.Timeout] | None,
) -> float | None:
"""Convert the Python OCR timeout to a single seconds value for the Rust bridge.
The Rust HTTP client takes one duration; ``httpx.Timeout`` carries separate
@ -84,15 +84,15 @@ def _timeout_to_seconds(
def _prepare_ocr_request(
model: str,
document: dict[str, Any],
api_key: Optional[str],
api_base: Optional[str],
timeout: Optional[Union[float, httpx.Timeout]],
custom_llm_provider: Optional[str],
extra_headers: Optional[dict[str, Any]],
api_key: str | None,
api_base: str | None,
timeout: Union[float, httpx.Timeout] | None,
custom_llm_provider: str | None,
extra_headers: dict[str, Any] | None,
kwargs: dict[str, Any],
) -> _PreparedOCRRequest:
litellm_logging_obj = cast(LiteLLMLoggingObj, kwargs.pop("litellm_logging_obj"))
litellm_call_id = cast(Optional[str], kwargs.get("litellm_call_id", None))
litellm_call_id = cast(str | None, kwargs.get("litellm_call_id", None))
if not isinstance(document, dict):
raise ValueError(
@ -173,7 +173,7 @@ def _prepare_ocr_request(
api_key=api_key,
api_base=api_base,
custom_llm_provider=custom_llm_provider,
extra_headers=cast(Optional[dict[str, object]], extra_headers),
extra_headers=cast(dict[str, object] | None, extra_headers),
provider_config=ocr_provider_config,
optional_params=cast(dict[str, object], optional_params),
litellm_params=dict(litellm_params),
@ -188,7 +188,7 @@ def _rust_ocr_supported(prepared_request: _PreparedOCRRequest) -> bool:
def _rust_bridge_optional_params(
prepared_request: _PreparedOCRRequest,
resolve_secret: Callable[[str], Optional[str]],
resolve_secret: Callable[[str], str | None],
) -> dict[str, object]:
optional_params = dict(prepared_request.optional_params)
if prepared_request.custom_llm_provider == "vertex_ai":
@ -214,8 +214,8 @@ def _rust_bridge_optional_params(
def _rust_bridge_api_base(
prepared_request: _PreparedOCRRequest,
resolve_secret: Callable[[str], Optional[str]],
) -> Optional[str]:
resolve_secret: Callable[[str], str | None],
) -> str | None:
if prepared_request.api_base is not None:
return prepared_request.api_base
if prepared_request.custom_llm_provider == "azure_ai/doc-intelligence":
@ -232,7 +232,7 @@ def _rust_bridge_api_base(
def _prepare_rust_ocr_call(
prepared_request: _PreparedOCRRequest,
resolve_api_key: Callable[[str], Optional[str]],
resolve_api_key: Callable[[str], str | None],
) -> _PreparedRustOCRCall:
provider_config = prepared_request.provider_config
api_key_env_var = provider_config.get_api_key_env_var()
@ -280,7 +280,7 @@ def _prepare_rust_ocr_call(
def _run_rust_ocr(
rust_ocr: RustOcr,
prepared_request: _PreparedOCRRequest,
resolve_api_key: Callable[[str], Optional[str]],
resolve_api_key: Callable[[str], str | None],
) -> OCRResponse:
"""Run the Mistral OCR call through the Rust bridge and wrap the result.
@ -311,7 +311,7 @@ def _run_rust_ocr(
async def _run_rust_aocr(
rust_aocr: RustAocr,
prepared_request: _PreparedOCRRequest,
resolve_api_key: Callable[[str], Optional[str]],
resolve_api_key: Callable[[str], str | None],
) -> OCRResponse:
prepared = _prepare_rust_ocr_call(
prepared_request=prepared_request,
@ -335,11 +335,11 @@ async def _run_rust_aocr(
async def aocr(
model: str,
document: dict[str, Any],
api_key: Optional[str] = None,
api_base: Optional[str] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
extra_headers: Optional[dict[str, Any]] = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: Union[float, httpx.Timeout] | None = None,
custom_llm_provider: str | None = None,
extra_headers: dict[str, Any] | None = None,
**kwargs,
) -> OCRResponse:
"""
@ -536,7 +536,7 @@ def convert_file_document_to_url_document(document: dict[str, Any]) -> dict[str,
file_bytes: bytes
mime_type: str = "application/octet-stream"
file_name: Optional[str] = None
file_name: str | None = None
if isinstance(file_input, str):
# Bare strings are rejected here. The OCR ``document`` accepts a
@ -606,11 +606,11 @@ def convert_file_document_to_url_document(document: dict[str, Any]) -> dict[str,
def ocr(
model: str,
document: dict[str, Any],
api_key: Optional[str] = None,
api_base: Optional[str] = None,
timeout: Optional[Union[float, httpx.Timeout]] = None,
custom_llm_provider: Optional[str] = None,
extra_headers: Optional[dict[str, Any]] = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: Union[float, httpx.Timeout] | None = None,
custom_llm_provider: str | None = None,
extra_headers: dict[str, Any] | None = None,
**kwargs,
) -> Union[OCRResponse, Coroutine[Any, Any, OCRResponse]]:
"""

View file

@ -732,3 +732,26 @@ def test_ocr_falls_back_to_python_when_bridge_unavailable(monkeypatch):
assert captured.get("called") is True # Python path was used
assert isinstance(response, OCRResponse)
def test_ocr_provider_configs_expose_api_key_env_vars():
from litellm.llms.azure_ai.ocr.document_intelligence.transformation import (
AzureDocumentIntelligenceOCRConfig,
)
from litellm.llms.azure_ai.ocr.transformation import AzureAIOCRConfig
from litellm.llms.base_llm.ocr.transformation import BaseOCRConfig
from litellm.llms.mistral.ocr.transformation import MistralOCRConfig
from litellm.llms.vertex_ai.ocr.deepseek_transformation import (
VertexAIDeepSeekOCRConfig,
)
from litellm.llms.vertex_ai.ocr.transformation import VertexAIOCRConfig
assert BaseOCRConfig().get_api_key_env_var() is None
assert MistralOCRConfig().get_api_key_env_var() == "MISTRAL_API_KEY"
assert AzureAIOCRConfig().get_api_key_env_var() == "AZURE_AI_API_KEY"
assert (
AzureDocumentIntelligenceOCRConfig().get_api_key_env_var()
== "AZURE_DOCUMENT_INTELLIGENCE_API_KEY"
)
assert VertexAIOCRConfig().get_api_key_env_var() == "VERTEX_AI_API_KEY"
assert VertexAIDeepSeekOCRConfig().get_api_key_env_var() == "VERTEX_AI_API_KEY"