PYTHON-1564 Add DriverInfo to handshake metadata
Allow drivers that wrap PyMongo to add their info to the handshake
metadata, using a "driver" option like:
MongoClient(driver=DriverInfo("MyDriver", "1.2.3"))
The DriverInfo is appended to PyMongo's own metadata.
This commit is contained in:
parent
981e39281f
commit
c63c068611
6
doc/api/pymongo/driver_info.rst
Normal file
6
doc/api/pymongo/driver_info.rst
Normal file
@ -0,0 +1,6 @@
|
||||
:mod:`driver_info`
|
||||
==================
|
||||
|
||||
.. automodule:: pymongo.driver_info
|
||||
|
||||
.. autoclass:: pymongo.driver_info.DriverInfo(name=None, version=None, platform=None)
|
||||
@ -31,25 +31,26 @@ Sub-modules:
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
database
|
||||
bulk
|
||||
change_stream
|
||||
client_session
|
||||
collation
|
||||
collection
|
||||
command_cursor
|
||||
cursor
|
||||
bulk
|
||||
cursor_manager
|
||||
database
|
||||
driver_info
|
||||
errors
|
||||
message
|
||||
monitoring
|
||||
mongo_client
|
||||
mongo_replica_set_client
|
||||
monitoring
|
||||
operations
|
||||
pool
|
||||
read_concern
|
||||
read_preferences
|
||||
results
|
||||
son_manipulator
|
||||
cursor_manager
|
||||
uri_parser
|
||||
write_concern
|
||||
|
||||
@ -121,6 +121,7 @@ def _parse_pool_options(options):
|
||||
wait_queue_multiple = options.get('waitqueuemultiple')
|
||||
event_listeners = options.get('event_listeners')
|
||||
appname = options.get('appname')
|
||||
driver = options.get('driver')
|
||||
compression_settings = CompressionSettings(
|
||||
options.get('compressors', []),
|
||||
options.get('zlibcompressionlevel', -1))
|
||||
@ -133,6 +134,7 @@ def _parse_pool_options(options):
|
||||
ssl_context, ssl_match_hostname, socket_keepalive,
|
||||
_EventListeners(event_listeners),
|
||||
appname,
|
||||
driver,
|
||||
compression_settings)
|
||||
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
|
||||
"""Functions and classes common to multiple pymongo modules."""
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import warnings
|
||||
|
||||
@ -28,6 +27,7 @@ from bson.raw_bson import RawBSONDocument
|
||||
from pymongo.auth import MECHANISMS
|
||||
from pymongo.compression_support import (validate_compressors,
|
||||
validate_zlib_compression_level)
|
||||
from pymongo.driver_info import DriverInfo
|
||||
from pymongo.errors import ConfigurationError
|
||||
from pymongo.monitoring import _validate_event_listeners
|
||||
from pymongo.read_concern import ReadConcern
|
||||
@ -464,6 +464,15 @@ def validate_appname_or_none(option, value):
|
||||
return value
|
||||
|
||||
|
||||
def validate_driver_or_none(option, value):
|
||||
"""Validate the driver keyword arg."""
|
||||
if value is None:
|
||||
return value
|
||||
if not isinstance(value, DriverInfo):
|
||||
raise TypeError("%s must be an instance of DriverInfo" % (option,))
|
||||
return value
|
||||
|
||||
|
||||
def validate_ok_for_replace(replacement):
|
||||
"""Validate a replacement document."""
|
||||
validate_is_mapping("replacement", replacement)
|
||||
@ -539,6 +548,7 @@ URI_VALIDATORS = {
|
||||
'connect': validate_boolean_or_string,
|
||||
'minpoolsize': validate_non_negative_integer,
|
||||
'appname': validate_appname_or_none,
|
||||
'driver': validate_driver_or_none,
|
||||
'unicode_decode_error_handler': validate_unicode_decode_error_handler,
|
||||
'retrywrites': validate_boolean_or_string,
|
||||
'compressors': validate_compressors,
|
||||
|
||||
39
pymongo/driver_info.py
Normal file
39
pymongo/driver_info.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright 2018-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.
|
||||
|
||||
"""Advanced options for MongoDB drivers implemented on top of PyMongo."""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from bson.py3compat import string_type
|
||||
|
||||
|
||||
class DriverInfo(namedtuple('DriverInfo', ['name', 'version', 'platform'])):
|
||||
"""Info about a driver wrapping PyMongo.
|
||||
|
||||
The MongoDB server logs PyMongo's name, version, and platform whenever
|
||||
PyMongo establishes a connection. A driver implemented on top of PyMongo
|
||||
can add its own info to this log message. Initialize with three strings
|
||||
like 'MyDriver', '1.2.3', 'some platform info'. Any of these strings may be
|
||||
None to accept PyMongo's default.
|
||||
"""
|
||||
def __new__(cls, name=None, version=None, platform=None):
|
||||
self = super(DriverInfo, cls).__new__(cls, name, version, platform)
|
||||
for name, value in self._asdict().items():
|
||||
if value is not None and not isinstance(value, string_type):
|
||||
raise TypeError("Wrong type for DriverInfo %s option, value "
|
||||
"must be an instance of %s" % (
|
||||
name, string_type.__name__))
|
||||
|
||||
return self
|
||||
@ -224,6 +224,10 @@ class MongoClient(common.BaseObject):
|
||||
print this value in the server log upon establishing each
|
||||
connection. It is also recorded in the slow query log and
|
||||
profile collections.
|
||||
- `driver`: (pair or None) A driver implemented on top of PyMongo can
|
||||
pass a :class:`~pymongo.driver_info.DriverInfo` to add its name,
|
||||
version, and platform to the message printed in the server log when
|
||||
establishing a connection.
|
||||
- `event_listeners`: a list or tuple of event listeners. See
|
||||
:mod:`~pymongo.monitoring` for details.
|
||||
- `retryWrites`: (boolean) Whether supported write operations
|
||||
@ -400,6 +404,9 @@ class MongoClient(common.BaseObject):
|
||||
Added support for mongodb+srv:// URIs.
|
||||
Added the ``retryWrites`` keyword argument and URI option.
|
||||
|
||||
.. versionchanged:: 3.7
|
||||
Added the ``driver`` keyword argument.
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
Add ``username`` and ``password`` options. Document the
|
||||
``authSource``, ``authMechanism``, and ``authMechanismProperties ``
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
# permissions and limitations under the License.
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
@ -280,7 +281,7 @@ class PoolOptions(object):
|
||||
'__connect_timeout', '__socket_timeout',
|
||||
'__wait_queue_timeout', '__wait_queue_multiple',
|
||||
'__ssl_context', '__ssl_match_hostname', '__socket_keepalive',
|
||||
'__event_listeners', '__appname', '__metadata',
|
||||
'__event_listeners', '__appname', '__driver', '__metadata',
|
||||
'__compression_settings')
|
||||
|
||||
def __init__(self, max_pool_size=100, min_pool_size=0,
|
||||
@ -288,7 +289,7 @@ class PoolOptions(object):
|
||||
socket_timeout=None, wait_queue_timeout=None,
|
||||
wait_queue_multiple=None, ssl_context=None,
|
||||
ssl_match_hostname=True, socket_keepalive=True,
|
||||
event_listeners=None, appname=None,
|
||||
event_listeners=None, appname=None, driver=None,
|
||||
compression_settings=None):
|
||||
|
||||
self.__max_pool_size = max_pool_size
|
||||
@ -303,11 +304,31 @@ class PoolOptions(object):
|
||||
self.__socket_keepalive = socket_keepalive
|
||||
self.__event_listeners = event_listeners
|
||||
self.__appname = appname
|
||||
self.__driver = driver
|
||||
self.__compression_settings = compression_settings
|
||||
self.__metadata = _METADATA.copy()
|
||||
self.__metadata = copy.deepcopy(_METADATA)
|
||||
if appname:
|
||||
self.__metadata['application'] = {'name': appname}
|
||||
|
||||
# Combine the "driver" MongoClient option with PyMongo's info, like:
|
||||
# {
|
||||
# 'driver': {
|
||||
# 'name': 'PyMongo|MyDriver',
|
||||
# 'version': '3.7.0|1.2.3',
|
||||
# },
|
||||
# 'platform': 'CPython 3.6.0|MyPlatform'
|
||||
# }
|
||||
if driver:
|
||||
if driver.name:
|
||||
self.__metadata['driver']['name'] = "%s|%s" % (
|
||||
_METADATA['driver']['name'], driver.name)
|
||||
if driver.version:
|
||||
self.__metadata['driver']['version'] = "%s|%s" % (
|
||||
_METADATA['driver']['version'], driver.version)
|
||||
if driver.platform:
|
||||
self.__metadata['platform'] = "%s|%s" % (
|
||||
_METADATA['platform'], driver.platform)
|
||||
|
||||
@property
|
||||
def max_pool_size(self):
|
||||
"""The maximum allowable number of concurrent connections to each
|
||||
@ -395,6 +416,12 @@ class PoolOptions(object):
|
||||
"""
|
||||
return self.__appname
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
"""Driver name and version, for sending with ismaster in handshake.
|
||||
"""
|
||||
return self.__driver
|
||||
|
||||
@property
|
||||
def compression_settings(self):
|
||||
return self.__compression_settings
|
||||
|
||||
@ -551,7 +551,8 @@ class Topology(object):
|
||||
ssl_context=options.ssl_context,
|
||||
ssl_match_hostname=options.ssl_match_hostname,
|
||||
event_listeners=options.event_listeners,
|
||||
appname=options.appname)
|
||||
appname=options.appname,
|
||||
driver=options.driver)
|
||||
|
||||
return self._settings.pool_class(address, monitor_pool_options,
|
||||
handshake=False)
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
"""Test the mongo_client module."""
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import datetime
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
@ -50,6 +50,7 @@ from pymongo.errors import (AutoReconnect,
|
||||
from pymongo.monitoring import (ServerHeartbeatListener,
|
||||
ServerHeartbeatStartedEvent)
|
||||
from pymongo.mongo_client import MongoClient
|
||||
from pymongo.driver_info import DriverInfo
|
||||
from pymongo.pool import SocketInfo, _METADATA
|
||||
from pymongo.read_preferences import ReadPreference
|
||||
from pymongo.server_selectors import (any_server_selector,
|
||||
@ -214,7 +215,7 @@ class ClientUnitTest(unittest.TestCase):
|
||||
self.assertEqual(c.read_preference, ReadPreference.NEAREST)
|
||||
|
||||
def test_metadata(self):
|
||||
metadata = _METADATA.copy()
|
||||
metadata = copy.deepcopy(_METADATA)
|
||||
metadata['application'] = {'name': 'foobar'}
|
||||
client = MongoClient(
|
||||
"mongodb://foo:27017/?appname=foobar&connect=false")
|
||||
@ -226,6 +227,25 @@ class ClientUnitTest(unittest.TestCase):
|
||||
# No error
|
||||
MongoClient(appname='x' * 128)
|
||||
self.assertRaises(ValueError, MongoClient, appname='x' * 129)
|
||||
# Bad "driver" options.
|
||||
self.assertRaises(TypeError, DriverInfo, 'Foo', 1, 'a')
|
||||
self.assertRaises(TypeError, MongoClient, driver=1)
|
||||
self.assertRaises(TypeError, MongoClient, driver='abc')
|
||||
self.assertRaises(TypeError, MongoClient, driver=('Foo', '1', 'a'))
|
||||
# Test appending to driver info.
|
||||
metadata['driver']['name'] = 'PyMongo|FooDriver'
|
||||
metadata['driver']['version'] = '%s|1.2.3' % (
|
||||
_METADATA['driver']['version'],)
|
||||
client = MongoClient('foo', 27017, appname='foobar',
|
||||
driver=DriverInfo('FooDriver', '1.2.3', None), connect=False)
|
||||
options = client._MongoClient__options
|
||||
self.assertEqual(options.pool_options.metadata, metadata)
|
||||
metadata['platform'] = '%s|FooPlatform' % (
|
||||
_METADATA['platform'],)
|
||||
client = MongoClient('foo', 27017, appname='foobar',
|
||||
driver=DriverInfo('FooDriver', '1.2.3', 'FooPlatform'), connect=False)
|
||||
options = client._MongoClient__options
|
||||
self.assertEqual(options.pool_options.metadata, metadata)
|
||||
|
||||
def test_kwargs_codec_options(self):
|
||||
# Ensure codec options are passed in correctly
|
||||
|
||||
Loading…
Reference in New Issue
Block a user