Skip to content

Commit e580153

Browse files
committed
RDBC-839 Pursuing single responsibility principle
1 parent eacfd1f commit e580153

File tree

5 files changed

+85
-58
lines changed

5 files changed

+85
-58
lines changed

ravendb/documents/bulk_insert_operation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def _write_string_no_escape(self, data: str) -> None:
322322

323323
def _write_document(self, entity: object, metadata: MetadataAsDictionary):
324324
document_info = DocumentInfo(metadata_instance=metadata)
325-
json_dict = EntityToJsonStatic.convert_entity_to_json(entity, self._conventions, document_info)
325+
json_dict = EntityToJsonStatic.convert_entity_to_json(entity, self._conventions, document_info, True)
326326
self._current_data_buffer += bytearray(json.dumps(json_dict), encoding="utf-8")
327327

328328
def _ensure_ongoing_operation(self) -> None:

ravendb/documents/conventions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def default_get_collection_name_for_dict(key: str) -> str:
331331
return result
332332

333333
@staticmethod
334-
def try_get_type_from_metadata(metadata):
334+
def try_get_type_from_metadata(metadata: Dict[str, Any]) -> Optional[str]:
335335
if "Raven-Python-Type" in metadata:
336336
return metadata["Raven-Python-Type"]
337337
return None

