hexkit 1.0.0__tar.gz → 1.1.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.
Files changed (43) hide show
  1. {hexkit-1.0.0/src/hexkit.egg-info → hexkit-1.1.0}/PKG-INFO +4 -4
  2. {hexkit-1.0.0 → hexkit-1.1.0}/pyproject.toml +4 -4
  3. hexkit-1.1.0/src/hexkit/correlation.py +97 -0
  4. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/inject.py +0 -5
  5. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/protocols/dao.py +1 -1
  6. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/protocols/objstorage.py +0 -2
  7. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/akafka/provider.py +1 -8
  8. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/mongodb/provider.py +3 -3
  9. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/provider.py +2 -8
  10. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/testutils.py +0 -3
  11. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/utils.py +14 -1
  12. {hexkit-1.0.0 → hexkit-1.1.0/src/hexkit.egg-info}/PKG-INFO +4 -4
  13. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit.egg-info/SOURCES.txt +1 -0
  14. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit.egg-info/requires.txt +3 -3
  15. {hexkit-1.0.0 → hexkit-1.1.0}/LICENSE +0 -0
  16. {hexkit-1.0.0 → hexkit-1.1.0}/MANIFEST.in +0 -0
  17. {hexkit-1.0.0 → hexkit-1.1.0}/README.md +0 -0
  18. {hexkit-1.0.0 → hexkit-1.1.0}/setup.cfg +0 -0
  19. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/__init__.py +0 -0
  20. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/__main__.py +0 -0
  21. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/base.py +0 -0
  22. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/config.py +0 -0
  23. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/custom_types.py +0 -0
  24. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/protocols/__init__.py +0 -0
  25. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/protocols/eventpub.py +0 -0
  26. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/protocols/eventsub.py +0 -0
  27. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/__init__.py +0 -0
  28. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  29. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  30. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/akafka/testutils.py +0 -0
  31. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  32. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
  33. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/__init__.py +0 -0
  34. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  35. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  36. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  37. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  38. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  39. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/testing/__init__.py +0 -0
  40. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/testing/eventpub.py +0 -0
  41. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit/providers/testing/utils.py +0 -0
  42. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  43. {hexkit-1.0.0 → hexkit-1.1.0}/src/hexkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hexkit
3
- Version: 1.0.0
3
+ Version: 1.1.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
@@ -31,13 +31,13 @@ Provides-Extra: mongodb
31
31
  Requires-Dist: motor<4,>=3.1.1; extra == "mongodb"
32
32
  Provides-Extra: test-akafka
33
33
  Requires-Dist: hexkit[akafka]; extra == "test-akafka"
34
- Requires-Dist: testcontainers[kafka]<4,>=3.4.1; extra == "test-akafka"
34
+ Requires-Dist: testcontainers[kafka]<4,>=3.7.1; extra == "test-akafka"
35
35
  Provides-Extra: test-s3
36
36
  Requires-Dist: hexkit[s3]; extra == "test-s3"
37
- Requires-Dist: testcontainers<4,>=3.4.1; extra == "test-s3"
37
+ Requires-Dist: testcontainers<4,>=3.7.1; extra == "test-s3"
38
38
  Provides-Extra: test-mongodb
39
39
  Requires-Dist: hexkit[mongodb]; extra == "test-mongodb"
40
- Requires-Dist: testcontainers[mongo]<4,>=3.4.1; extra == "test-mongodb"
40
+ Requires-Dist: testcontainers[mongo]<4,>=3.7.1; extra == "test-mongodb"
41
41
  Provides-Extra: test
42
42
  Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
43
43
  Provides-Extra: all
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hexkit"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -35,9 +35,9 @@ akafka = ["aiokafka~=0.8.0", "jsonschema >=4.17.3, <5"]
35
35
  s3 = ["boto3 >=1.26.50, <2", "botocore >=1.29.50, <2"]
36
36
  mongodb = ["motor >=3.1.1, <4"]
37
37
 
38
- test-akafka = ["hexkit[akafka]", "testcontainers[kafka] >=3.4.1, <4"]
39
- test-s3 = ["hexkit[s3]", "testcontainers >=3.4.1, <4"]
40
- test-mongodb = ["hexkit[mongodb]", "testcontainers[mongo] >=3.4.1, <4"]
38
+ test-akafka = ["hexkit[akafka]", "testcontainers[kafka] >=3.7.1, <4"]
39
+ test-s3 = ["hexkit[s3]", "testcontainers >=3.7.1, <4"]
40
+ test-mongodb = ["hexkit[mongodb]", "testcontainers[mongo] >=3.7.1, <4"]
41
41
  test = ["hexkit[test-akafka,test-s3,test-mongodb]"]
