Skip to content

Commit 3edba6f

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver into PYTHON-5215
2 parents 2a3d4c3 + b32da4b commit 3edba6f

File tree

13 files changed

+130
-62
lines changed

13 files changed

+130
-62
lines changed

pymongo/asynchronous/encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@
7676
NetworkTimeout,
7777
ServerSelectionTimeoutError,
7878
)
79+
from pymongo.helpers_shared import _get_timeout_details
7980
from pymongo.network_layer import PyMongoKMSProtocol, async_receive_kms, async_sendall
8081
from pymongo.operations import UpdateOne
8182
from pymongo.pool_options import PoolOptions
8283
from pymongo.pool_shared import (
8384
_configured_protocol_interface,
84-
_get_timeout_details,
8585
_raise_connection_failure,
8686
)
8787
from pymongo.read_concern import ReadConcern

pymongo/asynchronous/pool.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
WaitQueueTimeoutError,
5959
)
6060
from pymongo.hello import Hello, HelloCompat
61+
from pymongo.helpers_shared import _get_timeout_details, format_timeout_details
6162
from pymongo.lock import (
6263
_async_cond_wait,
6364
_async_create_condition,
@@ -79,9 +80,7 @@
7980
SSLErrors,
8081
_CancellationContext,
8182
_configured_protocol_interface,
82-
_get_timeout_details,
8383
_raise_connection_failure,
84-
format_timeout_details,
8584
)
8685
from pymongo.read_preferences import ReadPreference
8786
from pymongo.server_api import _add_to_command

pymongo/asynchronous/server.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
_SDAMStatusMessage,
3939
)
4040
from pymongo.message import _convert_exception, _GetMore, _OpMsg, _Query
41-
from pymongo.pool_shared import _get_timeout_details, format_timeout_details
4241
from pymongo.response import PinnedResponse, Response
4342

4443
if TYPE_CHECKING:
@@ -225,11 +224,7 @@ async def run_operation(
225224
if use_cmd:
226225
first = docs[0]
227226
await operation.client._process_response(first, operation.session) # type: ignore[misc, arg-type]
228-
# Append timeout details to MaxTimeMSExpired responses.
229-
if first.get("code") == 50:
230-
timeout_details = _get_timeout_details(conn.opts) # type:ignore[has-type]
231-
first["errmsg"] += format_timeout_details(timeout_details) # type:ignore[index]
232-
_check_command_response(first, conn.max_wire_version)
227+
_check_command_response(first, conn.max_wire_version, pool_opts=conn.opts) # type:ignore[has-type]
233228
except Exception as exc:
234229
duration = datetime.now() - start
235230
if isinstance(exc, (NotPrimaryError, OperationFailure)):

pymongo/helpers_shared.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
if TYPE_CHECKING:
4848
from pymongo.cursor_shared import _Hint
4949
from pymongo.operations import _IndexList
50+
from pymongo.pool_options import PoolOptions
5051
from pymongo.typings import _DocumentOut
5152

5253

@@ -108,6 +109,34 @@
108109
}
109110

110111

