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.. _versioned-api-ref:
18
19MongoDB Versioned API
20=====================
21
22Starting in MongoDB 5.0, applications can specify the server API version
23to use when creating a :class:`~pymongo.mongo_client.MongoClient`. Doing so
24ensures that the driver behaves in a manner compatible with that server API
25version, regardless of the server's actual release version.
26
27Declaring an API Version
28````````````````````````
29
30.. attention:: Versioned API requires MongoDB >=5.0.
31
32To configure MongoDB Versioned API, pass the ``server_api`` keyword option to
33:class:`~pymongo.mongo_client.MongoClient`::
34
35    >>> from pymongo.mongo_client import MongoClient
36    >>> from pymongo.server_api import ServerApi
37    >>>
38    >>> # Declare API version "1" for MongoClient "client"
39    >>> server_api = ServerApi('1')
40    >>> client = MongoClient(server_api=server_api)
41
42The declared API version is applied to all commands run through ``client``,
43including those sent through the generic
44:meth:`~pymongo.database.Database.command` helper.
45
46.. note:: Declaring an API version on the
47   :class:`~pymongo.mongo_client.MongoClient` **and** specifying versioned
48   API options in :meth:`~pymongo.database.Database.command` command document
49   is not supported and will lead to undefined behaviour.
50
51To run any command without declaring a server API version or using a different
52API version, create a separate :class:`~pymongo.mongo_client.MongoClient`
53instance.
54
55Strict Mode
56```````````
57
58Configuring ``strict`` mode will cause the MongoDB server to reject all
59commands that are not part of the declared :attr:`ServerApi.version`. This
60includes command options and aggregation pipeline stages.
61
62For example::
63
64    >>> server_api = ServerApi('1', strict=True)
65    >>> client = MongoClient(server_api=server_api)
66    >>> client.test.command('count', 'test')
67    Traceback (most recent call last):
68    ...
69    pymongo.errors.OperationFailure: Provided apiStrict:true, but the command count is not in API Version 1, full error: {'ok': 0.0, 'errmsg': 'Provided apiStrict:true, but the command count is not in API Version 1', 'code': 323, 'codeName': 'APIStrictError'
70
71Detecting API Deprecations
72``````````````````````````
73
74The ``deprecationErrors`` option can be used to enable command failures
75when using functionality that is deprecated from the configured
76:attr:`ServerApi.version`. For example::
77
78    >>> server_api = ServerApi('1', deprecation_errors=True)
79    >>> client = MongoClient(server_api=server_api)
80
81Note that at the time of this writing, no deprecated APIs exist.
82
83Classes
84=======
85"""
86
87
88class ServerApiVersion:
89    """An enum that defines values for :attr:`ServerApi.version`.
90
91    .. versionadded:: 3.12
92    """
93
94    V1 = "1"
95    """Server API version "1"."""
96
97
98class ServerApi(object):
99    """MongoDB Versioned API."""
100    def __init__(self, version, strict=None, deprecation_errors=None):
101        """Options to configure MongoDB Versioned API.
102
103        :Parameters:
104          - `version`: The API version string. Must be one of the values in
105            :class:`ServerApiVersion`.
106          - `strict` (optional): Set to ``True`` to enable API strict mode.
107            Defaults to ``None`` which means "use the server's default".
108          - `deprecation_errors` (optional): Set to ``True`` to enable
109            deprecation errors. Defaults to ``None`` which means "use the
110            server's default".
111
112        .. versionadded:: 3.12
113        """
114        if version != ServerApiVersion.V1:
115            raise ValueError("Unknown ServerApi version: %s" % (version,))
116        if strict is not None and not isinstance(strict, bool):
117            raise TypeError(
118                "Wrong type for ServerApi strict, value must be an instance "
119                "of bool, not %s" % (type(strict),))
120        if (deprecation_errors is not None and
121                not isinstance(deprecation_errors, bool)):
122            raise TypeError(
123                "Wrong type for ServerApi deprecation_errors, value must be "
124                "an instance of bool, not %s" % (type(deprecation_errors),))
125        self._version = version
126        self._strict = strict
127        self._deprecation_errors = deprecation_errors
128
129    @property
130    def version(self):
131        """The API version setting.
132
133        This value is sent to the server in the "apiVersion" field.
134        """
135        return self._version
136
137    @property
138    def strict(self):
139        """The API strict mode setting.
140
141        When set, this value is sent to the server in the "apiStrict" field.
142        """
143        return self._strict
144
145    @property
146    def deprecation_errors(self):
147        """The API deprecation errors setting.
148
149        When set, this value is sent to the server in the
150        "apiDeprecationErrors" field.
151        """
152        return self._deprecation_errors
153
154
155def _add_to_command(cmd, server_api):
156    """Internal helper which adds API versioning options to a command.
157
158    :Parameters:
159      - `cmd`: The command.
160      - `server_api` (optional): A :class:`ServerApi` or ``None``.
161    """
162    if not server_api:
163        return
164    cmd['apiVersion'] = server_api.version
165    if server_api.strict is not None:
166        cmd['apiStrict'] = server_api.strict
167    if server_api.deprecation_errors is not None:
168        cmd['apiDeprecationErrors'] = server_api.deprecation_errors
169