Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/cache_pydantic_utils.py: 61%

30 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1""" 

2DualCache presents a single API for reads and writes, but the two backends behave 

3differently: the in-memory layer can store arbitrary Python objects (including live 

4``BaseModel`` instances), while Redis persists strings and therefore needs JSON-safe 

5payloads (``json.dumps`` on the Redis side). 

6 

7Call sites therefore see cache ``value`` / ``cached`` as effectively ``Any``: the same 

8key may deserialize to a model on one process (memory hit) or to a ``dict`` after a 

9Redis round-trip. ``CacheCodec`` centralizes encode/decode at that boundary: 

10``CacheCodec.serialize`` before ``set``, ``CacheCodec.deserialize`` after ``get`` 

11when you need a typed ``BaseModel``. 

12 

13``dataclasses`` are not supported: only ``dict`` and Pydantic ``BaseModel`` inputs 

14are encoded; pass a Pydantic model or convert with e.g. ``dataclasses.asdict`` first. 

15""" 

16 

17from __future__ import annotations 

18 

19from typing import Any, TypeVar 

20 

21from pydantic import BaseModel, ValidationError 

22 

23from litellm._logging import verbose_proxy_logger 

24 

25T = TypeVar("T", bound=BaseModel) 

26 

27 

28class CacheCodec: 

29 """ 

30 Encode/decode Pydantic models for DualCache (memory vs Redis safe payloads). 

31 

32 Dataclasses are not supported yet (only ``dict`` and ``BaseModel``). 

33 

34 Use ``serialize`` with ``model_type`` when writing so the same schema is used 

35 as on read (``deserialize``). Pass ``model_type`` whenever you know it 

36 (validates ``dict`` payloads and normalizes ``BaseModel`` instances). 

37 """ 

38 

39 @staticmethod 

40 def serialize(value: object, model_type: type[T] | None = None) -> object: 

41 """ 

42 Encode a value for DualCache / Redis (``json.dumps``-safe). 

43 

44 If ``model_type`` is set, the payload is validated with that model, then 

45 ``model_dump(mode="json")`` — symmetric with ``deserialize``. 

46 

47 If the value is already an instance of ``model_type`` (or a subclass), 

48 ``model_validate`` is skipped to avoid an unnecessary Pydantic copy — the 

49 value is dumped directly. 

50 

51 If ``model_type`` is omitted, any ``BaseModel`` is dumped as above; other 

52 values (e.g. plain ``dict``) are returned unchanged. 

53 """ 

54 if model_type is not None: 

55 if isinstance(value, model_type): 55 ↛ 58line 55 didn't jump to line 58 because the condition on line 55 was always true

56 # Already the right type: dump directly, skip re-validation copy. 

57 return value.model_dump(mode="json") 

58 if isinstance(value, (dict, BaseModel)): 

59 return model_type.model_validate(value).model_dump(mode="json") 

60 return value 

61 if isinstance(value, BaseModel): 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true

62 return value.model_dump(mode="json") 

63 return value 

64 

65 @staticmethod 

66 def deserialize(cached: Any, model_type: type[T]) -> T | None: 

67 """ 

68 Decode a cache entry to ``model_type``. 

69 

70 - ``None`` → ``None`` 

71 - Already an instance of ``model_type`` (including subclasses) → returned as-is 

72 - ``dict`` → ``model_type.model_validate(...)``; on ``ValidationError``, 

73 logs a warning and returns ``None`` (treat as cache miss; avoids serving 

74 malformed or schema-drifted entries) 

75 - Any other type → ``None`` (caller should treat as cache miss or log) 

76 """ 

77 if cached is None: 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true

78 return None 

79 if isinstance(cached, model_type): 79 ↛ 80line 79 didn't jump to line 80 because the condition on line 79 was never true

80 return cached 

81 if isinstance(cached, dict): 81 ↛ 91line 81 didn't jump to line 91 because the condition on line 81 was always true

82 try: 

83 return model_type.model_validate(cached) 

84 except ValidationError as e: 

85 verbose_proxy_logger.warning( 

86 "CacheCodec.deserialize: validation failed for %s (%s)", 

87 model_type.__name__, 

88 e, 

89 ) 

90 return None 

91 return None