|
8 | 8 | from pydantic import BaseModel |
9 | 9 | from pydantic.type_adapter import TypeAdapter |
10 | 10 |
|
11 | | -from key_value.sync.code_gen.adapters.base import BasePydanticAdapter |
| 11 | +from key_value.sync.code_gen.adapters.pydantic.base import BasePydanticAdapter |
12 | 12 | from key_value.sync.code_gen.protocols.key_value import KeyValue |
13 | 13 |
|
14 | 14 | T = TypeVar("T", bound=BaseModel | Sequence[BaseModel]) |
@@ -45,199 +45,3 @@ def __init__( |
45 | 45 | def _get_model_type_name(self) -> str: |
46 | 46 | """Return the model type name for error messages.""" |
47 | 47 | return "Pydantic model" |
48 | | - |
49 | | - def _validate_model(self, value: dict[str, Any]) -> T | None: |
50 | | - """Validate and deserialize a dict into the configured Pydantic model. |
51 | | -
|
52 | | - This method handles both single models and list models. For list models, it expects the value |
53 | | - to contain an "items" key with the list data, following the convention used by `_serialize_model`. |
54 | | - If validation fails and `raise_on_validation_error` is False, returns None instead of raising. |
55 | | -
|
56 | | - Args: |
57 | | - value: The dict to validate and convert to a Pydantic model. |
58 | | -
|
59 | | - Returns: |
60 | | - The validated model instance, or None if validation fails and errors are suppressed. |
61 | | -
|
62 | | - Raises: |
63 | | - DeserializationError: If validation fails and `raise_on_validation_error` is True. |
64 | | - """ |
65 | | - try: |
66 | | - if self._is_list_model: |
67 | | - return self._type_adapter.validate_python(value.get("items", [])) |
68 | | - |
69 | | - return self._type_adapter.validate_python(value) |
70 | | - except ValidationError as e: |
71 | | - if self._raise_on_validation_error: |
72 | | - msg = f"Invalid Pydantic model: {value}" |
73 | | - raise DeserializationError(msg) from e |
74 | | - return None |
75 | | - |
76 | | - def _serialize_model(self, value: T) -> dict[str, Any]: |
77 | | - """Serialize a Pydantic model to a dict for storage. |
78 | | -
|
79 | | - This method handles both single models and list models. For list models, it wraps the serialized |
80 | | - list in a dict with an "items" key (e.g., {"items": [...]}) to ensure consistent dict-based storage |
81 | | - format across all value types. This wrapping convention is expected by `_validate_model` during |
82 | | - deserialization. |
83 | | -
|
84 | | - Args: |
85 | | - value: The Pydantic model instance to serialize. |
86 | | -
|
87 | | - Returns: |
88 | | - A dict representation of the model suitable for storage. |
89 | | -
|
90 | | - Raises: |
91 | | - SerializationError: If the model cannot be serialized. |
92 | | - """ |
93 | | - try: |
94 | | - if self._is_list_model: |
95 | | - return {"items": self._type_adapter.dump_python(value, mode="json")} |
96 | | - |
97 | | - return self._type_adapter.dump_python(value, mode="json") # pyright: ignore[reportAny] |
98 | | - except PydanticSerializationError as e: |
99 | | - msg = f"Invalid Pydantic model: {e}" |
100 | | - raise SerializationError(msg) from e |
101 | | - |
102 | | - @overload |
103 | | - def get(self, key: str, *, collection: str | None = None, default: T) -> T: ... |
104 | | - |
105 | | - @overload |
106 | | - def get(self, key: str, *, collection: str | None = None, default: None = None) -> T | None: ... |
107 | | - |
108 | | - def get(self, key: str, *, collection: str | None = None, default: T | None = None) -> T | None: |
109 | | - """Get and validate a model by key. |
110 | | -
|
111 | | - Args: |
112 | | - key: The key to retrieve. |
113 | | - collection: The collection to use. If not provided, uses the default collection. |
114 | | - default: The default value to return if the key doesn't exist or validation fails. |
115 | | -
|
116 | | - Returns: |
117 | | - The parsed model instance if found and valid, or the default value if key doesn't exist or validation fails. |
118 | | -
|
119 | | - Raises: |
120 | | - DeserializationError if the stored data cannot be validated as the model and the PydanticAdapter is configured to |
121 | | - raise on validation error. |
122 | | -
|
123 | | - Note: |
124 | | - When raise_on_validation_error=False and validation fails, returns the default value (which may be None). |
125 | | - When raise_on_validation_error=True and validation fails, raises DeserializationError. |
126 | | - """ |
127 | | - collection = collection or self._default_collection |
128 | | - |
129 | | - if value := self._key_value.get(key=key, collection=collection): |
130 | | - validated = self._validate_model(value=value) |
131 | | - if validated is not None: |
132 | | - return validated |
133 | | - |
134 | | - return default |
135 | | - |
136 | | - @overload |
137 | | - def get_many(self, keys: Sequence[str], *, collection: str | None = None, default: T) -> list[T]: ... |
138 | | - |
139 | | - @overload |
140 | | - def get_many(self, keys: Sequence[str], *, collection: str | None = None, default: None = None) -> list[T | None]: ... |
141 | | - |
142 | | - def get_many(self, keys: Sequence[str], *, collection: str | None = None, default: T | None = None) -> list[T] | list[T | None]: |
143 | | - """Batch get and validate models by keys, preserving order. |
144 | | -
|
145 | | - Args: |
146 | | - keys: The list of keys to retrieve. |
147 | | - collection: The collection to use. If not provided, uses the default collection. |
148 | | - default: The default value to return for keys that don't exist or fail validation. |
149 | | -
|
150 | | - Returns: |
151 | | - A list of parsed model instances, with default values for missing keys or validation failures. |
152 | | -
|
153 | | - Raises: |
154 | | - DeserializationError if the stored data cannot be validated as the model and the PydanticAdapter is configured to |
155 | | - raise on validation error. |
156 | | -
|
157 | | - Note: |
158 | | - When raise_on_validation_error=False and validation fails for any key, that position in the returned list |
159 | | - will contain the default value (which may be None). The method returns a complete list matching the order |
160 | | - and length of the input keys, with defaults substituted for missing or invalid entries. |
161 | | - """ |
162 | | - collection = collection or self._default_collection |
163 | | - |
164 | | - values: list[dict[str, Any] | None] = self._key_value.get_many(keys=keys, collection=collection) |
165 | | - |
166 | | - result: list[T | None] = [] |
167 | | - for value in values: |
168 | | - if value is None: |
169 | | - result.append(default) |
170 | | - else: |
171 | | - validated = self._validate_model(value=value) |
172 | | - result.append(validated if validated is not None else default) |
173 | | - return result |
174 | | - |
175 | | - def put(self, key: str, value: T, *, collection: str | None = None, ttl: SupportsFloat | None = None) -> None: |
176 | | - """Serialize and store a model. |
177 | | -
|
178 | | - Propagates SerializationError if the model cannot be serialized. |
179 | | - """ |
180 | | - collection = collection or self._default_collection |
181 | | - |
182 | | - value_dict: dict[str, Any] = self._serialize_model(value=value) |
183 | | - |
184 | | - self._key_value.put(key=key, value=value_dict, collection=collection, ttl=ttl) |
185 | | - |
186 | | - def put_many( |
187 | | - self, keys: Sequence[str], values: Sequence[T], *, collection: str | None = None, ttl: SupportsFloat | None = None |
188 | | - ) -> None: |
189 | | - """Serialize and store multiple models, preserving order alignment with keys.""" |
190 | | - collection = collection or self._default_collection |
191 | | - |
192 | | - value_dicts: list[dict[str, Any]] = [self._serialize_model(value=value) for value in values] |
193 | | - |
194 | | - self._key_value.put_many(keys=keys, values=value_dicts, collection=collection, ttl=ttl) |
195 | | - |
196 | | - def delete(self, key: str, *, collection: str | None = None) -> bool: |
197 | | - """Delete a model by key. Returns True if a value was deleted, else False.""" |
198 | | - collection = collection or self._default_collection |
199 | | - |
200 | | - return self._key_value.delete(key=key, collection=collection) |
201 | | - |
202 | | - def delete_many(self, keys: Sequence[str], *, collection: str | None = None) -> int: |
203 | | - """Delete multiple models by key. Returns the count of deleted entries.""" |
204 | | - collection = collection or self._default_collection |
205 | | - |
206 | | - return self._key_value.delete_many(keys=keys, collection=collection) |
207 | | - |
208 | | - def ttl(self, key: str, *, collection: str | None = None) -> tuple[T | None, float | None]: |
209 | | - """Get a model and its TTL seconds if present. |
210 | | -
|
211 | | - Args: |
212 | | - key: The key to retrieve. |
213 | | - collection: The collection to use. If not provided, uses the default collection. |
214 | | -
|
215 | | - Returns: |
216 | | - A tuple of (model, ttl_seconds). Returns (None, None) if the key is missing or validation fails. |
217 | | -
|
218 | | - Note: |
219 | | - When validation fails and raise_on_validation_error=False, returns (None, None) even if TTL data exists. |
220 | | - When validation fails and raise_on_validation_error=True, raises DeserializationError. |
221 | | - """ |
222 | | - collection = collection or self._default_collection |
223 | | - |
224 | | - entry: dict[str, Any] | None |
225 | | - ttl_info: float | None |
226 | | - |
227 | | - (entry, ttl_info) = self._key_value.ttl(key=key, collection=collection) |
228 | | - |
229 | | - if entry is None: |
230 | | - return (None, None) |
231 | | - |
232 | | - if validated_model := self._validate_model(value=entry): |
233 | | - return (validated_model, ttl_info) |
234 | | - |
235 | | - return (None, None) |
236 | | - |
237 | | - def ttl_many(self, keys: Sequence[str], *, collection: str | None = None) -> list[tuple[T | None, float | None]]: |
238 | | - """Batch get models with TTLs. Each element is (model|None, ttl_seconds|None).""" |
239 | | - collection = collection or self._default_collection |
240 | | - |
241 | | - entries: list[tuple[dict[str, Any] | None, float | None]] = self._key_value.ttl_many(keys=keys, collection=collection) |
242 | | - |
243 | | - return [(self._validate_model(value=entry) if entry else None, ttl_info) for (entry, ttl_info) in entries] |
0 commit comments