Skip to content

Commit c6a2901

Browse files
author
matmoncon
committed
feat: add option to raise exception if find_one, update_one, delete_one or disconnect methods do not find any matches
1 parent dc3ca1f commit c6a2901

File tree

3 files changed

+132
-79
lines changed

3 files changed

+132
-79
lines changed

pyneo4j_ogm/core/node.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
InstanceDestroyed,
3131
InstanceNotHydrated,
3232
InvalidFilters,
33-
NoResultsFound,
33+
NoResultFound,
34+
UnexpectedEmptyResult,
3435
UnregisteredModel,
3536
)
3637
from pyneo4j_ogm.fields.settings import NodeModelSettings, RelationshipModelSettings
@@ -130,7 +131,7 @@ async def create(self: T) -> T:
130131
instance is seen as `hydrated` and all methods can be called on it.
131132
132133
Raises:
133-
NoResultsFound: If the query should return a result but does not.
134+
UnexpectedEmptyResult: If the query should return a result but does not.
134135
135136
Returns:
136137
T: The current model instance.
@@ -155,7 +156,7 @@ async def create(self: T) -> T:
155156

156157
logger.debug("Checking if query returned a result")
157158
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
158-
raise NoResultsFound()
159+
raise UnexpectedEmptyResult()
159160

