hexkit 8.1.0__tar.gz → 8.2.1__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 (88) hide show
  1. {hexkit-8.1.0/src/hexkit.egg-info → hexkit-8.2.1}/PKG-INFO +11 -3
  2. {hexkit-8.1.0 → hexkit-8.2.1}/pyproject.toml +31 -5
  3. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/config.py +5 -6
  4. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/objstorage.py +57 -0
  5. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/provider/daosub.py +2 -2
  6. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/provider/eventsub.py +4 -2
  7. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/testutils.py +3 -3
  8. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/migrations/helpers.py +4 -4
  9. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/provider/dao.py +1 -1
  10. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/provider/kvstore.py +6 -2
  11. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/provider/utils.py +1 -1
  12. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/__init__.py +2 -0
  13. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/provider/daopub.py +9 -2
  14. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/provider/kvstore.py +8 -3
  15. hexkit-8.2.1/src/hexkit/providers/s3/__init__.py +36 -0
  16. hexkit-8.2.1/src/hexkit/providers/s3/config.py +68 -0
  17. {hexkit-8.1.0/src/hexkit/providers/s3 → hexkit-8.2.1/src/hexkit/providers/s3/provider}/__init__.py +16 -4
  18. hexkit-8.2.1/src/hexkit/providers/s3/provider/kvstore.py +250 -0
  19. hexkit-8.1.0/src/hexkit/providers/s3/provider.py → hexkit-8.2.1/src/hexkit/providers/s3/provider/objstorage.py +86 -111
  20. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/testutils/_fixtures.py +1 -1
  21. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/testing/__init__.py +10 -0
  22. hexkit-8.2.1/src/hexkit/providers/testing/kvstore.py +222 -0
  23. hexkit-8.1.0/src/hexkit/providers/testing/s3.py → hexkit-8.2.1/src/hexkit/providers/testing/objstorage.py +43 -16
  24. hexkit-8.2.1/src/hexkit/providers/vault/__init__.py +33 -0
  25. hexkit-8.2.1/src/hexkit/providers/vault/config.py +118 -0
  26. hexkit-8.2.1/src/hexkit/providers/vault/provider/__init__.py +20 -0
  27. hexkit-8.2.1/src/hexkit/providers/vault/provider/client.py +131 -0
  28. hexkit-8.2.1/src/hexkit/providers/vault/provider/kvstore.py +275 -0
  29. hexkit-8.2.1/src/hexkit/providers/vault/testutils.py +258 -0
  30. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/utils.py +3 -2
  31. {hexkit-8.1.0 → hexkit-8.2.1/src/hexkit.egg-info}/PKG-INFO +11 -3
  32. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit.egg-info/SOURCES.txt +12 -2
  33. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit.egg-info/requires.txt +9 -2
  34. {hexkit-8.1.0 → hexkit-8.2.1}/LICENSE +0 -0
  35. {hexkit-8.1.0 → hexkit-8.2.1}/MANIFEST.in +0 -0
  36. {hexkit-8.1.0 → hexkit-8.2.1}/README.md +0 -0
  37. {hexkit-8.1.0 → hexkit-8.2.1}/setup.cfg +0 -0
  38. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/__init__.py +0 -0
  39. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/__main__.py +0 -0
  40. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/base.py +0 -0
  41. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/correlation.py +0 -0
  42. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/custom_types.py +0 -0
  43. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/log.py +0 -0
  44. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/opentelemetry.py +0 -0
  45. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/__init__.py +0 -0
  46. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/dao.py +0 -0
  47. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/daopub.py +0 -0
  48. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/daosub.py +0 -0
  49. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/eventpub.py +0 -0
  50. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/eventsub.py +0 -0
  51. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/protocols/kvstore.py +0 -0
  52. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/__init__.py +0 -0
  53. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/__init__.py +0 -0
  54. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/config.py +0 -0
  55. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  56. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
  57. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  58. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  59. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/__init__.py +0 -0
  60. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/config.py +0 -0
  61. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
  62. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
  63. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
  64. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/provider/__init__.py +0 -0
  65. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/provider/client.py +0 -0
  66. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongodb/testutils.py +0 -0
  67. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/config.py +0 -0
  68. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
  69. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
  70. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  71. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/__init__.py +0 -0
  72. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/config.py +0 -0
  73. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/provider/__init__.py +0 -0
  74. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/provider/client.py +0 -0
  75. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/redis/testutils.py +0 -0
  76. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  77. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  78. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  79. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  80. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  81. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
  82. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  83. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
  84. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/testing/dao.py +0 -0
  85. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/providers/testing/eventpub.py +0 -0
  86. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit/py.typed +0 -0
  87. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit.egg-info/dependency_links.txt +0 -0
  88. {hexkit-8.1.0 → hexkit-8.2.1}/src/hexkit.egg-info/top_level.txt +0 -0
