-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PYTHON-3636 MongoClient should perform SRV resolution lazily #2191
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small clarity tweaks and async-specific changes, but great start!
Co-authored-by: Noah Stapp <[email protected]>
pymongo/asynchronous/mongo_client.py
Outdated
@@ -58,10 +59,11 @@ | |||
cast, | |||
) | |||
|
|||
import pymongo.asynchronous.uri_parser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded import since uri_parser
is also imported below.
pymongo/asynchronous/mongo_client.py
Outdated
from bson.codec_options import DEFAULT_CODEC_OPTIONS, CodecOptions, TypeRegistry | ||
from bson.timestamp import Timestamp | ||
from pymongo import _csot, common, helpers_shared, periodic_executor, uri_parser | ||
from pymongo.asynchronous import client_session, database | ||
from pymongo import _csot, common, helpers_shared, periodic_executor, uri_parser_shared |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded import since uri_parser_shared
is also imported from below.
pymongo/asynchronous/srv_resolver.py
Outdated
else: | ||
from dns import asyncresolver | ||
|
||
return await asyncresolver.resolve(*args, **kwargs) # type:ignore[return-value] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to check if resolver.resolve
exists in the async case. If it doesn't, that means the version of dnspython
in use is too old for async support and we need to throw an error.
pymongo/asynchronous/uri_parser.py
Outdated
return result | ||
|
||
|
||
def _validate_uri( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method does no network IO, can it be moved to uri_parser_shared.py
to reduce code duplication?
Co-authored-by: Noah Stapp <[email protected]>
Co-authored-by: Noah Stapp <[email protected]>
Co-authored-by: Noah Stapp <[email protected]>
…ver into PYTHON-3636
pymongo/synchronous/uri_parser.py
Outdated
} | ||
|
||
|
||
if __name__ == "__main__": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part should be moved back to pymongo/uri_parser.py
.
pymongo/asynchronous/mongo_client.py
Outdated
if hasattr(self, "_topology"): | ||
return hash(self._topology) | ||
else: | ||
raise InvalidOperation("Cannot hash client until it is connected") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems odd to disallow comparison and hashing without "connecting" first. I wonder if there's a better way to define these without relying on SRV or TXT resolution. Like could we compare some subset of MongoClient arguments?
pymongo/asynchronous/mongo_client.py
Outdated
] | ||
] | ||
else: | ||
options = ["host={self._host}", "port={self._port}"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not safe. self._host
is the entire URI so doing this would leak the user credentials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, i see, what should I use in the repr here instead then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about this?:
AsyncMongoClient(host='mongodb+srv://fqdn', ...
@@ -1911,28 +1911,37 @@ async def test_service_name_from_kwargs(self): | |||
srvServiceName="customname", | |||
connect=False, | |||
) | |||
await client.aconnect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these open/close calls still needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes because these are srv uris
pymongo/srv_resolver.py
Outdated
ttl = rrset.ttl if rrset else 0 | ||
return nodes, ttl | ||
__doc__ = original_doc | ||
__all__ = ["maybe_decode", "_SrvResolver"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for checking, let's remove it.
print(exc) # noqa: T201 | ||
sys.exit(0) | ||
from pymongo.synchronous.uri_parser import * # noqa: F403 | ||
from pymongo.synchronous.uri_parser import __doc__ as original_doc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By __doc__
I mean a module level docstring like:
"""Tools to parse and validate a MongoDB URI."""
pymongo/asynchronous/mongo_client.py
Outdated
return NotImplemented | ||
|
||
def __ne__(self, other: Any) -> bool: | ||
return not self == other | ||
|
||
def __hash__(self) -> int: | ||
return hash(self._topology) | ||
if self._topology is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to address hashing and equality. It's broken to change the hash of an object since it means that a single client could be added twice to a dictionary or set. The hash (and eq) methods need to use a single deterministic approach:
set = {}
c = AsyncMongoClient('mongodb+srv://...')
set.add(c)
await c.aconnect()
set.add(c)
assert len(set) == 1
This means we need to use the eq_props() approach in all cases.
pymongo/asynchronous/mongo_client.py
Outdated
@@ -750,6 +755,9 @@ def __init__( | |||
port = self.PORT | |||
if not isinstance(port, int): | |||
raise TypeError(f"port must be an instance of int, not {type(port)}") | |||
self._host = host | |||
self._port = port | |||
self._topology: Optional[Topology] = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding assert self._topology is not None
so many times feels a bit off since it adds runtime overhead for a static typing issue. What if we do this instead?:
self._topology: Topology = None # type: ignore[assignment]
pymongo/asynchronous/mongo_client.py
Outdated
self._kill_cursors_executor.join(), # type: ignore[func-returns-value] | ||
return_exceptions=True, | ||
) | ||
if self._topology is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than indenting this code (which causes code churn) how about we do this?:
if self._topology is not None:
return
session_ids = self._topology.pop_all_sessions()
...
) | ||
with self.assertRaisesRegex(ConfigurationError, "Invalid URI host: mongodb is not"): | ||
client = self.simple_client("mongodb+srv://mongodb") | ||
await client.aconnect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior change is important to call out in the changelog and jira ticket.
pymongo/asynchronous/mongo_client.py
Outdated
"%s:%d" % (host, port) if port is not None else host | ||
for host, port in self._topology_settings.seeds | ||
if self._topology is None: | ||
options = self._resolve_srv_info["seeds"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have tests for this? If not could we add some? I'd like to see the behavior difference in repr().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Nice work!
https://jira.mongodb.org/browse/PYTHON-3636