Skip to content

Commit d8cdb54

Browse files
dynamic pydantic model validation on load
1 parent 5eac215 commit d8cdb54

File tree

7 files changed

+824
-1231
lines changed

7 files changed

+824
-1231
lines changed

redisvl/exceptions.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
1-
class RedisVLException(Exception):
2-
"""Base RedisVL exception"""
1+
"""
2+
RedisVL Exception Classes
33
4+
This module defines all custom exceptions used throughout the RedisVL library.
5+
"""
46

5-
class RedisModuleVersionError(RedisVLException):
6-
"""Invalid module versions installed"""
77

8+
class RedisVLError(Exception):
9+
"""Base exception for all RedisVL errors."""
810

9-
class RedisSearchError(RedisVLException):
10-
"""Error while performing a search or aggregate request"""
11+
pass
12+
13+
14+
class RedisModuleVersionError(RedisVLError):
15+
"""Error raised when required Redis modules are missing or have incompatible versions."""
16+
17+
pass
18+
19+
20+
class RedisSearchError(RedisVLError):
21+
"""Error raised for Redis Search specific operations."""
22+
23+
pass
24+
25+
26+
class SchemaValidationError(RedisVLError):
27+
"""Error when validating data against a schema."""
28+
29+
def __init__(self, message, index=None):
30+
if index is not None:
31+
message = f"Validation failed for object at index {index}: {message}"
32+
super().__init__(message)

redisvl/index/index.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@
2828
import redis.asyncio as aredis
2929
from redis.commands.search.indexDefinition import IndexDefinition
3030

31-
from redisvl.exceptions import RedisModuleVersionError, RedisSearchError
31+
from redisvl.exceptions import (
32+
RedisModuleVersionError,
33+
RedisSearchError,
34+
RedisVLError,
35+
SchemaValidationError,
36+
)
3237
from redisvl.index.storage import BaseStorage, HashStorage, JsonStorage
3338
from redisvl.query import BaseQuery, CountQuery, FilterQuery
3439
from redisvl.query.filter import FilterExpression
@@ -574,27 +579,8 @@ def load(
574579
List[str]: List of keys loaded to Redis.
575580
576581
Raises:
577-
ValueError: If the length of provided keys does not match the length
578-
of objects or if validation fails when validate_on_load is enabled.
579-
580-
.. code-block:: python
581-
582-
data = [{"test": "foo"}, {"test": "bar"}]
583-
584-
# simple case
585-
keys = index.load(data)
586-
587-
# set 360 second ttl policy on data
588-
keys = index.load(data, ttl=360)
589-
590-
# load data with predefined keys
591-
keys = index.load(data, keys=["rvl:foo", "rvl:bar"])
592-
593-
# load data with preprocessing step
594-
def add_field(d):
595-
d["new_field"] = 123
596-
return d
597-
keys = index.load(data, preprocess=add_field)
582+
SchemaValidationError: If validation fails when validate_on_load is enabled.
583+
RedisVLError: If there's an error loading data to Redis.
598584
"""
599585
try:
600586
return self._storage.write(
@@ -607,9 +593,14 @@ def add_field(d):
607593
batch_size=batch_size,
608594
validate=self._validate_on_load,
609595
)
610-
except:
611-
logger.exception("Error while loading data to Redis")
596+
except SchemaValidationError:
597+
# Pass through validation errors directly
598+
logger.exception("Schema validation error while loading data")
612599
raise
600+
except Exception as e:
601+
# Wrap other errors as general RedisVL errors
602+
logger.exception("Error while loading data to Redis")
603+
raise RedisVLError(f"Failed to load data: {str(e)}") from e
613604

614605
def fetch(self, id: str) -> Optional[Dict[str, Any]]:
615606
"""Fetch an object from Redis by id.
@@ -1134,8 +1125,8 @@ async def load(
11341125
List[str]: List of keys loaded to Redis.
11351126
11361127
Raises:
1137-
ValueError: If the length of provided keys does not match the
1138-
length of objects or if validation fails when validate_on_load is enabled.
1128+
SchemaValidationError: If validation fails when validate_on_load is enabled.
1129+
RedisVLError: If there's an error loading data to Redis.
11391130
11401131
.. code-block:: python
11411132
@@ -1169,9 +1160,14 @@ def add_field(d):
11691160
batch_size=batch_size,
11701161
validate=self._validate_on_load,
11711162
)
1172-
except:
1173-
logger.exception("Error while loading data to Redis")
1163+
except SchemaValidationError:
1164+
# Pass through validation errors directly
1165+
logger.exception("Schema validation error while loading data")
11741166
raise
1167+
except Exception as e:
1168+
# Wrap other errors as general RedisVL errors
1169+
logger.exception("Error while loading data to Redis")
1170+
raise RedisVLError(f"Failed to load data: {str(e)}") from e
11751171

11761172
async def fetch(self, id: str) -> Optional[Dict[str, Any]]:
11771173
"""Asynchronously etch an object from Redis by id. The id is typically

redisvl/index/storage.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from redis.asyncio import Redis as AsyncRedis
66
from redis.commands.search.indexDefinition import IndexType
77

8+
from redisvl.exceptions import SchemaValidationError
89
from redisvl.redis.utils import convert_bytes
910
from redisvl.schema import IndexSchema
1011
from redisvl.schema.validation import validate_object
@@ -180,7 +181,8 @@ def _preprocess_and_validate_objects(
180181
List of tuples (key, processed_obj) for valid objects
181182
182183
Raises:
183-
ValueError: If any validation fails with object context
184+
SchemaValidationError: If validation fails, with context about which object failed
185+
ValueError: If any other processing errors occur
184186
"""
185187
prepared_objects = []
186188
keys_iterator = iter(keys) if keys else None
@@ -197,26 +199,22 @@ def _preprocess_and_validate_objects(
197199
# Preprocess
198200
processed_obj = self._preprocess(obj, preprocess)
199201

200-
# Basic type validation
201-
if not isinstance(processed_obj, dict):
202-
raise ValueError(
203-
f"Object must be a dictionary, got {type(processed_obj).__name__}"
204-
)
205-
206202
# Schema validation if enabled
207203
if validate:
208204
processed_obj = self.validate(processed_obj)
209205

210206
# Store valid object with its key for writing
211207
prepared_objects.append((key, processed_obj))
212208

209+
except ValidationError as e:
210+
# Convert Pydantic ValidationError to SchemaValidationError with index context
211+
raise SchemaValidationError(str(e), index=i) from e
213212
except Exception as e:
214-
# Enhance error message with object context
213+
# Capture other exceptions with context
215214
object_id = f"at index {i}"
216-
if id_field and isinstance(obj, dict) and id_field in obj:
217-
object_id = f"with {id_field}={obj[id_field]}"
218-
219-
raise ValueError(f"Validation failed for object {object_id}: {str(e)}")
215+
raise ValueError(
216+
f"Error processing object {object_id}: {str(e)}"
217+
) from e
220218

221219
return prepared_objects
222220

0 commit comments

Comments
 (0)