Skip to content

Commit 43d246f

Browse files
committed
PYTHON-2453 Add MongoDB Versioned API (#536)
Add pymongo.server_api.ServerApi and the MongoClient server_api option. Support Unified Test Format version 1.1 (serverParameters in runOnRequirements) Skip dropRole tests due to SERVER-53499. (cherry picked from commit ac2f506)
1 parent f5be1bc commit 43d246f

25 files changed

+3057
-10
lines changed

.evergreen/config.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ functions:
290290
STORAGE_ENGINE=${STORAGE_ENGINE} \
291291
DISABLE_TEST_COMMANDS=${DISABLE_TEST_COMMANDS} \
292292
ORCHESTRATION_FILE=${ORCHESTRATION_FILE} \
293+
REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \
293294
sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
294295
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
295296
- command: expansions.update
@@ -425,6 +426,7 @@ functions:
425426
AUTH=${AUTH} \
426427
SSL=${SSL} \
427428
DATA_LAKE=${DATA_LAKE} \
429+
MONGODB_API_VERSION=${MONGODB_API_VERSION} \
428430
sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
429431
430432
"run enterprise auth tests":
@@ -2023,6 +2025,19 @@ axes:
20232025
variables:
20242026
SETDEFAULTENCODING: "cp1251"
20252027

2028+
- id: requireApiVersion
2029+
display_name: "requireApiVersion"
2030+
values:
2031+
- id: "requireApiVersion1"
2032+
display_name: "requireApiVersion1"
2033+
tags: [ "requireApiVersion_tag" ]
2034+
variables:
2035+
# REQUIRE_API_VERSION is set to make drivers-evergreen-tools
2036+
# start a cluster with the requireApiVersion parameter.
2037+
REQUIRE_API_VERSION: "1"
2038+
# MONGODB_API_VERSION is the apiVersion to use in the test suite.
2039+
MONGODB_API_VERSION: "1"
2040+
20262041
buildvariants:
20272042
- matrix_name: "tests-all"
20282043
matrix_spec:
@@ -2605,6 +2620,17 @@ buildvariants:
26052620
tasks:
26062621
- name: atlas-data-lake-tests
26072622

2623+
- matrix_name: "versioned-api-tests"
2624+
matrix_spec:
2625+
platform: ubuntu-16.04
2626+
python-version: ["2.7", "3.9"]
2627+
auth: "auth"
2628+
requireApiVersion: "*"
2629+
display_name: "requireApiVersion ${python-version}"
2630+
tasks:
2631+
# Versioned API was introduced in MongoDB 4.7
2632+
- "test-latest-standalone"
2633+
26082634
- matrix_name: "ocsp-test"
26092635
matrix_spec:
26102636
platform: ubuntu-16.04

.evergreen/run-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ GREEN_FRAMEWORK=${GREEN_FRAMEWORK:-}
2626
C_EXTENSIONS=${C_EXTENSIONS:-}
2727
COVERAGE=${COVERAGE:-}
2828
COMPRESSORS=${COMPRESSORS:-}
29+
MONGODB_API_VERSION=${MONGODB_API_VERSION:-}
2930
TEST_ENCRYPTION=${TEST_ENCRYPTION:-}
3031
LIBMONGOCRYPT_URL=${LIBMONGOCRYPT_URL:-}
3132
SETDEFAULTENCODING=${SETDEFAULTENCODING:-}
@@ -35,6 +36,11 @@ if [ -n "$COMPRESSORS" ]; then
3536
export COMPRESSORS=$COMPRESSORS
3637
fi
3738

39+
if [ -n "$MONGODB_API_VERSION" ]; then
40+
export MONGODB_API_VERSION=$MONGODB_API_VERSION
41+
fi
42+
43+
3844
export JAVA_HOME=/opt/java/jdk8
3945

4046
if [ "$AUTH" != "noauth" ]; then

doc/api/pymongo/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Sub-modules:
5454
read_preferences
5555
results
5656
son_manipulator
57+
server_api
5758
uri_parser
5859
write_concern
5960
event_loggers

doc/api/pymongo/server_api.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:mod:`server_api` -- Support for MongoDB Versioned API
2+
======================================================
3+
4+
.. automodule:: pymongo.server_api
5+
:synopsis: Support for MongoDB Versioned API
6+
7+
.. autoclass:: pymongo.server_api.ServerApi
8+
:members:
9+
10+
.. autoclass:: pymongo.server_api.ServerApiVersion
11+
:members:

doc/changelog.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ Changelog
44
Changes in Version 3.12.0
55
-------------------------
66

7+
Notable improvements
8+
....................
9+
10+
- Support for MongoDB Versioned API, see :class:`~pymongo.server_api.ServerApi`.
11+
12+
Issues Resolved
13+
...............
14+
15+
See the `PyMongo 3.12 release notes in JIRA`_ for the list of resolved issues
16+
in this release.
17+
18+
.. _PyMongo 3.12 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=29594
19+
720

821
Changes in Version 3.11.1
922
-------------------------

pymongo/client_options.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def _parse_pool_options(options):
125125
event_listeners = options.get('event_listeners')
126126
appname = options.get('appname')
127127
driver = options.get('driver')
128+
server_api = options.get('server_api')
128129
compression_settings = CompressionSettings(
129130
options.get('compressors', []),
130131
options.get('zlibcompressionlevel', -1))
@@ -138,7 +139,8 @@ def _parse_pool_options(options):
138139
_EventListeners(event_listeners),
139140
appname,
140141
driver,
141-
compression_settings)
142+
compression_settings,
143+
server_api=server_api)
142144