112+
def _get_timeout_details(options: PoolOptions) -> dict[str, float]:
113+
from pymongo import _csot
114+
115+
details = {}
116+
timeout = _csot.get_timeout()
117+
socket_timeout = options.socket_timeout
118+
connect_timeout = options.connect_timeout
119+
if timeout:
120+
details["timeoutMS"] = timeout * 1000
121+
if socket_timeout and not timeout:
122+
details["socketTimeoutMS"] = socket_timeout * 1000
123+
if connect_timeout:
124+
details["connectTimeoutMS"] = connect_timeout * 1000
125+
return details
126+
127+
128+
def format_timeout_details(details: Optional[dict[str, float]]) -> str:
129+
result = ""
130+
if details:
131+
result += " (configured timeouts:"
132+
for timeout in ["socketTimeoutMS", "timeoutMS", "connectTimeoutMS"]:
133+
if timeout in details:
134+
result += f" {timeout}: {details[timeout]}ms,"
135+
result = result[:-1]
136+
result += ")"
137+
return result
138+
139+
111140
def _gen_index_name(keys: _IndexList) -> str:
112141
"""Generate an index name from the set of fields it is over."""
113142
return "_".join(["{}_{}".format(*item) for item in keys])
@@ -188,6 +217,7 @@ def _check_command_response(
188217
max_wire_version: Optional[int],
189218
allowable_errors: Optional[Container[Union[int, str]]] = None,
190219
parse_write_concern_error: bool = False,
220+
pool_opts: Optional[PoolOptions] = None,
191221
) -> None:
192222
"""Check the response to a command for errors."""
193223
if "ok" not in response:
@@ -243,6 +273,10 @@ def _check_command_response(
243273
if code in (11000, 11001, 12582):
244274
raise DuplicateKeyError(errmsg, code, response, max_wire_version)
245275
elif code == 50:
276+
# Append timeout details to MaxTimeMSExpired responses.
277+
if pool_opts:
278+
timeout_details = _get_timeout_details(pool_opts)
279+
errmsg += format_timeout_details(timeout_details)
246280
raise ExecutionTimeout(errmsg, code, response, max_wire_version)
247281
elif code == 43:
248282
raise CursorNotFound(errmsg, code, response, max_wire_version)

pymongo/pool_options.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,13 @@ def __init__(
386386

387387
def _update_metadata(self, driver: DriverInfo) -> None:
388388
"""Updates the client's metadata"""
389+
if driver.name and driver.name.lower() in self.__metadata["driver"]["name"].lower().split(
390+
"|"
391+
):
392+
return
389393

390394
metadata = copy.deepcopy(self.__metadata)
395+
391396
if driver.name:
392397
metadata["driver"]["name"] = "{}|{}".format(
393398
metadata["driver"]["name"],

pymongo/pool_shared.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
NetworkTimeout,
3535
_CertificateError,
3636
)
37+
from pymongo.helpers_shared import _get_timeout_details, format_timeout_details
3738
from pymongo.network_layer import (
3839
AsyncNetworkingInterface,
3940
NetworkingInterface,
@@ -151,32 +152,6 @@ def _raise_connection_failure(
151152
raise AutoReconnect(msg) from error
152153

153154

154-
def _get_timeout_details(options: PoolOptions) -> dict[str, float]:
155-
details = {}
156-
timeout = _csot.get_timeout()
157-
socket_timeout = options.socket_timeout
158-
connect_timeout = options.connect_timeout
159-
if timeout:
160-
details["timeoutMS"] = timeout * 1000
161-
if socket_timeout and not timeout:
162-
details["socketTimeoutMS"] = socket_timeout * 1000
163-
if connect_timeout:
164-
details["connectTimeoutMS"] = connect_timeout * 1000
165-
return details
166-
167-
168-
def format_timeout_details(details: Optional[dict[str, float]]) -> str:
169-
result = ""
170-
if details:
171-
result += " (configured timeouts:"
172-
for timeout in ["socketTimeoutMS", "timeoutMS", "connectTimeoutMS"]:
173-
if timeout in details:
174-
result += f" {timeout}: {details[timeout]}ms,"
175-
result = result[:-1]
176-
result += ")"
177-
return result
178-
179-
180155
class _CancellationContext:
181156
def __init__(self) -> None:
182157
self._cancelled = False

pymongo/synchronous/encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@
7070
NetworkTimeout,
7171
ServerSelectionTimeoutError,
7272
)
73+
from pymongo.helpers_shared import _get_timeout_details
7374
from pymongo.network_layer import PyMongoKMSProtocol, receive_kms, sendall
7475
from pymongo.operations import UpdateOne
7576
from pymongo.pool_options import PoolOptions
7677
from pymongo.pool_shared import (
7778
_configured_socket_interface,
78-
_get_timeout_details,
7979
_raise_connection_failure,
8080
)
8181
from pymongo.read_concern import ReadConcern

pymongo/synchronous/pool.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
WaitQueueTimeoutError,
5656
)
5757
from pymongo.hello import Hello, HelloCompat
58+
from pymongo.helpers_shared import _get_timeout_details, format_timeout_details
5859
from pymongo.lock import (
5960
_cond_wait,
6061
_create_condition,
@@ -76,9 +77,7 @@
7677
SSLErrors,
7778
_CancellationContext,
7879
_configured_socket_interface,
79-
_get_timeout_details,
8080
_raise_connection_failure,
81-
format_timeout_details,
8281
)
8382
from pymongo.read_preferences import ReadPreference
8483
from pymongo.server_api import _add_to_command

pymongo/synchronous/server.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
_SDAMStatusMessage,
3838
)
3939
from pymongo.message import _convert_exception, _GetMore, _OpMsg, _Query
40-
from pymongo.pool_shared import _get_timeout_details, format_timeout_details
4140
from pymongo.response import PinnedResponse, Response
4241
from pymongo.synchronous.helpers import _handle_reauth
4342

@@ -225,11 +224,7 @@ def run_operation(
225224
if use_cmd:
226225
first = docs[0]
227226
operation.client._process_response(first, operation.session) # type: ignore[misc, arg-type]
228-
# Append timeout details to MaxTimeMSExpired responses.
229-
if first.get("code") == 50:
230-
timeout_details = _get_timeout_details(conn.opts) # type:ignore[has-type]
231-
first["errmsg"] += format_timeout_details(timeout_details) # type:ignore[index]
232-
_check_command_response(first, conn.max_wire_version)
227+
_check_command_response(first, conn.max_wire_version, pool_opts=conn.opts) # type:ignore[has-type]
233228
except Exception as exc:
234229
duration = datetime.now() - start
235230
if isinstance(exc, (NotPrimaryError, OperationFailure)):

test/asynchronous/test_client_metadata.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,20 @@ async def check_metadata_added(
107107
new_name, new_version, new_platform, new_metadata = await self.send_ping_and_get_metadata(
108108
client, True
109109
)
110-
self.assertEqual(new_name, f"{name}|{add_name}" if add_name is not None else name)
111-
self.assertEqual(
112-
new_version,
113-
f"{version}|{add_version}" if add_version is not None else version,
114-
)
115-
self.assertEqual(
116-
new_platform,
117-
f"{platform}|{add_platform}" if add_platform is not None else platform,
118-
)
110+
if add_name is not None and add_name.lower() in name.lower().split("|"):
111+
self.assertEqual(name, new_name)
112+
self.assertEqual(version, new_version)
113+
self.assertEqual(platform, new_platform)
114+
else:
115+
self.assertEqual(new_name, f"{name}|{add_name}" if add_name is not None else name)
116+
self.assertEqual(
117+
new_version,
118+
f"{version}|{add_version}" if add_version is not None else version,
119+
)
120+
self.assertEqual(
121+
new_platform,
122+
f"{platform}|{add_platform}" if add_platform is not None else platform,
123+
)
119124

120125
metadata.pop("driver")
121126
metadata.pop("platform")
@@ -210,6 +215,18 @@ async def test_doesnt_update_established_connections(self):
210215
self.assertIsNone(self.handshake_req)
211216
self.assertEqual(listener.event_count(ConnectionClosedEvent), 0)
212217

218+
async def test_duplicate_driver_name_no_op(self):
219+
client = await self.async_rs_or_single_client(
220+
"mongodb://" + self.server.address_string,
221+
maxIdleTimeMS=1,
222+
)
223+
client.append_metadata(DriverInfo("library", "1.2", "Library Platform"))
224+
await self.check_metadata_added(client, "framework", None, None)
225+
# wait for connection to become idle
226+
await asyncio.sleep(0.005)
227+
# add same metadata again
228+
await self.check_metadata_added(client, "Framework", None, None)
229+
213230

214231
if __name__ == "__main__":
215232
unittest.main()

0 commit comments

Comments
 (0)