From 14e6737290d16524dd550feb122967413241e338 Mon Sep 17 00:00:00 2001 From: yucheng-berriai Date: Wed, 15 Jul 2026 13:04:07 -0700 Subject: [PATCH] chore(otel/v2): satisfy tightened lint budgets after merge The merge inherited upstream's lowered strict and LIT ceilings, which our PR's own new-file code sat just above. Modernize our annotations (Optional[X] to X | None), give the noqa suppressions the reason comments the LIT003 gate requires, and drop the inert `# type: ignore` comments that pyright already disables under this repo's config (LIT009). Every gate is back within its ceiling with no net budget bump --- litellm/integrations/otel/plumbing/routing.py | 12 ++++---- .../integrations/otel/presets/destinations.py | 14 ++++----- litellm/litellm_core_utils/litellm_logging.py | 2 +- litellm/models/credentials.py | 22 +++++++------- litellm/proxy/auth/user_api_key_auth.py | 4 +-- .../proxy/credential_endpoints/endpoints.py | 12 +++----- litellm/proxy/litellm_pre_call_utils.py | 7 +++-- .../key_management_endpoints.py | 4 +-- .../logging_exporter_access.py | 10 +++---- .../logging_exporter_validation.py | 30 +++++++++---------- litellm/types/utils.py | 2 +- 11 files changed, 56 insertions(+), 63 deletions(-) diff --git a/litellm/integrations/otel/plumbing/routing.py b/litellm/integrations/otel/plumbing/routing.py index b2136ed0be0..246acb4a19f 100644 --- a/litellm/integrations/otel/plumbing/routing.py +++ b/litellm/integrations/otel/plumbing/routing.py @@ -348,7 +348,7 @@ class TenantFanOutSpanProcessor(SpanProcessor): continue try: processor.on_end(_with_destination_resource(span, destination)) - except Exception as exc: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 # best-effort fan-out; one destination's failure must not break the others or the request verbose_logger.debug( "OTel V2 fan-out: forwarding span to %s failed: %s", destination.endpoint, @@ -359,7 +359,7 @@ class TenantFanOutSpanProcessor(SpanProcessor): for processor in self._processors.values(): try: processor.shutdown() - except Exception as exc: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 # a single processor's shutdown failure must not abort shutting down the rest verbose_logger.debug("OTel V2 fan-out: processor shutdown failed: %s", exc) self._processors.clear() @@ -369,7 +369,7 @@ class TenantFanOutSpanProcessor(SpanProcessor): try: if not processor.force_flush(timeout_millis): all_ok = False - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # a single processor's flush failure must not fail the whole force_flush all_ok = False return all_ok @@ -381,9 +381,11 @@ class TenantFanOutSpanProcessor(SpanProcessor): return cached from litellm.integrations.otel.plumbing.providers import ( _exporter_from_spec, - _processor_for as _build_processor, default_otlp_kind_for_backend, ) + from litellm.integrations.otel.plumbing.providers import ( + _processor_for as _build_processor, + ) try: spec = ExporterSpec( @@ -394,7 +396,7 @@ class TenantFanOutSpanProcessor(SpanProcessor): ) exporter = _exporter_from_spec(spec) processor = _build_processor(exporter, use_simple=False) - except Exception as exc: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 # a malformed destination spec must not break fan-out; skip this destination verbose_logger.debug( "OTel V2 fan-out: failed to build processor for %s: %s", destination.endpoint, diff --git a/litellm/integrations/otel/presets/destinations.py b/litellm/integrations/otel/presets/destinations.py index ad099989537..2b0ed6b1a5d 100644 --- a/litellm/integrations/otel/presets/destinations.py +++ b/litellm/integrations/otel/presets/destinations.py @@ -11,7 +11,7 @@ credential values only. """ import os -from typing import Callable, Mapping, Optional +from typing import Callable, Mapping from litellm.constants import LITELLM_LOGGING_CREDENTIAL_NAME_KEY from litellm.integrations.langfuse.langfuse_otel import ( @@ -37,7 +37,7 @@ def _langfuse_endpoint(host: str) -> str: return f"{normalized.rstrip('/')}/api/public/otel" -def _langfuse_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: +def _langfuse_destination(values: Mapping[str, str]) -> OtelDestination | None: public_key = values.get("langfuse_public_key") secret_key = values.get("langfuse_secret_key") if not public_key or not secret_key: @@ -48,7 +48,7 @@ def _langfuse_destination(values: Mapping[str, str]) -> Optional[OtelDestination return OtelDestination(endpoint=endpoint, headers={"Authorization": auth}) -def _arize_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: +def _arize_destination(values: Mapping[str, str]) -> OtelDestination | None: space = values.get("arize_space_id") or values.get("arize_space_key") api_key = values.get("arize_api_key") if not space or not api_key: @@ -69,7 +69,7 @@ def _arize_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: ) -def _weave_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: +def _weave_destination(values: Mapping[str, str]) -> OtelDestination | None: api_key = values.get("wandb_api_key") if not api_key: return None @@ -94,7 +94,7 @@ def _weave_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: return OtelDestination(endpoint=endpoint, headers=headers) -def _generic_destination(values: Mapping[str, str]) -> Optional[OtelDestination]: +def _generic_destination(values: Mapping[str, str]) -> OtelDestination | None: """Any OTLP backend: an explicit endpoint plus raw headers. The catch-all that makes the registry cover self-hosted collectors / Phoenix / Honeycomb / etc.""" endpoint = values.get("otel_endpoint") @@ -103,7 +103,7 @@ def _generic_destination(values: Mapping[str, str]) -> Optional[OtelDestination] return OtelDestination(endpoint=endpoint, headers=_parse_header_string(values.get("otel_headers", ""))) -_ADAPTERS: dict[str, Callable[[Mapping[str, str]], Optional[OtelDestination]]] = { +_ADAPTERS: dict[str, Callable[[Mapping[str, str]], OtelDestination | None]] = { "langfuse_otel": _langfuse_destination, "arize": _arize_destination, "weave_otel": _weave_destination, @@ -113,7 +113,7 @@ _ADAPTERS: dict[str, Callable[[Mapping[str, str]], Optional[OtelDestination]]] = OTEL_V2_DESTINATION_CALLBACKS = frozenset(_ADAPTERS) -def build_destination(callback_name: str, values: Mapping[str, str]) -> Optional[OtelDestination]: +def build_destination(callback_name: str, values: Mapping[str, str]) -> OtelDestination | None: """Map an admin credential's ``values`` to an ``OtelDestination`` for ``callback_name``, falling back to the generic OTLP passthrough. diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 6f62663f141..bff2b24dabb 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4003,7 +4003,7 @@ def _init_custom_logger_compatible_class( # ``generic`` destination gets the full trace (incl. the gen-AI span), not # just proxy-internal spans. Only meaningful as an admin-owned destination, # so there is no legacy fallback: None when no v2 logger is constructed. - return _maybe_construct_otel_v2("generic", _in_memory_loggers) # type: ignore + return _maybe_construct_otel_v2("generic", _in_memory_loggers) elif logging_integration == "pagerduty": for callback in _in_memory_loggers: if isinstance(callback, PagerDutyAlerting): diff --git a/litellm/models/credentials.py b/litellm/models/credentials.py index cdad7fdc24c..7c3cfef3265 100644 --- a/litellm/models/credentials.py +++ b/litellm/models/credentials.py @@ -5,8 +5,6 @@ These are the canonical credential types for the proxy. They live in the model layer; ``litellm.types.utils`` re-exports them for backwards compatibility. """ -from typing import Optional - from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -20,8 +18,8 @@ class CredentialItem(CredentialBase): class CreateCredentialItem(CredentialBase): - credential_values: Optional[dict] = None - model_id: Optional[str] = None + credential_values: dict | None = None + model_id: str | None = None @model_validator(mode="before") @classmethod @@ -40,9 +38,9 @@ class UpdateCredentialItem(BaseModel): access). ``credential_name`` is optional because most patches don't rename. """ - credential_name: Optional[str] = None - credential_values: Optional[dict] = None - credential_info: Optional[dict] = None + credential_name: str | None = None + credential_values: dict | None = None + credential_info: dict | None = None class CredentialAccess(BaseModel): @@ -71,9 +69,9 @@ class CredentialInfo(BaseModel): model_config = ConfigDict(extra="allow") - credential_type: Optional[str] = None - description: Optional[str] = None - host: Optional[str] = None - endpoint: Optional[str] = None - access: Optional[CredentialAccess] = None + credential_type: str | None = None + description: str | None = None + host: str | None = None + endpoint: str | None = None + access: CredentialAccess | None = None auto_enable: bool = False diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 7a364f48337..d255a102a53 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -1030,9 +1030,9 @@ async def _hoist_request_destinations(request: Request, user_api_key_dict: UserA set_request_destinations(destinations) try: request.state.otel_destinations = destinations_raw - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # request.state mirror is best-effort; the ContextVar is the source of truth pass - except Exception as exc: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 # destination hoist is best-effort telemetry setup; it must never fail auth verbose_proxy_logger.debug("OTel V2: hoist destination resolution failed: %s", exc) diff --git a/litellm/proxy/credential_endpoints/endpoints.py b/litellm/proxy/credential_endpoints/endpoints.py index 51fc514d449..0b780b3a9db 100644 --- a/litellm/proxy/credential_endpoints/endpoints.py +++ b/litellm/proxy/credential_endpoints/endpoints.py @@ -140,16 +140,14 @@ async def _caller_admin_scope( if m.organization_id and m.user_role == LitellmUserRoles.ORG_ADMIN.value ) org_teams = ( - await prisma_client.db.litellm_teamtable.find_many( # type: ignore[union-attr] - where={"organization_id": {"in": list(org_admin_of)}} - ) + await prisma_client.db.litellm_teamtable.find_many(where={"organization_id": {"in": list(org_admin_of)}}) if org_admin_of else [] ) org_grantable = frozenset(t.team_id for t in org_teams if t.team_id) return CallerAdminScope(team_admin_of | org_grantable, org_admin_of) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # fail closed to an empty admin scope so a lookup error never widens access verbose_proxy_logger.exception("caller admin-scope lookup failed") return CallerAdminScope(frozenset(), frozenset()) @@ -185,10 +183,8 @@ async def _credential_for_admin_gate(credential_name: str, prisma_client: object if prisma_client is None: return None try: - return await CredentialsRepository( - prisma_client # type: ignore[arg-type] - ).find_by_name(credential_name) - except Exception: # noqa: BLE001 + return await CredentialsRepository(prisma_client).find_by_name(credential_name) + except Exception: # noqa: BLE001 # treat any lookup failure as credential-not-found return None diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 93f9023a453..c0fab6f5ee8 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -605,7 +605,7 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i proxy_logging_obj=proxy_server.proxy_logging_obj, ) ) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # best-effort identity enrichment; a failed lookup must not block the request pass if user_api_key_dict.team_id: @@ -619,7 +619,7 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i proxy_logging_obj=proxy_server.proxy_logging_obj, ) ) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # best-effort identity enrichment; a failed lookup must not block the request pass if org_id: @@ -633,7 +633,7 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i proxy_logging_obj=proxy_server.proxy_logging_obj, ) ) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 # best-effort identity enrichment; a failed lookup must not block the request pass return names @@ -676,6 +676,7 @@ async def _resolve_logging_exporters( from litellm.proxy.management_endpoints.logging_exporter_access import ( _has_explicit_access_grants, ) + if _has_explicit_access_grants(info.access): return access_grants(info.access, team_ids, org_ids) return True # no grants = proxy-wide auto diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 412af4ecd07..e4549e45fb8 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -2538,7 +2538,7 @@ async def _validate_update_key_data( @router.post("/key/update", tags=["key management"], dependencies=[Depends(user_api_key_auth)]) @management_endpoint_wrapper -async def update_key_fn( # noqa: C901 +async def update_key_fn( # noqa: C901 # single endpoint handling many optional key-update fields; decomposition is out of scope here request: Request, data: UpdateKeyRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), @@ -4632,7 +4632,7 @@ async def _execute_virtual_key_regeneration( dependencies=[Depends(user_api_key_auth)], ) @management_endpoint_wrapper -async def regenerate_key_fn( # noqa: C901 +async def regenerate_key_fn( # noqa: C901 # single endpoint handling many optional key-regeneration fields; decomposition is out of scope here key: Optional[str] = None, data: Optional[RegenerateKeyRequest] = None, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), diff --git a/litellm/proxy/management_endpoints/logging_exporter_access.py b/litellm/proxy/management_endpoints/logging_exporter_access.py index 4514385bb60..774fb20fa54 100644 --- a/litellm/proxy/management_endpoints/logging_exporter_access.py +++ b/litellm/proxy/management_endpoints/logging_exporter_access.py @@ -15,14 +15,12 @@ admin scope is the given set of team ids and org ids. Single-identity callers and orgs, passes the full scope. """ -from typing import Optional - from pydantic import ValidationError from litellm.models.credentials import CredentialAccess, CredentialInfo -def parse_credential_info(raw: object) -> Optional[CredentialInfo]: +def parse_credential_info(raw: object) -> CredentialInfo | None: """Parse stored ``credential_info`` into the typed model, or ``None`` when it is absent or malformed. @@ -38,7 +36,7 @@ def parse_credential_info(raw: object) -> Optional[CredentialInfo]: return None -def identity_scope(team_id: Optional[str], org_id: Optional[str]) -> tuple[frozenset[str], frozenset[str]]: +def identity_scope(team_id: str | None, org_id: str | None) -> tuple[frozenset[str], frozenset[str]]: """A single request identity's admin scope as ``(team_ids, org_ids)`` for ``access_grants`` / ``is_destination_visible``.""" return ( @@ -48,7 +46,7 @@ def identity_scope(team_id: Optional[str], org_id: Optional[str]) -> tuple[froze def access_grants( - access: Optional[CredentialAccess], + access: CredentialAccess | None, team_ids: frozenset[str], org_ids: frozenset[str], ) -> bool: @@ -68,7 +66,7 @@ def access_grants( return not org_ids.isdisjoint(access.orgs) -def _has_explicit_access_grants(access: Optional[CredentialAccess]) -> bool: +def _has_explicit_access_grants(access: CredentialAccess | None) -> bool: """True when ``access`` contains at least one explicit grant (global, team, or org). Used to distinguish "access intentionally left empty" (proxy-wide fallback) from diff --git a/litellm/proxy/management_endpoints/logging_exporter_validation.py b/litellm/proxy/management_endpoints/logging_exporter_validation.py index 7776d1e4619..781b128dcc4 100644 --- a/litellm/proxy/management_endpoints/logging_exporter_validation.py +++ b/litellm/proxy/management_endpoints/logging_exporter_validation.py @@ -11,8 +11,6 @@ what enables it. The resolver (``litellm_pre_call_utils``) re-checks visibility request time, so this gate and the resolver agree on what "visible" means. """ -from typing import Optional - from fastapi import HTTPException, status import litellm @@ -26,7 +24,7 @@ from litellm.proxy.management_endpoints.logging_exporter_access import ( LOGGING_EXPORTERS_KEY = "logging_exporters" -def is_admin_gated_credential_info(credential_info: Optional[dict]) -> bool: +def is_admin_gated_credential_info(credential_info: dict | None) -> bool: """Whether a credential write must be proxy-admin only. True when the credential is a logging destination or carries an ``access`` grant, @@ -37,7 +35,7 @@ def is_admin_gated_credential_info(credential_info: Optional[dict]) -> bool: return credential_info.get("credential_type") == "logging" or "access" in credential_info -def validate_credential_access(credential_info: Optional[dict]) -> None: +def validate_credential_access(credential_info: dict | None) -> None: """Validate ``credential_info.access`` shape when the write sets one. No-op when ``access`` is absent. Otherwise it must be an object whose ``global`` (if @@ -87,8 +85,8 @@ def _logging_credential_names() -> set[str]: def _reject_unassignable_destinations( exporters: list[str], *, - scope_team_id: Optional[str], - scope_org_id: Optional[str], + scope_team_id: str | None, + scope_org_id: str | None, ) -> None: """Reject names a non-proxy-admin cannot assign in this scope. @@ -142,8 +140,8 @@ def _validate_exporters_shape_and_names(exporters: object) -> None: def _exporter_value_changes( - requested_metadata: Optional[dict], - existing_metadata: Optional[dict], + requested_metadata: dict | None, + existing_metadata: dict | None, ) -> bool: """True if the effective ``metadata.logging_exporters`` value would change. @@ -173,14 +171,14 @@ def _exporter_value_changes( def validate_logging_exporter_field( - requested_exporters: Optional[list], + requested_exporters: list | None, user_api_key_dict: UserAPIKeyAuth, *, caller_is_team_admin: bool = False, caller_is_org_admin: bool = False, - existing_exporters: Optional[list] = None, - scope_team_id: Optional[str] = None, - scope_org_id: Optional[str] = None, + existing_exporters: list | None = None, + scope_team_id: str | None = None, + scope_org_id: str | None = None, ) -> None: """Authorize a typed ``logging_exporters`` write (the column-backed field). @@ -205,14 +203,14 @@ def validate_logging_exporter_field( def validate_logging_exporter_assignment( - metadata: Optional[dict], + metadata: dict | None, user_api_key_dict: UserAPIKeyAuth, *, caller_is_team_admin: bool = False, caller_is_org_admin: bool = False, - existing_metadata: Optional[dict] = None, - scope_team_id: Optional[str] = None, - scope_org_id: Optional[str] = None, + existing_metadata: dict | None = None, + scope_team_id: str | None = None, + scope_org_id: str | None = None, ) -> None: """Validate a ``metadata.logging_exporters`` write on key / team / org endpoints. diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 0e5127edd0e..064b10d2448 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3638,7 +3638,7 @@ from litellm.models.credentials import CredentialItem as CredentialItem # noqa: from litellm.models.credentials import ( # noqa: E402 CreateCredentialItem as CreateCredentialItem, ) -from litellm.models.credentials import ( # noqa: E402 +from litellm.models.credentials import ( # noqa: E402 # at file end to avoid a circular import with litellm.models.credentials UpdateCredentialItem as UpdateCredentialItem, )