@@ -1,10 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexkit
3
- Version: 8.1.0
3
+ Version: 8.2.1
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
7
7
  Project-URL: Repository, https://github.com/ghga-de/hexkit
8
+ Project-URL: Documentation, https://ghga-de.github.io/hexkit/
9
+ Project-URL: Release Notes, https://github.com/ghga-de/hexkit/releases
10
+ Project-URL: Issue Tracker, https://github.com/ghga-de/hexkit/issues
8
11
  Classifier: Development Status :: 5 - Production/Stable
9
12
  Classifier: Operating System :: POSIX :: Linux
10
13
  Classifier: Programming Language :: Python :: 3.10
@@ -23,7 +26,7 @@ Requires-Dist: pydantic<3,>=2.12
23
26
  Requires-Dist: pydantic_settings<3,>=2.12
24
27
  Requires-Dist: PyYAML<7,>=6
25
28
  Provides-Extra: akafka
26
- Requires-Dist: aiokafka[lz4,snappy,zstd]~=0.13.0; extra == "akafka"
29
+ Requires-Dist: aiokafka[lz4,snappy,zstd]~=0.14.0; extra == "akafka"
27
30
  Requires-Dist: jsonschema<5,>=4.25; extra == "akafka"
28
31
  Provides-Extra: s3
29
32
  Requires-Dist: boto3<2,>=1.42; extra == "s3"
@@ -32,6 +35,8 @@ Provides-Extra: mongodb
32
35
  Requires-Dist: pymongo<5,>=4.16; extra == "mongodb"
33
36
  Provides-Extra: redis
34
37
  Requires-Dist: redis<8,>=7.1; extra == "redis"
38
+ Provides-Extra: vault
39
+ Requires-Dist: hvac<3,>=2.4; extra == "vault"
35
40
  Provides-Extra: opentelemetry-base
36
41
  Requires-Dist: opentelemetry-sdk<2,>=1.39.1; extra == "opentelemetry-base"
37
42
  Requires-Dist: opentelemetry-exporter-otlp<2,>=1.39.1; extra == "opentelemetry-base"
@@ -74,8 +79,11 @@ Requires-Dist: testcontainers[mongo]<5,>=4.14; extra == "test-mongodb"
74
79
  Provides-Extra: test-redis
75
80
  Requires-Dist: hexkit[redis]; extra == "test-redis"
76
81
  Requires-Dist: testcontainers[redis]<5,>=4.14; extra == "test-redis"
82
+ Provides-Extra: test-vault
83
+ Requires-Dist: hexkit[vault]; extra == "test-vault"
84
+ Requires-Dist: testcontainers<5,>=4.14; extra == "test-vault"
77
85
  Provides-Extra: test
78
- Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
86
+ Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3,test-vault]; extra == "test"
79
87
  Provides-Extra: all
80
88
  Requires-Dist: hexkit[test]; extra == "all"
81
89
  Requires-Dist: hexkit[opentelemetry]; extra == "all"
@@ -1,6 +1,6 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools>=80.10",
3
+ "setuptools>=82",
4
4
  ]
5
5
  build-backend = "setuptools.build_meta"
6
6
 