42
42
 
43
43
  all = ["hexkit[test]"]
@@ -0,0 +1,97 @@
1
+ # Copyright 2021 - 2023 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
+ """Utilities related to correlation IDs"""
18
+
19
+ import logging
20
+ from contextlib import asynccontextmanager
21
+ from contextvars import ContextVar
22
+ from uuid import UUID, uuid4
23
+
24
+ from hexkit.utils import set_context_var
25
+
26
+ log = logging.getLogger()
27
+
28
+ correlation_id_var: ContextVar[str] = ContextVar("correlation_id", default="")
29
+
30
+ __all__ = [
31
+ "set_correlation_id",
32
+ "get_correlation_id",
33
+ "new_correlation_id",
34
+ "validate_correlation_id",
35
+ "CorrelationIdContextError",
36
+ "InvalidCorrelationIdError",
37
+ ]
38
+
39
+
40
+ class CorrelationIdContextError(RuntimeError):
41
+ """Raised when the correlation ID ContextVar is unexpectedly not set."""
42
+
43
+
44
+ class InvalidCorrelationIdError(RuntimeError):
45
+ """Raised when a correlation ID fails validation."""
46
+
47
+ def __init__(self, *, correlation_id: str):
48
+ message = f"Invalid correlation ID found: '{correlation_id}'"
49
+ super().__init__(message)
50
+
51
+
52
+ def new_correlation_id() -> str:
53
+ """Generates a new correlation ID."""
54
+ return str(uuid4())
55
+
56
+
57
+ def validate_correlation_id(correlation_id: str):
58
+ """Raises an error if the correlation ID is invalid.
59
+
60
+ Raises:
61
+ InvalidCorrelationIdError: If the correlation ID is empty or invalid.
62
+ """
63
+ try:
64
+ UUID(correlation_id)
65
+ except ValueError as err:
66
+ raise InvalidCorrelationIdError(correlation_id=correlation_id) from err
67
+
68
+
69
+ @asynccontextmanager
70
+ async def set_correlation_id(correlation_id: str):
71
+ """Set the correlation ID for the life of the context.
72
+
73
+ Raises:
74
+ InvalidCorrelationIdError: when the correlation ID is empty or invalid.
75
+ """
76
+ validate_correlation_id(correlation_id)
77
+
78
+ async with set_context_var(correlation_id_var, correlation_id):
79
+ log.info("Set context correlation ID to %s", correlation_id)
80
+ yield
81
+
82
+
83
+ def get_correlation_id() -> str:
84
+ """Get the correlation ID.
85
+
86
+ This should only be called when the correlation ID ContextVar is expected to be set.
87
+
88
+ Raises:
89
+ CorrelationIdContextError: when the correlation ID ContextVar is not set.
90
+ InvalidCorrelationIdError: when the correlation ID is invalid.
91
+ """
92
+ if not (correlation_id := correlation_id_var.get()):
93
+ raise CorrelationIdContextError()
94
+
95
+ validate_correlation_id(correlation_id)
96
+
97
+ return correlation_id
@@ -46,8 +46,6 @@ __all__ = [
46
46
  "Configurator",
47
47
  ]
48
48
 
49
- # pylint: disable=c-extension-no-member
50
-
51
49
 
52
50
  class NotConstructableError(TypeError):
53
51
  """Thrown when an AsyncContextConstructable was expected but not obtained."""
@@ -108,9 +106,6 @@ class AsyncConstructor(dependency_injector.providers.Resource):
108
106
 
109
107
  return resource
110
108
 
