Skip to content

Commit dfcf2cc

Browse files
committed
PYTHON-2453 Add MongoDB Versioned API
1 parent c673d8b commit dfcf2cc

File tree

9 files changed

+114
-4
lines changed

9 files changed

+114
-4
lines changed

pymongo/bulk.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ def _execute_command(self, generator, write_concern, session,
296296
run.op_type, self.collection.codec_options)
297297

298298
while run.idx_offset < len(run.ops):
299+
sock_info.add_server_api(cmd, session)
299300
if session:
300301
# Start a new retryable write unless one was already
301302
# started for this command.

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
@@ -528,6 +529,15 @@ def validate_driver_or_none(option, value):
528529
return value
529530

530531

532+
def validate_server_api_or_none(option, value):
533+
"""Validate the server_api keyword arg."""
534+
if value is None:
535+
return value
536+
if not isinstance(value, ServerApi):
537+
raise TypeError("%s must be an instance of ServerApi" % (option,))
538+
return value
539+
540+
531541
def validate_is_callable_or_none(option, value):
532542
"""Validates that 'value' is a callable."""
533543
if value is None:
@@ -643,6 +653,7 @@ def validate_tzinfo(dummy, value):
643653
NONSPEC_OPTIONS_VALIDATOR_MAP = {
644654
'connect': validate_boolean_or_string,
645655
'driver': validate_driver_or_none,
656+
'server_api': validate_server_api_or_none,
646657
'fsync': validate_boolean_or_string,
647658
'minpoolsize': validate_non_negative_integer,
648659
'socketkeepalive': validate_boolean_or_string,

pymongo/message.py

Lines changed: 1 addition & 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.

pymongo/mongo_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,18 @@ 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.
507+
501508
.. mongodoc:: connections
502509
510+
.. versionchanged:: 3.12
511+
Added the ``server_api`` keyword argument.
512+
503513
.. versionchanged:: 3.11
504514
Added the following keyword arguments and URI options:
505515

pymongo/pool.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from pymongo.network import (command,
6060
receive_message)
6161
from pymongo.read_preferences import ReadPreference
62+
from pymongo.server_api import _add_to_command
6263
from pymongo.server_type import SERVER_TYPE
6364
from pymongo.socket_checker import SocketChecker
6465
# Always use our backport so we always have support for IP address matching
@@ -309,7 +310,7 @@ class PoolOptions(object):
309310
'__wait_queue_timeout', '__wait_queue_multiple',
310311
'__ssl_context', '__ssl_match_hostname', '__socket_keepalive',
311312
'__event_listeners', '__appname', '__driver', '__metadata',
312-
'__compression_settings', '__max_connecting')
313+
'__compression_settings', '__max_connecting', '__server_api')
313314

314315
def __init__(self, max_pool_size=MAX_POOL_SIZE,
315316
min_pool_size=MIN_POOL_SIZE,
@@ -318,7 +319,8 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
318319
wait_queue_multiple=None, ssl_context=None,
319320
ssl_match_hostname=True, socket_keepalive=True,
320321
event_listeners=None, appname=None, driver=None,
321-
compression_settings=None, max_connecting=MAX_CONNECTING):
322+
compression_settings=None, max_connecting=MAX_CONNECTING,
323+
server_api=None):
322324

323325
self.__max_pool_size = max_pool_size
324326
self.__min_pool_size = min_pool_size
@@ -335,6 +337,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
335337
self.__driver = driver
336338
self.__compression_settings = compression_settings
337339
self.__max_connecting = max_connecting
340+
self.__server_api = server_api
338341
self.__metadata = copy.deepcopy(_METADATA)
339342
if appname:
340343
self.__metadata['application'] = {'name': appname}
@@ -487,6 +490,12 @@ def metadata(self):
487490
"""
488491
return self.__metadata.copy()
489492

493+
@property
494+
def server_api(self):
495+
"""A pymongo.server_api.ServerApi or None.
496+
"""
497+
return self.__server_api
498+
490499

491500
def _negotiate_creds(all_credentials):
492501
"""Return one credential that needs mechanism negotiation, if any.
@@ -697,6 +706,7 @@ def command(self, dbname, spec, slave_ok=False,
697706
raise ConfigurationError(
698707
'Must be connected to MongoDB 3.4+ to use a collation.')
699708

709+
self.add_server_api(spec, session)
700710
if session:
701711
session._apply_to(spec, retryable_write, read_preference)
702712
self.send_cluster_time(spec, session, client)
@@ -886,6 +896,14 @@ def send_cluster_time(self, command, session, client):
886896
if self.max_wire_version >= 6 and client:
887897
client._send_cluster_time(command, session)
888898

899+
def add_server_api(self, command, session):
900+
"""Add server_api parameters."""
901+
if (session and session.in_transaction and
902+
not session._starting_transaction):
903+
return
904+
if self.opts.server_api:
905+
_add_to_command(command, self.opts.server_api)
906+
889907
def update_last_checkin_time(self):
890908
self.last_checkin_time = _time()
891909

pymongo/server_api.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2020-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you
4+
# may not use this file except in compliance with the License. You
5+
# may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
# implied. See the License for the specific language governing
13+
# permissions and limitations under the License.
14+
15+
"""Support for MongoDB Versioned API."""
16+
17+
18+
class ServerApiVersion:
19+
"""An enum that defines values for ServerApi `version`.
20+
21+
.. versionadded:: 3.12
22+
"""
23+
24+
V1 = "1"
25+
"""Server API version "1"."""
26+
27+
28+
class ServerApi(object):
29+
"""MongoDB Versioned API.
30+
31+
.. versionadded:: 3.12
32+
"""
33+
def __init__(self, version, strict=None, deprecation_errors=None):
34+
if version != ServerApiVersion.V1:
35+
raise ValueError("Unknown ServerApi version: %s" % (version,))
36+
if strict is not None and not isinstance(strict, bool):
37+
raise TypeError(
38+
"Wrong type for ServerApi strict, value must be an instance "
39+
"of bool, not %s" % (type(strict),))
40+
if (deprecation_errors is not None and
41+
not isinstance(deprecation_errors, bool)):
42+
raise TypeError(
43+
"Wrong type for ServerApi deprecation_errors, value must be "
44+
"an instance of bool, not %s" % (type(deprecation_errors),))
45+
self.version = version
46+
self.strict = strict
47+
self.deprecation_errors = deprecation_errors
48+
49+
50+
def _add_to_command(cmd, server_api):
51+
if not server_api:
52+
return
53+
cmd['apiVersion'] = server_api.version
54+
if server_api.strict is not None:
55+
cmd['apiStrict'] = server_api.strict
56+
if server_api.deprecation_errors is not None:
57+
cmd['apiDeprecationErrors'] = server_api.deprecation_errors

pymongo/topology.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,8 @@ def _create_pool_for_monitor(self, address):
686686
ssl_match_hostname=options.ssl_match_hostname,
687687
event_listeners=options.event_listeners,
688688
appname=options.appname,
689-
driver=options.driver)
689+
driver=options.driver,
690+
server_api=options.server_api)
690691

691692
return self._settings.pool_class(address, monitor_pool_options,
692693
handshake=False)

0 commit comments

Comments
 (0)