143145

144146
class ClientOptions(object):

pymongo/client_session.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ def __init__(self, opts):
297297
def active(self):
298298
return self.state in (_TxnState.STARTING, _TxnState.IN_PROGRESS)
299299

300+
def starting(self):
301+
return self.state == _TxnState.STARTING
302+
300303
def reset(self):
301304
self.state = _TxnState.NONE
302305
self.sharded = False
@@ -762,6 +765,12 @@ def in_transaction(self):
762765
"""
763766
return self._transaction.active()
764767

768+
@property
769+
def _starting_transaction(self):
770+
"""True if this session is starting a multi-statement transaction.
771+
"""
772+
return self._transaction.starting()
773+
765774
@property
766775
def _pinned_address(self):
767776
"""The mongos address this transaction was created on."""

pymongo/common.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pymongo.compression_support import (validate_compressors,
2828
validate_zlib_compression_level)
2929
from pymongo.driver_info import DriverInfo
30+
from pymongo.server_api import ServerApi
3031
from pymongo.encryption_options import validate_auto_encryption_opts_or_none
3132
from pymongo.errors import ConfigurationError
3233
from pymongo.monitoring import _validate_event_listeners
@@ -525,6 +526,15 @@ def validate_driver_or_none(option, value):
525526
return value
526527

527528

529+
def validate_server_api_or_none(option, value):
530+
"""Validate the server_api keyword arg."""
531+
if value is None:
532+
return value
533+
if not isinstance(value, ServerApi):
534+
raise TypeError("%s must be an instance of ServerApi" % (option,))
535+
return value
536+
537+
528538
def validate_is_callable_or_none(option, value):
529539
"""Validates that 'value' is a callable."""
530540
if value is None:
@@ -640,6 +650,7 @@ def validate_tzinfo(dummy, value):
640650
NONSPEC_OPTIONS_VALIDATOR_MAP = {
641651
'connect': validate_boolean_or_string,
642652
'driver': validate_driver_or_none,
653+
'server_api': validate_server_api_or_none,
643654
'fsync': validate_boolean_or_string,
644655
'minpoolsize': validate_non_negative_integer,
645656
'socketkeepalive': validate_boolean_or_string,

pymongo/database.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,12 @@ def command(self, command, value=1, check=True,
704704
.. note:: :meth:`command` does **not** apply any custom TypeDecoders
705705
when decoding the command response.
706706
707+
.. note:: If this client has been configured to use MongoDB Versioned
708+
API (see :ref:`versioned-api-ref`), then :meth:`command` will
709+
automactically add API versioning options to the given command.
710+
Explicitly adding API versioning options in the command and
711+
declaring an API version on the client is not supported.
712+
707713
.. versionchanged:: 3.6
708714
Added ``session`` parameter.
709715

pymongo/message.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ def as_command(self, sock_info):
307307
self.name = 'explain'
308308
cmd = SON([('explain', cmd)])
309309
session = self.session
310+
sock_info.add_server_api(cmd, session)
310311
if session:
311312
session._apply_to(cmd, False, self.read_preference)
312313
# Explain does not support readConcern.
@@ -892,6 +893,7 @@ def __init__(self, database_name, command, sock_info, operation_id,
892893
self.compress = True if sock_info.compression_context else False
893894
self.op_type = op_type
894895
self.codec = codec
896+
sock_info.add_server_api(command, session)
895897

896898
def _batch_command(self, docs):
897899
namespace = self.db_name + '.$cmd'

pymongo/mongo_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,19 @@ def __init__(
498498
and automatically decrypt results. See
499499
:ref:`automatic-client-side-encryption` for an example.
500500
501+
| **Versioned API options:**
502+
| (If not set explicitly, Versioned API will not be enabled.)
503+
504+
- `server_api`: A
505+
:class:`~pymongo.server_api.ServerApi` which configures this
506+
client to use Versioned API. See :ref:`versioned-api-ref` for
507+
details.
508+
501509
.. mongodoc:: connections
502510
511+
.. versionchanged:: 3.12
512+
Added the ``server_api`` keyword argument.
513+
503514
.. versionchanged:: 3.11
504515
Added the following keyword arguments and URI options:
505516

pymongo/pool.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from pymongo.network import (command,
5959
receive_message)
6060
from pymongo.read_preferences import ReadPreference
61+
from pymongo.server_api import _add_to_command
6162
from pymongo.server_type import SERVER_TYPE
6263
from pymongo.socket_checker import SocketChecker
6364
# Always use our backport so we always have support for IP address matching
@@ -294,7 +295,7 @@ class PoolOptions(object):
294295
'__wait_queue_timeout', '__wait_queue_multiple',
295296
'__ssl_context', '__ssl_match_hostname', '__socket_keepalive',
296297
'__event_listeners', '__appname', '__driver', '__metadata',
297-
'__compression_settings')
298+
'__compression_settings', '__server_api')
298299

299300
def __init__(self, max_pool_size=MAX_POOL_SIZE,
300301
min_pool_size=MIN_POOL_SIZE,
@@ -303,8 +304,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
303304
wait_queue_multiple=None, ssl_context=None,
304305
ssl_match_hostname=True, socket_keepalive=True,
305306
event_listeners=None, appname=None, driver=None,
306-
compression_settings=None):
307-
307+
compression_settings=None, server_api=None):
308308
self.__max_pool_size = max_pool_size
309309
self.__min_pool_size = min_pool_size
310310
self.__max_idle_time_seconds = max_idle_time_seconds
@@ -319,6 +319,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
319319
self.__appname = appname
320320
self.__driver = driver
321321
self.__compression_settings = compression_settings
322+
self.__server_api = server_api
322323
self.__metadata = copy.deepcopy(_METADATA)
323324
if appname:
324325
self.__metadata['application'] = {'name': appname}
@@ -462,6 +463,12 @@ def metadata(self):
462463
"""
463464
return self.__metadata.copy()
464465

466+
@property
467+
def server_api(self):
468+
"""A pymongo.server_api.ServerApi or None.
469+
"""
470+
return self.__server_api
471+
465472

466473
def _negotiate_creds(all_credentials):
467474
"""Return one credential that needs mechanism negotiation, if any.
@@ -672,6 +679,7 @@ def command(self, dbname, spec, slave_ok=False,
672679
raise ConfigurationError(
673680
'Must be connected to MongoDB 3.4+ to use a collation.')
674681

682+
self.add_server_api(spec, session)
675683
if session:
676684
session._apply_to(spec, retryable_write, read_preference)
677685
self.send_cluster_time(spec, session, client)
@@ -861,6 +869,14 @@ def send_cluster_time(self, command, session, client):
861869
if self.max_wire_version >= 6 and client:
862870
client._send_cluster_time(command, session)
863871

872+
def add_server_api(self, command, session):
873+
"""Add server_api parameters."""
874+
if (session and session.in_transaction and
875+
not session._starting_transaction):
876+
return
877+
if self.opts.server_api:
878+
_add_to_command(command, self.opts.server_api)
879+
864880
def update_last_checkin_time(self):
865881
self.last_checkin_time = _time()
866882

0 commit comments

Comments
 (0)