Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4f4ce7f
resync spec tests
juliusgeo Sep 29, 2021
9a89ed4
add srvservicename to dictionary
juliusgeo Oct 1, 2021
ef4db92
might fix some tests
juliusgeo Oct 1, 2021
d88f1eb
found right spot to get the srvServiceName from options
juliusgeo Oct 2, 2021
fcaf49b
fix resolve_uri
juliusgeo Oct 4, 2021
3e63bd5
fix formatting
juliusgeo Oct 4, 2021
029f222
run tests without changes in uri_parser.py
juliusgeo Oct 4, 2021
9fdfb38
make srv_service_name last argument
juliusgeo Oct 4, 2021
944786e
add in plumbing to get srv_service_name to _SrvResolver
juliusgeo Oct 4, 2021
90466a4
fix _SrvResolver call so that keyword arguments are passed that way
juliusgeo Oct 4, 2021
1b48a20
get default value for srv_service_name
juliusgeo Oct 4, 2021
459f5e7
add _ to prefix srvservicename
juliusgeo Oct 4, 2021
be53d49
remove _ from default values
juliusgeo Oct 4, 2021
4a8b1d0
ressurect dead code, add note to changelog
juliusgeo Oct 4, 2021
94e44c2
change get to pop? might fix
juliusgeo Oct 5, 2021
9c93a7a
remove test that shouldn't be there
juliusgeo Oct 5, 2021
ce2f48b
go back to get
juliusgeo Oct 5, 2021
ad366dd
fix change that shouldn't have been included
juliusgeo Oct 5, 2021
241e105
add spaces around + and remove str cast
juliusgeo Oct 5, 2021
f415705
shane fixes and added plumbing to monitor.py
juliusgeo Oct 5, 2021
70dcb1f
fix use before assignment error
juliusgeo Oct 5, 2021
c148364
fix keyerror by using get
juliusgeo Oct 5, 2021
57c19be
shane fixes
juliusgeo Oct 6, 2021
d3b210f
fixed formatting of docstring
juliusgeo Oct 6, 2021
ec58a75
shane fixes, along with moving location of pop so that kwargs overrides
juliusgeo Oct 6, 2021
2cbd7ce
change it so that the behavior will always choose the kwargs over the…
juliusgeo Oct 7, 2021
891446b
add exact same URI as spec tests into our tests, tweaked the code for…
juliusgeo Oct 7, 2021
f54b120
more tweaks, went back to None instead of mongodb
juliusgeo Oct 7, 2021
e5c7e9d
fixed typo and added more testing
juliusgeo Oct 7, 2021
b9dc33a
remove part of test that is not possible to test at this point in time
juliusgeo Oct 7, 2021
8a31961
shane fixes, add more testing
juliusgeo Oct 7, 2021
e593759
Merge branch 'master' into PYTHON-2823
juliusgeo Oct 7, 2021
edec74e
shane fixes
juliusgeo Oct 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ Breaking Changes in 4.0
- The ``hint`` option is now required when using ``min`` or ``max`` queries
with :meth:`~pymongo.collection.Collection.find`.
- ``name`` is now a required argument for the :class:`pymongo.driver_info.DriverInfo` class.
- When providing a "mongodb+srv://" URI to
:class:`~pymongo.mongo_client.MongoClient` constructor you can now use the
``srvServiceName`` URI option to specify your own SRV service name.
- :meth:`~bson.son.SON.items` now returns a ``dict_items`` object rather
than a list.
- Removed :meth:`bson.son.SON.iteritems`.
Expand All @@ -160,7 +163,6 @@ Breaking Changes in 4.0
- ``MongoClient()`` now raises a :exc:`~pymongo.errors.ConfigurationError`
when more than one URI is passed into the ``hosts`` argument.


Notable improvements
....................

Expand Down
4 changes: 4 additions & 0 deletions pymongo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@
# From the driver sessions spec.
_MAX_END_SESSIONS = 10000

# Default value for srvServiceName
SRV_SERVICE_NAME = "mongodb"


def partition_node(node):
"""Split a host:port string into (host, int(port)) pair."""
Expand Down Expand Up @@ -626,6 +629,7 @@ def validate_tzinfo(dummy, value):
'w': validate_non_negative_int_or_basestring,
'wtimeoutms': validate_non_negative_integer,
'zlibcompressionlevel': validate_zlib_compression_level,
'srvservicename': validate_string
}

