sensu-plugins-mongodb-mrtrotl 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +27 -0
  5. data/bin/check-mongodb-metric.rb +144 -0
  6. data/bin/check-mongodb-query-count.rb +267 -0
  7. data/bin/check-mongodb.py +1644 -0
  8. data/bin/check-mongodb.rb +5 -0
  9. data/bin/metrics-mongodb-replication.rb +254 -0
  10. data/bin/metrics-mongodb.rb +133 -0
  11. data/lib/bson/__init__.py +1347 -0
  12. data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
  13. data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
  14. data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
  15. data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
  16. data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
  17. data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
  18. data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
  19. data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
  20. data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
  21. data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
  22. data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
  23. data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
  24. data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
  25. data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
  26. data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
  27. data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
  28. data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
  29. data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
  30. data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
  31. data/lib/bson/_helpers.py +41 -0
  32. data/lib/bson/binary.py +364 -0
  33. data/lib/bson/code.py +101 -0
  34. data/lib/bson/codec_options.py +414 -0
  35. data/lib/bson/codec_options.pyi +100 -0
  36. data/lib/bson/dbref.py +133 -0
  37. data/lib/bson/decimal128.py +314 -0
  38. data/lib/bson/errors.py +35 -0
  39. data/lib/bson/int64.py +39 -0
  40. data/lib/bson/json_util.py +874 -0
  41. data/lib/bson/max_key.py +55 -0
  42. data/lib/bson/min_key.py +55 -0
  43. data/lib/bson/objectid.py +286 -0
  44. data/lib/bson/py.typed +2 -0
  45. data/lib/bson/raw_bson.py +175 -0
  46. data/lib/bson/regex.py +135 -0
  47. data/lib/bson/son.py +208 -0
  48. data/lib/bson/timestamp.py +124 -0
  49. data/lib/bson/tz_util.py +52 -0
  50. data/lib/gridfs/__init__.py +1015 -0
  51. data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
  52. data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
  53. data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
  54. data/lib/gridfs/errors.py +33 -0
  55. data/lib/gridfs/grid_file.py +907 -0
  56. data/lib/gridfs/py.typed +2 -0
  57. data/lib/pymongo/__init__.py +185 -0
  58. data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
  59. data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
  60. data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
  61. data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
  62. data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
  63. data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
  64. data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
  65. data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
  66. data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
  67. data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
  68. data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
  69. data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
  70. data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
  71. data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
  72. data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
  73. data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
  74. data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
  75. data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
  76. data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
  77. data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
  78. data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
  79. data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
  80. data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
  81. data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
  82. data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
  83. data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
  84. data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
  85. data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
  86. data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
  87. data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
  88. data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
  89. data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
  90. data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
  91. data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
  92. data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
  93. data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
  94. data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
  95. data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
  96. data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
  97. data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
  98. data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
  99. data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
  100. data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
  101. data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
  102. data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
  103. data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
  104. data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
  105. data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
  106. data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
  107. data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
  108. data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
  109. data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
  110. data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
  111. data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
  112. data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
  113. data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
  114. data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
  115. data/lib/pymongo/_csot.py +118 -0
  116. data/lib/pymongo/aggregation.py +229 -0
  117. data/lib/pymongo/auth.py +549 -0
  118. data/lib/pymongo/auth_aws.py +94 -0
  119. data/lib/pymongo/bulk.py +513 -0
  120. data/lib/pymongo/change_stream.py +457 -0
  121. data/lib/pymongo/client_options.py +302 -0
  122. data/lib/pymongo/client_session.py +1112 -0
  123. data/lib/pymongo/collation.py +224 -0
  124. data/lib/pymongo/collection.py +3204 -0
  125. data/lib/pymongo/command_cursor.py +353 -0
  126. data/lib/pymongo/common.py +984 -0
  127. data/lib/pymongo/compression_support.py +149 -0
  128. data/lib/pymongo/cursor.py +1345 -0
  129. data/lib/pymongo/daemon.py +141 -0
  130. data/lib/pymongo/database.py +1202 -0
  131. data/lib/pymongo/driver_info.py +42 -0
  132. data/lib/pymongo/encryption.py +884 -0
  133. data/lib/pymongo/encryption_options.py +221 -0
  134. data/lib/pymongo/errors.py +365 -0
  135. data/lib/pymongo/event_loggers.py +221 -0
  136. data/lib/pymongo/hello.py +219 -0
  137. data/lib/pymongo/helpers.py +259 -0
  138. data/lib/pymongo/max_staleness_selectors.py +114 -0
  139. data/lib/pymongo/message.py +1440 -0
  140. data/lib/pymongo/mongo_client.py +2144 -0
  141. data/lib/pymongo/monitor.py +440 -0
  142. data/lib/pymongo/monitoring.py +1801 -0
  143. data/lib/pymongo/network.py +311 -0
  144. data/lib/pymongo/ocsp_cache.py +87 -0
  145. data/lib/pymongo/ocsp_support.py +372 -0
  146. data/lib/pymongo/operations.py +507 -0
  147. data/lib/pymongo/periodic_executor.py +183 -0
  148. data/lib/pymongo/pool.py +1660 -0
  149. data/lib/pymongo/py.typed +2 -0
  150. data/lib/pymongo/pyopenssl_context.py +383 -0
  151. data/lib/pymongo/read_concern.py +75 -0
  152. data/lib/pymongo/read_preferences.py +609 -0
  153. data/lib/pymongo/response.py +109 -0
  154. data/lib/pymongo/results.py +217 -0
  155. data/lib/pymongo/saslprep.py +113 -0
  156. data/lib/pymongo/server.py +247 -0
  157. data/lib/pymongo/server_api.py +170 -0
  158. data/lib/pymongo/server_description.py +285 -0
  159. data/lib/pymongo/server_selectors.py +153 -0
  160. data/lib/pymongo/server_type.py +32 -0
  161. data/lib/pymongo/settings.py +159 -0
  162. data/lib/pymongo/socket_checker.py +104 -0
  163. data/lib/pymongo/srv_resolver.py +126 -0
  164. data/lib/pymongo/ssl_context.py +39 -0
  165. data/lib/pymongo/ssl_support.py +99 -0
  166. data/lib/pymongo/topology.py +890 -0
  167. data/lib/pymongo/topology_description.py +639 -0
  168. data/lib/pymongo/typings.py +39 -0
  169. data/lib/pymongo/uri_parser.py +624 -0
  170. data/lib/pymongo/write_concern.py +129 -0
  171. data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
  172. data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
  173. data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
  174. data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
  175. data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
  176. data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
  177. data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
  178. data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
  179. data/lib/sensu-plugins-mongodb/version.rb +9 -0
  180. data/lib/sensu-plugins-mongodb.rb +1 -0
  181. metadata +407 -0
@@ -0,0 +1,1112 @@
1
+ # Copyright 2017 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You 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 implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Logical sessions for ordering sequential operations.
16
+
17
+ .. versionadded:: 3.6
18
+
19
+ Causally Consistent Reads
20
+ =========================
21
+
22
+ .. code-block:: python
23
+
24
+ with client.start_session(causal_consistency=True) as session:
25
+ collection = client.db.collection
26
+ collection.update_one({'_id': 1}, {'$set': {'x': 10}}, session=session)
27
+ secondary_c = collection.with_options(
28
+ read_preference=ReadPreference.SECONDARY)
29
+
30
+ # A secondary read waits for replication of the write.
31
+ secondary_c.find_one({'_id': 1}, session=session)
32
+
33
+ If `causal_consistency` is True (the default), read operations that use
34
+ the session are causally after previous read and write operations. Using a
35
+ causally consistent session, an application can read its own writes and is
36
+ guaranteed monotonic reads, even when reading from replica set secondaries.
37
+
38
+ .. seealso:: The MongoDB documentation on `causal-consistency <https://dochub.mongodb.org/core/causal-consistency>`_.
39
+
40
+ .. _transactions-ref:
41
+
42
+ Transactions
43
+ ============
44
+
45
+ .. versionadded:: 3.7
46
+
47
+ MongoDB 4.0 adds support for transactions on replica set primaries. A
48
+ transaction is associated with a :class:`ClientSession`. To start a transaction
49
+ on a session, use :meth:`ClientSession.start_transaction` in a with-statement.
50
+ Then, execute an operation within the transaction by passing the session to the
51
+ operation:
52
+
53
+ .. code-block:: python
54
+
55
+ orders = client.db.orders
56
+ inventory = client.db.inventory
57
+ with client.start_session() as session:
58
+ with session.start_transaction():
59
+ orders.insert_one({"sku": "abc123", "qty": 100}, session=session)
60
+ inventory.update_one({"sku": "abc123", "qty": {"$gte": 100}},
61
+ {"$inc": {"qty": -100}}, session=session)
62
+
63
+ Upon normal completion of ``with session.start_transaction()`` block, the
64
+ transaction automatically calls :meth:`ClientSession.commit_transaction`.
65
+ If the block exits with an exception, the transaction automatically calls
66
+ :meth:`ClientSession.abort_transaction`.
67
+
68
+ In general, multi-document transactions only support read/write (CRUD)
69
+ operations on existing collections. However, MongoDB 4.4 adds support for
70
+ creating collections and indexes with some limitations, including an
71
+ insert operation that would result in the creation of a new collection.
72
+ For a complete description of all the supported and unsupported operations
73
+ see the `MongoDB server's documentation for transactions
74
+ <http://dochub.mongodb.org/core/transactions>`_.
75
+
76
+ A session may only have a single active transaction at a time, multiple
77
+ transactions on the same session can be executed in sequence.
78
+
79
+ Sharded Transactions
80
+ ^^^^^^^^^^^^^^^^^^^^
81
+
82
+ .. versionadded:: 3.9
83
+
84
+ PyMongo 3.9 adds support for transactions on sharded clusters running MongoDB
85
+ >=4.2. Sharded transactions have the same API as replica set transactions.
86
+ When running a transaction against a sharded cluster, the session is
87
+ pinned to the mongos server selected for the first operation in the
88
+ transaction. All subsequent operations that are part of the same transaction
89
+ are routed to the same mongos server. When the transaction is completed, by
90
+ running either commitTransaction or abortTransaction, the session is unpinned.
91
+
92
+ .. seealso:: The MongoDB documentation on `transactions <https://dochub.mongodb.org/core/transactions>`_.
93
+
94
+ .. _snapshot-reads-ref:
95
+
96
+ Snapshot Reads
97
+ ==============
98
+
99
+ .. versionadded:: 3.12
100
+
101
+ MongoDB 5.0 adds support for snapshot reads. Snapshot reads are requested by
102
+ passing the ``snapshot`` option to
103
+ :meth:`~pymongo.mongo_client.MongoClient.start_session`.
104
+ If ``snapshot`` is True, all read operations that use this session read data
105
+ from the same snapshot timestamp. The server chooses the latest
106
+ majority-committed snapshot timestamp when executing the first read operation
107
+ using the session. Subsequent reads on this session read from the same
108
+ snapshot timestamp. Snapshot reads are also supported when reading from
109
+ replica set secondaries.
110
+
111
+ .. code-block:: python
112
+
113
+ # Each read using this session reads data from the same point in time.
114
+ with client.start_session(snapshot=True) as session:
115
+ order = orders.find_one({"sku": "abc123"}, session=session)
116
+ inventory = inventory.find_one({"sku": "abc123"}, session=session)
117
+
118
+ Snapshot Reads Limitations
119
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
120
+
121
+ Snapshot reads sessions are incompatible with ``causal_consistency=True``.
122
+ Only the following read operations are supported in a snapshot reads session:
123
+
124
+ - :meth:`~pymongo.collection.Collection.find`
125
+ - :meth:`~pymongo.collection.Collection.find_one`
126
+ - :meth:`~pymongo.collection.Collection.aggregate`
127
+ - :meth:`~pymongo.collection.Collection.count_documents`
128
+ - :meth:`~pymongo.collection.Collection.distinct` (on unsharded collections)
129
+
130
+ Classes
131
+ =======
132
+ """
133
+
134
+ import collections
135
+ import time
136
+ import uuid
137
+ from collections.abc import Mapping as _Mapping
138
+ from typing import (
139
+ TYPE_CHECKING,
140
+ Any,
141
+ Callable,
142
+ ContextManager,
143
+ Mapping,
144
+ NoReturn,
145
+ Optional,
146
+ TypeVar,
147
+ )
148
+
149
+ from bson.binary import Binary
150
+ from bson.int64 import Int64
151
+ from bson.son import SON
152
+ from bson.timestamp import Timestamp
153
+ from pymongo import _csot
154
+ from pymongo.cursor import _SocketManager
155
+ from pymongo.errors import (
156
+ ConfigurationError,
157
+ ConnectionFailure,
158
+ InvalidOperation,
159
+ OperationFailure,
160
+ PyMongoError,
161
+ WTimeoutError,
162
+ )
163
+ from pymongo.helpers import _RETRYABLE_ERROR_CODES
164
+ from pymongo.read_concern import ReadConcern
165
+ from pymongo.read_preferences import ReadPreference, _ServerMode
166
+ from pymongo.server_type import SERVER_TYPE
167
+ from pymongo.write_concern import WriteConcern
168
+
169
+
170
+ class SessionOptions(object):
171
+ """Options for a new :class:`ClientSession`.
172
+
173
+ :Parameters:
174
+ - `causal_consistency` (optional): If True, read operations are causally
175
+ ordered within the session. Defaults to True when the ``snapshot``
176
+ option is ``False``.
177
+ - `default_transaction_options` (optional): The default
178
+ TransactionOptions to use for transactions started on this session.
179
+ - `snapshot` (optional): If True, then all reads performed using this
180
+ session will read from the same snapshot. This option is incompatible
181
+ with ``causal_consistency=True``. Defaults to ``False``.
182
+
183
+ .. versionchanged:: 3.12
184
+ Added the ``snapshot`` parameter.
185
+ """
186
+
187
+ def __init__(
188
+ self,
189
+ causal_consistency: Optional[bool] = None,
190
+ default_transaction_options: Optional["TransactionOptions"] = None,
191
+ snapshot: Optional[bool] = False,
192
+ ) -> None:
193
+ if snapshot:
194
+ if causal_consistency:
195
+ raise ConfigurationError("snapshot reads do not support causal_consistency=True")
196
+ causal_consistency = False
197
+ elif causal_consistency is None:
198
+ causal_consistency = True
199
+ self._causal_consistency = causal_consistency
200
+ if default_transaction_options is not None:
201
+ if not isinstance(default_transaction_options, TransactionOptions):
202
+ raise TypeError(
203
+ "default_transaction_options must be an instance of "
204
+ "pymongo.client_session.TransactionOptions, not: %r"
205
+ % (default_transaction_options,)
206
+ )
207
+ self._default_transaction_options = default_transaction_options
208
+ self._snapshot = snapshot
209
+
210
+ @property
211
+ def causal_consistency(self) -> bool:
212
+ """Whether causal consistency is configured."""
213
+ return self._causal_consistency
214
+
215
+ @property
216
+ def default_transaction_options(self) -> Optional["TransactionOptions"]:
217
+ """The default TransactionOptions to use for transactions started on
218
+ this session.
219
+
220
+ .. versionadded:: 3.7
221
+ """
222
+ return self._default_transaction_options
223
+
224
+ @property
225
+ def snapshot(self) -> Optional[bool]:
226
+ """Whether snapshot reads are configured.
227
+
228
+ .. versionadded:: 3.12
229
+ """
230
+ return self._snapshot
231
+
232
+
233
+ class TransactionOptions(object):
234
+ """Options for :meth:`ClientSession.start_transaction`.
235
+
236
+ :Parameters:
237
+ - `read_concern` (optional): The
238
+ :class:`~pymongo.read_concern.ReadConcern` to use for this transaction.
239
+ If ``None`` (the default) the :attr:`read_preference` of
240
+ the :class:`MongoClient` is used.
241
+ - `write_concern` (optional): The
242
+ :class:`~pymongo.write_concern.WriteConcern` to use for this
243
+ transaction. If ``None`` (the default) the :attr:`read_preference` of
244
+ the :class:`MongoClient` is used.
245
+ - `read_preference` (optional): The read preference to use. If
246
+ ``None`` (the default) the :attr:`read_preference` of this
247
+ :class:`MongoClient` is used. See :mod:`~pymongo.read_preferences`
248
+ for options. Transactions which read must use
249
+ :attr:`~pymongo.read_preferences.ReadPreference.PRIMARY`.
250
+ - `max_commit_time_ms` (optional): The maximum amount of time to allow a
251
+ single commitTransaction command to run. This option is an alias for
252
+ maxTimeMS option on the commitTransaction command. If ``None`` (the
253
+ default) maxTimeMS is not used.
254
+
255
+ .. versionchanged:: 3.9
256
+ Added the ``max_commit_time_ms`` option.
257
+
258
+ .. versionadded:: 3.7
259
+ """
260
+
261
+ def __init__(
262
+ self,
263
+ read_concern: Optional[ReadConcern] = None,
264
+ write_concern: Optional[WriteConcern] = None,
265
+ read_preference: Optional[_ServerMode] = None,
266
+ max_commit_time_ms: Optional[int] = None,
267
+ ) -> None:
268
+ self._read_concern = read_concern
269
+ self._write_concern = write_concern
270
+ self._read_preference = read_preference
271
+ self._max_commit_time_ms = max_commit_time_ms
272
+ if read_concern is not None:
273
+ if not isinstance(read_concern, ReadConcern):
274
+ raise TypeError(
275
+ "read_concern must be an instance of "
276
+ "pymongo.read_concern.ReadConcern, not: %r" % (read_concern,)
277
+ )
278
+ if write_concern is not None:
279
+ if not isinstance(write_concern, WriteConcern):
280
+ raise TypeError(
281
+ "write_concern must be an instance of "
282
+ "pymongo.write_concern.WriteConcern, not: %r" % (write_concern,)
283
+ )
284
+ if not write_concern.acknowledged:
285
+ raise ConfigurationError(
286
+ "transactions do not support unacknowledged write concern"
287
+ ": %r" % (write_concern,)
288
+ )
289
+ if read_preference is not None:
290
+ if not isinstance(read_preference, _ServerMode):
291
+ raise TypeError(
292
+ "%r is not valid for read_preference. See "
293
+ "pymongo.read_preferences for valid "
294
+ "options." % (read_preference,)
295
+ )
296
+ if max_commit_time_ms is not None:
297
+ if not isinstance(max_commit_time_ms, int):
298
+ raise TypeError("max_commit_time_ms must be an integer or None")
299
+
300
+ @property
301
+ def read_concern(self) -> Optional[ReadConcern]:
302
+ """This transaction's :class:`~pymongo.read_concern.ReadConcern`."""
303
+ return self._read_concern
304
+
305
+ @property
306
+ def write_concern(self) -> Optional[WriteConcern]:
307
+ """This transaction's :class:`~pymongo.write_concern.WriteConcern`."""
308
+ return self._write_concern
309
+
310
+ @property
311
+ def read_preference(self) -> Optional[_ServerMode]:
312
+ """This transaction's :class:`~pymongo.read_preferences.ReadPreference`."""
313
+ return self._read_preference
314
+
315
+ @property
316
+ def max_commit_time_ms(self) -> Optional[int]:
317
+ """The maxTimeMS to use when running a commitTransaction command.
318
+
319
+ .. versionadded:: 3.9
320
+ """
321
+ return self._max_commit_time_ms
322
+
323
+
324
+ def _validate_session_write_concern(session, write_concern):
325
+ """Validate that an explicit session is not used with an unack'ed write.
326
+
327
+ Returns the session to use for the next operation.
328
+ """
329
+ if session:
330
+ if write_concern is not None and not write_concern.acknowledged:
331
+ # For unacknowledged writes without an explicit session,
332
+ # drivers SHOULD NOT use an implicit session. If a driver
333
+ # creates an implicit session for unacknowledged writes
334
+ # without an explicit session, the driver MUST NOT send the
335
+ # session ID.
336
+ if session._implicit:
337
+ return None
338
+ else:
339
+ raise ConfigurationError(
340
+ "Explicit sessions are incompatible with "
341
+ "unacknowledged write concern: %r" % (write_concern,)
342
+ )
343
+ return session
344
+
345
+
346
+ class _TransactionContext(object):
347
+ """Internal transaction context manager for start_transaction."""
348
+
349
+ def __init__(self, session):
350
+ self.__session = session
351
+
352
+ def __enter__(self):
353
+ return self
354
+
355
+ def __exit__(self, exc_type, exc_val, exc_tb):
356
+ if self.__session.in_transaction:
357
+ if exc_val is None:
358
+ self.__session.commit_transaction()
359
+ else:
360
+ self.__session.abort_transaction()
361
+
362
+
363
+ class _TxnState(object):
364
+ NONE = 1
365
+ STARTING = 2
366
+ IN_PROGRESS = 3
367
+ COMMITTED = 4
368
+ COMMITTED_EMPTY = 5
369
+ ABORTED = 6
370
+
371
+
372
+ class _Transaction(object):
373
+ """Internal class to hold transaction information in a ClientSession."""
374
+
375
+ def __init__(self, opts, client):
376
+ self.opts = opts
377
+ self.state = _TxnState.NONE
378
+ self.sharded = False
379
+ self.pinned_address = None
380
+ self.sock_mgr = None
381
+ self.recovery_token = None
382
+ self.attempt = 0
383
+ self.client = client
384
+
385
+ def active(self):
386
+ return self.state in (_TxnState.STARTING, _TxnState.IN_PROGRESS)
387
+
388
+ def starting(self):
389
+ return self.state == _TxnState.STARTING
390
+
391
+ @property
392
+ def pinned_conn(self):
393
+ if self.active() and self.sock_mgr:
394
+ return self.sock_mgr.sock
395
+ return None
396
+
397
+ def pin(self, server, sock_info):
398
+ self.sharded = True
399
+ self.pinned_address = server.description.address
400
+ if server.description.server_type == SERVER_TYPE.LoadBalancer:
401
+ sock_info.pin_txn()
402
+ self.sock_mgr = _SocketManager(sock_info, False)
403
+
404
+ def unpin(self):
405
+ self.pinned_address = None
406
+ if self.sock_mgr:
407
+ self.sock_mgr.close()
408
+ self.sock_mgr = None
409
+
410
+ def reset(self):
411
+ self.unpin()
412
+ self.state = _TxnState.NONE
413
+ self.sharded = False
414
+ self.recovery_token = None
415
+ self.attempt = 0
416
+
417
+ def __del__(self):
418
+ if self.sock_mgr:
419
+ # Reuse the cursor closing machinery to return the socket to the
420
+ # pool soon.
421
+ self.client._close_cursor_soon(0, None, self.sock_mgr)
422
+ self.sock_mgr = None
423
+
424
+
425
+ def _reraise_with_unknown_commit(exc: Any) -> NoReturn:
426
+ """Re-raise an exception with the UnknownTransactionCommitResult label."""
427
+ exc._add_error_label("UnknownTransactionCommitResult")
428
+ raise
429
+
430
+
431
+ def _max_time_expired_error(exc):
432
+ """Return true if exc is a MaxTimeMSExpired error."""
433
+ return isinstance(exc, OperationFailure) and exc.code == 50
434
+
435
+
436
+ # From the transactions spec, all the retryable writes errors plus
437
+ # WriteConcernFailed.
438
+ _UNKNOWN_COMMIT_ERROR_CODES = _RETRYABLE_ERROR_CODES | frozenset(
439
+ [
440
+ 64, # WriteConcernFailed
441
+ 50, # MaxTimeMSExpired
442
+ ]
443
+ )
444
+
445
+ # From the Convenient API for Transactions spec, with_transaction must
446
+ # halt retries after 120 seconds.
447
+ # This limit is non-configurable and was chosen to be twice the 60 second
448
+ # default value of MongoDB's `transactionLifetimeLimitSeconds` parameter.
449
+ _WITH_TRANSACTION_RETRY_TIME_LIMIT = 120
450
+
451
+
452
+ def _within_time_limit(start_time):
453
+ """Are we within the with_transaction retry limit?"""
454
+ return time.monotonic() - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT
455
+
456
+
457
+ _T = TypeVar("_T")
458
+
459
+ if TYPE_CHECKING:
460
+ from pymongo.mongo_client import MongoClient
461
+
462
+
463
+ class ClientSession:
464
+ """A session for ordering sequential operations.
465
+
466
+ :class:`ClientSession` instances are **not thread-safe or fork-safe**.
467
+ They can only be used by one thread or process at a time. A single
468
+ :class:`ClientSession` cannot be used to run multiple operations
469
+ concurrently.
470
+
471
+ Should not be initialized directly by application developers - to create a
472
+ :class:`ClientSession`, call
473
+ :meth:`~pymongo.mongo_client.MongoClient.start_session`.
474
+ """
475
+
476
+ def __init__(
477
+ self,
478
+ client: "MongoClient",
479
+ server_session: Any,
480
+ options: SessionOptions,
481
+ implicit: bool,
482
+ ) -> None:
483
+ # A MongoClient, a _ServerSession, a SessionOptions, and a set.
484
+ self._client: MongoClient = client
485
+ self._server_session = server_session
486
+ self._options = options
487
+ self._cluster_time = None
488
+ self._operation_time = None
489
+ self._snapshot_time = None
490
+ # Is this an implicitly created session?
491
+ self._implicit = implicit
492
+ self._transaction = _Transaction(None, client)
493
+
494
+ def end_session(self) -> None:
495
+ """Finish this session. If a transaction has started, abort it.
496
+
497
+ It is an error to use the session after the session has ended.
498
+ """
499
+ self._end_session(lock=True)
500
+
501
+ def _end_session(self, lock):
502
+ if self._server_session is not None:
503
+ try:
504
+ if self.in_transaction:
505
+ self.abort_transaction()
506
+ # It's possible we're still pinned here when the transaction
507
+ # is in the committed state when the session is discarded.
508
+ self._unpin()
509
+ finally:
510
+ self._client._return_server_session(self._server_session, lock)
511
+ self._server_session = None
512
+
513
+ def _check_ended(self):
514
+ if self._server_session is None:
515
+ raise InvalidOperation("Cannot use ended session")
516
+
517
+ def __enter__(self) -> "ClientSession":
518
+ return self
519
+
520
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
521
+ self._end_session(lock=True)
522
+
523
+ @property
524
+ def client(self) -> "MongoClient":
525
+ """The :class:`~pymongo.mongo_client.MongoClient` this session was
526
+ created from.
527
+ """
528
+ return self._client
529
+
530
+ @property
531
+ def options(self) -> SessionOptions:
532
+ """The :class:`SessionOptions` this session was created with."""
533
+ return self._options
534
+
535
+ @property
536
+ def session_id(self) -> Mapping[str, Any]:
537
+ """A BSON document, the opaque server session identifier."""
538
+ self._check_ended()
539
+ return self._server_session.session_id
540
+
541
+ @property
542
+ def cluster_time(self) -> Optional[Mapping[str, Any]]:
543
+ """The cluster time returned by the last operation executed
544
+ in this session.
545
+ """
546
+ return self._cluster_time
547
+
548
+ @property
549
+ def operation_time(self) -> Optional[Timestamp]:
550
+ """The operation time returned by the last operation executed
551
+ in this session.
552
+ """
553
+ return self._operation_time
554
+
555
+ def _inherit_option(self, name, val):
556
+ """Return the inherited TransactionOption value."""
557
+ if val:
558
+ return val
559
+ txn_opts = self.options.default_transaction_options
560
+ val = txn_opts and getattr(txn_opts, name)
561
+ if val:
562
+ return val
563
+ return getattr(self.client, name)
564
+
565
+ def with_transaction(
566
+ self,
567
+ callback: Callable[["ClientSession"], _T],
568
+ read_concern: Optional[ReadConcern] = None,
569
+ write_concern: Optional[WriteConcern] = None,
570
+ read_preference: Optional[_ServerMode] = None,
571
+ max_commit_time_ms: Optional[int] = None,
572
+ ) -> _T:
573
+ """Execute a callback in a transaction.
574
+
575
+ This method starts a transaction on this session, executes ``callback``
576
+ once, and then commits the transaction. For example::
577
+
578
+ def callback(session):
579
+ orders = session.client.db.orders
580
+ inventory = session.client.db.inventory
581
+ orders.insert_one({"sku": "abc123", "qty": 100}, session=session)
582
+ inventory.update_one({"sku": "abc123", "qty": {"$gte": 100}},
583
+ {"$inc": {"qty": -100}}, session=session)
584
+
585
+ with client.start_session() as session:
586
+ session.with_transaction(callback)
587
+
588
+ To pass arbitrary arguments to the ``callback``, wrap your callable
589
+ with a ``lambda`` like this::
590
+
591
+ def callback(session, custom_arg, custom_kwarg=None):
592
+ # Transaction operations...
593
+
594
+ with client.start_session() as session:
595
+ session.with_transaction(
596
+ lambda s: callback(s, "custom_arg", custom_kwarg=1))
597
+
598
+ In the event of an exception, ``with_transaction`` may retry the commit
599
+ or the entire transaction, therefore ``callback`` may be invoked
600
+ multiple times by a single call to ``with_transaction``. Developers
601
+ should be mindful of this possiblity when writing a ``callback`` that
602
+ modifies application state or has any other side-effects.
603
+ Note that even when the ``callback`` is invoked multiple times,
604
+ ``with_transaction`` ensures that the transaction will be committed
605
+ at-most-once on the server.
606
+
607
+ The ``callback`` should not attempt to start new transactions, but
608
+ should simply run operations meant to be contained within a
609
+ transaction. The ``callback`` should also not commit the transaction;
610
+ this is handled automatically by ``with_transaction``. If the
611
+ ``callback`` does commit or abort the transaction without error,
612
+ however, ``with_transaction`` will return without taking further
613
+ action.
614
+
615
+ :class:`ClientSession` instances are **not thread-safe or fork-safe**.
616
+ Consequently, the ``callback`` must not attempt to execute multiple
617
+ operations concurrently.
618
+
619
+ When ``callback`` raises an exception, ``with_transaction``
620
+ automatically aborts the current transaction. When ``callback`` or
621
+ :meth:`~ClientSession.commit_transaction` raises an exception that
622
+ includes the ``"TransientTransactionError"`` error label,
623
+ ``with_transaction`` starts a new transaction and re-executes
624
+ the ``callback``.
625
+
626
+ When :meth:`~ClientSession.commit_transaction` raises an exception with
627
+ the ``"UnknownTransactionCommitResult"`` error label,
628
+ ``with_transaction`` retries the commit until the result of the
629
+ transaction is known.
630
+
631
+ This method will cease retrying after 120 seconds has elapsed. This
632
+ timeout is not configurable and any exception raised by the
633
+ ``callback`` or by :meth:`ClientSession.commit_transaction` after the
634
+ timeout is reached will be re-raised. Applications that desire a
635
+ different timeout duration should not use this method.
636
+
637
+ :Parameters:
638
+ - `callback`: The callable ``callback`` to run inside a transaction.
639
+ The callable must accept a single argument, this session. Note,
640
+ under certain error conditions the callback may be run multiple
641
+ times.
642
+ - `read_concern` (optional): The
643
+ :class:`~pymongo.read_concern.ReadConcern` to use for this
644
+ transaction.
645
+ - `write_concern` (optional): The
646
+ :class:`~pymongo.write_concern.WriteConcern` to use for this
647
+ transaction.
648
+ - `read_preference` (optional): The read preference to use for this
649
+ transaction. If ``None`` (the default) the :attr:`read_preference`
650
+ of this :class:`Database` is used. See
651
+ :mod:`~pymongo.read_preferences` for options.
652
+
653
+ :Returns:
654
+ The return value of the ``callback``.
655
+
656
+ .. versionadded:: 3.9
657
+ """
658
+ start_time = time.monotonic()
659
+ while True:
660
+ self.start_transaction(read_concern, write_concern, read_preference, max_commit_time_ms)
661
+ try:
662
+ ret = callback(self)
663
+ except Exception as exc:
664
+ if self.in_transaction:
665
+ self.abort_transaction()
666
+ if (
667
+ isinstance(exc, PyMongoError)
668
+ and exc.has_error_label("TransientTransactionError")
669
+ and _within_time_limit(start_time)
670
+ ):
671
+ # Retry the entire transaction.
672
+ continue
673
+ raise
674
+
675
+ if not self.in_transaction:
676
+ # Assume callback intentionally ended the transaction.
677
+ return ret
678
+
679
+ while True:
680
+ try:
681
+ self.commit_transaction()
682
+ except PyMongoError as exc:
683
+ if (
684
+ exc.has_error_label("UnknownTransactionCommitResult")
685
+ and _within_time_limit(start_time)
686
+ and not _max_time_expired_error(exc)
687
+ ):
688
+ # Retry the commit.
689
+ continue
690
+
691
+ if exc.has_error_label("TransientTransactionError") and _within_time_limit(
692
+ start_time
693
+ ):
694
+ # Retry the entire transaction.
695
+ break
696
+ raise
697
+
698
+ # Commit succeeded.
699
+ return ret
700
+
701
+ def start_transaction(
702
+ self,
703
+ read_concern: Optional[ReadConcern] = None,
704
+ write_concern: Optional[WriteConcern] = None,
705
+ read_preference: Optional[_ServerMode] = None,
706
+ max_commit_time_ms: Optional[int] = None,
707
+ ) -> ContextManager:
708
+ """Start a multi-statement transaction.
709
+
710
+ Takes the same arguments as :class:`TransactionOptions`.
711
+
712
+ .. versionchanged:: 3.9
713
+ Added the ``max_commit_time_ms`` option.
714
+
715
+ .. versionadded:: 3.7
716
+ """
717
+ self._check_ended()
718
+
719
+ if self.options.snapshot:
720
+ raise InvalidOperation("Transactions are not supported in snapshot sessions")
721
+
722
+ if self.in_transaction:
723
+ raise InvalidOperation("Transaction already in progress")
724
+
725
+ read_concern = self._inherit_option("read_concern", read_concern)
726
+ write_concern = self._inherit_option("write_concern", write_concern)
727
+ read_preference = self._inherit_option("read_preference", read_preference)
728
+ if max_commit_time_ms is None:
729
+ opts = self.options.default_transaction_options
730
+ if opts:
731
+ max_commit_time_ms = opts.max_commit_time_ms
732
+
733
+ self._transaction.opts = TransactionOptions(
734
+ read_concern, write_concern, read_preference, max_commit_time_ms
735
+ )
736
+ self._transaction.reset()
737
+ self._transaction.state = _TxnState.STARTING
738
+ self._start_retryable_write()
739
+ return _TransactionContext(self)
740
+
741
+ def commit_transaction(self) -> None:
742
+ """Commit a multi-statement transaction.
743
+
744
+ .. versionadded:: 3.7
745
+ """
746
+ self._check_ended()
747
+ state = self._transaction.state
748
+ if state is _TxnState.NONE:
749
+ raise InvalidOperation("No transaction started")
750
+ elif state in (_TxnState.STARTING, _TxnState.COMMITTED_EMPTY):
751
+ # Server transaction was never started, no need to send a command.
752
+ self._transaction.state = _TxnState.COMMITTED_EMPTY
753
+ return
754
+ elif state is _TxnState.ABORTED:
755
+ raise InvalidOperation("Cannot call commitTransaction after calling abortTransaction")
756
+ elif state is _TxnState.COMMITTED:
757
+ # We're explicitly retrying the commit, move the state back to
758
+ # "in progress" so that in_transaction returns true.
759
+ self._transaction.state = _TxnState.IN_PROGRESS
760
+
761
+ try:
762
+ self._finish_transaction_with_retry("commitTransaction")
763
+ except ConnectionFailure as exc:
764
+ # We do not know if the commit was successfully applied on the
765
+ # server or if it satisfied the provided write concern, set the
766
+ # unknown commit error label.
767
+ exc._remove_error_label("TransientTransactionError")
768
+ _reraise_with_unknown_commit(exc)
769
+ except WTimeoutError as exc:
770
+ # We do not know if the commit has satisfied the provided write
771
+ # concern, add the unknown commit error label.
772
+ _reraise_with_unknown_commit(exc)
773
+ except OperationFailure as exc:
774
+ if exc.code not in _UNKNOWN_COMMIT_ERROR_CODES:
775
+ # The server reports errorLabels in the case.
776
+ raise
777
+ # We do not know if the commit was successfully applied on the
778
+ # server or if it satisfied the provided write concern, set the
779
+ # unknown commit error label.
780
+ _reraise_with_unknown_commit(exc)
781
+ finally:
782
+ self._transaction.state = _TxnState.COMMITTED
783
+
784
+ def abort_transaction(self) -> None:
785
+ """Abort a multi-statement transaction.
786
+
787
+ .. versionadded:: 3.7
788
+ """
789
+ self._check_ended()
790
+
791
+ state = self._transaction.state
792
+ if state is _TxnState.NONE:
793
+ raise InvalidOperation("No transaction started")
794
+ elif state is _TxnState.STARTING:
795
+ # Server transaction was never started, no need to send a command.
796
+ self._transaction.state = _TxnState.ABORTED
797
+ return
798
+ elif state is _TxnState.ABORTED:
799
+ raise InvalidOperation("Cannot call abortTransaction twice")
800
+ elif state in (_TxnState.COMMITTED, _TxnState.COMMITTED_EMPTY):
801
+ raise InvalidOperation("Cannot call abortTransaction after calling commitTransaction")
802
+
803
+ try:
804
+ self._finish_transaction_with_retry("abortTransaction")
805
+ except (OperationFailure, ConnectionFailure):
806
+ # The transactions spec says to ignore abortTransaction errors.
807
+ pass
808
+ finally:
809
+ self._transaction.state = _TxnState.ABORTED
810
+ self._unpin()
811
+
812
+ def _finish_transaction_with_retry(self, command_name):
813
+ """Run commit or abort with one retry after any retryable error.
814
+
815
+ :Parameters:
816
+ - `command_name`: Either "commitTransaction" or "abortTransaction".
817
+ """
818
+
819
+ def func(session, sock_info, retryable):
820
+ return self._finish_transaction(sock_info, command_name)
821
+
822
+ return self._client._retry_internal(True, func, self, None)
823
+
824
+ def _finish_transaction(self, sock_info, command_name):
825
+ self._transaction.attempt += 1
826
+ opts = self._transaction.opts
827
+ wc = opts.write_concern
828
+ cmd = SON([(command_name, 1)])
829
+ if command_name == "commitTransaction":
830
+ if opts.max_commit_time_ms and _csot.get_timeout() is None:
831
+ cmd["maxTimeMS"] = opts.max_commit_time_ms
832
+
833
+ # Transaction spec says that after the initial commit attempt,
834
+ # subsequent commitTransaction commands should be upgraded to use
835
+ # w:"majority" and set a default value of 10 seconds for wtimeout.
836
+ if self._transaction.attempt > 1:
837
+ wc_doc = wc.document
838
+ wc_doc["w"] = "majority"
839
+ wc_doc.setdefault("wtimeout", 10000)
840
+ wc = WriteConcern(**wc_doc)
841
+
842
+ if self._transaction.recovery_token:
843
+ cmd["recoveryToken"] = self._transaction.recovery_token
844
+
845
+ return self._client.admin._command(
846
+ sock_info, cmd, session=self, write_concern=wc, parse_write_concern_error=True
847
+ )
848
+
849
+ def _advance_cluster_time(self, cluster_time):
850
+ """Internal cluster time helper."""
851
+ if self._cluster_time is None:
852
+ self._cluster_time = cluster_time
853
+ elif cluster_time is not None:
854
+ if cluster_time["clusterTime"] > self._cluster_time["clusterTime"]:
855
+ self._cluster_time = cluster_time
856
+
857
+ def advance_cluster_time(self, cluster_time: Mapping[str, Any]) -> None:
858
+ """Update the cluster time for this session.
859
+
860
+ :Parameters:
861
+ - `cluster_time`: The
862
+ :data:`~pymongo.client_session.ClientSession.cluster_time` from
863
+ another `ClientSession` instance.
864
+ """
865
+ if not isinstance(cluster_time, _Mapping):
866
+ raise TypeError("cluster_time must be a subclass of collections.Mapping")
867
+ if not isinstance(cluster_time.get("clusterTime"), Timestamp):
868
+ raise ValueError("Invalid cluster_time")
869
+ self._advance_cluster_time(cluster_time)
870
+
871
+ def _advance_operation_time(self, operation_time):
872
+ """Internal operation time helper."""
873
+ if self._operation_time is None:
874
+ self._operation_time = operation_time
875
+ elif operation_time is not None:
876
+ if operation_time > self._operation_time:
877
+ self._operation_time = operation_time
878
+
879
+ def advance_operation_time(self, operation_time: Timestamp) -> None:
880
+ """Update the operation time for this session.
881
+
882
+ :Parameters:
883
+ - `operation_time`: The
884
+ :data:`~pymongo.client_session.ClientSession.operation_time` from
885
+ another `ClientSession` instance.
886
+ """
887
+ if not isinstance(operation_time, Timestamp):
888
+ raise TypeError("operation_time must be an instance of bson.timestamp.Timestamp")
889
+ self._advance_operation_time(operation_time)
890
+
891
+ def _process_response(self, reply):
892
+ """Process a response to a command that was run with this session."""
893
+ self._advance_cluster_time(reply.get("$clusterTime"))
894
+ self._advance_operation_time(reply.get("operationTime"))
895
+ if self._options.snapshot and self._snapshot_time is None:
896
+ if "cursor" in reply:
897
+ ct = reply["cursor"].get("atClusterTime")
898
+ else:
899
+ ct = reply.get("atClusterTime")
900
+ self._snapshot_time = ct
901
+ if self.in_transaction and self._transaction.sharded:
902
+ recovery_token = reply.get("recoveryToken")
903
+ if recovery_token:
904
+ self._transaction.recovery_token = recovery_token
905
+
906
+ @property
907
+ def has_ended(self) -> bool:
908
+ """True if this session is finished."""
909
+ return self._server_session is None
910
+
911
+ @property
912
+ def in_transaction(self) -> bool:
913
+ """True if this session has an active multi-statement transaction.
914
+
915
+ .. versionadded:: 3.10
916
+ """
917
+ return self._transaction.active()
918
+
919
+ @property
920
+ def _starting_transaction(self):
921
+ """True if this session is starting a multi-statement transaction."""
922
+ return self._transaction.starting()
923
+
924
+ @property
925
+ def _pinned_address(self):
926
+ """The mongos address this transaction was created on."""
927
+ if self._transaction.active():
928
+ return self._transaction.pinned_address
929
+ return None
930
+
931
+ @property
932
+ def _pinned_connection(self):
933
+ """The connection this transaction was started on."""
934
+ return self._transaction.pinned_conn
935
+
936
+ def _pin(self, server, sock_info):
937
+ """Pin this session to the given Server or to the given connection."""
938
+ self._transaction.pin(server, sock_info)
939
+
940
+ def _unpin(self):
941
+ """Unpin this session from any pinned Server."""
942
+ self._transaction.unpin()
943
+
944
+ def _txn_read_preference(self):
945
+ """Return read preference of this transaction or None."""
946
+ if self.in_transaction:
947
+ return self._transaction.opts.read_preference
948
+ return None
949
+
950
+ def _materialize(self):
951
+ if isinstance(self._server_session, _EmptyServerSession):
952
+ old = self._server_session
953
+ self._server_session = self._client._topology.get_server_session()
954
+ if old.started_retryable_write:
955
+ self._server_session.inc_transaction_id()
956
+
957
+ def _apply_to(self, command, is_retryable, read_preference, sock_info):
958
+ self._check_ended()
959
+ self._materialize()
960
+ if self.options.snapshot:
961
+ self._update_read_concern(command, sock_info)
962
+
963
+ self._server_session.last_use = time.monotonic()
964
+ command["lsid"] = self._server_session.session_id
965
+
966
+ if is_retryable:
967
+ command["txnNumber"] = self._server_session.transaction_id
968
+ return
969
+
970
+ if self.in_transaction:
971
+ if read_preference != ReadPreference.PRIMARY:
972
+ raise InvalidOperation(
973
+ "read preference in a transaction must be primary, not: "
974
+ "%r" % (read_preference,)
975
+ )
976
+
977
+ if self._transaction.state == _TxnState.STARTING:
978
+ # First command begins a new transaction.
979
+ self._transaction.state = _TxnState.IN_PROGRESS
980
+ command["startTransaction"] = True
981
+
982
+ if self._transaction.opts.read_concern:
983
+ rc = self._transaction.opts.read_concern.document
984
+ if rc:
985
+ command["readConcern"] = rc
986
+ self._update_read_concern(command, sock_info)
987
+
988
+ command["txnNumber"] = self._server_session.transaction_id
989
+ command["autocommit"] = False
990
+
991
+ def _start_retryable_write(self):
992
+ self._check_ended()
993
+ self._server_session.inc_transaction_id()
994
+
995
+ def _update_read_concern(self, cmd, sock_info):
996
+ if self.options.causal_consistency and self.operation_time is not None:
997
+ cmd.setdefault("readConcern", {})["afterClusterTime"] = self.operation_time
998
+ if self.options.snapshot:
999
+ if sock_info.max_wire_version < 13:
1000
+ raise ConfigurationError("Snapshot reads require MongoDB 5.0 or later")
1001
+ rc = cmd.setdefault("readConcern", {})
1002
+ rc["level"] = "snapshot"
1003
+ if self._snapshot_time is not None:
1004
+ rc["atClusterTime"] = self._snapshot_time
1005
+
1006
+ def __copy__(self) -> NoReturn:
1007
+ raise TypeError("A ClientSession cannot be copied, create a new session instead")
1008
+
1009
+
1010
+ class _EmptyServerSession:
1011
+ __slots__ = "dirty", "started_retryable_write"
1012
+
1013
+ def __init__(self):
1014
+ self.dirty = False
1015
+ self.started_retryable_write = False
1016
+
1017
+ def mark_dirty(self):
1018
+ self.dirty = True
1019
+
1020
+ def inc_transaction_id(self):
1021
+ self.started_retryable_write = True
1022
+
1023
+
1024
+ class _ServerSession(object):
1025
+ def __init__(self, generation):
1026
+ # Ensure id is type 4, regardless of CodecOptions.uuid_representation.
1027
+ self.session_id = {"id": Binary(uuid.uuid4().bytes, 4)}
1028
+ self.last_use = time.monotonic()
1029
+ self._transaction_id = 0
1030
+ self.dirty = False
1031
+ self.generation = generation
1032
+
1033
+ def mark_dirty(self):
1034
+ """Mark this session as dirty.
1035
+
1036
+ A server session is marked dirty when a command fails with a network
1037
+ error. Dirty sessions are later discarded from the server session pool.
1038
+ """
1039
+ self.dirty = True
1040
+
1041
+ def timed_out(self, session_timeout_minutes):
1042
+ idle_seconds = time.monotonic() - self.last_use
1043
+
1044
+ # Timed out if we have less than a minute to live.
1045
+ return idle_seconds > (session_timeout_minutes - 1) * 60
1046
+
1047
+ @property
1048
+ def transaction_id(self):
1049
+ """Positive 64-bit integer."""
1050
+ return Int64(self._transaction_id)
1051
+
1052
+ def inc_transaction_id(self):
1053
+ self._transaction_id += 1
1054
+
1055
+
1056
+ class _ServerSessionPool(collections.deque):
1057
+ """Pool of _ServerSession objects.
1058
+
1059
+ This class is not thread-safe, access it while holding the Topology lock.
1060
+ """
1061
+
1062
+ def __init__(self, *args, **kwargs):
1063
+ super(_ServerSessionPool, self).__init__(*args, **kwargs)
1064
+ self.generation = 0
1065
+
1066
+ def reset(self):
1067
+ self.generation += 1
1068
+ self.clear()
1069
+
1070
+ def pop_all(self):
1071
+ ids = []
1072
+ while self:
1073
+ ids.append(self.pop().session_id)
1074
+ return ids
1075
+
1076
+ def get_server_session(self, session_timeout_minutes):
1077
+ # Although the Driver Sessions Spec says we only clear stale sessions
1078
+ # in return_server_session, PyMongo can't take a lock when returning
1079
+ # sessions from a __del__ method (like in Cursor.__die), so it can't
1080
+ # clear stale sessions there. In case many sessions were returned via
1081
+ # __del__, check for stale sessions here too.
1082
+ self._clear_stale(session_timeout_minutes)
1083
+
1084
+ # The most recently used sessions are on the left.
1085
+ while self:
1086
+ s = self.popleft()
1087
+ if not s.timed_out(session_timeout_minutes):
1088
+ return s
1089
+
1090
+ return _ServerSession(self.generation)
1091
+
1092
+ def return_server_session(self, server_session, session_timeout_minutes):
1093
+ if session_timeout_minutes is not None:
1094
+ self._clear_stale(session_timeout_minutes)
1095
+ if server_session.timed_out(session_timeout_minutes):
1096
+ return
1097
+ self.return_server_session_no_lock(server_session)
1098
+
1099
+ def return_server_session_no_lock(self, server_session):
1100
+ # Discard sessions from an old pool to avoid duplicate sessions in the
1101
+ # child process after a fork.
1102
+ if server_session.generation == self.generation and not server_session.dirty:
1103
+ self.appendleft(server_session)
1104
+
1105
+ def _clear_stale(self, session_timeout_minutes):
1106
+ # Clear stale sessions. The least recently used are on the right.
1107
+ while self:
1108
+ if self[-1].timed_out(session_timeout_minutes):
1109
+ self.pop()
1110
+ else:
1111
+ # The remaining sessions also haven't timed out.
1112
+ break