Skip to content

Commit 0e07e18

Browse files
committed
Unwrap ChainMaps during logging
1 parent 36187bb commit 0e07e18

File tree

5 files changed

+43
-13
lines changed

5 files changed

+43
-13
lines changed

pymongo/_client_bulk_shared.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"""Constants, types, and classes shared across Client Bulk Write API implementations."""
1717
from __future__ import annotations
1818

19-
from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, NoReturn
19+
from typing import TYPE_CHECKING, Any, ChainMap, Mapping, MutableMapping, NoReturn
2020

2121
from pymongo.errors import ClientBulkWriteException, OperationFailure
2222
from pymongo.helpers_shared import _get_wce_doc
@@ -63,6 +63,10 @@ def _throw_client_bulk_write_exception(
6363
"""Raise a ClientBulkWriteException from the full result."""
6464
# retryWrites on MMAPv1 should raise an actionable error.
6565
if full_result["writeErrors"]:
66+
# Unpack ChainMaps into the original document only
67+
for doc in full_result["writeErrors"]:
68+
if "document" in doc["op"] and isinstance(doc["op"]["document"], ChainMap):
69+
doc["op"]["document"] = doc["op"]["document"].maps[0]
6670
full_result["writeErrors"].sort(key=lambda error: error["idx"])
6771
err = full_result["writeErrors"][0]
6872
code = err["code"]

pymongo/asynchronous/client_bulk.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,16 @@ def add_insert(self, namespace: str, document: _DocumentOut) -> None:
133133
"""Add an insert document to the list of ops."""
134134
validate_is_document_type("document", document)
135135
# Generate ObjectId client side.
136-
if not (isinstance(document, RawBSONDocument) or "_id" in document):
137-
document = ChainMap(document, {"_id": ObjectId()})
138-
elif not isinstance(document, RawBSONDocument) and "_id" in document:
139-
document = ChainMap(document, {"_id": document["_id"]})
136+
if not isinstance(document, RawBSONDocument):
137+
# Since the data document itself is nested within the insert document
138+
# it won't be automatically re-ordered by the BSON conversion.
139+
# We use ChainMap here to make the _id field the first field instead.
140+
if "_id" in document:
141+
document = ChainMap(document, {"_id": document["_id"]})
142+
else:
143+
id = ObjectId()
144+
document["_id"] = id
145+
document = ChainMap(document, {"_id": id})
140146
cmd = {"insert": -1, "document": document}
141147
self.ops.append(("insert", cmd))
142148
self.namespaces.append(namespace)

pymongo/monitoring.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def connection_checked_in(self, event):
190190

191191
import datetime
192192
from collections import abc, namedtuple
193-
from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence
193+
from typing import TYPE_CHECKING, Any, ChainMap, Mapping, Optional, Sequence
194194

195195
from bson.objectid import ObjectId
196196
from pymongo.hello import Hello, HelloCompat
@@ -625,6 +625,11 @@ def __init__(
625625
raise ValueError(f"{command!r} is not a valid command")
626626
# Command name must be first key.
627627
command_name = next(iter(command))
628+
# Unpack ChainMaps into the original document only
629+
if command_name == "bulkWrite" and "ops" in command:
630+
for doc in command["ops"]:
631+
if "document" in doc and isinstance(doc["document"], ChainMap):
632+
doc["document"] = doc["document"].maps[0]
628633
super().__init__(
629634
command_name,
630635
request_id,

pymongo/synchronous/client_bulk.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,16 @@ def add_insert(self, namespace: str, document: _DocumentOut) -> None:
133133
"""Add an insert document to the list of ops."""
134134
validate_is_document_type("document", document)
135135
# Generate ObjectId client side.
136-
if not (isinstance(document, RawBSONDocument) or "_id" in document):
137-
document = ChainMap(document, {"_id": ObjectId()})
138-
elif not isinstance(document, RawBSONDocument) and "_id" in document:
139-
document = ChainMap(document, {"_id": document["_id"]})
136+
if not isinstance(document, RawBSONDocument):
137+
# Since the data document itself is nested within the insert document
138+
# it won't be automatically re-ordered by the BSON conversion.
139+
# We use ChainMap here to make the _id field the first field instead.
140+
if "_id" in document:
141+
document = ChainMap(document, {"_id": document["_id"]})
142+
else:
143+
id = ObjectId()
144+
document["_id"] = id
145+
document = ChainMap(document, {"_id": id})
140146
cmd = {"insert": -1, "document": document}
141147
self.ops.append(("insert", cmd))
142148
self.namespaces.append(namespace)

test/mockupdb/test_id_ordering.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,31 @@ def test_id_ordering(self):
4848
server.run()
4949
self.addCleanup(server.stop)
5050

51+
# We also verify that the original document contains an _id field after each insert
52+
document = {"x": 1}
53+
5154
client = self.simple_client(server.uri, loadBalanced=True)
5255
collection = client.db.coll
53-
with going(collection.insert_one, {"x": 1}):
56+
with going(collection.insert_one, document):
5457
request = server.receives()
5558
self.assertEqual("_id", next(iter(request["documents"][0])))
5659
request.reply({"ok": 1})
60+
self.assertIn("_id", document)
61+
62+
document = {"x1": 1}
5763

58-
with going(collection.bulk_write, [InsertOne({"x1": 1})]):
64+
with going(collection.bulk_write, [InsertOne(document)]):
5965
request = server.receives()
6066
self.assertEqual("_id", next(iter(request["documents"][0])))
6167
request.reply({"ok": 1})
68+
self.assertIn("_id", document)
6269

63-
with going(client.bulk_write, [InsertOne(namespace="db.coll", document={"x2": 1})]):
70+
document = {"x2": 1}
71+
with going(client.bulk_write, [InsertOne(namespace="db.coll", document=document)]):
6472
request = server.receives()
6573
self.assertEqual("_id", next(iter(request["ops"][0]["document"])))
6674
request.reply({"ok": 1})
75+
self.assertIn("_id", document)
6776

6877
# Re-ordering user-supplied _id fields is not required by the spec, but PyMongo does it for performance reasons
6978
with going(collection.insert_one, {"x": 1, "_id": 111}):

0 commit comments

Comments
 (0)