hexkit 7.2.0__tar.gz → 7.3.0__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.
- {hexkit-7.2.0/src/hexkit.egg-info → hexkit-7.3.0}/PKG-INFO +25 -16
- {hexkit-7.2.0 → hexkit-7.3.0}/pyproject.toml +28 -16
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/correlation.py +10 -5
- hexkit-7.3.0/src/hexkit/protocols/kvstore.py +55 -0
- hexkit-7.3.0/src/hexkit/providers/mongodb/__init__.py +41 -0
- hexkit-7.3.0/src/hexkit/providers/mongodb/config.py +55 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongodb/migrations/_manager.py +1 -1
- hexkit-7.3.0/src/hexkit/providers/mongodb/provider/__init__.py +52 -0
- hexkit-7.3.0/src/hexkit/providers/mongodb/provider/client.py +102 -0
- hexkit-7.2.0/src/hexkit/providers/mongodb/provider.py → hexkit-7.3.0/src/hexkit/providers/mongodb/provider/dao.py +15 -162
- hexkit-7.3.0/src/hexkit/providers/mongodb/provider/kvstore.py +230 -0
- hexkit-7.3.0/src/hexkit/providers/mongodb/provider/utils.py +66 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongodb/testutils.py +4 -7
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongokafka/__init__.py +1 -1
- {hexkit-7.2.0/src/hexkit/providers/mongokafka/provider → hexkit-7.3.0/src/hexkit/providers/mongokafka}/config.py +1 -1
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongokafka/provider/__init__.py +2 -3
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongokafka/provider/daopub.py +2 -2
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +6 -1
- hexkit-7.3.0/src/hexkit/providers/redis/__init__.py +35 -0
- hexkit-7.3.0/src/hexkit/providers/redis/config.py +48 -0
- {hexkit-7.2.0/src/hexkit/providers/mongodb → hexkit-7.3.0/src/hexkit/providers/redis/provider}/__init__.py +17 -4
- hexkit-7.3.0/src/hexkit/providers/redis/provider/client.py +53 -0
- hexkit-7.3.0/src/hexkit/providers/redis/provider/kvstore.py +250 -0
- hexkit-7.3.0/src/hexkit/providers/redis/testutils.py +147 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/provider.py +1 -1
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/testutils/_utils.py +5 -5
- hexkit-7.3.0/src/hexkit/providers/testing/dao.py +475 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/utils.py +9 -1
- {hexkit-7.2.0 → hexkit-7.3.0/src/hexkit.egg-info}/PKG-INFO +25 -16
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit.egg-info/SOURCES.txt +14 -2
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit.egg-info/requires.txt +27 -15
- hexkit-7.2.0/src/hexkit/providers/testing/dao.py +0 -146
- {hexkit-7.2.0 → hexkit-7.3.0}/LICENSE +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/MANIFEST.in +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/README.md +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/setup.cfg +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/__main__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/base.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/config.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/custom_types.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/log.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/opentelemetry.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/dao.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/daopub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/daosub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/eventpub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/eventsub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/protocols/objstorage.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/config.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/akafka/testutils.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongodb/migrations/helpers.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/testing/__init__.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/testing/eventpub.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/providers/testing/s3.py +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit/py.typed +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
- {hexkit-7.2.0 → hexkit-7.3.0}/src/hexkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexkit
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.3.0
|
|
4
4
|
Summary: A Toolkit for Building Microservices using the Hexagonal Architecture
|
|
5
5
|
Author-email: "German Human Genome Phenome Archive (GHGA)" <contact@ghga.de>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -18,39 +18,45 @@ Classifier: Intended Audience :: Developers
|
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: opentelemetry-api<2,>=1.
|
|
22
|
-
Requires-Dist: pydantic<3,>=2.
|
|
23
|
-
Requires-Dist: pydantic_settings<3,>=2.
|
|
21
|
+
Requires-Dist: opentelemetry-api<2,>=1.39
|
|
22
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
23
|
+
Requires-Dist: pydantic_settings<3,>=2.12
|
|
24
24
|
Requires-Dist: PyYAML<7,>=6
|
|
25
25
|
Provides-Extra: akafka
|
|
26
|
-
Requires-Dist: aiokafka[lz4,snappy,zstd]~=0.
|
|
26
|
+
Requires-Dist: aiokafka[lz4,snappy,zstd]~=0.13.0; extra == "akafka"
|
|
27
27
|
Requires-Dist: jsonschema<5,>=4.25; extra == "akafka"
|
|
28
28
|
Provides-Extra: s3
|
|
29
|
-
Requires-Dist: boto3<2,>=1.
|
|
30
|
-
Requires-Dist: botocore<2,>=1.
|
|
29
|
+
Requires-Dist: boto3<2,>=1.42; extra == "s3"
|
|
30
|
+
Requires-Dist: botocore<2,>=1.42; extra == "s3"
|
|
31
31
|
Provides-Extra: mongodb
|
|
32
32
|
Requires-Dist: pymongo<5,>=4.15; extra == "mongodb"
|
|
33
|
+
Provides-Extra: redis
|
|
34
|
+
Requires-Dist: redis<8,>=7.1; extra == "redis"
|
|
33
35
|
Provides-Extra: opentelemetry-base
|
|
34
|
-
Requires-Dist: opentelemetry-sdk<2,>=1.
|
|
35
|
-
Requires-Dist: opentelemetry-exporter-otlp<2,>=1.
|
|
36
|
-
Requires-Dist: opentelemetry-instrumentation>=0.
|
|
37
|
-
Requires-Dist: opentelemetry-instrumentation-httpx>=0.
|
|
38
|
-
Requires-Dist: opentelemetry-distro>=0.
|
|
36
|
+
Requires-Dist: opentelemetry-sdk<2,>=1.39.1; extra == "opentelemetry-base"
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp<2,>=1.39.1; extra == "opentelemetry-base"
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation>=0.60b1; extra == "opentelemetry-base"
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "opentelemetry-base"
|
|
40
|
+
Requires-Dist: opentelemetry-distro>=0.60b1; extra == "opentelemetry-base"
|
|
39
41
|
Provides-Extra: opentelemetry-akafka
|
|
40
42
|
Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-akafka"
|
|
41
43
|
Requires-Dist: hexkit[akafka]; extra == "opentelemetry-akafka"
|
|
42
|
-
Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.
|
|
44
|
+
Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.60b1; extra == "opentelemetry-akafka"
|
|
43
45
|
Provides-Extra: opentelemetry-mongodb
|
|
44
46
|
Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-mongodb"
|
|
45
47
|
Requires-Dist: hexkit[mongodb]; extra == "opentelemetry-mongodb"
|
|
46
|
-
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.
|
|
48
|
+
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.60b1; extra == "opentelemetry-mongodb"
|
|
47
49
|
Provides-Extra: opentelemetry-fastapi
|
|
48
50
|
Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-fastapi"
|
|
49
|
-
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.
|
|
51
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "opentelemetry-fastapi"
|
|
50
52
|
Provides-Extra: opentelemetry-s3
|
|
51
53
|
Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-s3"
|
|
52
54
|
Requires-Dist: hexkit[s3]; extra == "opentelemetry-s3"
|
|
53
|
-
Requires-Dist: opentelemetry-instrumentation-botocore>=0.
|
|
55
|
+
Requires-Dist: opentelemetry-instrumentation-botocore>=0.60b1; extra == "opentelemetry-s3"
|
|
56
|
+
Provides-Extra: opentelemetry-redis
|
|
57
|
+
Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-redis"
|
|
58
|
+
Requires-Dist: hexkit[redis]; extra == "opentelemetry-redis"
|
|
59
|
+
Requires-Dist: opentelemetry-instrumentation-redis>=0.60b1; extra == "opentelemetry-redis"
|
|
54
60
|
Provides-Extra: opentelemetry
|
|
55
61
|
Requires-Dist: hexkit[opentelemetry-akafka]; extra == "opentelemetry"
|
|
56
62
|
Requires-Dist: hexkit[opentelemetry-mongodb]; extra == "opentelemetry"
|
|
@@ -65,6 +71,9 @@ Requires-Dist: testcontainers<5,>=4.13; extra == "test-s3"
|
|
|
65
71
|
Provides-Extra: test-mongodb
|
|
66
72
|
Requires-Dist: hexkit[mongodb]; extra == "test-mongodb"
|
|
67
73
|
Requires-Dist: testcontainers[mongo]<5,>=4.13; extra == "test-mongodb"
|
|
74
|
+
Provides-Extra: test-redis
|
|
75
|
+
Requires-Dist: hexkit[redis]; extra == "test-redis"
|
|
76
|
+
Requires-Dist: testcontainers[redis]<5,>=4.13; extra == "test-redis"
|
|
68
77
|
Provides-Extra: test
|
|
69
78
|
Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
|
|
70
79
|
Provides-Extra: all
|
|
@@ -23,12 +23,12 @@ classifiers = [
|
|
|
23
23
|
"Intended Audience :: Developers",
|
|
24
24
|
]
|
|
25
25
|
name = "hexkit"
|
|
26
|
-
version = "7.
|
|
26
|
+
version = "7.3.0"
|
|
27
27
|
description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
|
|
28
28
|
dependencies = [
|
|
29
|
-
"opentelemetry-api >=1.
|
|
30
|
-
"pydantic >=2.
|
|
31
|
-
"pydantic_settings >=2.
|
|
29
|
+
"opentelemetry-api >=1.39, <2",
|
|
30
|
+
"pydantic >=2.12, <3",
|
|
31
|
+
"pydantic_settings >=2.12, <3",
|
|
32
32
|
"PyYAML >=6, <7",
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -37,41 +37,49 @@ text = "Apache 2.0"
|
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
39
39
|
akafka = [
|
|
40
|
-
"aiokafka[zstd,lz4,snappy]~=0.
|
|
40
|
+
"aiokafka[zstd,lz4,snappy]~=0.13.0",
|
|
41
41
|
"jsonschema >=4.25, <5",
|
|
42
42
|
]
|
|
43
43
|
s3 = [
|
|
44
|
-
"boto3 >=1.
|
|
45
|
-
"botocore >=1.
|
|
44
|
+
"boto3 >=1.42, <2",
|
|
45
|
+
"botocore >=1.42, <2",
|
|
46
46
|
]
|
|
47
47
|
mongodb = [
|
|
48
48
|
"pymongo >=4.15, <5",
|
|
49
49
|
]
|
|
50
|
+
redis = [
|
|
51
|
+
"redis >=7.1, <8",
|
|
52
|
+
]
|
|
50
53
|
opentelemetry-base = [
|
|
51
|
-
"opentelemetry-sdk >=1.
|
|
52
|
-
"opentelemetry-exporter-otlp >=1.
|
|
53
|
-
"opentelemetry-instrumentation >=0.
|
|
54
|
-
"opentelemetry-instrumentation-httpx >=0.
|
|
55
|
-
"opentelemetry-distro >=0.
|
|
54
|
+
"opentelemetry-sdk >=1.39.1, <2",
|
|
55
|
+
"opentelemetry-exporter-otlp >=1.39.1, <2",
|
|
56
|
+
"opentelemetry-instrumentation >=0.60b1",
|
|
57
|
+
"opentelemetry-instrumentation-httpx >=0.60b1",
|
|
58
|
+
"opentelemetry-distro >=0.60b1",
|
|
56
59
|
]
|
|
57
60
|
opentelemetry-akafka = [
|
|
58
61
|
"hexkit[opentelemetry-base]",
|
|
59
62
|
"hexkit[akafka]",
|
|
60
|
-
"opentelemetry-instrumentation-aiokafka >=0.
|
|
63
|
+
"opentelemetry-instrumentation-aiokafka >=0.60b1",
|
|
61
64
|
]
|
|
62
65
|
opentelemetry-mongodb = [
|
|
63
66
|
"hexkit[opentelemetry-base]",
|
|
64
67
|
"hexkit[mongodb]",
|
|
65
|
-
"opentelemetry-instrumentation-pymongo >=0.
|
|
68
|
+
"opentelemetry-instrumentation-pymongo >=0.60b1",
|
|
66
69
|
]
|
|
67
70
|
opentelemetry-fastapi = [
|
|
68
71
|
"hexkit[opentelemetry-base]",
|
|
69
|
-
"opentelemetry-instrumentation-fastapi >=0.
|
|
72
|
+
"opentelemetry-instrumentation-fastapi >=0.60b1",
|
|
70
73
|
]
|
|
71
74
|
opentelemetry-s3 = [
|
|
72
75
|
"hexkit[opentelemetry-base]",
|
|
73
76
|
"hexkit[s3]",
|
|
74
|
-
"opentelemetry-instrumentation-botocore >=0.
|
|
77
|
+
"opentelemetry-instrumentation-botocore >=0.60b1",
|
|
78
|
+
]
|
|
79
|
+
opentelemetry-redis = [
|
|
80
|
+
"hexkit[opentelemetry-base]",
|
|
81
|
+
"hexkit[redis]",
|
|
82
|
+
"opentelemetry-instrumentation-redis >=0.60b1",
|
|
75
83
|
]
|
|
76
84
|
opentelemetry = [
|
|
77
85
|
"hexkit[opentelemetry-akafka]",
|
|
@@ -91,6 +99,10 @@ test-mongodb = [
|
|
|
91
99
|
"hexkit[mongodb]",
|
|
92
100
|
"testcontainers[mongo] >=4.13, <5",
|
|
93
101
|
]
|
|
102
|
+
test-redis = [
|
|
103
|
+
"hexkit[redis]",
|
|
104
|
+
"testcontainers[redis] >=4.13, <5",
|
|
105
|
+
]
|
|
94
106
|
test = [
|
|
95
107
|
"hexkit[test-akafka,test-s3,test-mongodb]",
|
|
96
108
|
]
|
|
@@ -63,13 +63,13 @@ def new_correlation_id() -> UUID4:
|
|
|
63
63
|
return uuid4()
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def validate_correlation_id(correlation_id: Any):
|
|
66
|
+
def validate_correlation_id(correlation_id: Any) -> None:
|
|
67
67
|
"""Validate the correlation ID.
|
|
68
68
|
|
|
69
69
|
Raises:
|
|
70
|
-
InvalidCorrelationIdError: If the correlation ID is not a
|
|
70
|
+
InvalidCorrelationIdError: If the correlation ID is not a UUID4.
|
|
71
71
|
"""
|
|
72
|
-
if not isinstance(correlation_id, UUID):
|
|
72
|
+
if not isinstance(correlation_id, UUID) or correlation_id.version != 4:
|
|
73
73
|
raise InvalidCorrelationIdError(correlation_id=correlation_id)
|
|
74
74
|
|
|
75
75
|
|
|
@@ -77,13 +77,18 @@ def correlation_id_from_str(correlation_id: str) -> UUID4:
|
|
|
77
77
|
"""Convert a string to a UUID4.
|
|
78
78
|
|
|
79
79
|
Raises:
|
|
80
|
-
InvalidCorrelationIdError: If the string is not a valid
|
|
80
|
+
InvalidCorrelationIdError: If the string is not a valid UUID4.
|
|
81
81
|
"""
|
|
82
82
|
try:
|
|
83
|
-
|
|
83
|
+
converted_id = UUID(correlation_id)
|
|
84
84
|
except ValueError as err:
|
|
85
85
|
raise InvalidCorrelationIdError(correlation_id=correlation_id) from err
|
|
86
86
|
|
|
87
|
+
if converted_id.version != 4:
|
|
88
|
+
raise InvalidCorrelationIdError(correlation_id=correlation_id)
|
|
89
|
+
|
|
90
|
+
return converted_id
|
|
91
|
+
|
|
87
92
|
|
|
88
93
|
@asynccontextmanager
|
|
89
94
|
async def set_correlation_id(correlation_id: UUID4):
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
|
|
2
|
+
# for the German Human Genome-Phenome Archive (GHGA)
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
"""Protocol supporting key-value store operations."""
|
|
18
|
+
|
|
19
|
+
from typing import Generic, Protocol, TypeVar
|
|
20
|
+
|
|
21
|
+
V = TypeVar("V")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class KeyValueStoreProtocol(Protocol, Generic[V]):
|
|
25
|
+
"""A protocol for a key-value store."""
|
|
26
|
+
|
|
27
|
+
async def get(self, key: str, default: V | None = None) -> V | None:
|
|
28
|
+
"""Retrieve the value for the given key.
|
|
29
|
+
|
|
30
|
+
Returns the specified default value if there is no such value in the store.
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
async def set(self, key: str, value: V) -> None:
|
|
35
|
+
"""Set the value for the given key.
|
|
36
|
+
|
|
37
|
+
Providers must reject storing a value of None if they
|
|
38
|
+
support a value domain V that can contain such values.
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
async def delete(self, key: str) -> None:
|
|
43
|
+
"""Delete the value for the given key.
|
|
44
|
+
|
|
45
|
+
Does nothing if there is no such value in the store.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
async def exists(self, key: str) -> bool:
|
|
50
|
+
"""Check if the given key exists.
|
|
51
|
+
|
|
52
|
+
Default implementation for classes inheriting from this protocol.
|
|
53
|
+
Providers can use something more efficient.
|
|
54
|
+
"""
|
|
55
|
+
return (await self.get(key)) is not None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
|
|
2
|
+
# for the German Human Genome-Phenome Archive (GHGA)
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
"""Subpackage containing MongoDB-based providers and related utilities."""
|
|
18
|
+
|
|
19
|
+
from .config import MongoDbConfig
|
|
20
|
+
from .provider import (
|
|
21
|
+
ConfiguredMongoClient,
|
|
22
|
+
MongoDbBytesKeyValueStore,
|
|
23
|
+
MongoDbDao,
|
|
24
|
+
MongoDbDaoFactory,
|
|
25
|
+
MongoDbDtoKeyValueStore,
|
|
26
|
+
MongoDbJsonKeyValueStore,
|
|
27
|
+
MongoDbStrKeyValueStore,
|
|
28
|
+
translate_pymongo_errors,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"ConfiguredMongoClient",
|
|
33
|
+
"MongoDbBytesKeyValueStore",
|
|
34
|
+
"MongoDbConfig",
|
|
35
|
+
"MongoDbDao",
|
|
36
|
+
"MongoDbDaoFactory",
|
|
37
|
+
"MongoDbDtoKeyValueStore",
|
|
38
|
+
"MongoDbJsonKeyValueStore",
|
|
39
|
+
"MongoDbStrKeyValueStore",
|
|
40
|
+
"translate_pymongo_errors",
|
|
41
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
|
|
2
|
+
# for the German Human Genome-Phenome Archive (GHGA)
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
"""MongoDB specific configuration."""
|
|
18
|
+
|
|
19
|
+
from pydantic import Field, MongoDsn, PositiveInt, Secret
|
|
20
|
+
from pydantic_settings import BaseSettings
|
|
21
|
+
|
|
22
|
+
__all__ = ["MongoDbConfig"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MongoDbConfig(BaseSettings):
|
|
26
|
+
"""Configuration parameters for connecting to a MongoDB server.
|
|
27
|
+
|
|
28
|
+
Inherit your config class from this class if your application uses MongoDB.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
mongo_dsn: Secret[MongoDsn] = Field(
|
|
32
|
+
...,
|
|
33
|
+
examples=["mongodb://localhost:27017"],
|
|
34
|
+
description=(
|
|
35
|
+
"MongoDB connection string. Might include credentials."
|
|
36
|
+
+ " For more information see:"
|
|
37
|
+
+ " https://naiveskill.com/mongodb-connection-string/"
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
db_name: str = Field(
|
|
41
|
+
...,
|
|
42
|
+
examples=["my-database"],
|
|
43
|
+
description="Name of the database located on the MongoDB server.",
|
|
44
|
+
)
|
|
45
|
+
mongo_timeout: PositiveInt | None = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
examples=[300, 600, None],
|
|
48
|
+
description=(
|
|
49
|
+
"Timeout in seconds for API calls to MongoDB. The timeout applies to all steps"
|
|
50
|
+
+ " needed to complete the operation, including server selection, connection"
|
|
51
|
+
+ " checkout, serialization, and server-side execution. When the timeout"
|
|
52
|
+
+ " expires, PyMongo raises a timeout exception. If set to None, the"
|
|
53
|
+
+ " operation will not time out (default MongoDB behavior)."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
@@ -27,7 +27,7 @@ from pymongo import AsyncMongoClient
|
|
|
27
27
|
from pymongo.asynchronous.database import AsyncDatabase
|
|
28
28
|
from pymongo.errors import DuplicateKeyError
|
|
29
29
|
|
|
30
|
-
from hexkit.providers.mongodb import MongoDbConfig
|
|
30
|
+
from hexkit.providers.mongodb.config import MongoDbConfig
|
|
31
31
|
from hexkit.providers.mongodb.provider import ConfiguredMongoClient
|
|
32
32
|
|
|
33
33
|
from ._utils import MigrationDefinition, Reversible
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
|
|
2
|
+
# for the German Human Genome-Phenome Archive (GHGA)
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
"""A subpackage containing all MongoDB-specific providers.
|
|
18
|
+
|
|
19
|
+
Require dependencies of the `mongodb` extra.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .client import ConfiguredMongoClient
|
|
23
|
+
from .dao import (
|
|
24
|
+
MongoDbDao,
|
|
25
|
+
MongoDbDaoFactory,
|
|
26
|
+
get_single_hit,
|
|
27
|
+
replace_id_field_in_find_mapping,
|
|
28
|
+
validate_find_mapping,
|
|
29
|
+
)
|
|
30
|
+
from .kvstore import (
|
|
31
|
+
MongoDbBytesKeyValueStore,
|
|
32
|
+
MongoDbDtoKeyValueStore,
|
|
33
|
+
MongoDbJsonKeyValueStore,
|
|
34
|
+
MongoDbStrKeyValueStore,
|
|
35
|
+
)
|
|
36
|
+
from .utils import document_to_dto, dto_to_document, translate_pymongo_errors
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"ConfiguredMongoClient",
|
|
40
|
+
"MongoDbBytesKeyValueStore",
|
|
41
|
+
"MongoDbDao",
|
|
42
|
+
"MongoDbDaoFactory",
|
|
43
|
+
"MongoDbDtoKeyValueStore",
|
|
44
|
+
"MongoDbJsonKeyValueStore",
|
|
45
|
+
"MongoDbStrKeyValueStore",
|
|
46
|
+
"document_to_dto",
|
|
47
|
+
"dto_to_document",
|
|
48
|
+
"get_single_hit",
|
|
49
|
+
"replace_id_field_in_find_mapping",
|
|
50
|
+
"translate_pymongo_errors",
|
|
51
|
+
"validate_find_mapping",
|
|
52
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln
|
|
2
|
+
# for the German Human Genome-Phenome Archive (GHGA)
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Client used for the MongoDB providers."""
|
|
17
|
+
|
|
18
|
+
from typing import TypeVar
|
|
19
|
+
|
|
20
|
+
from pymongo import AsyncMongoClient, MongoClient
|
|
21
|
+
|
|
22
|
+
from hexkit.providers.mongodb.config import MongoDbConfig
|
|
23
|
+
|
|
24
|
+
ClientType = TypeVar("ClientType", AsyncMongoClient, MongoClient)
|
|
25
|
+
|
|
26
|
+
__all__ = ["ConfiguredMongoClient"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ConfiguredMongoClient:
|
|
30
|
+
"""A context manager for a configured MongoDB client, sync or async.
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
```python
|
|
34
|
+
from hexkit.providers.mongodb import MongoDbConfig, ConfiguredMongoClient
|
|
35
|
+
|
|
36
|
+
with ConfiguredMongoClient(config=MongoDbConfig(...)) as client:
|
|
37
|
+
# Use the client here
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
async with ConfiguredMongoClient(config=MongoDbConfig(...)) as client:
|
|
41
|
+
# Use the async client here
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# Client is automatically closed after exiting the context manager
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
config: MongoDbConfig
|
|
48
|
+
|
|
49
|
+
def __init__(self, *, config: MongoDbConfig):
|
|
50
|
+
self._config = config
|
|
51
|
+
|
|
52
|
+
async def __aenter__(self) -> AsyncMongoClient:
|
|
53
|
+
"""Enter a context manager and return the _asynchronous_ MongoDB client."""
|
|
54
|
+
self._async_client = self.get_client(
|
|
55
|
+
config=self._config, client_cls=AsyncMongoClient
|
|
56
|
+
)
|
|
57
|
+
return self._async_client
|
|
58
|
+
|
|
59
|
+
async def __aexit__(self, exc_type_, exc_value, exc_tb):
|
|
60
|
+
"""Close the async MongoDB client."""
|
|
61
|
+
await self._async_client.close()
|
|
62
|
+
|
|
63
|
+
def __enter__(self) -> MongoClient:
|
|
64
|
+
"""Enter a context manager and return the _synchronous_ MongoDB client."""
|
|
65
|
+
self._client = self.get_client(config=self._config, client_cls=MongoClient)
|
|
66
|
+
return self._client
|
|
67
|
+
|
|
68
|
+
def __exit__(self, exc_type_, exc_value, exc_tb):
|
|
69
|
+
"""Close the synchronous MongoDB client."""
|
|
70
|
+
self._client.close()
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def get_client(
|
|
74
|
+
cls,
|
|
75
|
+
*,
|
|
76
|
+
config: MongoDbConfig,
|
|
77
|
+
client_cls: type[ClientType],
|
|
78
|
+
) -> ClientType:
|
|
79
|
+
"""Creates a configured MongoDB client based on the provided configuration,
|
|
80
|
+
with the timeout, uuid representation, and timezone awareness set.
|
|
81
|
+
|
|
82
|
+
*Does not* automatically close the client!
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
config: MongoDB-specific configuration parameters.
|
|
86
|
+
client_cls: The class of the MongoDB client to instantiate.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A MongoDB client instance configured with the provided parameters.
|
|
90
|
+
"""
|
|
91
|
+
timeout_ms = (
|
|
92
|
+
int(config.mongo_timeout * 1000)
|
|
93
|
+
if config.mongo_timeout is not None
|
|
94
|
+
else None
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return client_cls(
|
|
98
|
+
str(config.mongo_dsn.get_secret_value()),
|
|
99
|
+
timeoutMS=timeout_ms,
|
|
100
|
+
uuidRepresentation="standard",
|
|
101
|
+
tz_aware=True,
|
|
102
|
+
)
|