# Dictionary where keys are the names of URI options specific to pymongo,
Expand Down
15 changes: 14 additions & 1 deletion pymongo/mongo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,11 @@ def __init__(
a Unicode-related error occurs during BSON decoding that would
otherwise raise :exc:`UnicodeDecodeError`. Valid options include
'strict', 'replace', and 'ignore'. Defaults to 'strict'.
- ``srvServiceName`: (string) The SRV service name to use for
"mongodb+srv://" URIs. Defaults to "mongodb". Use it like so::

MongoClient("mongodb+srv://example.com/?srvServiceName=customname")


| **Write Concern options:**
| (Only set if passed. No default values.)
Expand Down Expand Up @@ -499,6 +504,7 @@ def __init__(
arguments.
The default for `uuidRepresentation` was changed from
``pythonLegacy`` to ``unspecified``.
Added the ``srvServiceName`` URI and keyword argument.

.. versionchanged:: 3.12
Added the ``server_api`` keyword argument.
Expand Down Expand Up @@ -644,6 +650,8 @@ def __init__(
dbase = None
opts = common._CaseInsensitiveDictionary()
fqdn = None
srv_service_name = keyword_opts.get("srvservicename", None)

if len([h for h in host if "/" in h]) > 1:
raise ConfigurationError("host must not contain multiple MongoDB "
"URIs")
Expand All @@ -659,7 +667,7 @@ def __init__(
keyword_opts.cased_key("connecttimeoutms"), timeout)
res = uri_parser.parse_uri(
entity, port, validate=True, warn=True, normalize=False,
connect_timeout=timeout)
connect_timeout=timeout, srv_service_name=srv_service_name)
seeds.update(res["nodelist"])
username = res["username"] or username
password = res["password"] or password
Expand Down Expand Up @@ -689,6 +697,10 @@ def __init__(

# Override connection string options with kwarg options.
opts.update(keyword_opts)

if srv_service_name is None:
srv_service_name = opts.get("srvServiceName", common.SRV_SERVICE_NAME)

# Handle security-option conflicts in combined options.
opts = _handle_security_options(opts)
# Normalize combined options.
Expand Down Expand Up @@ -728,6 +740,7 @@ def __init__(
server_selector=options.server_selector,
heartbeat_frequency=options.heartbeat_frequency,
fqdn=fqdn,
srv_service_name=srv_service_name,
direct_connection=options.direct_connection,
load_balanced=options.load_balanced,
)
Expand Down
6 changes: 5 additions & 1 deletion pymongo/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ def __init__(self, topology, topology_settings):
self._settings = topology_settings
self._seedlist = self._settings._seeds
self._fqdn = self._settings.fqdn
self._srv_service_name = self._settings._srv_service_name

def _run(self):
seedlist = self._get_seedlist()
Expand All @@ -316,7 +317,10 @@ def _get_seedlist(self):
Returns a list of ServerDescriptions.
"""
try:
seedlist, ttl = _SrvResolver(self._fqdn).get_hosts_and_min_ttl()
resolver = _SrvResolver(self._fqdn,
self._settings.pool_options.connect_timeout,
self._srv_service_name)
seedlist, ttl = resolver.get_hosts_and_min_ttl()
if len(seedlist) == 0:
# As per the spec: this should be treated as a failure.
raise Exception
Expand Down
2 changes: 2 additions & 0 deletions pymongo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __init__(self,
heartbeat_frequency=common.HEARTBEAT_FREQUENCY,
server_selector=None,
fqdn=None,
srv_service_name=common.SRV_SERVICE_NAME,
direct_connection=False,
load_balanced=None):
"""Represent MongoClient's configuration.
Expand All @@ -60,6 +61,7 @@ def __init__(self,
self._server_selection_timeout = server_selection_timeout
self._server_selector = server_selector
self._fqdn = fqdn
self._srv_service_name = srv_service_name
self._heartbeat_frequency = heartbeat_frequency

self._direct = direct_connection
Expand Down
8 changes: 5 additions & 3 deletions pymongo/srv_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ def _resolve(*args, **kwargs):
"Did you mean to use 'mongodb://'?")

class _SrvResolver(object):
def __init__(self, fqdn, connect_timeout=None):
def __init__(self, fqdn,
connect_timeout, srv_service_name):
self.__fqdn = fqdn
self.__srv = srv_service_name
self.__connect_timeout = connect_timeout or CONNECT_TIMEOUT

# Validate the fully qualified domain name.
Expand Down Expand Up @@ -83,8 +85,8 @@ def get_options(self):

def _resolve_uri(self, encapsulate_errors):
try:
results = _resolve('_mongodb._tcp.' + self.__fqdn, 'SRV',
lifetime=self.__connect_timeout)
results = _resolve('_' + self.__srv + '._tcp.' + self.__fqdn,
'SRV', lifetime=self.__connect_timeout)
except Exception as exc:
if not encapsulate_errors:
# Raise the original error.
Expand Down
12 changes: 10 additions & 2 deletions pymongo/uri_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from urllib.parse import unquote_plus

from pymongo.common import (
SRV_SERVICE_NAME,
get_validated_options, INTERNAL_URI_OPTION_NAME_MAP,
URI_OPTIONS_DEPRECATION_MAP, _CaseInsensitiveDictionary)
from pymongo.errors import ConfigurationError, InvalidURI
Expand Down Expand Up @@ -373,7 +374,7 @@ def _check_options(nodes, options):


def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
normalize=True, connect_timeout=None):
normalize=True, connect_timeout=None, srv_service_name=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the docstring below to reflect the new srv_service_name param.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"""Parse and validate a MongoDB URI.

Returns a dict of the form::
Expand Down Expand Up @@ -405,6 +406,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
to their internally-used names. Default: ``True``.
- `connect_timeout` (optional): The maximum time in milliseconds to
wait for a response from the DNS server.
- 'srv_service_name` (optional): A custom SRV service name

.. versionchanged:: 3.9
Added the ``normalize`` parameter.
Expand Down Expand Up @@ -468,6 +470,9 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
if opts:
options.update(split_options(opts, validate, warn, normalize))

if srv_service_name is None:
srv_service_name = options.get("srvServiceName", SRV_SERVICE_NAME)

if '@' in host_part:
userinfo, _, hosts = host_part.rpartition('@')
user, passwd = parse_userinfo(userinfo)
Expand Down Expand Up @@ -499,7 +504,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
# Use the connection timeout. connectTimeoutMS passed as a keyword
# argument overrides the same option passed in the connection string.
connect_timeout = connect_timeout or options.get("connectTimeoutMS")
dns_resolver = _SrvResolver(fqdn, connect_timeout=connect_timeout)
dns_resolver = _SrvResolver(fqdn, connect_timeout, srv_service_name)
nodes = dns_resolver.get_hosts()
dns_options = dns_resolver.get_options()
if dns_options:
Expand All @@ -514,6 +519,9 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
options[opt] = val
if "tls" not in options and "ssl" not in options:
options["tls"] = True if validate else 'true'
elif not is_srv and options.get("srvServiceName") is not None:
raise ConfigurationError("The srvServiceName option is only allowed "
"with 'mongodb+srv://' URIs")
else:
nodes = split_hosts(hosts, default_port=default_port)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"uri": "mongodb+srv://test4.test.build.10gen.cc/?loadBalanced=true",
"seeds": [],
"hosts": [],
"error": true,
"comment": "Should fail because no SRV records are present for this URI."
}
16 changes: 16 additions & 0 deletions test/srv_seedlist/replica-set/srv-service-name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"uri": "mongodb+srv://test22.test.build.10gen.cc/?srvServiceName=customname",
"seeds": [
"localhost.test.build.10gen.cc:27017",
"localhost.test.build.10gen.cc:27018"
],
"hosts": [
"localhost:27017",
"localhost:27018",
"localhost:27019"
],
"options": {
"ssl": true,
"srvServiceName": "customname"
}
}
21 changes: 21 additions & 0 deletions test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,27 @@ def test_network_error_message(self):
with self.assertRaisesRegex(AutoReconnect, expected):
client.pymongo_test.test.find_one({})

