Skip to content

Commit aefd02a

Browse files
committed
PYTHON-1798 Support pipelines in update commands
1 parent 694a4a5 commit aefd02a

File tree

6 files changed

+290
-12
lines changed

6 files changed

+290
-12
lines changed

doc/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ Version 3.9 adds support for MongoDB 4.2. Highlights include:
5151
:mod:`~pymongo.monitoring` for an example.
5252
- :meth:`pymongo.collection.Collection.aggregate` and
5353
:meth:`pymongo.database.Database.aggregate` now support the ``$merge`` pipeline
54+
- Support for specifying a pipeline or document in
55+
:meth:`~pymongo.collection.Collection.update_one`,
56+
:meth:`~pymongo.collection.Collection.update_many`,
57+
:meth:`~pymongo.collection.Collection.find_one_and_update`,
58+
:meth:`~pymongo.operations.UpdateOne`, and
59+
:meth:`~pymongo.operations.UpdateMany`.
5460

5561
.. _URI options specification: https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst
5662

pymongo/collection.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,9 @@ def update_one(self, filter, update, upsert=False,
974974
.. note:: `bypass_document_validation` requires server version
975975
**>= 3.2**
976976
977+
.. versionchanged:: 3.9
978+
Added the ability to accept a pipeline as the `update`.
979+
977980
.. versionchanged:: 3.6
978981
Added the `array_filters` and ``session`` parameters.
979982
@@ -1044,6 +1047,9 @@ def update_many(self, filter, update, upsert=False, array_filters=None,
10441047
.. note:: `bypass_document_validation` requires server version
10451048
**>= 3.2**
10461049
1050+
.. versionchanged:: 3.9
1051+
Added the ability to accept a pipeline as the `update`.
1052+
10471053
.. versionchanged:: 3.6
10481054
Added ``array_filters`` and ``session`` parameters.
10491055
@@ -3087,6 +3093,8 @@ def find_one_and_update(self, filter, update,
30873093
as keyword arguments (for example maxTimeMS can be used with
30883094
recent server versions).
30893095
3096+
.. versionchanged:: 3.9
3097+
Added the ability to accept a pipeline as the `update`.
30903098
.. versionchanged:: 3.6
30913099
Added the `array_filters` and `session` options.
30923100
.. versionchanged:: 3.4

pymongo/common.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,19 @@ def validate_list_or_none(option, value):
457457
return validate_list(option, value)
458458

459459

460+
def validate_list_or_mapping(option, value):
461+
"""Validates that 'value' is a list or a document."""
462+
if not isinstance(value, (abc.Mapping, list)):
463+
raise TypeError("%s must either be a list or an instance of dict, "
464+
"bson.son.SON, or any other type that inherits from "
465+
"collections.Mapping" % (option,))
466+
467+
460468
def validate_is_mapping(option, value):
461469
"""Validate the type of method arguments that expect a document."""
462470
if not isinstance(value, abc.Mapping):
463471
raise TypeError("%s must be an instance of dict, bson.son.SON, or "
464-
"other type that inherits from "
472+
"any other type that inherits from "
465473
"collections.Mapping" % (option,))
466474

467475

@@ -515,12 +523,14 @@ def validate_ok_for_replace(replacement):
515523

516524
def validate_ok_for_update(update):
517525
"""Validate an update document."""
518-
validate_is_mapping("update", update)
519-
# Update can not be {}
526+
validate_list_or_mapping("update", update)
527+
# Update cannot be {}.
520528
if not update:
521-
raise ValueError('update only works with $ operators')
529+
raise ValueError('update cannot be empty')
530+
531+
is_document = not isinstance(update, list)
522532
first = next(iter(update))
523-
if not first.startswith('$'):
533+
if is_document and not first.startswith('$'):
524534
raise ValueError('update only works with $ operators')
525535

526536

pymongo/operations.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ def __init__(self, filter, update, upsert=False, collation=None,
243243
- `array_filters` (optional): A list of filters specifying which
244244
array elements an update should apply. Requires MongoDB 3.6+.
245245
246+
.. versionchanged:: 3.9
247+
Added the ability to accept a pipeline as the `update`.
246248
.. versionchanged:: 3.6
247249
Added the `array_filters` option.
248250
.. versionchanged:: 3.5
@@ -280,6 +282,8 @@ def __init__(self, filter, update, upsert=False, collation=None,
280282
- `array_filters` (optional): A list of filters specifying which
281283
array elements an update should apply. Requires MongoDB 3.6+.
282284
285+
.. versionchanged:: 3.9
286+
Added the ability to accept a pipeline as the `update`.
283287
.. versionchanged:: 3.6
284288
Added the `array_filters` option.
285289
.. versionchanged:: 3.5

test/crud/v2/updateWithPipelines.json

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
{
2+
"data": [
3+
{
4+
"_id": 1,
5+
"x": 1,
6+
"y": 1,
7+
"t": {
8+
"u": {
9+
"v": 1
10+
}
11+
}
12+
},
13+
{
14+
"_id": 2,
15+
"x": 2,
16+
"y": 1
17+
}
18+
],
19+
"minServerVersion": "4.1.11",
20+
"collection_name": "test",
21+
"database_name": "crud-tests",
22+
"tests": [
23+
{
24+
"description": "UpdateOne using pipelines",
25+
"operations": [
26+
{
27+
"name": "updateOne",
28+
"arguments": {
29+
"filter": {
30+
"_id": 1
31+
},
32+
"update": [
33+
{
34+
"$replaceRoot": {
35+
"newRoot": "$t"
36+
}
37+
},
38+
{
39+
"$addFields": {
40+
"foo": 1
41+
}
42+
}
43+
]
44+
},
45+
"result": {
46+
"matchedCount": 1,
47+
"modifiedCount": 1,
48+
"upsertedCount": 0
49+
}
50+
}
51+
],
52+
"expectations": [
53+
{
54+
"command_started_event": {
55+
"command": {
56+
"update": "test",
57+
"updates": [
58+
{
59+
"q": {
60+
"_id": 1
61+
},
62+
"u": [
63+
{
64+
"$replaceRoot": {
65+
"newRoot": "$t"
66+
}
67+
},
68+
{
69+
"$addFields": {
70+
"foo": 1
71+
}
72+
}
73+
]
74+
}
75+
]
76+
},
77+
"command_name": "update",
78+
"database_name": "crud-tests"
79+
}
80+
}
81+
],
82+
"outcome": {
83+
"collection": {
84+
"data": [
85+
{
86+
"_id": 1,
87+
"u": {
88+
"v": 1
89+
},
90+
"foo": 1
91+
},
92+
{
93+
"_id": 2,
94+
"x": 2,
95+
"y": 1
96+
}
97+
]
98+
}
99+
}
100+
},
101+
{
102+
"description": "UpdateMany using pipelines",
103+
"operations": [
104+
{
105+
"name": "updateMany",
106+
"arguments": {
107+
"filter": {},
108+
"update": [
109+
{
110+
"$project": {
111+
"x": 1
112+
}
113+
},
114+
{
115+
"$addFields": {
116+
"foo": 1
117+
}
118+
}
119+
]
120+
},
121+
"result": {
122+
"matchedCount": 2,
123+
"modifiedCount": 2,
124+
"upsertedCount": 0
125+
}
126+
}
127+
],
128+
"expectations": [
129+
{
130+
"command_started_event": {
131+
"command": {
132+
"update": "test",
133+
"updates": [
134+
{
135+
"q": {},
136+
"u": [
137+
{
138+
"$project": {
139+
"x": 1
140+
}
141+
},
142+
{
143+
"$addFields": {
144+
"foo": 1
145+
}
146+
}
147+
],
148+
"multi": true
149+
}
150+
]
151+
},
152+
"command_name": "update",
153+
"database_name": "crud-tests"
154+
}
155+
}
156+
],
157+
"outcome": {
158+
"collection": {
159+
"data": [
160+
{
161+
"_id": 1,
162+
"x": 1,
163+
"foo": 1
164+
},
165+
{
166+
"_id": 2,
167+
"x": 2,
168+
"foo": 1
169+
}
170+
]
171+
}
172+
}
173+
},
174+
{
175+
"description": "FindOneAndUpdate using pipelines",
176+
"operations": [
177+
{
178+
"name": "findOneAndUpdate",
179+
"arguments": {
180+
"filter": {
181+
"_id": 1
182+
},
183+
"update": [
184+
{
185+
"$project": {
186+
"x": 1
187+
}
188+
},
189+
{
190+
"$addFields": {
191+
"foo": 1
192+
}
193+
}
194+
]
195+
}
196+
}
197+
],
198+
"expectations": [
199+
{
200+
"command_started_event": {
201+
"command": {
202+
"findAndModify": "test",
203+
"update": [
204+
{
205+
"$project": {
206+
"x": 1
207+
}
208+
},
209+
{
210+
"$addFields": {
211+
"foo": 1
212+
}
213+
}
214+
]
215+
},
216+
"command_name": "findAndModify",
217+
"database_name": "crud-tests"
218+
}
219+
}
220+
],
221+
"outcome": {
222+
"collection": {
223+
"data": [
224+
{
225+
"_id": 1,
226+
"x": 1,
227+
"foo": 1
228+
},
229+
{
230+
"_id": 2,
231+
"x": 2,
232+
"y": 1
233+
}
234+
]
235+
}
236+
}
237+
}
238+
]
239+
}

0 commit comments

Comments
 (0)