Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RDBC-664 Client cannot serialize entities while completing raw query #163

Merged
merged 1 commit into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 51 additions & 22 deletions ravendb/documents/session/entity_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,75 +146,104 @@ 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,
object_type: [_T],
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:
entity = Utils.initialize_object(document_deepcopy, object_type)
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):
Expand Down
34 changes: 34 additions & 0 deletions ravendb/tests/issue_tests/test_RDBC_664.py
Original file line number Diff line number Diff line change
@@ -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))