@unittest.skipUnless(
_HAVE_DNSPYTHON, "DNS-related tests need dnspython to be installed")
def test_service_name_from_kwargs(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs:

    @unittest.skipUnless(
        _HAVE_DNSPYTHON, "DNS-related tests need dnspython to be installed")
    def test_service_name_from_kwargs(self):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

client = MongoClient(
'mongodb+srv://user:[email protected]',
srvServiceName='customname', connect=False)
self.assertEqual(client._topology_settings._srv_service_name,
'customname')
client = MongoClient(
'mongodb+srv://user:[email protected]'
'/?srvServiceName=shouldbeoverriden',
srvServiceName='customname', connect=False)
self.assertEqual(client._topology_settings._srv_service_name,
'customname')
client = MongoClient(
'mongodb+srv://user:[email protected]'
'/?srvServiceName=customname',
connect=False)
self.assertEqual(client._topology_settings._srv_service_name,
'customname')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a test which actually checks that the kwarg overrides the URI. Like this:

        client = MongoClient(
             'mongodb+srv://user:[email protected]'
             '/?srvServiceName=shouldBeOverridden',
             srvServiceName='customname',
             connect=False)
        self.assertEqual(client._topology_settings._srv_service_name,
                         'customname')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.



class TestExhaustCursor(IntegrationTest):
"""Test that clients properly handle errors from exhaust cursors."""
Expand Down
24 changes: 24 additions & 0 deletions test/uri_options/srv-service-name-option.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"tests": [
{
"description": "SRV URI with custom srvServiceName",
"uri": "mongodb+srv://test22.test.build.10gen.cc/?srvServiceName=customname",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"srvServiceName": "customname"
}
},
{
"description": "Non-SRV URI with custom srvServiceName",
"uri": "mongodb://example.com/?srvServiceName=customname",
"valid": false,
"warning": true,
"hosts": null,
"auth": null,
"options": {}
}
]
}