From 2b74294ae86550ac2fa8c15583660861931495fc Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Tue, 9 Aug 2022 22:22:30 +0300 Subject: [PATCH 1/5] support null values for optional fields Signed-off-by: wiseaidev --- aredis_om/model/model.py | 6 ++++-- tests/test_hash_model.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index 2a1083df..e3533294 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -1316,6 +1316,8 @@ async def get(cls, pk: Any) -> "HashModel": if not document: raise NotFoundError try: + # restore none values + document = {key: val if val != "0" else None for key, val in document.items()} result = cls.parse_obj(document) except TypeError as e: log.warning( @@ -1332,14 +1334,14 @@ async def get(cls, pk: Any) -> "HashModel": @no_type_check def _get_value(cls, *args, **kwargs) -> Any: """ - Always send None as an empty string. + Always send None as a zero string: "0" to handle Optional int and float fields. TODO: We do this because redis-py's hset() method requires non-null values. Is there a better way? """ val = super()._get_value(*args, **kwargs) if val is None: - return "" + return "0" return val @classmethod diff --git a/tests/test_hash_model.py b/tests/test_hash_model.py index 77418bad..bb0f5be9 100644 --- a/tests/test_hash_model.py +++ b/tests/test_hash_model.py @@ -48,6 +48,8 @@ class Member(BaseHashModel): last_name: str = Field(index=True) email: str = Field(index=True) join_date: datetime.date + height: Optional[int] = None + weight: Optional[float] = None age: int = Field(index=True, sortable=True) bio: str = Field(index=True, full_text_search=True) From 1efdbdfbc058eda8053f51adbf5ee46eae655c02 Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Tue, 9 Aug 2022 22:57:33 +0300 Subject: [PATCH 2/5] support null values for optional int, float fields on save method Signed-off-by: wiseaidev --- aredis_om/model/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index e3533294..d4e63da9 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -1291,6 +1291,8 @@ async def save(self, pipeline: Optional[Pipeline] = None) -> "HashModel": db = pipeline document = jsonable_encoder(self.dict()) # TODO: Wrap any Redis response errors in a custom exception? + # store null values as string zero: "0" + document = {key: val if val else "0" for key, val in document.items()} await db.hset(self.key(), mapping=document) return self From 3b07e3f8df403c40656bfb73c6739b4f2e965d76 Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Wed, 10 Aug 2022 00:25:40 +0300 Subject: [PATCH 3/5] trying a hacky solution Signed-off-by: wiseaidev --- aredis_om/model/model.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index d4e63da9..d9bdd7f9 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -1283,16 +1283,18 @@ def __init_subclass__(cls, **kwargs): f"HashModels cannot index dataclass fields. Field: {name}" ) + def dict(self) -> Dict[str, Any]: + # restore none values + return dict(self) + async def save(self, pipeline: Optional[Pipeline] = None) -> "HashModel": self.check() if pipeline is None: db = self.db() else: db = pipeline - document = jsonable_encoder(self.dict()) + document = jsonable_encoder({key: val if val else "0" for key, val in self.dict().items()}) # TODO: Wrap any Redis response errors in a custom exception? - # store null values as string zero: "0" - document = {key: val if val else "0" for key, val in document.items()} await db.hset(self.key(), mapping=document) return self From 47913656a48ad9bbf6ea72ad3159d8a841fd7480 Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Fri, 12 Aug 2022 10:35:19 +0300 Subject: [PATCH 4/5] mv restore_null_values from_redis Signed-off-by: wiseaidev --- aredis_om/model/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index f9791edc..4bc2f8c3 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -1105,7 +1105,6 @@ class Config: extra = "allow" def __init__(__pydantic_self__, **data: Any) -> None: - data = {key: val for key, val in data.items() if val} super().__init__(**data) __pydantic_self__.validate_primary_key() @@ -1211,6 +1210,8 @@ def to_string(s): map(to_string, res[i + offset][1::2]), ) ) + # restore null values + fields = {key: val if val not in ["0", "0.0"] else None for key, val in fields.items()} # $ means a json entry if fields.get("$"): json_fields = json.loads(fields.pop("$")) @@ -1346,7 +1347,7 @@ async def get(cls, pk: Any) -> "HashModel": raise NotFoundError try: # restore none values - document = {key: val if val != "0" else None for key, val in document.items()} + document = {key: val if val not in ["0", "0.0"] else None for key, val in document.items()} result = cls.parse_obj(document) except TypeError as e: log.warning( From 187ca0c97abf699d6816ae45c40df048e7dccbfc Mon Sep 17 00:00:00 2001 From: wiseaidev Date: Fri, 12 Aug 2022 10:56:06 +0300 Subject: [PATCH 5/5] mv optional_fields after_required_fields Signed-off-by: wiseaidev --- tests/test_hash_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_hash_model.py b/tests/test_hash_model.py index 57a70bd6..20eab834 100644 --- a/tests/test_hash_model.py +++ b/tests/test_hash_model.py @@ -51,10 +51,10 @@ class Member(BaseHashModel): last_name: str = Field(index=True) email: str = Field(index=True) join_date: datetime.date - height: Optional[int] = None - weight: Optional[float] = None age: int = Field(index=True, sortable=True) bio: str = Field(index=True, full_text_search=True) + height: Optional[int] = Field(index=True) + weight: Optional[float] = Field(index=True) class Meta: model_key_prefix = "member"