@@ -23,7 +23,7 @@ classifiers = [
23
23
  "Intended Audience :: Developers",
24
24
  ]
25
25
  name = "hexkit"
26
- version = "8.1.0"
26
+ version = "8.2.1"
27
27
  description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
28
28
  dependencies = [
29
29
  "opentelemetry-api >=1.39, <2",
@@ -37,7 +37,7 @@ text = "Apache 2.0"
37
37
 
38
38
  [project.optional-dependencies]
39
39
  akafka = [
40
- "aiokafka[zstd,lz4,snappy]~=0.13.0",
40
+ "aiokafka[zstd,lz4,snappy]~=0.14.0",
41
41
  "jsonschema >=4.25, <5",
42
42
  ]
43
43
  s3 = [
@@ -50,6 +50,9 @@ mongodb = [
50
50
  redis = [
51
51
  "redis >=7.1, <8",
52
52
  ]
53
+ vault = [
54
+ "hvac >=2.4, <3",
55
+ ]
53
56
  opentelemetry-base = [
54
57
  "opentelemetry-sdk >=1.39.1, <2",
55
58
  "opentelemetry-exporter-otlp >=1.39.1, <2",
@@ -103,8 +106,12 @@ test-redis = [
103
106
  "hexkit[redis]",
104
107
  "testcontainers[redis] >=4.14, <5",
105
108
  ]
109
+ test-vault = [
110
+ "hexkit[vault]",
111
+ "testcontainers >=4.14, <5",
112
+ ]
106
113
  test = [
107
- "hexkit[test-akafka,test-s3,test-mongodb]",
114
+ "hexkit[test-akafka,test-s3,test-mongodb,test-vault]",
108
115
  ]
109
116
  all = [
110
117
  "hexkit[test]",
@@ -113,6 +120,9 @@ all = [
113
120
 
114
121
  [project.urls]
115
122
  Repository = "https://github.com/ghga-de/hexkit"
123
+ Documentation = "https://ghga-de.github.io/hexkit/"
124
+ "Release Notes" = "https://github.com/ghga-de/hexkit/releases"
125
+ "Issue Tracker" = "https://github.com/ghga-de/hexkit/issues"
116
126
 
117
127
  [tool.setuptools.packages.find]
118
128
  where = [
@@ -235,7 +245,7 @@ source = [
235
245
  [tool.tox]
236
246
  legacy_tox_ini = """
237
247
  [tox]
238
- env_list = py3{10,11,12,13}
248
+ env_list = py3{10,11,12,13}, docs
239
249
 
240
250
  [gh-actions]
241
251
  python =
@@ -244,6 +254,22 @@ legacy_tox_ini = """
244
254
  3.12: py312
245
255
  3.13: py313
246
256
 
257
+ [testenv:docs]
258
+ basepython = python3.13
259
+ allowlist_externals =
260
+ mv
261
+ rm
262
+ deps =
263
+ --no-deps -r ./lock/requirements-dev.txt
264
+ commands =
265
+ rm -rf site/api
266
+ pdoc -d google -o docs/api hexkit
267
+ mv docs/api/index.html docs/api/index.md
268
+ mkdocs build
269
+ mv docs/api/index.md docs/api/index.html
270
+ rm -rf site/api
271
+ mv docs/api site/api
272
+
247
273
  [testenv]
248
274
  pass_env =
249
275
  TC_HOST
@@ -112,18 +112,16 @@ def config_from_yaml(
112
112
  """
113
113
 
114
114
  def decorator(settings) -> Callable:
115
- # settings should be a pydantic BaseSetting,
116
- # there is no type hint here to not restrict
117
- # autocompletion for attributes of the
118
- # modified settings class returned by the
119
- # contructor_wrapper
120
115
  """The actual decorator function.
121
116
 
122
117
  Args
123
118
  settings (BaseSettings):
124
119
  A pydantic BaseSettings class to be modified.
125
120
  """
126
- # check if settings inherits from pydantic BaseSettings:
121
+ # The settings argument should be a pydantic BaseSetting,
122
+ # there is no type hint here to not restrict autocompletion for attributes
123
+ # of the modified settings class returned by the constructor_wrapper.
124
+ # Check if settings inherits from pydantic BaseSettings:
127
125
  if not issubclass(settings, BaseSettings):
128
126
  raise TypeError(
129
127
  "The specified settings class is not a subclass of pydantic_settings.BaseSettings"
@@ -152,6 +150,7 @@ def config_from_yaml(
152
150
  frozen=True,
153
151
  env_prefix=f"{prefix}_",
154
152
  env_file=(".env", dotenv_prefix / ".env"),
153
+ env_nested_delimiter="__",
155
154
  )
156
155
 
157
156
  @classmethod
@@ -195,6 +195,38 @@ class ObjectStorageProtocol(ABC):
195
195
  bucket_id=bucket_id, object_id=object_id
196
196
  )
197
197
 
198
+ async def list_parts(
199
+ self,
200
+ *,
201
+ bucket_id: str,
202
+ object_id: str,
203
+ upload_id: str,
204
+ max_parts: int | None = None,
205
+ first_part_no: int | None = None,
206
+ ) -> list[dict]:
207
+ """Lists the parts that have been uploaded for a specific multipart upload.
208
+
209
+ Specify `max_parts` to return a limited parts list. If not specified, all
210
+ parts will be returned (up to 10,000).
211
+
212
+ Specify `first_part_no` to get parts starting with that part number. If not
213
+ specified, retrieved parts will start with the first part. Part numbers
214
+ start at 1, not 0.
215
+
216
+ Raises:
217
+ ValueError if `max_parts` or `first_part_no` are invalid.
218
+ MultiPartUploadNotFoundError if no upload with `upload_id` exists.
219
+ """
220
+ self._validate_bucket_id(bucket_id)
221
+ self._validate_object_id(object_id)
222
+ return await self._list_parts(
223
+ bucket_id=bucket_id,
224
+ object_id=object_id,
225
+ upload_id=upload_id,
226
+ max_parts=max_parts,
227
+ first_part_no=first_part_no,
228
+ )
229
+
198
230
  async def get_all_multipart_uploads(self, *, bucket_id: str) -> dict[str, str]:
199
231
  """Gets all active multipart uploads for the given bucket ID.
200
232
 
@@ -440,6 +472,31 @@ class ObjectStorageProtocol(ABC):
440
472
  """
441
473
  ...
442
474
 
475
+ @abstractmethod
476
+ async def _list_parts(
477
+ self,
478
+ *,
479
+ bucket_id: str,
480
+ object_id: str,
481
+ upload_id: str,
482
+ max_parts: int | None = None,
483
+ first_part_no: int | None = None,
484
+ ) -> list[dict]:
485
+ """Lists the parts that have been uploaded for a specific multipart upload.
486
+
487
+ Specify `max_parts` to return a limited parts list. If not specified, all
488
+ parts will be returned (up to 10,000).
489
+
490
+ Specify `first_part_no` to get parts starting with that part number. If not
491
+ specified, retrieved parts will start with the first part. Part numbers
492
+ start at 1, not 0.
493
+
494
+ Raises:
495
+ ValueError if `max_parts` or `first_part_no` are invalid.
496
+ MultiPartUploadNotFoundError if no upload with `upload_id` exists.
497
+ """
498
+ ...
499
+
443
500
  @abstractmethod
444
501
  async def _get_all_multipart_uploads(self, *, bucket_id: str) -> dict[str, str]:
445
502
  """Gets all active multipart uploads for the given bucket ID.
@@ -19,7 +19,7 @@
19
19
  """
20
20
 
21
21
  import logging
22
- from collections.abc import Sequence
22
+ from collections.abc import AsyncGenerator, Sequence
23
23
  from contextlib import asynccontextmanager
24
24
 
25
25
  from aiokafka import AIOKafkaConsumer
@@ -57,7 +57,7 @@ class KafkaOutboxSubscriber(InboundProviderBase):
57
57
  translators: Sequence[DaoSubscriberProtocol],
58
58
  dlq_publisher: EventPublisherProtocol | None = None,
59
59
  kafka_consumer_cls: type[KafkaConsumerCompatible] = AIOKafkaConsumer,
60
- ):
60
+ ) -> AsyncGenerator["KafkaOutboxSubscriber", None]:
61
61
  """Setup and teardown an instance of the provider.
62
62
 
63
63
  Args:
@@ -331,9 +331,11 @@ class KafkaConsumerCompatible(Protocol):
331
331
  An identifier for the consumer group.
332
332
  auto_offset_reset:
333
333
  Can be set to "earliest".
334
- key_serializer:
334
+ enable_auto_commit:
335
+ Whether to enable auto-committing of offsets.
336
+ key_deserializer:
335
337
  Function to deserialize the keys into strings.
336
- value_serializer:
338
+ value_deserializer:
337
339
  Function to deserialize the values into strings.
338
340
  max_partition_fetch_bytes:
339
341
  Maximum receivable message size
@@ -644,11 +644,11 @@ class KafkaContainerFixture(KafkaContainer):
644
644
  """Run the given command in the kafka testcontainer.
645
645
 
646
646
  Args:
647
- - `command`: The full command to run.
648
- - `run_in_shell`: If True, will run the command in a shell.
647
+ command: The full command to run.
648
+ run_in_shell: If True, will run the command in a shell.
649
649
 
650
650
  Raises:
651
- - `RuntimeError`: when the exit code returned by the command is not zero.
651
+ RuntimeError: when the exit code returned by the command is not zero.
652
652
  """
653
653
  cmd = ["sh", "-c", command] if run_in_shell else command
654
654
  exit_code, output = self.get_wrapped_container().exec_run(cmd)
@@ -13,7 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- """Prefab functions to help with migrations caused by changes in Hexkit."""
16
+ """Prefab functions to help with migrations caused by changes in hexkit."""
17
17
 
18
18
  from collections.abc import Awaitable, Callable
19
19
  from datetime import datetime, timezone
@@ -34,7 +34,7 @@ def convert_uuids_and_datetimes_v6(
34
34
  uuid_fields: list[str] | None = None,
35
35
  date_fields: list[str] | None = None,
36
36
  ) -> Callable[[Document], Awaitable[Document]]:
37
- """Produce a function to convert a document to the format expected by Hexkit v6.
37
+ """Produce a function to convert a document to the format expected by hexkit v6.
38
38
 
39
39
  If `uuid_fields` is provided, it will convert those fields from string to UUID.
40
40
  If `date_fields` is provided, it will convert those fields from isoformat strings
@@ -60,7 +60,7 @@ def convert_uuids_and_datetimes_v6(
60
60
 
61
61
 
62
62
  async def convert_outbox_correlation_id_v6(doc: Document) -> Document:
63
- """Convert an outbox document's correlation ID to the format expected by Hexkit v6.
63
+ """Convert an outbox document's correlation ID to the format expected by hexkit v6.
64
64
 
65
65
  This will only convert the __metadata__.correlation_id to a UUID.
66
66
 
@@ -77,7 +77,7 @@ _convert_persistent_event_existing_fields_v6 = convert_uuids_and_datetimes_v6(
77
77
 
78
78
 
79
79
  async def convert_persistent_event_v6(doc: Document) -> Document:
80
- """Convert a persistent event document to the format expected by Hexkit v6.
80
+ """Convert a persistent event document to the format expected by hexkit v6.
81
81
 
82
82
  This will:
83
83
  - convert correlation_id to UUID
@@ -16,7 +16,7 @@
16
16
 
17
17
  """MongoDB-based provider implementing the DaoFactoryProtocol.
18
18
 
19
- Utilities for testing are located in `./testutils.py`.
19
+ Utilities for testing are located in `../testutils.py`.
20
20
  """
21
21
 
22
22
  from collections.abc import AsyncIterator, Callable, Collection, Mapping
@@ -83,9 +83,13 @@ class MongoDbBaseKeyValueStore(ABC, KeyValueStoreProtocol):
83
83
  client: An instance of an async MongoDB client.
84
84
  config: MongoDB-specific config parameters.
85
85
  collection_name: Name of the collection to hold the key-value pairs.
86
- **kwargs: Additional arguments specific to subclasses.
86
+
87
+ You might be able to pass additional arguments specific to subclasses.
87
88
  """
88
- args = [str(config)]
89
+ config_args = ", ".join(
90
+ f"{k}={v!r}" for k, v in config.model_dump(exclude_defaults=True).items()
91
+ )
92
+ args = [f"config=MongoDbConfig({config_args})"]
89
93
  if collection_name != DEFAULT_KV_COLLECTION_NAME:
90
94
  args.append(f"collection_name={collection_name!r}")
91
95
  for key, val in kwargs.items():
@@ -15,7 +15,7 @@
15
15
 
16
16
  """MongoDB-based provider utilities.
17
17
 
18
- Utilities for testing are located in `./testutils.py`.
18
+ Utilities for testing are located in `../testutils.py`.
19
19
  """
20
20
 
21
21
  from contextlib import contextmanager
@@ -21,6 +21,7 @@ from .provider import (
21
21
  CHANGE_EVENT_TYPE,
22
22
  DELETE_EVENT_TYPE,
23
23
  MongoKafkaDaoPublisherFactory,
24
+ PersistentKafkaEvent,
24
25
  PersistentKafkaPublisher,
25
26
  document_to_dto,
26
27
  dto_to_document,
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "DELETE_EVENT_TYPE",
32
33
  "MongoKafkaConfig",
33
34
  "MongoKafkaDaoPublisherFactory",
35
+ "PersistentKafkaEvent",
34
36
  "PersistentKafkaPublisher",
35
37
  "document_to_dto",
36
38
  "dto_to_document",
@@ -19,7 +19,14 @@
19
19
  Requires dependencies of the `akafka` and `mongodb` extras.
20
20
  """
21
21
 
22
- from collections.abc import AsyncIterator, Awaitable, Callable, Collection, Mapping
22
+ from collections.abc import (
23
+ AsyncGenerator,
24
+ AsyncIterator,
25
+ Awaitable,
26
+ Callable,
27
+ Collection,
28
+ Mapping,
29
+ )
23
30
  from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
24
31
  from functools import partial
25
32
  from typing import Any, Generic
@@ -476,7 +483,7 @@ class MongoKafkaDaoPublisherFactory(DaoPublisherFactoryProtocol[MongoDbIndex]):
476
483
  *,
477
484
  config: MongoKafkaConfig,
478
485
  kafka_producer_cls: type[KafkaProducerCompatible] = AIOKafkaProducer,
479
- ):
486
+ ) -> AsyncGenerator["MongoKafkaDaoPublisherFactory", None]:
480
487
  """Setup and teardown an instance of the provider.
481
488
  This method will automatically close the DB connection upon exit.
482
489
 
@@ -68,7 +68,8 @@ class RedisBaseKeyValueStore(ABC, KeyValueStoreProtocol):
68
68
  Args:
69
69
  config: Redis-specific config parameters.
70
70
  key_prefix: Optional prefix for all keys (e.g., "myapp:users:").
71
- **kwargs: Additional arguments specific to subclasses.
71
+
72
+ You might be able to pass additional arguments specific to subclasses.
72
73
  """
73
74
  async with ConfiguredRedisClient(config) as client:
74
75
  yield cls(
@@ -92,9 +93,13 @@ class RedisBaseKeyValueStore(ABC, KeyValueStoreProtocol):
92
93
  client: An instance of an async Redis client.
93
94
  config: Redis-specific config parameters.
94
95
  key_prefix: Prefix for all keys to enable logical separation.
95
- **kwargs: Additional arguments specific to subclasses.
96
+
97
+ You might be able to pass additional arguments specific to subclasses.
96
98
  """
97
- args = [str(config)]
99
+ config_args = ", ".join(
100
+ f"{k}={v!r}" for k, v in config.model_dump(exclude_defaults=True).items()
101
+ )
102
+ args = [f"config=RedisConfig({config_args})"]
98
103
  if key_prefix != DEFAULT_KV_PREFIX:
99
104
  args.append(f"key_prefix={key_prefix!r}")
100
105
  for key, val in kwargs.items():
@@ -0,0 +1,36 @@
1
+ # Copyright 2021 - 2026 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 an S3-based providers and associated utilities."""
18
+
19
+ # shortcuts:
20
+ from hexkit.providers.s3.config import S3Config
21
+ from hexkit.providers.s3.provider import (
22
+ S3BytesKeyValueStore,
23
+ S3DtoKeyValueStore,
24
+ S3JsonKeyValueStore,
25
+ S3ObjectStorage,
26
+ S3StrKeyValueStore,
27
+ )
28
+
29
+ __all__ = [
30
+ "S3BytesKeyValueStore",
31
+ "S3Config",
32
+ "S3DtoKeyValueStore",
33
+ "S3JsonKeyValueStore",
34
+ "S3ObjectStorage",
35
+ "S3StrKeyValueStore",
36
+ ]
@@ -0,0 +1,68 @@
1
+ # Copyright 2021 - 2026 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
+ """S3-specific configuration."""
17
+
18
+ from pathlib import Path
19
+
20
+ from pydantic import Field, SecretStr
21
+ from pydantic_settings import BaseSettings
22
+
23
+ __all__ = ["S3Config"]
24
+
25
+
26
+ class S3Config(BaseSettings):
27
+ """S3-specific config params.
28
+
29
+ Inherit your config class from this class if you need to talk
30
+ to an S3 service in the backend.
31
+ """
32
+
33
+ s3_endpoint_url: str = Field(
34
+ ..., examples=["http://localhost:4566"], description="URL to the S3 API."
35
+ )
36
+ s3_access_key_id: str = Field(
37
+ ...,
38
+ examples=["my-access-key-id"],
39
+ description=(
40
+ "Part of credentials for login into the S3 service. See:"
41
+ + " https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
42
+ ),
43
+ )
44
+ s3_secret_access_key: SecretStr = Field(
45
+ ...,
46
+ examples=["my-secret-access-key"],
47
+ description=(
48
+ "Part of credentials for login into the S3 service. See:"
49
+ + " https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
50
+ ),
51
+ )
52
+ s3_session_token: SecretStr | None = Field(
53
+ None,
54
+ examples=["my-session-token"],
55
+ description=(
56
+ "Part of credentials for login into the S3 service. See:"
57
+ + " https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
58
+ ),
59
+ )
60
+ aws_config_ini: Path | None = Field(
61
+ None,
62
+ examples=["~/.aws/config"],
63
+ description=(
64
+ "Path to a config file for specifying more advanced S3 parameters."
65
+ + " This should follow the format described here:"
66
+ + " https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#using-a-configuration-file"
67
+ ),
68
+ )
@@ -14,11 +14,23 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- """Subpackage containing an S3-based provider implementing the ObjectStorageProtocol
18
- and associated utilities.
17
+ """Subpackage containing S3-based providers implementing the ObjectStorageProtocol
18
+ and KeyValueStoreProtocol, and associated utilities.
19
19
  """
20
20
 
21
21
  # shortcuts:
22
- from hexkit.providers.s3.provider import S3Config, S3ObjectStorage
22
+ from .kvstore import (
23
+ S3BytesKeyValueStore,
24
+ S3DtoKeyValueStore,
25
+ S3JsonKeyValueStore,
26
+ S3StrKeyValueStore,
27
+ )
28
+ from .objstorage import S3ObjectStorage
23
29
 
24
- __all__ = ["S3Config", "S3ObjectStorage"]
30
+ __all__ = [
31
+ "S3BytesKeyValueStore",
32
+ "S3DtoKeyValueStore",
33
+ "S3JsonKeyValueStore",
34
+ "S3ObjectStorage",
35
+ "S3StrKeyValueStore",
36
+ ]