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.
- {kover-2.6.2 → kover-2.7.2}/.gitignore +3 -4
- {kover-2.6.2 → kover-2.7.2}/PKG-INFO +3 -3
- {kover-2.6.2 → kover-2.7.2}/README.md +2 -2
- {kover-2.6.2 → kover-2.7.2}/kover/__init__.py +3 -1
- {kover-2.6.2 → kover-2.7.2}/kover/_internals/_mixins.py +5 -1
- {kover-2.6.2 → kover-2.7.2}/kover/_internals/_serializers.py +6 -6
- kover-2.7.2/kover/bulk_write_builder.py +104 -0
- {kover-2.6.2 → kover-2.7.2}/kover/client.py +146 -82
- {kover-2.6.2 → kover-2.7.2}/kover/collection.py +49 -40
- {kover-2.6.2 → kover-2.7.2}/kover/cursor.py +3 -1
- {kover-2.6.2 → kover-2.7.2}/kover/database.py +6 -4
- {kover-2.6.2 → kover-2.7.2}/kover/enums.py +2 -0
- {kover-2.6.2 → kover-2.7.2}/kover/exceptions.py +2 -0
- {kover-2.6.2 → kover-2.7.2}/kover/gridfs/exceptions.py +3 -0
- {kover-2.6.2 → kover-2.7.2}/kover/gridfs/gridfs.py +13 -15
- {kover-2.6.2 → kover-2.7.2}/kover/gridfs/models.py +2 -0
- {kover-2.6.2 → kover-2.7.2}/kover/helpers.py +43 -5
- {kover-2.6.2 → kover-2.7.2}/kover/metadata/definitions.py +5 -0
- {kover-2.6.2 → kover-2.7.2}/kover/models/operations.py +35 -5
- {kover-2.6.2 → kover-2.7.2}/kover/models/other.py +6 -2
- {kover-2.6.2 → kover-2.7.2}/kover/models/replset.py +7 -1
- {kover-2.6.2 → kover-2.7.2}/kover/network/auth.py +16 -8
- {kover-2.6.2 → kover-2.7.2}/kover/network/compressors.py +11 -1
- {kover-2.6.2 → kover-2.7.2}/kover/network/resolver.py +7 -1
- kover-2.7.2/kover/network/transport.py +187 -0
- {kover-2.6.2 → kover-2.7.2}/kover/network/wirehelper.py +34 -9
- {kover-2.6.2 → kover-2.7.2}/kover/schema.py +37 -10
- {kover-2.6.2 → kover-2.7.2}/kover/session.py +11 -5
- {kover-2.6.2 → kover-2.7.2}/kover/transaction.py +11 -7
- {kover-2.6.2 → kover-2.7.2}/kover/typings.py +14 -2
- {kover-2.6.2 → kover-2.7.2}/kover/uri_parser.py +36 -12
- {kover-2.6.2 → kover-2.7.2}/pyproject.toml +24 -15
- kover-2.6.2/kover/network/transport.py +0 -143
- {kover-2.6.2 → kover-2.7.2}/CODE_OF_CONDUCT.md +0 -0
- {kover-2.6.2 → kover-2.7.2}/LICENSE +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/_internals/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/_helpers.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/binary.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/code.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/codec_options.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/datetime_ms.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/dbref.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/decimal128.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/errors.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/int64.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/json_util.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/max_key.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/min_key.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/objectid.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/raw_bson.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/regex.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/son.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/timestamp.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/typings.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/bson/tz_util.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/codes.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/gridfs/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/metadata/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/models/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/network/__init__.py +0 -0
- {kover-2.6.2 → kover-2.7.2}/kover/py.typed +0 -0
- {kover-2.6.2 → kover-2.7.2}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kover
|
|
3
|
-
Version: 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
|
|
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: **
|
|
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
|
|
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: **
|
|
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.
|
|
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]) #
|
|
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]) #
|
|
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]
|
|
133
|
+
func = origin_map[origin]
|
|
134
134
|
|
|
135
|
-
elif isinstance(attr_t, type) and issubclass(attr_t, Enum): #
|
|
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) #
|
|
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
|
|
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 .
|
|
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("
|
|
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
|
|
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.
|
|
69
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 =
|
|
111
|
-
nodes[0],
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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 =
|
|
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
|
-
|
|
173
|
-
host
|
|
199
|
+
pool = create_connection_pool(
|
|
200
|
+
host, port, max_pool_size, tls=tls, loop=loop)
|
|
174
201
|
|
|
175
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
215
|
-
return Session(document=req["id"],
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
489
|
+
await self.request(command)
|