160161
logger.debug("Hydrating instance values")
161162
setattr(self, "_element_id", getattr(cast(T, results[0][0]), "_element_id"))
@@ -174,7 +175,7 @@ async def update(self) -> None:
174175
Updates the corresponding node in the graph with the current instance values.
175176
176177
Raises:
177-
NoResultsFound: If the query should return a result but does not.
178+
UnexpectedEmptyResult: If the query should return a result but does not.
178179
"""
179180
deflated = self._deflate()
180181

@@ -203,7 +204,7 @@ async def update(self) -> None:
203204

204205
logger.debug("Checking if query returned a result")
205206
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
206-
raise NoResultsFound()
207+
raise UnexpectedEmptyResult()
207208

208209
logger.debug("Resetting modified properties")
209210
self._db_properties = self.dict(exclude=self._relationships_properties)
@@ -217,7 +218,7 @@ async def delete(self) -> None:
217218
another method is called on this instance, an `InstanceDestroyed` will be raised.
218219
219220
Raises:
220-
NoResultsFound: If the query should return a result but does not.
221+
UnexpectedEmptyResult: If the query should return a result but does not.
221222
"""
222223
logger.info("Deleting node %s", self)
223224
results, _ = await self._client.cypher(
@@ -232,7 +233,7 @@ async def delete(self) -> None:
232233

233234
logger.debug("Checking if query returned a result")
234235
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
235-
raise NoResultsFound()
236+
raise UnexpectedEmptyResult()
236237

237238
logger.debug("Marking instance as destroyed")
238239
setattr(self, "_destroyed", True)
@@ -245,7 +246,7 @@ async def refresh(self) -> None:
245246
Refreshes the current instance with the corresponding values from the graph.
246247
247248
Raises:
248-
NoResultsFound: If the query should return a result but does not.
249+
UnexpectedEmptyResult: If the query should return a result but does not.
249250
"""
250251
logger.info("Refreshing node %s with values from database", self)
251252
results, _ = await self._client.cypher(
@@ -259,7 +260,7 @@ async def refresh(self) -> None:
259260

260261
logger.debug("Checking if query returned a result")
261262
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
262-
raise NoResultsFound()
263+
raise UnexpectedEmptyResult()
263264

264265
logger.debug("Updating current instance")
265266
self.__dict__.update(results[0][0].__dict__)
@@ -416,6 +417,7 @@ async def find_one(
416417
projections: Optional[Dict[str, str]] = None,
417418
auto_fetch_nodes: bool = False,
418419
auto_fetch_models: Optional[List[Union[str, Type["NodeModel"]]]] = None,
420+
raise_on_empty: bool = False,
419421
) -> Optional[Union[T, Dict[str, Any]]]:
420422
"""
421423
Finds the first node that matches `filters` and returns it. If no matching node is found,
@@ -430,9 +432,12 @@ async def find_one(
430432
identical option defined in `Settings`. Can not be used with projections. Defaults to `False`.
431433
auto_fetch_models (List[Union[str, Type["NodeModel"]]], optional): A list of models to auto-fetch.
432434
`auto_fetch_nodes` has to be set to `True` for this to have any effect. Defaults to `[]`.
435+
raise_on_empty (bool, optional): Whether to raise an `NoResultFound` if no match is found. Defaults to
436+
`False`.
433437
434438
Raises:
435439
InvalidFilters: If no filters or invalid filters are provided.
440+
NoResultFound: If no match is found and `raise_on_empty` is set to `True`.
436441
437442
Returns:
438443
T | Dict[str, Any] | None: A instance of the model or None if no match is found or a dictionary of the
@@ -500,6 +505,8 @@ async def find_one(
500505
or results[0][0] is None
501506
or (isinstance(results[0][0], dict) and len(results[0][0]) == 0)
502507
):
508+
if raise_on_empty:
509+
raise NoResultFound(filters)
503510
return None
504511

505512
if isinstance(results[0][0], Node):
@@ -654,7 +661,9 @@ async def find_many(
654661

655662
@classmethod
656663
@hooks
657-
async def update_one(cls: Type[T], update: Dict[str, Any], filters: NodeFilters, new: bool = False) -> Optional[T]:
664+
async def update_one(
665+
cls: Type[T], update: Dict[str, Any], filters: NodeFilters, new: bool = False, raise_on_empty: bool = False
666+
) -> Optional[T]:
658667
"""
659668
Finds the first node that matches `filters` and updates it with the values defined by
660669
`update`. If no match is found, `None` is returned instead.
@@ -664,9 +673,12 @@ async def update_one(cls: Type[T], update: Dict[str, Any], filters: NodeFilters,
664673
filters (NodeFilters): The filters to apply to the query.
665674
new (bool, optional): Whether to return the updated node. By default, the old node is
666675
returned. Defaults to `False`.
676+
raise_on_empty (bool, optional): Whether to raise an `NoResultFound` if no match is found. Defaults to
677+
`False`.
667678
668679
Raises:
669680
InvalidFilters: If no filters or invalid filters are provided.
681+
NoResultFound: If no match is found and `raise_on_empty` is set to `True`.
670682
671683
Returns:
672684
T | None: By default, the old node instance is returned. If `new` is set to `True`, the result
@@ -702,6 +714,8 @@ async def update_one(cls: Type[T], update: Dict[str, Any], filters: NodeFilters,
702714

703715
logger.debug("Checking if query returned a result")
704716
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
717+
if raise_on_empty:
718+
raise NoResultFound(filters)
705719
return None
706720

707721
old_instance = results[0][0] if isinstance(results[0][0], cls) else cls._inflate(node=results[0][0])
@@ -830,17 +844,20 @@ async def update_many(
830844

831845
@classmethod
832846
@hooks
833-
async def delete_one(cls: Type[T], filters: NodeFilters) -> int:
847+
async def delete_one(cls: Type[T], filters: NodeFilters, raise_on_empty: bool = False) -> int:
834848
"""
835849
Finds the first node that matches `filters` and deletes it. If no match is found, a
836-
`NoResultsFound` is raised.
850+
`UnexpectedEmptyResult` is raised.
837851
838852
Args:
839853
filters (NodeFilters): The filters to apply to the query.
854+
raise_on_empty (bool, optional): Whether to raise an `NoResultFound` if no match is found. Defaults to
855+
`False`.
840856
841857
Raises:
842-
NoResultsFound: If the query should return a result but does not.
858+
UnexpectedEmptyResult: If the query should return a result but does not.
843859
InvalidFilters: If no filters or invalid filters are provided.
860+
NoResultFound: If no match is found and `raise_on_empty` is set to `True`.
844861
845862
Returns:
846863
int: The number of deleted nodes.
@@ -869,9 +886,11 @@ async def delete_one(cls: Type[T], filters: NodeFilters) -> int:
869886

870887
logger.debug("Checking if query returned a result")
871888
if len(result) == 0 or len(result[0]) == 0 or result[0][0] is None:
872-
raise NoResultsFound()
889+
raise UnexpectedEmptyResult()
873890

874891
logger.info("Deleted %s nodes", result[0][0])
892+
if result[0][0] == 0 and raise_on_empty:
893+
raise NoResultFound(filters)
875894
return result[0][0]
876895

877896
@classmethod
@@ -903,7 +922,7 @@ async def delete_many(cls: Type[T], filters: Optional[NodeFilters] = None) -> in
903922

904923
logger.debug("Checking if query returned a result")
905924
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
906-
raise NoResultsFound()
925+
raise UnexpectedEmptyResult()
907926

908927
logger.info("Deleted %s nodes", len(results))
909928
return results[0][0]
@@ -940,7 +959,7 @@ async def count(cls: Type[T], filters: Optional[NodeFilters] = None) -> int:
940959

941960
logger.debug("Checking if query returned a result")
942961
if len(results) == 0 or len(results[0]) == 0 or results[0][0] is None:
943-
raise NoResultsFound()
962+
raise UnexpectedEmptyResult()
944963

945964
return results[0][0]
946965

@@ -970,7 +989,7 @@ def _inflate(cls: Type[T], node: Node) -> T:
970989
node (Node): Node to inflate.
971990
972991
Raises:
973-
InflationFailure: Raised if inflating the node fails.
992+
InflationFailure: If inflating the node fails.
974993
975994
Returns:
976995
T: A new instance of the current model with the properties from the node instance.
@@ -1006,7 +1025,7 @@ def _build_auto_fetch(
10061025
10071026
Args:
10081027
nodes_to_fetch (List[Union[str, Type["NodeModel"]]] | None): The nodes to fetch. Can contain the actual
1009-
Model of the Node or the model name as a string. If None, all nodes will be fetched. Defaults to None.
1028+
model of the node or the model name as a string. If `None`, all nodes will be fetched. Defaults to `None`.
10101029
ref (str, optional): The reference to use for the node. Defaults to "n".
10111030
10121031
Returns:

0 commit comments

Comments
 (0)