Skip to content

Commit d73ce19

Browse files
fix index from existing parsing and model serialization issues
1 parent 77ae328 commit d73ce19

File tree

3 files changed

+62
-25
lines changed

3 files changed

+62
-25
lines changed

redisvl/redis/connection.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,27 @@ def parse_attrs(attrs):
105105
# TODO 'WITHSUFFIXTRIE' is another boolean attr, but is not returned by ft.info
106106
original = attrs.copy()
107107
parsed_attrs = {}
108-
if "NOSTEM" in attrs:
109-
parsed_attrs["no_stem"] = True
110-
attrs.remove("NOSTEM")
111-
if "CASESENSITIVE" in attrs:
112-
parsed_attrs["case_sensitive"] = True
113-
attrs.remove("CASESENSITIVE")
114-
if "SORTABLE" in attrs:
115-
parsed_attrs["sortable"] = True
116-
attrs.remove("SORTABLE")
117-
if "UNF" in attrs:
118-
attrs.remove("UNF") # UNF present on sortable numeric fields only
108+
109+
# Handle all boolean attributes first, regardless of position
110+
boolean_attrs = {
111+
"NOSTEM": "no_stem",
112+
"CASESENSITIVE": "case_sensitive",
113+
"SORTABLE": "sortable",
114+
"INDEXMISSING": "index_missing",
115+
"INDEXEMPTY": "index_empty",
116+
}
117+
118+
for redis_attr, python_attr in boolean_attrs.items():
119+
if redis_attr in attrs:
120+
parsed_attrs[python_attr] = True
121+
attrs.remove(redis_attr)
122+
123+
# Handle UNF which is associated with SORTABLE
124+
if "UNF" in attrs:
125+
attrs.remove("UNF") # UNF present on sortable numeric fields only
119126

120127
try:
128+
# Parse remaining attributes as key-value pairs starting from index 6
121129
parsed_attrs.update(
122130
{attrs[i].lower(): attrs[i + 1] for i in range(6, len(attrs), 2)}
123131
)

redisvl/schema/schema.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,14 @@ def to_dict(self) -> Dict[str, Any]:
432432
Returns:
433433
Dict[str, Any]: The index schema as a dictionary.
434434
"""
435-
dict_schema = model_to_dict(self)
436-
# cast fields back to a pure list
437-
dict_schema["fields"] = [
438-
field for field_name, field in dict_schema["fields"].items()
439-
]
435+
# Manually serialize to ensure all field attributes are preserved
436+
dict_schema = {
437+
"index": model_to_dict(self.index),
438+
"fields": [
439+
model_to_dict(field) for field_name, field in self.fields.items()
440+
],
441+
"version": self.version,
442+
}
440443
return dict_schema
441444

442445
def to_yaml(self, file_path: str, overwrite: bool = True) -> None:

redisvl/utils/utils.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def model_to_dict(model: BaseModel) -> Dict[str, Any]:
3838
def serialize_item(item):
3939
if isinstance(item, Enum):
4040
return item.value.lower()
41+
elif isinstance(item, BaseModel):
42+
# Recursively serialize nested BaseModel instances with exclude_defaults=False
43+
nested_data = item.model_dump(exclude_none=True, exclude_defaults=False)
44+
return {key: serialize_item(value) for key, value in nested_data.items()}
4145
elif isinstance(item, dict):
4246
return {key: serialize_item(value) for key, value in item.items()}
4347
elif isinstance(item, list):
@@ -171,29 +175,51 @@ def wrapper(*args, **kwargs):
171175

172176
def sync_wrapper(fn: Callable[[], Coroutine[Any, Any, Any]]) -> Callable[[], None]:
173177
def wrapper():
178+
# Check if the interpreter is shutting down
179+
if sys is None or getattr(sys, "_getframe", None) is None:
180+
# Interpreter is shutting down, skip cleanup
181+
return
182+
174183
try:
175184
loop = asyncio.get_running_loop()
176185
except RuntimeError:
177186
loop = None
187+
except Exception:
188+
# Any other exception during loop detection means we should skip cleanup
189+
return
190+
178191
try:
179192
if loop is None or not loop.is_running():
193+
# Check if asyncio module is still available
194+
if asyncio is None:
195+
return
196+
180197
loop = asyncio.new_event_loop()
181198
asyncio.set_event_loop(loop)
182199
task = loop.create_task(fn())
183200
loop.run_until_complete(task)
184-
except RuntimeError:
201+
except (RuntimeError, AttributeError, TypeError) as e:
185202
# This could happen if an object stored an event loop and now
186-
# that event loop is closed. There's nothing we can do other than
187-
# advise the user to use explicit cleanup methods.
203+
# that event loop is closed, or if asyncio modules are being
204+
# torn down during interpreter shutdown.
188205
#
189206
# Uses logging module instead of get_logger() to avoid I/O errors
190207
# if the wrapped function is called as a finalizer.
191-
logging.info(
192-
f"Could not run the async function {fn.__name__} because the event loop is closed. "
193-
"This usually means the object was not properly cleaned up. Please use explicit "
194-
"cleanup methods (e.g., disconnect(), close()) or use the object as an async "
195-
"context manager.",
196-
)
208+
if logging is not None:
209+
try:
210+
logging.info(
211+
f"Could not run the async function {fn.__name__} because the event loop is closed "
212+
"or the interpreter is shutting down. "
213+
"This usually means the object was not properly cleaned up. Please use explicit "
214+
"cleanup methods (e.g., disconnect(), close()) or use the object as an async "
215+
"context manager.",
216+
)
217+
except Exception:
218+
# Even logging failed, interpreter is really shutting down
219+
pass
220+
return
221+
except Exception:
222+
# Any other unexpected exception should be silently ignored during shutdown
197223
return
198224

199225
return wrapper

0 commit comments

Comments
 (0)