Compare commits

...

4 Commits

Author SHA1 Message Date
Shane Harvey
eb9e036b72
BUMP 4.0.1 (#811) 2021-12-07 14:32:49 -08:00
Julius Park
f0d1563c1f PYTHON-3028 $regex as a field name does not allow for non-string values (#807)
(cherry picked from commit 70f7fe7542)
2021-12-06 18:06:47 -08:00
Shane Harvey
fd9a8bf269 PYTHON-3033 Fix typo in uuid docs (#808)
(cherry picked from commit 44853ea9c3)
2021-12-06 11:27:10 -08:00
Shane Harvey
d715f5844d PYTHON-3027 Fix server selection when topology type is Unknown (#806)
(cherry picked from commit 5ec4e6cc4c)
2021-12-02 13:46:26 -08:00
12 changed files with 136 additions and 12 deletions

View File

@ -508,7 +508,7 @@ def object_hook(dct, json_options=DEFAULT_JSON_OPTIONS):
def _parse_legacy_regex(doc): def _parse_legacy_regex(doc):
pattern = doc["$regex"] pattern = doc["$regex"]
# Check if this is the $regex query operator. # Check if this is the $regex query operator.
if isinstance(pattern, Regex): if not isinstance(pattern, (str, bytes)):
return doc return doc
flags = 0 flags = 0
# PyMongo always adds $options but some other tools may not. # PyMongo always adds $options but some other tools may not.

View File

@ -1,6 +1,27 @@
Changelog Changelog
========= =========
Changes in Version 4.0.1
-------------------------
Issues Resolved
...............
Version 4.0.1 fixes a number of bugs:
- Fixed a bug that prevented :meth:`bson.json_util.loads` from
decoding a document with a non-string "$regex" field (`PYTHON-3028`_).
- Fixed a bug where a client may select a hidden/ghost or not yet initialized
replica set member leading to unexpected "connection pool paused" errors
(`PYTHON-3027`_).
See the `PyMongo 4.0.1 release notes in JIRA`_ for the list of resolved issues
in this release.
.. _PYTHON-3027: https://jira.mongodb.org/browse/PYTHON-3027
.. _PYTHON-3028: https://jira.mongodb.org/browse/PYTHON-3028
.. _PyMongo 4.0.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=32504
Changes in Version 4.0 Changes in Version 4.0
---------------------- ----------------------

View File

@ -1,3 +1,4 @@
.. _handling-uuid-data-example: .. _handling-uuid-data-example:
Handling UUID Data Handling UUID Data
@ -12,7 +13,7 @@ to MongoDB and retrieve them as native :class:`uuid.UUID` objects::
from uuid import uuid4 from uuid import uuid4
# use the 'standard' representation for cross-language compatibility. # use the 'standard' representation for cross-language compatibility.
client = MongoClient(uuid_representation=UuidRepresentation.STANDARD) client = MongoClient(uuidRepresentation='standard')
collection = client.get_database('uuid_db').get_collection('uuid_coll') collection = client.get_database('uuid_db').get_collection('uuid_coll')
# remove all documents from collection # remove all documents from collection

View File

@ -53,7 +53,7 @@ TEXT = "text"
.. _text index: http://docs.mongodb.org/manual/core/index-text/ .. _text index: http://docs.mongodb.org/manual/core/index-text/
""" """
version_tuple = (4, 0, 1, '.dev0') version_tuple = (4, 0, 1)
def get_version_string(): def get_version_string():
if isinstance(version_tuple[-1], str): if isinstance(version_tuple[-1], str):

View File

@ -325,9 +325,9 @@ class MongoClient(common.BaseObject):
speed. 9 is best compression. Defaults to -1. speed. 9 is best compression. Defaults to -1.
- `uuidRepresentation`: The BSON representation to use when encoding - `uuidRepresentation`: The BSON representation to use when encoding
from and decoding to instances of :class:`~uuid.UUID`. Valid from and decoding to instances of :class:`~uuid.UUID`. Valid
values are `pythonLegacy`, `javaLegacy`, `csharpLegacy`, `standard` values are the strings: "standard", "pythonLegacy", "javaLegacy",
and `unspecified` (the default). New applications "csharpLegacy", and "unspecified" (the default). New applications
should consider setting this to `standard` for cross language should consider setting this to "standard" for cross language
compatibility. See :ref:`handling-uuid-data-example` for details. compatibility. See :ref:`handling-uuid-data-example` for details.
- `unicode_decode_error_handler`: The error handler to apply when - `unicode_decode_error_handler`: The error handler to apply when
a Unicode-related error occurs during BSON decoding that would a Unicode-related error occurs during BSON decoding that would

View File

@ -263,9 +263,10 @@ class TopologyDescription(object):
selector.min_wire_version, selector.min_wire_version,
common_wv)) common_wv))
if self.topology_type in (TOPOLOGY_TYPE.Single, if self.topology_type == TOPOLOGY_TYPE.Unknown:
TOPOLOGY_TYPE.LoadBalanced, return []
TOPOLOGY_TYPE.Unknown): elif self.topology_type in (TOPOLOGY_TYPE.Single,
TOPOLOGY_TYPE.LoadBalanced):
# Ignore selectors for standalone and load balancer mode. # Ignore selectors for standalone and load balancer mode.
return self.known_servers return self.known_servers
elif address: elif address:

View File

@ -36,7 +36,7 @@ except ImportError:
except ImportError: except ImportError:
_HAVE_SPHINX = False _HAVE_SPHINX = False
version = "4.0.1.dev0" version = "4.0.1"
f = open("README.rst") f = open("README.rst")
try: try:

View File

@ -0,0 +1,52 @@
# Copyright 2021-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test connections to RSGhost nodes."""
import datetime
from mockupdb import going, MockupDB
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError
import unittest
class TestRSGhost(unittest.TestCase):
def test_rsghost(self):
rsother_response = {
'ok': 1.0, 'ismaster': False, 'secondary': False,
'info': 'Does not have a valid replica set config',
'isreplicaset': True, 'maxBsonObjectSize': 16777216,
'maxMessageSizeBytes': 48000000, 'maxWriteBatchSize': 100000,
'localTime': datetime.datetime(2021, 11, 30, 0, 53, 4, 99000),
'logicalSessionTimeoutMinutes': 30, 'connectionId': 3,
'minWireVersion': 0, 'maxWireVersion': 15, 'readOnly': False}
server = MockupDB(auto_ismaster=rsother_response)
server.run()
self.addCleanup(server.stop)
# Default auto discovery yields a server selection timeout.
with MongoClient(server.uri, serverSelectionTimeoutMS=250) as client:
with self.assertRaises(ServerSelectionTimeoutError):
client.test.command('ping')
# Direct connection succeeds.
with MongoClient(server.uri, directConnection=True) as client:
with going(client.test.command, 'ping'):
request = server.receives(ping=1)
request.reply()
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,18 @@
{
"topology_description": {
"type": "Unknown",
"servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSGhost"
}
]
},
"operation": "read",
"read_preference": {
"mode": "Nearest"
},
"suitable_servers": [],
"in_latency_window": []
}

View File

@ -0,0 +1,18 @@
{
"topology_description": {
"type": "Unknown",
"servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSGhost"
}
]
},
"operation": "write",
"read_preference": {
"mode": "Nearest"
},
"suitable_servers": [],
"in_latency_window": []
}

View File

@ -270,6 +270,15 @@ class TestJsonUtil(unittest.TestCase):
json_util.dumps(Regex('.*', re.M | re.X), json_util.dumps(Regex('.*', re.M | re.X),
json_options=LEGACY_JSON_OPTIONS)) json_options=LEGACY_JSON_OPTIONS))
def test_regex_validation(self):
non_str_types = [10, {}, []]
docs = [{"$regex": i} for i in non_str_types]
for doc in docs:
self.assertEqual(doc, json_util.loads(json.dumps(doc)))
doc = {"$regex": ""}
self.assertIsInstance(json_util.loads(json.dumps(doc)), Regex)
def test_minkey(self): def test_minkey(self):
self.round_trip({"m": MinKey()}) self.round_trip({"m": MinKey()})

View File

@ -63,7 +63,7 @@ def make_server_description(server, hosts):
return ServerDescription(clean_node(server['address']), Hello({})) return ServerDescription(clean_node(server['address']), Hello({}))
hello_response = {'ok': True, 'hosts': hosts} hello_response = {'ok': True, 'hosts': hosts}
if server_type != "Standalone" and server_type != "Mongos": if server_type not in ("Standalone", "Mongos", "RSGhost"):
hello_response['setName'] = "rs" hello_response['setName'] = "rs"
if server_type == "RSPrimary": if server_type == "RSPrimary":
@ -72,6 +72,10 @@ def make_server_description(server, hosts):
hello_response['secondary'] = True hello_response['secondary'] = True
elif server_type == "Mongos": elif server_type == "Mongos":
hello_response['msg'] = 'isdbgrid' hello_response['msg'] = 'isdbgrid'
elif server_type == "RSGhost":
hello_response['isreplicaset'] = True
elif server_type == "RSArbiter":
hello_response['arbiterOnly'] = True
hello_response['lastWrite'] = { hello_response['lastWrite'] = {
'lastWriteDate': make_last_write_date(server) 'lastWriteDate': make_last_write_date(server)
@ -149,7 +153,7 @@ def create_topology(scenario_def, **kwargs):
# Assert that descriptions match # Assert that descriptions match
assert (scenario_def['topology_description']['type'] == assert (scenario_def['topology_description']['type'] ==
topology.description.topology_type_name) topology.description.topology_type_name), topology.description.topology_type_name
return topology return topology