111
- # This pylint error is inherited from the dependency_injector framework, other
112
- # DI providers use the same basic signature:
113
- # pylint: disable=keyword-arg-before-vararg
114
109
  def __init__(
115
110
  self,
116
111
  provides: Optional[
@@ -450,7 +450,7 @@ class DaoFactoryProtocol(ABC):
450
450
 
451
451
  @overload
452
452
  @abstractmethod
453
- async def _get_dao( # pylint: disable=arguments-differ
453
+ async def _get_dao(
454
454
  self,
455
455
  *,
456
456
  name: str,
@@ -155,7 +155,6 @@ class ObjectStorageProtocol(ABC):
155
155
  upload_id=upload_id, bucket_id=bucket_id, object_id=object_id
156
156
  )
157
157
 
158
- # pylint: disable=too-many-arguments
159
158
  async def complete_multipart_upload(
160
159
  self,
161
160
  *,
@@ -367,7 +366,6 @@ class ObjectStorageProtocol(ABC):
367
366
  """
368
367
  ...
369
368
 
370
- # pylint: disable=too-many-arguments
371
369
  @abstractmethod
372
370
  async def _complete_multipart_upload(
373
371
  self,
@@ -25,7 +25,7 @@ import json
25
25
  import logging
26
26
  import ssl
27
27
  from contextlib import asynccontextmanager
28
- from typing import Any, Callable, Optional, Protocol, TypeVar
28
+ from typing import Any, Callable, Literal, Optional, Protocol, TypeVar
29
29
 
30
30
  from aiokafka import AIOKafkaConsumer, AIOKafkaProducer
31
31
  from aiokafka.helpers import create_ssl_context
@@ -37,11 +37,6 @@ from hexkit.custom_types import Ascii, JsonObject
37
37
  from hexkit.protocols.eventpub import EventPublisherProtocol
38
38
  from hexkit.protocols.eventsub import EventSubscriberProtocol
39
39
 
40
- try: # workaround for https://github.com/pydantic/pydantic/issues/5821
41
- from typing_extensions import Literal
42
- except ImportError:
43
- from typing import Literal # type: ignore
44
-
45
40
  __all__ = [
46
41
  "KafkaConfig",
47
42
  "KafkaEventPublisher",
@@ -358,8 +353,6 @@ class KafkaEventSubscriber(InboundProviderBase):
358
353
  finally:
359
354
  await consumer.stop()
360
355
 
361
- # pylint: disable=too-many-arguments
362
- # (some arguments are only used for testing)
363
356
  def __init__(
364
357
  self, *, consumer: KafkaConsumerCompatible, translator: EventSubscriberProtocol
365
358
  ):
@@ -111,7 +111,7 @@ class MongoDbDaoBase(ABC, Generic[Dto]):
111
111
  Raises:
112
112
  ResourceNotFoundError: when resource with the specified id_ was not found
113
113
  """
114
- document = await self._collection.find_one( # type: ignore
114
+ document = await self._collection.find_one(
115
115
  {"_id": id_},
116
116
  session=self._session,
117
117
  )
@@ -406,7 +406,7 @@ class MongoDbDaoFactory(DaoFactoryProtocol):
406
406
  self._config = config
407
407
 
408
408
  # get a database-specific client:
409
- self._client = AsyncIOMotorClient(
409
+ self._client = AsyncIOMotorClient( # type: ignore
410
410
  self._config.db_connection_str.get_secret_value()
411
411
  )
412
412
  self._db = self._client[self._config.db_name]
@@ -428,7 +428,7 @@ class MongoDbDaoFactory(DaoFactoryProtocol):
428
428
  ...
429
429
 
430
430
  @overload
431
- async def _get_dao( # pylint: disable=arguments-differ
431
+ async def _get_dao(
432
432
  self,
433
433
  *,
434
434
  name: str,
@@ -97,7 +97,6 @@ class S3Config(BaseSettings):
97
97
  description=(
98
98
  "Path to a config file for specifying more advanced S3 parameters."
99
99
  + " This should follow the format described here:"
100
- # pylint: disable=line-too-long
101
100
  + " https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#using-a-configuration-file"
102
101
  ),
103
102
  )
@@ -114,12 +113,10 @@ def read_aws_config_ini(aws_config_ini: Path) -> botocore.config.Config:
114
113
  return botocore.config.Config(**config_profile)
115
114
 
116
115
 
117
- class S3ObjectStorage(
118
- ObjectStorageProtocol
119
- ): # pylint: disable=too-many-instance-attributes
116
+ class S3ObjectStorage(ObjectStorageProtocol):
120
117
  """S3-based provider implementing the ObjectStorageProtocol."""
121
118
 
122
- def __init__( # pylint: disable=too-many-arguments
119
+ def __init__(
123
120
  self,
124
121
  *,
125
122
  config: S3Config,
@@ -548,7 +545,6 @@ class S3ObjectStorage(
548
545
  object_id=object_id,
549
546
  ) from error
550
547
 
551
- # pylint: disable=too-many-arguments
552
548
  async def _check_uploaded_parts(
553
549
  self,
554
550
  *,
@@ -629,7 +625,6 @@ class S3ObjectStorage(
629
625
  + f" first part which had only {first_part_size}.",
630
626
  )
631
627
 
632
- # pylint: disable=too-many-arguments
633
628
  async def _abort_multipart_upload(
634
629
  self,
635
630
  *,
@@ -686,7 +681,6 @@ class S3ObjectStorage(
686
681
  upload_id=upload_id, bucket_id=bucket_id, object_id=object_id
687
682
  )
688
683
 
689
- # pylint: disable=too-many-arguments
690
684
  async def _complete_multipart_upload(
691
685
  self,
692
686
  *,
@@ -200,7 +200,6 @@ def check_part_size(file_path: Path, anticipated_size: int) -> None:
200
200
  )
201
201
 
202
202
 
203
- # pylint: disable=too-many-arguments
204
203
  async def upload_part(
205
204
  storage_dao: ObjectStorageProtocol,
206
205
  upload_id: str,
@@ -220,7 +219,6 @@ async def upload_part(
220
219
  response.raise_for_status()
221
220
 
222
221
 
223
- # pylint: disable=too-many-arguments
224
222
  async def upload_part_of_size(
225
223
  storage_dao: ObjectStorageProtocol,
226
224
  upload_id: str,
@@ -372,7 +370,6 @@ async def prepare_non_completed_upload(s3_fixture_: S3Fixture):
372
370
  # This workflow is defined as a seperate function so that it can also be used
373
371
  # outside of the `tests` package e.g. to test the compliance of an S3-compatible
374
372
  # object storage implementation:
375
- # pylint: disable=too-many-arguments
376
373
  async def typical_workflow(
377
374
  storage_client: ObjectStorageProtocol,
378
375
  test_file_path: Path,
@@ -16,7 +16,9 @@
16
16
  """General utilities that don't require heavy dependencies."""
17
17
 
18
18
  from collections.abc import Collection
19
- from typing import Optional
19
+ from contextlib import asynccontextmanager
20
+ from contextvars import ContextVar
21
+ from typing import Any, Optional
20
22
 
21
23
  from pydantic import BaseModel
22
24
 
@@ -111,3 +113,14 @@ def validate_fields_in_model(
111
113
  if not fields_set.issubset(existing_fields_set):
112
114
  unexpected_fields = fields_set.difference(existing_fields_set)
113
115
  raise FieldNotInModelError(model=model, unexpected_field=unexpected_fields)
116
+
117
+
118
+ @asynccontextmanager
119
+ async def set_context_var(context_var: ContextVar, value: Any):
120
+ """An async context manager to simplify the use of ContextVars.
121
+
122
+ The value will be reset upon exiting the context.
123
+ """
124
+ token = context_var.set(value)
125
+ yield
126
+ context_var.reset(token)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hexkit
3
- Version: 1.0.0
3
+ Version: 1.1.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
@@ -31,13 +31,13 @@ Provides-Extra: mongodb
31
31
  Requires-Dist: motor<4,>=3.1.1; extra == "mongodb"
32
32
  Provides-Extra: test-akafka
33
33
  Requires-Dist: hexkit[akafka]; extra == "test-akafka"
34
- Requires-Dist: testcontainers[kafka]<4,>=3.4.1; extra == "test-akafka"
34
+ Requires-Dist: testcontainers[kafka]<4,>=3.7.1; extra == "test-akafka"
35
35
  Provides-Extra: test-s3
36
36
  Requires-Dist: hexkit[s3]; extra == "test-s3"
37
- Requires-Dist: testcontainers<4,>=3.4.1; extra == "test-s3"
37
+ Requires-Dist: testcontainers<4,>=3.7.1; extra == "test-s3"
38
38
  Provides-Extra: test-mongodb
39
39
  Requires-Dist: hexkit[mongodb]; extra == "test-mongodb"
40
- Requires-Dist: testcontainers[mongo]<4,>=3.4.1; extra == "test-mongodb"
40
+ Requires-Dist: testcontainers[mongo]<4,>=3.7.1; extra == "test-mongodb"
41
41
  Provides-Extra: test
42
42
  Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
43
43
  Provides-Extra: all
@@ -6,6 +6,7 @@ src/hexkit/__init__.py
6
6
  src/hexkit/__main__.py
7
7
  src/hexkit/base.py
8
8
  src/hexkit/config.py
9
+ src/hexkit/correlation.py
9
10
  src/hexkit/custom_types.py
10
11
  src/hexkit/inject.py
11
12
  src/hexkit/utils.py
@@ -22,12 +22,12 @@ hexkit[test-akafka,test-mongodb,test-s3]
22
22
 
23
23
  [test-akafka]
24
24
  hexkit[akafka]
25
- testcontainers[kafka]<4,>=3.4.1
25
+ testcontainers[kafka]<4,>=3.7.1
26
26
 
27
27
  [test-mongodb]
28
28
  hexkit[mongodb]
29
- testcontainers[mongo]<4,>=3.4.1
29
+ testcontainers[mongo]<4,>=3.7.1
30
30
 
31
31
  [test-s3]
32
32
  hexkit[s3]
33
- testcontainers<4,>=3.4.1
33
+ testcontainers<4,>=3.7.1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes