Skip to content

Commit d69aa1d

Browse files
fix: replace deprecated json_encoders with Pydantic V2 field serializers
Replace deprecated json_encoders usage in memory schema models with modern @field_serializer decorators to eliminate Pydantic V2.0 deprecation warnings. This fixes the warnings reported when running basic-memory commands on Windows 11. - Remove json_encoders from EntitySummary, RelationSummary, ObservationSummary, and MemoryMetadata models - Add @field_serializer decorators for datetime fields - Update tests to verify field serializers instead of json_encoders - Maintains same ISO datetime serialization behavior Fixes #294 Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
1 parent 0499319 commit d69aa1d

2 files changed

Lines changed: 49 additions & 23 deletions

File tree

src/basic_memory/schemas/memory.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import List, Optional, Annotated, Sequence, Literal, Union
55

66
from annotated_types import MinLen, MaxLen
7-
from pydantic import BaseModel, Field, BeforeValidator, TypeAdapter, ConfigDict
7+
from pydantic import BaseModel, Field, BeforeValidator, TypeAdapter, ConfigDict, field_serializer
88

99
from basic_memory.schemas.search import SearchItemType
1010

@@ -118,21 +118,21 @@ def memory_url_path(url: memory_url) -> str: # pyright: ignore
118118
class EntitySummary(BaseModel):
119119
"""Simplified entity representation."""
120120

121-
model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()})
122-
123121
type: Literal["entity"] = "entity"
124122
permalink: Optional[str]
125123
title: str
126124
content: Optional[str] = None
127125
file_path: str
128126
created_at: datetime
129127

128+
@field_serializer('created_at')
129+
def serialize_created_at(self, dt: datetime) -> str:
130+
return dt.isoformat()
131+
130132

131133
class RelationSummary(BaseModel):
132134
"""Simplified relation representation."""
133135

134-
model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()})
135-
136136
type: Literal["relation"] = "relation"
137137
title: str
138138
file_path: str
@@ -142,12 +142,14 @@ class RelationSummary(BaseModel):
142142
to_entity: Optional[str] = None
143143
created_at: datetime
144144

145+
@field_serializer('created_at')
146+
def serialize_created_at(self, dt: datetime) -> str:
147+
return dt.isoformat()
148+
145149

146150
class ObservationSummary(BaseModel):
147151
"""Simplified observation representation."""
148152

149-
model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()})
150-
151153
type: Literal["observation"] = "observation"
152154
title: str
153155
file_path: str
@@ -156,12 +158,14 @@ class ObservationSummary(BaseModel):
156158
content: str
157159
created_at: datetime
158160

161+
@field_serializer('created_at')
162+
def serialize_created_at(self, dt: datetime) -> str:
163+
return dt.isoformat()
164+
159165

160166
class MemoryMetadata(BaseModel):
161167
"""Simplified response metadata."""
162168

163-
model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()})
164-
165169
uri: Optional[str] = None
166170
types: Optional[List[SearchItemType]] = None
167171
depth: int
@@ -173,6 +177,10 @@ class MemoryMetadata(BaseModel):
173177
total_relations: Optional[int] = None
174178
total_observations: Optional[int] = None
175179

180+
@field_serializer('generated_at')
181+
def serialize_generated_at(self, dt: datetime) -> str:
182+
return dt.isoformat()
183+
176184

177185
class ContextResult(BaseModel):
178186
"""Context result containing a primary item with its observations and related items."""

tests/schemas/test_memory_serialization.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,36 @@ def test_mcp_schema_validation_compatibility(self):
197197
assert "T" in datetime_str # Contains date-time separator
198198
assert len(datetime_str) >= 19 # At least YYYY-MM-DDTHH:MM:SS format
199199

200-
def test_all_models_have_json_encoders_configured(self):
201-
"""Test that all memory schema models have datetime json_encoders configured."""
202-
models_to_test = [EntitySummary, RelationSummary, ObservationSummary, MemoryMetadata]
203-
204-
for model_class in models_to_test:
205-
# Check that ConfigDict with json_encoders is configured
206-
assert hasattr(model_class, "model_config")
207-
assert "json_encoders" in model_class.model_config
208-
assert datetime in model_class.model_config["json_encoders"]
209-
210-
# Verify the encoder function produces ISO format
211-
encoder = model_class.model_config["json_encoders"][datetime]
200+
def test_all_models_have_datetime_serializers_configured(self):
201+
"""Test that all memory schema models have datetime field serializers configured."""
202+
models_to_test = [
203+
(EntitySummary, "created_at"),
204+
(RelationSummary, "created_at"),
205+
(ObservationSummary, "created_at"),
206+
(MemoryMetadata, "generated_at")
207+
]
208+
209+
for model_class, datetime_field in models_to_test:
210+
# Create a test instance with a datetime field
212211
test_datetime = datetime(2023, 12, 8, 10, 30, 0)
213-
result = encoder(test_datetime)
214-
assert result == "2023-12-08T10:30:00"
212+
213+
if model_class == EntitySummary:
214+
instance = model_class(
215+
permalink="test", title="Test", file_path="test.md", created_at=test_datetime
216+
)
217+
elif model_class == RelationSummary:
218+
instance = model_class(
219+
title="Test", file_path="test.md", permalink="test",
220+
relation_type="test", created_at=test_datetime
221+
)
222+
elif model_class == ObservationSummary:
223+
instance = model_class(
224+
title="Test", file_path="test.md", permalink="test",
225+
category="test", content="Test", created_at=test_datetime
226+
)
227+
elif model_class == MemoryMetadata:
228+
instance = model_class(depth=1, generated_at=test_datetime)
229+
230+
# Test that model_dump produces ISO format for datetime field
231+
data = instance.model_dump()
232+
assert data[datetime_field] == "2023-12-08T10:30:00"

0 commit comments

Comments
 (0)