ravendb/documents/operations/compare_exchange/operations.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ def __init__(self, key: str, value: _T, index: int, metadata: MetadataAsDictiona
7171
self._metadata = metadata
7272

7373
def get_command(self, store: DocumentStore, conventions: DocumentConventions, cache: HttpCache) -> RavenCommand[_T]:
74-
return self.__PutCompareExchangeValueCommand(
75-
self._key, self._value, self._index, self._metadata, conventions
76-
)
74+
return self.__PutCompareExchangeValueCommand(self._key, self._value, self._index, self._metadata, conventions)
7775

7876
class __PutCompareExchangeValueCommand(RavenCommand[_T], RaftCommand, Generic[_T]):
7977
def __init__(

ravendb/documents/session/entity_to_json.py

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import inspect
22
from copy import deepcopy
3-
from typing import Optional, TYPE_CHECKING, Union, Type, TypeVar, Dict, Any
3+
from typing import Optional, TYPE_CHECKING, Union, Type, TypeVar, Dict, Any, Tuple
44

55
from ravendb.primitives import constants
66
from ravendb.documents.session.document_info import DocumentInfo
@@ -136,86 +136,49 @@ def convert_to_entity_by_key(
136136

137137
@staticmethod
138138
def convert_to_entity(
139-
document: dict,
140-
object_type: [_T],
139+
document: Dict[str, Any],
140+
object_type: Type[_T],
141141
conventions: "DocumentConventions",
142142
session: Optional["InMemoryDocumentSessionOperations"] = None,
143143
key: str = None,
144144
) -> _T:
145-
# This method has two steps - extract the type (I), and then convert it into the entity (II)
146-
# todo: Separate it into two different functions and isolate the return statements from the first part
147-
148-
# I. Extract the object type
149145
metadata = document.get("@metadata")
150146
document_deepcopy = deepcopy(document)
151147

152-
# 1. Get type from metadata
153-
type_from_metadata = conventions.try_get_type_from_metadata(metadata)
154-
is_projection = False
155-
key = key or metadata.get(constants.Documents.Metadata.ID, None)
148+
object_type, is_projection, should_update_metadata_python_type = EntityToJsonUtils.determine_object_type(
149+
document, conventions, object_type, metadata
150+
)
156151

157-
# 1.1 Check if passed object type (or extracted from metadata) is a dictionary and if document isn't a dict
158-
if object_type == dict or (object_type is None and type_from_metadata == "builtins.dict"):
152+
if object_type is dict:
159153
EntityToJsonUtils.invoke_after_conversion_to_entity_event(session, key, object_type, document_deepcopy)
160154
return document_deepcopy
161155

162-
# 1.2 If there's no object type in metadata
163-
if type_from_metadata is None:
164-
# 1.2.1 Try to set it with passed object type
165-
if object_type is not None:
166-
metadata["Raven-Python-Type"] = "{0}.{1}".format(object_type.__module__, object_type.__name__)
167-
# 1.2.2 no type defined on document or during load, return a dict
168-
else:
169-
dyn = DynamicStructure(**document_deepcopy)
170-
EntityToJsonUtils.invoke_after_conversion_to_entity_event(session, key, object_type, document_deepcopy)
171-
return dyn
156+
if object_type is DynamicStructure:
157+
dyn = DynamicStructure(**document_deepcopy)
158+
EntityToJsonUtils.invoke_after_conversion_to_entity_event(session, key, object_type, document_deepcopy)
159+
return dyn
172160

173-
# 2. There was a type in the metadata
174-
else:
175-
object_from_metadata = Utils.import_class(type_from_metadata)
176-
177-
# 2.0 Check if the user wants to cast dict to type
178-
if object_from_metadata == dict and object_type != dict:
179-
pass
180-
# 2.1 Import was successful
181-
elif object_from_metadata is not None:
182-
# 2.1.1 Set object_type to successfully imported type/ from metadata inherits from passed object_type
183-
if object_type is None or Utils.is_inherit(object_type, object_from_metadata):
184-
object_type = object_from_metadata
185-
186-
# 2.1.2 Passed type is not a type from metadata, neither there's no inheritance - probably projection
187-
elif object_type is not object_from_metadata:
188-
is_projection = True
189-
if not all([name in object_from_metadata.__dict__ for name in object_type.__dict__]):
190-
raise exceptions.InvalidOperationException(
191-
f"Cannot covert document from type {object_from_metadata} to {object_type}"
192-
)
193-
194-
# We have object type set - it was either extracted or passed through args
161+
if should_update_metadata_python_type:
162+
EntityToJsonUtils.set_python_type_in_metadata(metadata, object_type)
195163

196164
# Fire before conversion to entity events
197165
if session:
198166
session.before_conversion_to_entity_invoke(
199167
BeforeConversionToEntityEventArgs(session, key, object_type, document_deepcopy)
200168
)
201169

202-
# II. Conversion to entity part
170+
# Conversion to entity
203171

204-
# By custom defined 'from_json' serializer class method
205-
# todo: make separate interface to do from_json
206172
if "from_json" in object_type.__dict__ and inspect.ismethod(object_type.from_json):
173+
# By custom defined 'from_json' serializer class method
207174
entity = object_type.from_json(document_deepcopy)
208-
209-
# By projection
210175
elif is_projection:
211176
entity = DynamicStructure(**document_deepcopy)
212177
entity.__class__ = object_type
213178
try:
214179
entity = Utils.initialize_object(document_deepcopy, object_type)
215180
except TypeError as e:
216181
raise InvalidOperationException("Probably projection error", e)
217-
218-
# Happy path - successful extraction of the type from metadata, if not - got object_type passed to arguments
219182
else:
220183
entity = Utils.convert_json_dict_to_object(document_deepcopy, object_type)
221184

@@ -278,3 +241,69 @@ def write_metadata(json_node: dict, document_info: DocumentInfo):
278241

279242
if set_metadata:
280243
json_node.update({constants.Documents.Metadata.KEY: metadata_node})
244+
245+
@staticmethod
246+
def determine_object_type(
247+
document: Dict[str, Any],
248+
conventions: DocumentConventions,
249+
object_type_from_user: Optional[Type[Any]] = None,
250+
metadata: Dict[str, Any] = None,
251+
) -> Tuple[
252+
Type[Union[Any, DynamicStructure, Dict[str, Any]]], bool, bool
253+
]: # -> object_type, is_projection, should_update_metadata_python_type
254+
# Try to extract the object type from the metadata
255+
type_name_from_metadata = conventions.try_get_type_from_metadata(metadata)
256+
257+
# Check if user needs dictionary or if we can return dictionary
258+
if object_type_from_user is dict or (
259+
object_type_from_user is None and type_name_from_metadata == "builtins.dict"
260+
):
261+
return dict, False, False
262+
263+
# No Python type in metadata
264+
if type_name_from_metadata is None:
265+
if object_type_from_user is not None:
266+
# Try using passed object type
267+
return object_type_from_user, False, True
268+
else:
269+
# No type defined, but the user didn't explicitly say that they need a dict - return DynamicStructure
270+
return DynamicStructure, False, False
271+
272+
# Python object type is in the metadata
273+
object_type_from_metadata = Utils.import_class(type_name_from_metadata)
274+
if object_type_from_metadata is None:
275+
# Unsuccessful import means the document has been probably stored within other Python project
276+
# or the original object class has been removed - essentially we have only object_type to rely on
277+
if object_type_from_user is None:
278+
raise RuntimeError(
279+
f"Cannot import class '{type_name_from_metadata}' to convert '{document}' to an object, "
280+
f"it might be removed from your project. Provide an alternative object type "
281+
f"to convert the document to or pass 'dict' to receive dictionary JSON representation."
282+
)
283+
return object_type_from_user, False, False
284+
285+
# Successfully imported the class from metadata - but before conversion check for projections and inheritance
286+
287+
# Maybe user wants to cast from dict to their type
288+
if object_type_from_metadata is dict:
289+
return object_type_from_user, False, False
290+
291+
# User doesn't need projection, or class from metadata is a child of a class provided by user
292+
# We can safely use class from metadata
293+
if object_type_from_user is None or Utils.is_inherit(object_type_from_user, object_type_from_metadata):
294+
return object_type_from_metadata, False, False
295+
296+
# Passed type is not a type from metadata, neither there's no inheritance - probably projection
297+
elif object_type_from_user is not object_type_from_metadata:
298+
if not all([name in object_type_from_metadata.__dict__ for name in object_type_from_user.__dict__]):
299+
# Document from database and object_type from user aren't compatible
300+
raise exceptions.InvalidOperationException(
301+
f"Cannot covert document from type {object_type_from_metadata} to {object_type_from_user}"
302+
)
303+
304+
# Projection
305+
return object_type_from_user, True, False
306+
307+
@staticmethod
308+
def set_python_type_in_metadata(metadata: Dict[str, Any], object_type: Type[Any]) -> None:
309+
metadata["Raven-Python-Type"] = "{0}.{1}".format(object_type.__module__, object_type.__name__)

ravendb/tools/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def get_change_vector_from_header(response):
402402
return header[1 : len(header) - 2]
403403

404404
@staticmethod
405-
def import_class(name) -> Type:
405+
def import_class(name) -> Optional[Type]:
406406
components = name.split(".")
407407
module_name = ".".join(name.split(".")[:-1])
408408
mod = None

0 commit comments

Comments
 (0)