From 82f1ce1621305bc4b70d16c8f1f7d5a4d7a119d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20AVELIN?= Date: Wed, 25 Jun 2025 23:16:30 +0200 Subject: [PATCH] feat: support in aggregation pipeline --- mongomock/aggregate.py | 26 +++++++++++++++++++++++++- tests/test__mongomock.py | 6 ++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/mongomock/aggregate.py b/mongomock/aggregate.py index 59bad84ad..2eac7ed51 100644 --- a/mongomock/aggregate.py +++ b/mongomock/aggregate.py @@ -275,7 +275,9 @@ def parse(self, expression): return self._handle_type_operator(k, v) if k in boolean_operators: return self._handle_boolean_operator(k, v) - if k in text_search_operators + projection_operators + object_operators: + if k in object_operators: + return self._handle_object_operator(k, v) + if k in text_search_operators + projection_operators: raise NotImplementedError( f"'{k}' is a valid operation but it is not supported by Mongomock yet." ) @@ -301,6 +303,28 @@ def parse_many(self, values): else: raise + def _handle_object_operator(self, operator, expression): + if operator == '$mergeObjects': + return self._merge_objects(expression) + + def _merge_objects(self, docs): + """ + Merges a list of dictionaries into a single dictionary. + Ignores values that are not dictionaries. + """ + merged = {} + # Parse the expression to resolve any nested operations + docs = self.parse(docs) if not isinstance(docs, list) else docs + + for doc in docs: + # Handle all value types + parsed = self.parse(doc) if isinstance(doc, dict) else doc + + if isinstance(parsed, dict): + merged.update(parsed) + + return merged + def _parse_to_bool(self, expression): """Parse a MongoDB expression and then convert it to bool""" # handles converting `undefined` (in form of KeyError) to False diff --git a/tests/test__mongomock.py b/tests/test__mongomock.py index c8f68f10b..1a15e763e 100644 --- a/tests/test__mongomock.py +++ b/tests/test__mongomock.py @@ -4704,6 +4704,12 @@ def test__aggregate_merge_objects(self): {'_id': ObjectId(), 'a': 3, 'b': None}, {'_id': ObjectId(), 'a': 3, 'b': {}}, {'_id': ObjectId(), 'a': 4, 'b': None}, + {'_id': ObjectId(), 'a': 'multi', 'b': {'x': 1}}, + {'_id': ObjectId(), 'a': 'multi', 'b': {'y': 2}}, + {'_id': ObjectId(), 'a': 'multi', 'b': {'z': 3}}, + {'a': 'non_dict', 'b': {'note': 'was a string'}}, + {'_id': ObjectId(), 'a': 'non_dict', 'b': {'valid': True}}, + {'_id': ObjectId(), 'a': 'empty', 'b': {}} ] ) pipeline = [