mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
style: black + prettier formatting and rename shadowed _processor_for import
CI uses Black (not ruff format) and the changed-files frontend lint runs prettier; apply both. Also alias the providers._processor_for import inside TenantFanOutSpanProcessor._processor_for so the recursive-function detector no longer false-positives on the same-name call.
This commit is contained in:
parent
9af9714ed3
commit
8062bdf389
10 changed files with 152 additions and 69 deletions
|
|
@ -112,15 +112,17 @@ class OpenTelemetryV2(CustomLogger):
|
|||
self._tracer_provider: TracerProvider = (
|
||||
tracer_provider
|
||||
if tracer_provider is not None
|
||||
else build_tracer_provider(
|
||||
self.config, tenant_fan_out_owner=callback_name
|
||||
)
|
||||
else build_tracer_provider(self.config, tenant_fan_out_owner=callback_name)
|
||||
)
|
||||
self.tracer: Tracer = get_tracer(self._tracer_provider, LITELLM_TRACER_NAME)
|
||||
self._metrics_recorder = self._init_metrics(meter_provider)
|
||||
self._metric_filter_error_logged = False
|
||||
self._emitter = SpanEmitter(self.tracer, self.config, mappers=resolve_mappers(self.config.mapper_names))
|
||||
self._tenant_tracers = TenantTracerCache(self.config, callback_name, LITELLM_TRACER_NAME)
|
||||
self._emitter = SpanEmitter(
|
||||
self.tracer, self.config, mappers=resolve_mappers(self.config.mapper_names)
|
||||
)
|
||||
self._tenant_tracers = TenantTracerCache(
|
||||
self.config, callback_name, LITELLM_TRACER_NAME
|
||||
)
|
||||
self._open_llm_calls: "OrderedDict[str, _LLMCallSpan]" = OrderedDict()
|
||||
self._init_otel_logger_on_litellm_proxy()
|
||||
|
||||
|
|
@ -144,7 +146,9 @@ class OpenTelemetryV2(CustomLogger):
|
|||
|
||||
def _register_in_callback_list(self, callbacks: list) -> None:
|
||||
already_otel = any(
|
||||
cb.__class__.__module__.startswith(_OTEL_MODULES) for cb in callbacks if hasattr(cb, "__class__")
|
||||
cb.__class__.__module__.startswith(_OTEL_MODULES)
|
||||
for cb in callbacks
|
||||
if hasattr(cb, "__class__")
|
||||
)
|
||||
if not already_otel:
|
||||
callbacks.append(self)
|
||||
|
|
@ -171,7 +175,9 @@ class OpenTelemetryV2(CustomLogger):
|
|||
each logger exports only the destinations tagged with its own callback_name,
|
||||
so each backend's span keeps its own attribute vocabulary.
|
||||
"""
|
||||
return tuple(d for d in call.otel_destinations if d.callback_name == self.callback_name)
|
||||
return tuple(
|
||||
d for d in call.otel_destinations if d.callback_name == self.callback_name
|
||||
)
|
||||
|
||||
# ====================================================================== #
|
||||
# LLM-call callbacks — the span is opened at the ``pre_call`` boundary and
|
||||
|
|
@ -220,9 +226,13 @@ class OpenTelemetryV2(CustomLogger):
|
|||
call.provisional_span_name,
|
||||
parent_context=parent_context,
|
||||
start_time_ns=start_time_ns,
|
||||
tracer=self._tenant_tracers.tracer_for(self.tracer, self._destinations_for_backend(call)),
|
||||
tracer=self._tenant_tracers.tracer_for(
|
||||
self.tracer, self._destinations_for_backend(call)
|
||||
),
|
||||
)
|
||||
self._open_llm_calls[call_id] = _LLMCallSpan(span=span, start_time_ns=start_time_ns)
|
||||
self._open_llm_calls[call_id] = _LLMCallSpan(
|
||||
span=span, start_time_ns=start_time_ns
|
||||
)
|
||||
# Evict the oldest open call if the map is over budget. A call that opens
|
||||
# but never closes (a stream that only fires stream events) would linger
|
||||
# otherwise; the evicted span is simply dropped (never exported).
|
||||
|
|
@ -274,7 +284,9 @@ class OpenTelemetryV2(CustomLogger):
|
|||
no boundary to open it at), deduped on the call id by the emitter.
|
||||
"""
|
||||
raw_payload = kwargs.get("standard_logging_object")
|
||||
if not raw_payload or not is_mcp_tool_call(cast(Mapping[str, object], raw_payload)):
|
||||
if not raw_payload or not is_mcp_tool_call(
|
||||
cast(Mapping[str, object], raw_payload)
|
||||
):
|
||||
return False
|
||||
payload = cast("StandardLoggingPayload", raw_payload)
|
||||
data = MCPToolCallSpanData.from_standard_logging_payload(
|
||||
|
|
@ -321,7 +333,9 @@ class OpenTelemetryV2(CustomLogger):
|
|||
destinations = self._destinations_for_backend(call)
|
||||
if payload is None or not destinations:
|
||||
return None
|
||||
return self._emit_deferred_llm_call(payload, destinations, to_ns(start_time), to_ns(end_time))
|
||||
return self._emit_deferred_llm_call(
|
||||
payload, destinations, to_ns(start_time), to_ns(end_time)
|
||||
)
|
||||
|
||||
end_time_ns = to_ns(end_time)
|
||||
if payload is None:
|
||||
|
|
@ -329,9 +343,13 @@ class OpenTelemetryV2(CustomLogger):
|
|||
carrier.span.end(end_time=end_time_ns)
|
||||
return None
|
||||
|
||||
data = LLMCallSpanData.from_standard_logging_payload(payload, capture_content=self.config.capture_span_content)
|
||||
data = LLMCallSpanData.from_standard_logging_payload(
|
||||
payload, capture_content=self.config.capture_span_content
|
||||
)
|
||||
if carrier.span is not None:
|
||||
self._emitter.finish_span(SpanRole.LLM_CALL, carrier.span, data, end_time_ns=end_time_ns)
|
||||
self._emitter.finish_span(
|
||||
SpanRole.LLM_CALL, carrier.span, data, end_time_ns=end_time_ns
|
||||
)
|
||||
return carrier.span
|
||||
return self._emit_deferred_llm_call(
|
||||
payload,
|
||||
|
|
@ -355,7 +373,9 @@ class OpenTelemetryV2(CustomLogger):
|
|||
Both anchor to the request's root span via the worker-copied context and
|
||||
seed identity Baggage so the span is labeled consistently.
|
||||
"""
|
||||
data = LLMCallSpanData.from_standard_logging_payload(payload, capture_content=self.config.capture_span_content)
|
||||
data = LLMCallSpanData.from_standard_logging_payload(
|
||||
payload, capture_content=self.config.capture_span_content
|
||||
)
|
||||
base_ctx = resolve_request_span_context()
|
||||
bag = promoted_baggage(
|
||||
data.identity,
|
||||
|
|
@ -437,7 +457,12 @@ class OpenTelemetryV2(CustomLogger):
|
|||
# zero-duration root with no context, so skip it. Real background work
|
||||
# (budget/reset jobs, spend flush) passes start/end times and still emits
|
||||
# as a root; anything with a parent emits regardless.
|
||||
if error_override is None and start_time is None and end_time is None and parent_otel_span is None:
|
||||
if (
|
||||
error_override is None
|
||||
and start_time is None
|
||||
and end_time is None
|
||||
and parent_otel_span is None
|
||||
):
|
||||
return None
|
||||
if error_override is not None and data.error is None:
|
||||
data = ServiceSpanData(
|
||||
|
|
@ -583,7 +608,9 @@ def select_global_otel_v2_logger(
|
|||
"""
|
||||
if registered is not None:
|
||||
return registered
|
||||
existing = next((cb for cb in in_memory_loggers if isinstance(cb, OpenTelemetryV2)), None)
|
||||
existing = next(
|
||||
(cb for cb in in_memory_loggers if isinstance(cb, OpenTelemetryV2)), None
|
||||
)
|
||||
return existing if existing is not None else OpenTelemetryV2()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ _PROPAGATOR = TraceContextTextMapPropagator()
|
|||
# and is inherited by ``asyncio.create_task`` children — i.e. the async logging
|
||||
# callbacks that close the span. It is never reset: the contextvar dies with the
|
||||
# request task, so there is nothing to leak.
|
||||
_request_root_span: "ContextVar[Span | None]" = ContextVar("litellm_otel_request_root_span", default=None)
|
||||
_request_root_span: "ContextVar[Span | None]" = ContextVar(
|
||||
"litellm_otel_request_root_span", default=None
|
||||
)
|
||||
|
||||
# Per-request admin-resolved destinations. Set once at the auth boundary (the
|
||||
# earliest point a request's identity is known) and read by the global-provider
|
||||
|
|
@ -38,7 +40,9 @@ _request_root_span: "ContextVar[Span | None]" = ContextVar("litellm_otel_request
|
|||
# ``asyncio.create_task`` children (the success/failure logging callbacks close
|
||||
# the LLM span in a worker copied from the request context). Request-scoped: the
|
||||
# contextvar dies with the request task, so nothing leaks across requests.
|
||||
_request_destinations: "ContextVar[tuple]" = ContextVar("litellm_otel_request_destinations", default=())
|
||||
_request_destinations: "ContextVar[tuple]" = ContextVar(
|
||||
"litellm_otel_request_destinations", default=()
|
||||
)
|
||||
|
||||
|
||||
def set_request_destinations(destinations: tuple) -> None:
|
||||
|
|
@ -68,7 +72,9 @@ def request_root_span() -> "Span | None":
|
|||
return span if is_recordable_span(span) else None
|
||||
|
||||
|
||||
def set_request_baggage(values: Mapping[str, str], context: Context | None = None) -> Context:
|
||||
def set_request_baggage(
|
||||
values: Mapping[str, str], context: Context | None = None
|
||||
) -> Context:
|
||||
"""Return a context with ``values`` written into Baggage."""
|
||||
ctx = context
|
||||
for key, value in values.items():
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ if TYPE_CHECKING:
|
|||
# Bound on cached per-destination processors. One processor per
|
||||
# ``(endpoint, sorted(headers))`` pair, so the working set is one entry per
|
||||
# admin-resolved tenant credential -- a real-world deployment with hundreds of
|
||||
# tenants stays well under this. The LRU shuts down the evicted processor's
|
||||
# exporter thread so the working set is reclaimed.
|
||||
# tenants stays well under this. Evicted entries are dropped (not shut down; see
|
||||
# the eviction site) and reclaimed at process exit.
|
||||
_MAX_CACHED_PROCESSORS = 256
|
||||
|
||||
|
||||
|
|
@ -100,7 +100,9 @@ class TenantFanOutSpanProcessor(SpanProcessor):
|
|||
try:
|
||||
processor.shutdown()
|
||||
except Exception as exc:
|
||||
verbose_logger.debug("OTel V2 fan-out: processor shutdown failed: %s", exc)
|
||||
verbose_logger.debug(
|
||||
"OTel V2 fan-out: processor shutdown failed: %s", exc
|
||||
)
|
||||
self._processors.clear()
|
||||
|
||||
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
||||
|
|
@ -121,7 +123,7 @@ class TenantFanOutSpanProcessor(SpanProcessor):
|
|||
return cached
|
||||
from litellm.integrations.otel.plumbing.providers import (
|
||||
_exporter_from_spec,
|
||||
_processor_for,
|
||||
_processor_for as _build_processor,
|
||||
default_otlp_kind_for_backend,
|
||||
)
|
||||
|
||||
|
|
@ -133,7 +135,7 @@ class TenantFanOutSpanProcessor(SpanProcessor):
|
|||
owner=None,
|
||||
)
|
||||
exporter = _exporter_from_spec(spec)
|
||||
processor = _processor_for(exporter, use_simple=False)
|
||||
processor = _build_processor(exporter, use_simple=False)
|
||||
except Exception as exc:
|
||||
verbose_logger.debug(
|
||||
"OTel V2 fan-out: failed to build processor for %s: %s",
|
||||
|
|
@ -177,7 +179,9 @@ def _destination_resource_attrs(destination: "OtelDestination") -> dict[str, str
|
|||
return {}
|
||||
|
||||
|
||||
def _with_destination_resource(span: ReadableSpan, destination: "OtelDestination") -> ReadableSpan:
|
||||
def _with_destination_resource(
|
||||
span: ReadableSpan, destination: "OtelDestination"
|
||||
) -> ReadableSpan:
|
||||
"""Return ``span`` with its Resource augmented by the destination's required
|
||||
attributes. The original span object is left untouched; a shallow wrapper
|
||||
reuses every other field and only swaps the ``resource`` property."""
|
||||
|
|
|
|||
|
|
@ -53,7 +53,9 @@ def to_otel_span_kind(kind: LiteLLMSpanKind) -> SpanKind:
|
|||
_EXPORTER_FACTORIES: dict[str, Callable[[ExporterSpec], SpanExporter]] = {}
|
||||
|
||||
|
||||
def register_exporter_factory(kind: str, factory: Callable[[ExporterSpec], SpanExporter]) -> None:
|
||||
def register_exporter_factory(
|
||||
kind: str, factory: Callable[[ExporterSpec], SpanExporter]
|
||||
) -> None:
|
||||
"""Register a custom exporter ``factory`` for the exporter ``kind``."""
|
||||
_EXPORTER_FACTORIES[kind.lower()] = factory
|
||||
|
||||
|
|
@ -70,7 +72,9 @@ class LiteLLMBaggageSpanProcessor(SpanProcessor):
|
|||
self._allowed_prefixes = tuple(allowed_prefixes)
|
||||
|
||||
def _is_allowed(self, key: str) -> bool:
|
||||
return key in self._allowed_keys or any(key.startswith(prefix) for prefix in self._allowed_prefixes)
|
||||
return key in self._allowed_keys or any(
|
||||
key.startswith(prefix) for prefix in self._allowed_prefixes
|
||||
)
|
||||
|
||||
def on_start(self, span: Span, parent_context: Context | None = None) -> None:
|
||||
for key, value in baggage.get_all(parent_context).items():
|
||||
|
|
@ -164,7 +168,11 @@ def build_span_exporter(config: OpenTelemetryV2Config) -> SpanExporter:
|
|||
``exporter`` / ``endpoint`` / ``headers`` fields. To configure multiple
|
||||
exporters, populate ``config.exporters`` directly.
|
||||
"""
|
||||
return _exporter_from_spec(ExporterSpec(kind=config.exporter, endpoint=config.endpoint, headers=config.headers))
|
||||
return _exporter_from_spec(
|
||||
ExporterSpec(
|
||||
kind=config.exporter, endpoint=config.endpoint, headers=config.headers
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _otlp_metrics_endpoint(endpoint: str | None) -> str | None:
|
||||
|
|
@ -313,7 +321,9 @@ def build_tracer_provider(
|
|||
"""
|
||||
provider = TracerProvider(resource=build_resource(config))
|
||||
if baggage_processor is None:
|
||||
baggage_processor = LiteLLMBaggageSpanProcessor(allowed_keys=config.baggage_promoted_keys)
|
||||
baggage_processor = LiteLLMBaggageSpanProcessor(
|
||||
allowed_keys=config.baggage_promoted_keys
|
||||
)
|
||||
provider.add_span_processor(baggage_processor)
|
||||
|
||||
if tenant_fan_out_owner is not None:
|
||||
|
|
@ -321,7 +331,9 @@ def build_tracer_provider(
|
|||
TenantFanOutSpanProcessor,
|
||||
)
|
||||
|
||||
provider.add_span_processor(TenantFanOutSpanProcessor(owner_callback_name=tenant_fan_out_owner))
|
||||
provider.add_span_processor(
|
||||
TenantFanOutSpanProcessor(owner_callback_name=tenant_fan_out_owner)
|
||||
)
|
||||
|
||||
if exporter is not None:
|
||||
provider.add_span_processor(_processor_for(exporter, use_simple_processor))
|
||||
|
|
@ -334,7 +346,11 @@ def build_tracer_provider(
|
|||
provider.add_span_processor(
|
||||
_processor_for(
|
||||
exp,
|
||||
(spec.use_simple_processor if spec.use_simple_processor is not None else use_simple_processor),
|
||||
(
|
||||
spec.use_simple_processor
|
||||
if spec.use_simple_processor is not None
|
||||
else use_simple_processor
|
||||
),
|
||||
)
|
||||
)
|
||||
return provider
|
||||
|
|
|
|||
|
|
@ -34,12 +34,18 @@ def _require_proxy_admin(user_api_key_dict: UserAPIKeyAuth) -> None:
|
|||
|
||||
def _credential_in_memory(credential_name: str) -> Optional[CredentialItem]:
|
||||
return next(
|
||||
(cred for cred in litellm.credential_list if cred.credential_name == credential_name),
|
||||
(
|
||||
cred
|
||||
for cred in litellm.credential_list
|
||||
if cred.credential_name == credential_name
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
async def _credential_for_admin_gate(credential_name: str, prisma_client: object) -> Optional[CredentialItem]:
|
||||
async def _credential_for_admin_gate(
|
||||
credential_name: str, prisma_client: object
|
||||
) -> Optional[CredentialItem]:
|
||||
"""Authoritative credential lookup for the admin gate on update/delete.
|
||||
|
||||
The in-process ``litellm.credential_list`` can be stale: a credential created
|
||||
|
|
@ -70,7 +76,9 @@ class CredentialHelperUtils:
|
|||
"""Encrypt values in credential.credential_values and add to DB"""
|
||||
encrypted_credential_values = {}
|
||||
for key, value in (credential.credential_values or {}).items():
|
||||
encrypted_credential_values[key] = encrypt_value_helper(value, new_encryption_key)
|
||||
encrypted_credential_values[key] = encrypt_value_helper(
|
||||
value, new_encryption_key
|
||||
)
|
||||
|
||||
# Return a new object to avoid mutating the caller's credential, which
|
||||
# is kept in memory and should remain unencrypted.
|
||||
|
|
@ -119,7 +127,9 @@ async def create_credential(
|
|||
model = llm_router.get_deployment(credential.model_id)
|
||||
if model is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
credential_values = llm_router.get_deployment_credentials(credential.model_id)
|
||||
credential_values = llm_router.get_deployment_credentials(
|
||||
credential.model_id
|
||||
)
|
||||
if credential_values is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
credential.credential_values = credential_values
|
||||
|
|
@ -134,7 +144,9 @@ async def create_credential(
|
|||
credential_values=credential.credential_values,
|
||||
credential_info=credential.credential_info,
|
||||
)
|
||||
encrypted_credential = CredentialHelperUtils.encrypt_credential_values(processed_credential)
|
||||
encrypted_credential = CredentialHelperUtils.encrypt_credential_values(
|
||||
processed_credential
|
||||
)
|
||||
credentials_dict = encrypted_credential.model_dump()
|
||||
credentials_dict_jsonified = jsonify_object(credentials_dict)
|
||||
await CredentialsRepository(prisma_client).create(
|
||||
|
|
@ -190,7 +202,9 @@ async def get_credentials(
|
|||
async def get_credential_by_name(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
credential_name: str = Path(..., description="The credential name, percent-decoded; may contain slashes"),
|
||||
credential_name: str = Path(
|
||||
..., description="The credential name, percent-decoded; may contain slashes"
|
||||
),
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
|
@ -268,7 +282,9 @@ async def get_credential_by_model(
|
|||
async def delete_credential(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
credential_name: str = Path(..., description="The credential name, percent-decoded; may contain slashes"),
|
||||
credential_name: str = Path(
|
||||
..., description="The credential name, percent-decoded; may contain slashes"
|
||||
),
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
|
@ -277,7 +293,9 @@ async def delete_credential(
|
|||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
existing = await _credential_for_admin_gate(credential_name, prisma_client)
|
||||
if existing is not None and is_admin_gated_credential_info(existing.credential_info):
|
||||
if existing is not None and is_admin_gated_credential_info(
|
||||
existing.credential_info
|
||||
):
|
||||
_require_proxy_admin(user_api_key_dict)
|
||||
|
||||
try:
|
||||
|
|
@ -289,7 +307,11 @@ async def delete_credential(
|
|||
await CredentialsRepository(prisma_client).delete_by_name(credential_name)
|
||||
|
||||
## DELETE FROM LITELLM ##
|
||||
litellm.credential_list = [cred for cred in litellm.credential_list if cred.credential_name != credential_name]
|
||||
litellm.credential_list = [
|
||||
cred
|
||||
for cred in litellm.credential_list
|
||||
if cred.credential_name != credential_name
|
||||
]
|
||||
return {"success": True, "message": "Credential deleted successfully"}
|
||||
except Exception as e:
|
||||
return handle_exception_on_proxy(e)
|
||||
|
|
@ -320,7 +342,9 @@ def update_db_credential(
|
|||
# update litellm params
|
||||
if encrypted_credential.credential_values:
|
||||
# Encrypt any sensitive values
|
||||
encrypted_params = {k: v for k, v in encrypted_credential.credential_values.items()}
|
||||
encrypted_params = {
|
||||
k: v for k, v in encrypted_credential.credential_values.items()
|
||||
}
|
||||
|
||||
merged_credential.credential_values.update(encrypted_params)
|
||||
|
||||
|
|
@ -345,7 +369,9 @@ async def update_credential(
|
|||
request: Request,
|
||||
fastapi_response: Response,
|
||||
credential: CredentialItem,
|
||||
credential_name: str = Path(..., description="The credential name, percent-decoded; may contain slashes"),
|
||||
credential_name: str = Path(
|
||||
..., description="The credential name, percent-decoded; may contain slashes"
|
||||
),
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
|
@ -355,7 +381,8 @@ async def update_credential(
|
|||
|
||||
existing = await _credential_for_admin_gate(credential_name, prisma_client)
|
||||
if is_admin_gated_credential_info(credential.credential_info) or (
|
||||
existing is not None and is_admin_gated_credential_info(existing.credential_info)
|
||||
existing is not None
|
||||
and is_admin_gated_credential_info(existing.credential_info)
|
||||
):
|
||||
_require_proxy_admin(user_api_key_dict)
|
||||
validate_credential_access(credential.credential_info)
|
||||
|
|
@ -402,7 +429,11 @@ async def update_credential(
|
|||
)
|
||||
# Remove old entry if renamed, then use upsert_credentials to handle duplicates
|
||||
if new_name != credential_name:
|
||||
litellm.credential_list = [c for c in litellm.credential_list if c.credential_name != credential_name]
|
||||
litellm.credential_list = [
|
||||
c
|
||||
for c in litellm.credential_list
|
||||
if c.credential_name != credential_name
|
||||
]
|
||||
CredentialAccessor.upsert_credentials([updated_in_memory])
|
||||
|
||||
return {"success": True, "message": "Credential updated successfully"}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ def _connected_db(monkeypatch):
|
|||
repo.create = AsyncMock()
|
||||
repo.delete_by_name = AsyncMock()
|
||||
monkeypatch.setattr(endpoints, "CredentialsRepository", lambda _client: repo)
|
||||
monkeypatch.setattr(endpoints.CredentialAccessor, "upsert_credentials", lambda creds: None)
|
||||
monkeypatch.setattr(
|
||||
endpoints.CredentialAccessor, "upsert_credentials", lambda creds: None
|
||||
)
|
||||
return repo
|
||||
|
||||
|
||||
|
|
@ -115,7 +117,9 @@ async def test_update_logging_credential_forbidden_for_non_admin(_connected_db):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_existing_logging_credential_forbidden_even_without_logging_patch(_connected_db, monkeypatch):
|
||||
async def test_update_existing_logging_credential_forbidden_even_without_logging_patch(
|
||||
_connected_db, monkeypatch
|
||||
):
|
||||
"""A non-admin cannot edit a stored logging credential's values, even with a patch
|
||||
that omits credential_info (the gate consults the in-memory credential too)."""
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -176,7 +180,9 @@ def test_update_db_credential_preserves_existing_info_on_partial_patch():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_logging_credential_forbidden_for_non_admin(_connected_db, monkeypatch):
|
||||
async def test_delete_logging_credential_forbidden_for_non_admin(
|
||||
_connected_db, monkeypatch
|
||||
):
|
||||
monkeypatch.setattr(
|
||||
litellm,
|
||||
"credential_list",
|
||||
|
|
@ -200,7 +206,9 @@ async def test_delete_logging_credential_forbidden_for_non_admin(_connected_db,
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_db_only_logging_credential_forbidden_for_non_admin(_connected_db, monkeypatch):
|
||||
async def test_update_db_only_logging_credential_forbidden_for_non_admin(
|
||||
_connected_db, monkeypatch
|
||||
):
|
||||
"""A logging credential that exists ONLY in the DB (not resident in the
|
||||
in-memory ``credential_list`` -- e.g. created on another scaled instance or
|
||||
before a restart) must still gate a non-admin update. The gate falls back to
|
||||
|
|
@ -230,7 +238,9 @@ async def test_update_db_only_logging_credential_forbidden_for_non_admin(_connec
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_db_only_logging_credential_forbidden_for_non_admin(_connected_db, monkeypatch):
|
||||
async def test_delete_db_only_logging_credential_forbidden_for_non_admin(
|
||||
_connected_db, monkeypatch
|
||||
):
|
||||
"""Same DB-only fallback for delete: a non-admin can't delete a logging
|
||||
credential that is resident only in the DB."""
|
||||
monkeypatch.setattr(litellm, "credential_list", [])
|
||||
|
|
|
|||
|
|
@ -157,9 +157,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
},
|
||||
metadata: {
|
||||
...(values.metadata ? JSON.parse(values.metadata) : {}),
|
||||
...(values.logging_exporters !== undefined
|
||||
? { logging_exporters: values.logging_exporters }
|
||||
: {}),
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,11 +39,7 @@ import {
|
|||
setCallbacksCall,
|
||||
} from "./networking";
|
||||
import { LoggingCallbacksTable } from "./Settings/LoggingAndAlerts/LoggingCallbacks/LoggingCallbacksTable";
|
||||
import {
|
||||
AlertingObject,
|
||||
CredentialAccess,
|
||||
ResolvedScope,
|
||||
} from "./Settings/LoggingAndAlerts/LoggingCallbacks/types";
|
||||
import { AlertingObject, CredentialAccess, ResolvedScope } from "./Settings/LoggingAndAlerts/LoggingCallbacks/types";
|
||||
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
|
||||
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
||||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
|
|
@ -273,8 +269,7 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
// access for the destination branch of the unified Add modal
|
||||
const [addAccess, setAddAccess] = useState<CredentialAccess>({});
|
||||
const addingDestination = selectedCallback != null && LOGGING_BACKEND_IDS.has(selectedCallback);
|
||||
const addingDestinationFields =
|
||||
LOGGING_DESTINATION_BACKENDS.find((b) => b.id === selectedCallback)?.fields ?? [];
|
||||
const addingDestinationFields = LOGGING_DESTINATION_BACKENDS.find((b) => b.id === selectedCallback)?.fields ?? [];
|
||||
|
||||
const teamAlias = (id: string): string => {
|
||||
const t = (teamsData ?? []).find((team) => team.team_id === id);
|
||||
|
|
@ -876,7 +871,9 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
key={f.name}
|
||||
label={<span className="text-sm font-medium text-gray-700">{f.label}</span>}
|
||||
name={f.name}
|
||||
rules={f.optional ? undefined : [{ required: true, message: `Please enter the ${f.label.toLowerCase()}` }]}
|
||||
rules={
|
||||
f.optional ? undefined : [{ required: true, message: `Please enter the ${f.label.toLowerCase()}` }]
|
||||
}
|
||||
>
|
||||
{f.type === "password" ? <Input.Password size="large" /> : <Input size="large" />}
|
||||
</FormItem>
|
||||
|
|
|
|||
|
|
@ -551,9 +551,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
|||
guardrails: (values.guardrails || []).filter((n: string) => !globalGuardrailNames.has(n)),
|
||||
opted_out_global_guardrails: optedOutGlobalGuardrails,
|
||||
...(values.logging_settings?.length > 0 ? { logging: values.logging_settings } : {}),
|
||||
...(values.logging_exporters !== undefined
|
||||
? { logging_exporters: values.logging_exporters }
|
||||
: {}),
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
disable_global_guardrails: killSwitchOnAtSave,
|
||||
soft_budget_alerting_emails:
|
||||
typeof values.soft_budget_alerting_emails === "string"
|
||||
|
|
|
|||
|
|
@ -278,9 +278,7 @@ export default function KeyInfoView({
|
|||
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined
|
||||
? { logging_exporters: formValues.logging_exporters }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
|
||||
...(formValues.disabled_callbacks?.length > 0
|
||||
? {
|
||||
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
|
||||
|
|
@ -302,9 +300,7 @@ export default function KeyInfoView({
|
|||
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined
|
||||
? { logging_exporters: formValues.logging_exporters }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
|
||||
...(formValues.disabled_callbacks?.length > 0
|
||||
? {
|
||||
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue