kover 2.6.2__tar.gz → 2.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {kover-2.6.2 → kover-2.7.2}/.gitignore +3 -4
  2. {kover-2.6.2 → kover-2.7.2}/PKG-INFO +3 -3
  3. {kover-2.6.2 → kover-2.7.2}/README.md +2 -2
  4. {kover-2.6.2 → kover-2.7.2}/kover/__init__.py +3 -1
  5. {kover-2.6.2 → kover-2.7.2}/kover/_internals/_mixins.py +5 -1
  6. {kover-2.6.2 → kover-2.7.2}/kover/_internals/_serializers.py +6 -6
  7. kover-2.7.2/kover/bulk_write_builder.py +104 -0
  8. {kover-2.6.2 → kover-2.7.2}/kover/client.py +146 -82
  9. {kover-2.6.2 → kover-2.7.2}/kover/collection.py +49 -40
  10. {kover-2.6.2 → kover-2.7.2}/kover/cursor.py +3 -1
  11. {kover-2.6.2 → kover-2.7.2}/kover/database.py +6 -4
  12. {kover-2.6.2 → kover-2.7.2}/kover/enums.py +2 -0
  13. {kover-2.6.2 → kover-2.7.2}/kover/exceptions.py +2 -0
  14. {kover-2.6.2 → kover-2.7.2}/kover/gridfs/exceptions.py +3 -0
  15. {kover-2.6.2 → kover-2.7.2}/kover/gridfs/gridfs.py +13 -15
  16. {kover-2.6.2 → kover-2.7.2}/kover/gridfs/models.py +2 -0
  17. {kover-2.6.2 → kover-2.7.2}/kover/helpers.py +43 -5
  18. {kover-2.6.2 → kover-2.7.2}/kover/metadata/definitions.py +5 -0
  19. {kover-2.6.2 → kover-2.7.2}/kover/models/operations.py +35 -5
  20. {kover-2.6.2 → kover-2.7.2}/kover/models/other.py +6 -2
  21. {kover-2.6.2 → kover-2.7.2}/kover/models/replset.py +7 -1
  22. {kover-2.6.2 → kover-2.7.2}/kover/network/auth.py +16 -8
  23. {kover-2.6.2 → kover-2.7.2}/kover/network/compressors.py +11 -1
  24. {kover-2.6.2 → kover-2.7.2}/kover/network/resolver.py +7 -1
  25. kover-2.7.2/kover/network/transport.py +187 -0
  26. {kover-2.6.2 → kover-2.7.2}/kover/network/wirehelper.py +34 -9
  27. {kover-2.6.2 → kover-2.7.2}/kover/schema.py +37 -10
  28. {kover-2.6.2 → kover-2.7.2}/kover/session.py +11 -5
  29. {kover-2.6.2 → kover-2.7.2}/kover/transaction.py +11 -7
  30. {kover-2.6.2 → kover-2.7.2}/kover/typings.py +14 -2
  31. {kover-2.6.2 → kover-2.7.2}/kover/uri_parser.py +36 -12
  32. {kover-2.6.2 → kover-2.7.2}/pyproject.toml +24 -15
  33. kover-2.6.2/kover/network/transport.py +0 -143
  34. {kover-2.6.2 → kover-2.7.2}/CODE_OF_CONDUCT.md +0 -0
  35. {kover-2.6.2 → kover-2.7.2}/LICENSE +0 -0
  36. {kover-2.6.2 → kover-2.7.2}/kover/_internals/__init__.py +0 -0
  37. {kover-2.6.2 → kover-2.7.2}/kover/bson/__init__.py +0 -0
  38. {kover-2.6.2 → kover-2.7.2}/kover/bson/_helpers.py +0 -0
  39. {kover-2.6.2 → kover-2.7.2}/kover/bson/binary.py +0 -0
  40. {kover-2.6.2 → kover-2.7.2}/kover/bson/code.py +0 -0
  41. {kover-2.6.2 → kover-2.7.2}/kover/bson/codec_options.py +0 -0
  42. {kover-2.6.2 → kover-2.7.2}/kover/bson/datetime_ms.py +0 -0
  43. {kover-2.6.2 → kover-2.7.2}/kover/bson/dbref.py +0 -0
  44. {kover-2.6.2 → kover-2.7.2}/kover/bson/decimal128.py +0 -0
  45. {kover-2.6.2 → kover-2.7.2}/kover/bson/errors.py +0 -0
  46. {kover-2.6.2 → kover-2.7.2}/kover/bson/int64.py +0 -0
  47. {kover-2.6.2 → kover-2.7.2}/kover/bson/json_util.py +0 -0
  48. {kover-2.6.2 → kover-2.7.2}/kover/bson/max_key.py +0 -0
  49. {kover-2.6.2 → kover-2.7.2}/kover/bson/min_key.py +0 -0
  50. {kover-2.6.2 → kover-2.7.2}/kover/bson/objectid.py +0 -0
  51. {kover-2.6.2 → kover-2.7.2}/kover/bson/raw_bson.py +0 -0
  52. {kover-2.6.2 → kover-2.7.2}/kover/bson/regex.py +0 -0
  53. {kover-2.6.2 → kover-2.7.2}/kover/bson/son.py +0 -0
  54. {kover-2.6.2 → kover-2.7.2}/kover/bson/timestamp.py +0 -0
  55. {kover-2.6.2 → kover-2.7.2}/kover/bson/typings.py +0 -0
  56. {kover-2.6.2 → kover-2.7.2}/kover/bson/tz_util.py +0 -0
  57. {kover-2.6.2 → kover-2.7.2}/kover/codes.py +0 -0
  58. {kover-2.6.2 → kover-2.7.2}/kover/gridfs/__init__.py +0 -0
  59. {kover-2.6.2 → kover-2.7.2}/kover/metadata/__init__.py +0 -0
  60. {kover-2.6.2 → kover-2.7.2}/kover/models/__init__.py +0 -0
  61. {kover-2.6.2 → kover-2.7.2}/kover/network/__init__.py +0 -0
  62. {kover-2.6.2 → kover-2.7.2}/kover/py.typed +0 -0
  63. {kover-2.6.2 → kover-2.7.2}/requirements.txt +0 -0
@@ -1,6 +1,7 @@
1
+ uv.lock
1
2
  __pycache__
2
3
  .vscode
3
- main.py
4
+ test.py
4
5
  .git
5
6
  out
6
7
  badges.txt
@@ -8,6 +9,4 @@ badges.txt
8
9
  dist
9
10
  .ruff_cache
10
11
  .python-version
11
- .token
12
- uv.lock
13
- kover/bulk
12
+ .token
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kover
3
- Version: 2.6.2
3
+ Version: 2.7.2
4
4
  Summary: Fully async mongodb driver for mongod and replica sets.
5
5
  Project-URL: Repository, https://github.com/megawattka/kover
6
6
  Project-URL: Documentation, https://github.com/megawattka/kover
@@ -62,11 +62,11 @@ if __name__ == "__main__":
62
62
  ```
63
63
 
64
64
  The main reason why i created this project is that Motor - official async wrapper for mongodb, uses ThreadPool executor and it's just a wrapper around pymongo. In general thats slower than clear asyncio and looks more dirty.
65
- - 02.12.24 UPDATE: pymongo added async support but its kinda messed up and not clear. pymongo's code looks dirty and have lots of unnecessary things. kover almost 1.5-2 times faster than pymongo.
65
+ - 02.12.24 UPDATE: pymongo added async support but its kinda messed up. pymongo's code looks complicated, dirty and unclean.
66
66
 
67
67
  # Status
68
68
  it still missing features. <br>
69
- e.g: **bulk write API** and **Encryption**
69
+ e.g: **Encryption** and **AWS auth**
70
70
  but its already very cool! <br>
71
71
 
72
72
  # Dependencies
@@ -32,11 +32,11 @@ if __name__ == "__main__":
32
32
  ```
33
33
 
34
34
  The main reason why i created this project is that Motor - official async wrapper for mongodb, uses ThreadPool executor and it's just a wrapper around pymongo. In general thats slower than clear asyncio and looks more dirty.
35
- - 02.12.24 UPDATE: pymongo added async support but its kinda messed up and not clear. pymongo's code looks dirty and have lots of unnecessary things. kover almost 1.5-2 times faster than pymongo.
35
+ - 02.12.24 UPDATE: pymongo added async support but its kinda messed up. pymongo's code looks complicated, dirty and unclean.
36
36
 
37
37
  # Status
38
38
  it still missing features. <br>
39
- e.g: **bulk write API** and **Encryption**
39
+ e.g: **Encryption** and **AWS auth**
40
40
  but its already very cool! <br>
41
41
 
42
42
  # Dependencies
@@ -20,11 +20,12 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
- __version__ = "2.6.2"
23
+ __version__ = "2.7.2"
24
24
  __author__ = "megawattka"
25
25
  __license__ = "MIT"
26
26
  __copyright__ = "Copyright (C) 2024-present megawattka"
27
27
 
28
+ from .bulk_write_builder import BulkWriteBuilder
28
29
  from .client import Kover
29
30
  from .collection import Collection
30
31
  from .cursor import Cursor
@@ -65,6 +66,7 @@ from .typings import xJsonT
65
66
  __all__ = (
66
67
  "AuthCredentials",
67
68
  "BuildInfo",
69
+ "BulkWriteBuilder",
68
70
  "Collation",
69
71
  "CollationStrength",
70
72
  "Collection",
@@ -28,7 +28,11 @@ class ModelMixin(BaseModel):
28
28
  )
29
29
 
30
30
  def to_dict(self) -> xJsonT:
31
- """Convert the model to a dictionary with camel case keys."""
31
+ """Convert the model to a dictionary with camel case keys.
32
+
33
+ Returns:
34
+ A dictionary representation of the model with camel case keys.
35
+ """
32
36
  return self.model_dump(by_alias=True, exclude_none=True)
33
37
 
34
38
  def __str__(self) -> str:
@@ -52,11 +52,11 @@ def _serialize_literal(
52
52
  *,
53
53
  is_optional: bool = False,
54
54
  ) -> xJsonT:
55
- args = [
55
+ args: list[Any] = [
56
56
  x.value if issubclass(x.__class__, Enum)
57
57
  else x for x in attr_t.__args__
58
58
  ]
59
- dtypes = chain([_lookup_type(type(val)) for val in args]) # type: ignore
59
+ dtypes = chain([_lookup_type(type(val)) for val in args]) # pyright: ignore[reportUnknownArgumentType]
60
60
  return {
61
61
  "enum": args + ([None] if is_optional else []),
62
62
  "bsonType": sorted(set(dtypes)),
@@ -93,7 +93,7 @@ def _serialize_enum(
93
93
  is_optional: bool = False,
94
94
  ) -> xJsonT:
95
95
  values = [z.value for z in attr_t]
96
- dtypes = chain([_lookup_type(type(val)) for val in values]) # type: ignore
96
+ dtypes = chain([_lookup_type(type(val)) for val in values]) # pyright: ignore[reportUnknownArgumentType]
97
97
  return {
98
98
  "enum": values + ([None] if is_optional else []),
99
99
  "bsonType": sorted(set(dtypes)) + (
@@ -130,14 +130,14 @@ def value_to_json_schema(
130
130
  dict: _serialize_dict,
131
131
  }
132
132
  if origin in origin_map:
133
- func = origin_map[origin] # type: ignore
133
+ func = origin_map[origin]
134
134
 
135
- elif isinstance(attr_t, type) and issubclass(attr_t, Enum): # type: ignore
135
+ elif isinstance(attr_t, type) and issubclass(attr_t, Enum): # pyright: ignore[reportUnnecessaryIsInstance]
136
136
  func = _serialize_enum
137
137
 
138
138
  elif attr_t in _TYPE_MAP:
139
139
  func = _serialize_simple_type
140
140
  if func is not None:
141
- return func(attr_t, is_optional=is_optional) # type: ignore
141
+ return func(attr_t, is_optional=is_optional) # pyright: ignore[reportArgumentType]
142
142
 
143
143
  return None # ruff requires this to be explicit
@@ -0,0 +1,104 @@
1
+ """Builder for kover BulkWrite operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from .helpers import filter_non_null
8
+
9
+ if TYPE_CHECKING:
10
+ from .models import Delete, Update
11
+ from .typings import xJsonT
12
+
13
+
14
+ # https://www.mongodb.com/docs/manual/reference/command/bulkWrite
15
+ class BulkWriteBuilder:
16
+ """Builder for bulk write operations."""
17
+
18
+ def __init__(
19
+ self,
20
+ *,
21
+ ordered: bool = True,
22
+ bypass_document_validation: bool = False,
23
+ comment: str | None = None,
24
+ let: xJsonT | None = None,
25
+ errors_only: bool = False,
26
+ cursor_batch_size: int | None = None,
27
+ write_concern: str = "majority",
28
+ ) -> None:
29
+ self.ordered = ordered
30
+ self.bypass_document_validation = bypass_document_validation
31
+ self.comment = comment
32
+ self.let = let
33
+ self.errors_only = errors_only
34
+ self.cursor = {"batchSize": cursor_batch_size or 101}
35
+ self.write_concern = write_concern
36
+ self._operations: list[xJsonT] = []
37
+ self._namespaces: list[str] = []
38
+
39
+ def _prepair_additional_params(self) -> xJsonT:
40
+ return filter_non_null({
41
+ "ordered": self.ordered,
42
+ "bypassDocumentValidation": self.bypass_document_validation,
43
+ "comment": self.comment,
44
+ "let": self.let,
45
+ "errorsOnly": self.errors_only,
46
+ "cursor": self.cursor,
47
+ "writeConcern": {"w": self.write_concern},
48
+ })
49
+
50
+ def _get_ns_idx(self, namespace: str) -> int:
51
+ if namespace not in self._namespaces:
52
+ self._namespaces.append(namespace)
53
+ return self._namespaces.index(namespace)
54
+
55
+ def add_insert(
56
+ self,
57
+ documents: list[xJsonT],
58
+ /,
59
+ *,
60
+ ns: str,
61
+ ) -> None:
62
+ """Adds an Insert operations into the builder."""
63
+ idx = self._get_ns_idx(ns)
64
+ for document in documents:
65
+ self._operations.append({"insert": idx, "document": document})
66
+
67
+ def add_update(
68
+ self,
69
+ *updates: Update,
70
+ ns: str,
71
+ ) -> None:
72
+ """Adds an Update operations into the builder."""
73
+ idx = self._get_ns_idx(ns)
74
+ for update in updates:
75
+ self._operations.append({
76
+ "update": idx,
77
+ **update.as_bulk_write_op(),
78
+ })
79
+
80
+ def add_delete(
81
+ self,
82
+ *deletes: Delete,
83
+ ns: str,
84
+ ) -> None:
85
+ """Adds an Delete operations into the builder."""
86
+ idx = self._get_ns_idx(ns)
87
+ for delete in deletes:
88
+ self._operations.append({
89
+ "delete": idx,
90
+ **delete.as_bulk_write_op(),
91
+ })
92
+
93
+ def build(self) -> xJsonT:
94
+ """Build the command.
95
+
96
+ Returns:
97
+ The command that was built for Kover.bulk_write method.
98
+ """
99
+ return {
100
+ "bulkWrite": 1,
101
+ "ops": self._operations,
102
+ "nsInfo": [{"ns": ns} for ns in self._namespaces],
103
+ **self._prepair_additional_params(),
104
+ }
@@ -1,72 +1,89 @@
1
+ """Kover Client Module."""
2
+
1
3
  from __future__ import annotations
2
4
 
5
+ import asyncio
3
6
  import json
4
7
  from typing import TYPE_CHECKING, Literal
5
8
 
6
9
  from typing_extensions import Self
7
10
 
8
11
  from .database import Database
9
- from .helpers import classrepr, filter_non_null, maybe_to_dict
12
+ from .helpers import (
13
+ classrepr,
14
+ filter_non_null,
15
+ maybe_to_dict,
16
+ )
10
17
  from .models import BuildInfo, WriteConcern
11
18
  from .network import MongoTransport, SrvResolver
12
19
  from .session import Session
20
+ from .typings import DEFAULT_MONGODB_PORT
13
21
  from .uri_parser import parse_uri
14
22
 
15
23
  if TYPE_CHECKING:
16
- import asyncio
17
-
18
24
  from .models import ReplicaSetConfig
19
25
  from .network import AuthCredentials
20
- from .typings import COMPRESSION_T, xJsonT
26
+ from .transaction import Transaction
27
+ from .typings import COMPRESSION_T, DocumentT, xJsonT
28
+
29
+
30
+ def create_connection_pool(
31
+ host: str,
32
+ port: int,
33
+ size: int,
34
+ *,
35
+ tls: bool,
36
+ loop: asyncio.AbstractEventLoop | None = None,
37
+ ) -> asyncio.Queue[MongoTransport]:
38
+ """Gives us a filled connection pool.
39
+
40
+ Returns:
41
+ Pool, filled with non connected connections.
42
+ """
43
+ pool: asyncio.Queue[MongoTransport] = asyncio.Queue()
44
+ for _ in range(size):
45
+ pool.put_nowait(MongoTransport(host, port, loop=loop, tls=tls))
46
+ return pool
21
47
 
22
48
 
23
- @classrepr("signature", "transport", "is_primary")
49
+ @classrepr("_write_concern", "_compression", "_application")
24
50
  class Kover:
25
- """Kover client for interacting with a MongoDB server.
26
-
27
- Attributes:
28
- transport : The underlying transport to the MongoDB server.
29
- signature : The authentication signature, if authenticated.
30
- default_database : The name of the default database.
31
- is_primary : The flag that tells us whether that
32
- this is primary or secondary instance
33
- """
51
+ """Kover client for interacting with a MongoDB server."""
34
52
 
35
53
  def __init__(
36
54
  self,
37
- transport: MongoTransport,
38
- signature: bytes | None,
39
55
  *,
40
- is_primary: bool = False,
41
56
  w: str | int = "majority",
57
+ pool: asyncio.Queue[MongoTransport],
58
+ credentials: AuthCredentials | None = None,
59
+ compression: COMPRESSION_T | None = None,
60
+ application: xJsonT | None = None,
42
61
  ) -> None:
43
- self.transport = transport
44
- self.signature = signature
45
- self._is_primary = is_primary
46
62
  self._write_concern = WriteConcern(w=w)
63
+ self._pool = pool
64
+ self._credentials = credentials
65
+ self._compression = compression
66
+ self._application = application
47
67
 
48
68
  async def __aenter__(self) -> Self:
49
69
  return self
50
70
 
51
71
  async def __aexit__(self, *exc: object) -> bool:
52
- if self.signature is not None:
53
- await self.logout()
54
72
  await self.close()
55
73
  return True
56
74
 
57
- @property
58
- def is_primary(self) -> bool:
59
- """Whether this instance is a primary or not."""
60
- return self._is_primary
61
-
62
75
  async def close(self) -> None:
63
- """Close the underlying transport connection.
76
+ """Close the underlying transport connections.
64
77
 
65
78
  This method closes the transport writer
66
79
  and waits until the connection is fully closed.
67
80
  """
68
- self.transport.writer.close()
69
- await self.transport.writer.wait_closed()
81
+ if self._pool.empty():
82
+ return
83
+
84
+ while not self._pool.empty():
85
+ conn = await self._pool.get()
86
+ await conn.close()
70
87
 
71
88
  def get_database(self, name: str) -> Database:
72
89
  """Get a Database instance for the specified database name.
@@ -90,7 +107,11 @@ class Kover:
90
107
  j: bool | None = None,
91
108
  wtimeout: int = 0,
92
109
  ) -> Self:
93
- """This sets a WriteConcern for all requests."""
110
+ """This sets a WriteConcern for all requests.
111
+
112
+ Returns:
113
+ The Kover client instance with the updated write concern.
114
+ """
94
115
  self._write_concern = WriteConcern(w=w, j=j, wtimeout=wtimeout)
95
116
  return self
96
117
 
@@ -100,49 +121,54 @@ class Kover:
100
121
  uri: str,
101
122
  loop: asyncio.AbstractEventLoop | None = None,
102
123
  ) -> Kover:
103
- """Create an instance of Kover client by passing a uri."""
124
+ """Create an instance of Kover client by passing a uri.
125
+
126
+ Parameters:
127
+ uri : The uri itself.
128
+ loop : Optional asyncio loop
129
+
130
+ Returns:
131
+ An instance of newly created Kover client.
132
+ """
104
133
  parsed = parse_uri(uri)
105
134
 
106
- resolver = SrvResolver()
107
- nodes = await resolver.get_nodes(parsed.hostname)
108
- assert nodes, "Node resolution failed."
135
+ if parsed.scheme == "mongodb+srv":
136
+ resolver = SrvResolver()
137
+ nodes = await resolver.get_nodes(parsed.hostname)
138
+ assert nodes, "Node resolution failed."
139
+ else:
140
+ nodes = [parsed.hostname]
109
141
 
110
- transport = await MongoTransport.make(
111
- nodes[0], port=parsed.port, tls=parsed.tls, loop=loop)
142
+ transport = MongoTransport(
143
+ nodes[0], parsed.port, loop=loop, tls=parsed.tls)
112
144
 
145
+ await transport.connect()
113
146
  hello = await transport.hello(
114
147
  parsed.compressors, parsed.credentials, parsed.application)
148
+ await transport.close()
115
149
 
116
150
  if not hello.is_primary:
117
151
  assert hello.primary_node, "Primary node resolution failed."
118
152
 
119
153
  host, _ = hello.primary_node.split(":")
120
- return await cls.make_client(
121
- host=host,
122
- port=parsed.port,
123
- credentials=parsed.credentials,
124
- loop=loop,
125
- compression=parsed.compressors,
126
- tls=parsed.tls,
127
- application=parsed.application,
128
- write_concern=parsed.write_concern,
129
- )
154
+ nodes = [host]
130
155
 
131
- mechanism = hello.get_auth_mechanism()
132
- signature = await transport.authorize(mechanism, parsed.credentials)
156
+ args = (nodes[0], parsed.port, parsed.max_pool_size)
157
+ pool = create_connection_pool(*args, tls=parsed.tls, loop=loop)
133
158
 
134
159
  return cls(
135
- transport=transport,
136
- signature=signature,
137
- is_primary=hello.is_primary,
138
160
  w=parsed.write_concern,
161
+ pool=pool,
162
+ credentials=parsed.credentials,
163
+ compression=parsed.compressors,
164
+ application=parsed.application,
139
165
  )
140
166
 
141
167
  @classmethod
142
168
  async def make_client(
143
169
  cls,
144
170
  host: str = "127.0.0.1",
145
- port: int = 27017,
171
+ port: int = DEFAULT_MONGODB_PORT,
146
172
  *,
147
173
  credentials: AuthCredentials | None = None,
148
174
  loop: asyncio.AbstractEventLoop | None = None,
@@ -150,6 +176,7 @@ class Kover:
150
176
  tls: bool = False,
151
177
  application: xJsonT | None = None,
152
178
  write_concern: str | int = "majority",
179
+ max_pool_size: int = 100,
153
180
  ) -> Kover:
154
181
  """Create and return a new Kover client instance.
155
182
 
@@ -169,23 +196,60 @@ class Kover:
169
196
  Returns:
170
197
  An instance of the Kover client.
171
198
  """
172
- transport = await MongoTransport.make(
173
- host=host, port=port, loop=loop, tls=tls)
199
+ pool = create_connection_pool(
200
+ host, port, max_pool_size, tls=tls, loop=loop)
174
201
 
175
- hello = await transport.hello(
202
+ return cls(
203
+ w=write_concern,
204
+ pool=pool,
176
205
  credentials=credentials,
177
206
  compression=compression,
178
207
  application=application,
179
208
  )
180
- mechanism = hello.get_auth_mechanism()
181
- signature = await transport.authorize(mechanism, credentials)
182
209
 
183
- return cls(
184
- transport,
185
- signature,
186
- is_primary=hello.is_primary,
187
- w=write_concern,
188
- )
210
+ async def request(
211
+ self,
212
+ doc: DocumentT,
213
+ *,
214
+ db_name: str = "admin",
215
+ transaction: Transaction | None = None,
216
+ wait_response: bool = True,
217
+ ) -> xJsonT:
218
+ """Send a request to MongoDB Server.
219
+
220
+ Returns:
221
+ Document, containing response from the server.
222
+ """
223
+ conn = await self._pool.get()
224
+ if not conn.is_connected:
225
+ await conn.connect()
226
+ hello = await conn.hello(
227
+ self._compression, self._credentials, self._application)
228
+
229
+ if hello.requires_auth:
230
+ mechanism = hello.get_auth_mechanism()
231
+ await conn.authorize(mechanism, credentials=self._credentials)
232
+ try:
233
+ return await conn.request(
234
+ doc,
235
+ db_name=db_name,
236
+ transaction=transaction,
237
+ wait_response=wait_response,
238
+ )
239
+ finally:
240
+ await self._pool.put(conn)
241
+
242
+ async def bulk_write(
243
+ self,
244
+ document: xJsonT,
245
+ transaction: Transaction | None = None,
246
+ ) -> xJsonT:
247
+ """Execute a bulkWrite operation and return info about it.
248
+
249
+ Returns:
250
+ Document, containing info about this operation.
251
+ """
252
+ return await self.request(document, transaction=transaction)
189
253
 
190
254
  async def refresh_sessions(self, sessions: list[Session]) -> None:
191
255
  """Refresh the provided list of sessions.
@@ -194,7 +258,7 @@ class Kover:
194
258
  sessions : A list of Session objects to be refreshed.
195
259
  """
196
260
  documents: list[xJsonT] = [x.document for x in sessions]
197
- await self.transport.request({"refreshSessions": documents})
261
+ await self.request({"refreshSessions": documents})
198
262
 
199
263
  async def end_sessions(self, sessions: list[Session]) -> None:
200
264
  """End the provided list of sessions.
@@ -203,7 +267,7 @@ class Kover:
203
267
  sessions : A list of Session objects to be ended.
204
268
  """
205
269
  documents: list[xJsonT] = [x.document for x in sessions]
206
- await self.transport.request({"endSessions": documents})
270
+ await self.request({"endSessions": documents})
207
271
 
208
272
  async def start_session(self) -> Session:
209
273
  """Start a new session.
@@ -211,8 +275,8 @@ class Kover:
211
275
  Returns:
212
276
  An instance of the Session class representing the started session.
213
277
  """
214
- req = await self.transport.request({"startSession": 1.0})
215
- return Session(document=req["id"], transport=self.transport)
278
+ req = await self.request({"startSession": 1.0})
279
+ return Session(document=req["id"], client=self)
216
280
 
217
281
  async def build_info(self) -> BuildInfo:
218
282
  """Retrieve build information from the MongoDB server.
@@ -220,7 +284,7 @@ class Kover:
220
284
  Returns:
221
285
  An instance of BuildInfo containing server build details.
222
286
  """
223
- request = await self.transport.request({"buildInfo": 1.0})
287
+ request = await self.request({"buildInfo": 1.0})
224
288
  return BuildInfo.model_validate(request)
225
289
 
226
290
  async def logout(self) -> None:
@@ -229,7 +293,7 @@ class Kover:
229
293
  This method sends a logout request to the server
230
294
  to terminate the current authenticated session.
231
295
  """
232
- await self.transport.request({"logout": 1.0})
296
+ await self.request({"logout": 1.0})
233
297
 
234
298
  async def list_database_names(self) -> list[str]:
235
299
  """Retrieve the names of all databases on the MongoDB server.
@@ -241,7 +305,7 @@ class Kover:
241
305
  "listDatabases": 1.0,
242
306
  "nameOnly": True # noqa: COM812
243
307
  }
244
- request = await self.transport.request(command)
308
+ request = await self.request(command)
245
309
  return [x["name"] for x in request["databases"]]
246
310
 
247
311
  async def drop_database(self, name: str) -> None:
@@ -250,7 +314,7 @@ class Kover:
250
314
  Parameters:
251
315
  name : The name of the database to drop.
252
316
  """
253
- await self.transport.request({"dropDatabase": 1.0}, db_name=name)
317
+ await self.request({"dropDatabase": 1.0}, db_name=name)
254
318
 
255
319
  # https://www.mongodb.com/docs/manual/reference/command/replSetInitiate/
256
320
  async def replica_set_initiate(
@@ -264,7 +328,7 @@ class Kover:
264
328
  default configuration is used.
265
329
  """
266
330
  document = maybe_to_dict(config) or {}
267
- await self.transport.request({"replSetInitiate": document})
331
+ await self.request({"replSetInitiate": document})
268
332
 
269
333
  # https://www.mongodb.com/docs/manual/reference/command/replSetReconfig/
270
334
  async def replica_set_reconfig(
@@ -284,7 +348,7 @@ class Kover:
284
348
  "force": force,
285
349
  "maxTimeMS": max_time_ms,
286
350
  })
287
- await self.transport.request(document)
351
+ await self.request(document)
288
352
 
289
353
  # https://www.mongodb.com/docs/manual/reference/command/replSetGetStatus/
290
354
  async def get_replica_set_status(self) -> xJsonT:
@@ -293,7 +357,7 @@ class Kover:
293
357
  Returns:
294
358
  A JSON document containing the replica set status information.
295
359
  """
296
- return await self.transport.request({"replSetGetStatus": 1.0})
360
+ return await self.request({"replSetGetStatus": 1.0})
297
361
 
298
362
  # https://www.mongodb.com/docs/manual/reference/command/shutdown/
299
363
  async def shutdown(
@@ -316,7 +380,7 @@ class Kover:
316
380
  "timeoutSecs": timeout,
317
381
  "comment": comment,
318
382
  })
319
- await self.transport.request(command, wait_response=False)
383
+ await self.request(command, wait_response=False)
320
384
 
321
385
  # https://www.mongodb.com/docs/manual/reference/command/getCmdLineOpts/
322
386
  async def get_commandline(self) -> list[str]:
@@ -325,7 +389,7 @@ class Kover:
325
389
  Returns:
326
390
  A list of command line arguments.
327
391
  """
328
- r = await self.transport.request({"getCmdLineOpts": 1.0})
392
+ r = await self.request({"getCmdLineOpts": 1.0})
329
393
  return r["argv"]
330
394
 
331
395
  # https://www.mongodb.com/docs/manual/reference/command/getLog/#getlog
@@ -342,7 +406,7 @@ class Kover:
342
406
  Returns:
343
407
  A list of log entries as JSON objects.
344
408
  """
345
- r = await self.transport.request({"getLog": parameter})
409
+ r = await self.request({"getLog": parameter})
346
410
  return [
347
411
  json.loads(info) for info in r["log"]
348
412
  ]
@@ -371,7 +435,7 @@ class Kover:
371
435
  "dropTarget": drop_target,
372
436
  "comment": comment,
373
437
  })
374
- await self.transport.request(command)
438
+ await self.request(command)
375
439
 
376
440
  # https://www.mongodb.com/docs/manual/reference/command/setUserWriteBlockMode/
377
441
  async def set_user_write_block_mode(self, *, param: bool) -> None:
@@ -381,7 +445,7 @@ class Kover:
381
445
  param : Blocks writes on a cluster when set to true.
382
446
  To enable writes on a cluster, set global: false.
383
447
  """
384
- await self.transport.request({
448
+ await self.request({
385
449
  "setUserWriteBlockMode": 1.0,
386
450
  "global": param,
387
451
  })
@@ -409,7 +473,7 @@ class Kover:
409
473
  "lock": lock,
410
474
  "comment": comment,
411
475
  })
412
- await self.transport.request(command)
476
+ await self.request(command)
413
477
 
414
478
  # https://www.mongodb.com/docs/manual/reference/command/fsyncUnlock/
415
479
  async def fsync_unlock(self, comment: str | None = None) -> None:
@@ -422,4 +486,4 @@ class Kover:
422
486
  "fsyncUnlock": 1.0,
423
487
  "comment": comment,
424
488
  })
425
- await self.transport.request(command)
489
+ await self.request(command)