From ac120783dbcffcdca3e247d87686c3e0989af183 Mon Sep 17 00:00:00 2001 From: Gracjan Sadowicz Date: Thu, 22 Dec 2022 12:52:28 +0100 Subject: [PATCH] RDBC-664 Provide better comments and simplify convert_to_entity_static, add the test for documents serialization when no object_type specified --- ravendb/documents/session/entity_to_json.py | 73 ++++++++++++++------- ravendb/tests/issue_tests/test_RDBC_664.py | 34 ++++++++++ 2 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 ravendb/tests/issue_tests/test_RDBC_664.py diff --git a/ravendb/documents/session/entity_to_json.py b/ravendb/documents/session/entity_to_json.py index bd26fdc4..0b790745 100644 --- a/ravendb/documents/session/entity_to_json.py +++ b/ravendb/documents/session/entity_to_json.py @@ -146,6 +146,18 @@ def convert_to_entity_by_key_static( except Exception as e: raise RuntimeError(f"Could not convert document {key} to entity of type {entity_class}", e) + @staticmethod + def _invoke_after_conversion_to_entity_event( + session_hook: Optional["InMemoryDocumentSessionOperations"], + key: str, + object_type: Optional[_T], + document_deepcopy: dict, + ): + if session_hook: + session_hook.after_conversion_to_entity_invoke( + AfterConversionToEntityEventArgs(session_hook, key, object_type, document_deepcopy) + ) + @staticmethod def convert_to_entity_static( document: dict, @@ -153,55 +165,68 @@ def convert_to_entity_static( conventions: "DocumentConventions", session_hook: Optional["InMemoryDocumentSessionOperations"] = None, ) -> _T: + # This method has two steps - extract the type (I), and then convert it into the entity (II) + # todo: Separate it into two different functions and isolate the return statements from the first part + + # I. Extract the object type metadata = document.pop("@metadata") document_deepcopy = deepcopy(document) + + # 1. Get type from metadata type_from_metadata = conventions.try_get_type_from_metadata(metadata) - is_inherit = False + is_projection = False key = metadata.get(constants.Documents.Metadata.ID, None) + + # Fire before conversion to entity events if session_hook: session_hook.before_conversion_to_entity_invoke( BeforeConversionToEntityEventArgs(session_hook, key, object_type, document_deepcopy) ) + # 1.1 Check if passed object type (or extracted from metadata) is a dictionary if object_type == dict or type_from_metadata == "builtins.dict": - session_hook.after_conversion_to_entity_invoke( - AfterConversionToEntityEventArgs(session_hook, key, document_deepcopy, document_deepcopy) - ) + EntityToJson._invoke_after_conversion_to_entity_event(session_hook, key, object_type, document_deepcopy) return document_deepcopy + # 1.2 If there's no object type in metadata if type_from_metadata is None: + # 1.2.1 Try to set it with passed object type if object_type is not None: metadata["Raven-Python-Type"] = "{0}.{1}".format(object_type.__module__, object_type.__name__) - else: # no type defined on document or during load, return a dict + # 1.2.2 no type defined on document or during load, return a dict + else: dyn = _DynamicStructure(**document_deepcopy) - if session_hook: - session_hook.after_conversion_to_entity_invoke( - AfterConversionToEntityEventArgs(session_hook, key, document_deepcopy, dyn) - ) + EntityToJson._invoke_after_conversion_to_entity_event(session_hook, key, object_type, document_deepcopy) return dyn + + # 2. There was a type in the metadata else: object_from_metadata = Utils.import_class(type_from_metadata) + # 2.1 Import was successful if object_from_metadata is not None: - if object_type is None: + # 2.1.1 Set object_type to successfully imported type/ from metadata inherits from passed object_type + if object_type is None or Utils.is_inherit(object_type, object_from_metadata): object_type = object_from_metadata - elif Utils.is_inherit(object_type, object_from_metadata): - object_type = object_from_metadata - is_inherit = True + # 2.1.2 Passed type is not a type from metadata, neither there's no inheritance - probably projection elif object_type is not object_from_metadata: - # todo: projection + is_projection = True if not all([name in object_from_metadata.__dict__ for name in object_type.__dict__]): raise exceptions.InvalidOperationException( f"Cannot covert document from type {object_from_metadata} to {object_type}" ) + + # We have object type set - it was either extracted or passed through args + + # II. Conversion to entity part + + # By custom defined 'from_json' serializer class method # todo: make separate interface to do from_json if "from_json" in object_type.__dict__ and inspect.ismethod(object_type.from_json): entity = object_type.from_json(document_deepcopy) - elif is_inherit: - entity = Utils.convert_json_dict_to_object(document_deepcopy, object_type) - - else: + # By projection + elif is_projection: entity = _DynamicStructure(**document_deepcopy) entity.__class__ = object_type try: @@ -209,12 +234,16 @@ def convert_to_entity_static( except TypeError as e: raise InvalidOperationException("Probably projection error", e) + # Happy path - successful extraction of the type from metadata, if not - got object_type passed to arguments + else: + entity = Utils.convert_json_dict_to_object(document_deepcopy, object_type) + + EntityToJson._invoke_after_conversion_to_entity_event(session_hook, key, object_type, document_deepcopy) + + # Try to set Id if "Id" in entity.__dict__: entity.Id = metadata.get("@id", None) - if session_hook: - session_hook.after_conversion_to_entity_invoke( - AfterConversionToEntityEventArgs(session_hook, key, document_deepcopy, entity) - ) + return entity def remove_from_missing(self, entity): diff --git a/ravendb/tests/issue_tests/test_RDBC_664.py b/ravendb/tests/issue_tests/test_RDBC_664.py new file mode 100644 index 00000000..a4134f47 --- /dev/null +++ b/ravendb/tests/issue_tests/test_RDBC_664.py @@ -0,0 +1,34 @@ +from ravendb import DocumentStore +from ravendb.tests.test_base import TestBase + + +class DocName(dict): + def __init__(self, x, y): + super().__init__() + self["x"] = x + self["y"] = y + self["z"] = 123 + + +class TestRDBC664(TestBase): + def setUp(self): + super(TestRDBC664, self).setUp() + + def write_to_ravenDB(self, array1, array2, store: DocumentStore): + with store.open_session() as session: + for x, y in zip(array1, array2): + x, y = int(x), int(y) + input_to_store = DocName(x, y) + session.store(input_to_store, "abc_%d" % x) + + session.save_changes() + + def query_ravenDB(self, query_string, store: DocumentStore): + with store.open_session() as session: + results = list(session.advanced.raw_query(query_string).wait_for_non_stale_results()) + return results + + def test_can_deserialize_documents_after_raw_query_with_no_object_type_specified(self): + self.write_to_ravenDB([i for i in range(10)], [i * 3 for i in range(10)], self.store) + results = self.query_ravenDB("from 'DocNames' where z==123", self.store) + self.assertEqual(10, len(results))