hexkit 3.4.0__tar.gz → 3.5.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 (56) hide show
  1. {hexkit-3.4.0/src/hexkit.egg-info → hexkit-3.5.0}/PKG-INFO +1 -1
  2. {hexkit-3.4.0 → hexkit-3.5.0}/pyproject.toml +1 -1
  3. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/config.py +6 -29
  4. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/dao.py +1 -1
  5. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/config.py +7 -1
  6. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/provider/eventpub.py +4 -0
  7. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/provider/eventsub.py +4 -0
  8. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongodb/provider.py +6 -1
  9. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongokafka/provider.py +12 -0
  10. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/testutils/__init__.py +32 -3
  11. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/testing/eventpub.py +2 -0
  12. {hexkit-3.4.0 → hexkit-3.5.0/src/hexkit.egg-info}/PKG-INFO +1 -1
  13. {hexkit-3.4.0 → hexkit-3.5.0}/LICENSE +0 -0
  14. {hexkit-3.4.0 → hexkit-3.5.0}/MANIFEST.in +0 -0
  15. {hexkit-3.4.0 → hexkit-3.5.0}/README.md +0 -0
  16. {hexkit-3.4.0 → hexkit-3.5.0}/setup.cfg +0 -0
  17. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/__init__.py +0 -0
  18. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/__main__.py +0 -0
  19. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/base.py +0 -0
  20. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/correlation.py +0 -0
  21. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/custom_types.py +0 -0
  22. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/log.py +0 -0
  23. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/__init__.py +0 -0
  24. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/daopub.py +0 -0
  25. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/daosub.py +0 -0
  26. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/eventpub.py +0 -0
  27. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/eventsub.py +0 -0
  28. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/protocols/objstorage.py +0 -0
  29. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/__init__.py +0 -0
  30. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  31. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  32. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
  33. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  34. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  35. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/akafka/testutils.py +0 -0
  36. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  37. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
  38. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
  39. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  40. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/__init__.py +0 -0
  41. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/provider.py +0 -0
  42. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  43. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  44. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  45. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  46. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  47. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
  48. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  49. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
  50. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/providers/testing/__init__.py +0 -0
  51. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/py.typed +0 -0
  52. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit/utils.py +0 -0
  53. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit.egg-info/SOURCES.txt +0 -0
  54. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  55. {hexkit-3.4.0 → hexkit-3.5.0}/src/hexkit.egg-info/requires.txt +0 -0
  56. {hexkit-3.4.0 → hexkit-3.5.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: 3.4.0
3
+ Version: 3.5.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
@@ -23,7 +23,7 @@ classifiers = [
23
23
  "Intended Audience :: Developers",
24
24
  ]
25
25
  name = "hexkit"
26
- version = "3.4.0"
26
+ version = "3.5.0"
27
27
  description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
28
28
  dependencies = [
29
29
  "pydantic >=2, <3",
@@ -17,13 +17,13 @@
17
17
 
18
18
  import os
19
19
  from pathlib import Path
20
- from typing import Any, Callable, Final, Optional
20
+ from typing import Callable, Final, Optional
21
21
 
22
- import yaml
23
22
  from pydantic_settings import (
24
23
  BaseSettings,
25
24
  PydanticBaseSettingsSource,
26
25
  SettingsConfigDict,
26
+ YamlConfigSettingsSource,
27
27
  )
28
28
 
29
29
  # Default config prefix:
@@ -79,29 +79,6 @@ def get_default_config_yaml(prefix: str) -> Optional[Path]:
79
79
  return None
80
80
 
81
81
 
82
- def yaml_settings_factory(
83
- config_yaml: Optional[Path] = None,
84
- ) -> Callable[[], dict[str, Any]]:
85
- """
86
- A factory of source methods for pydantic's BaseSettings Config that load
87
- settings from a yaml file.
88
-
89
- Args:
90
- config_yaml (str, Optional):
91
- Path to the yaml file to read from.
92
- """
93
-
94
- def yaml_settings() -> dict[str, Any]:
95
- """Source method for loading pydantic BaseSettings from a yaml file"""
96
- if config_yaml is None:
97
- return {}
98
-
99
- with open(config_yaml, encoding="utf8") as yaml_file:
100
- return yaml.safe_load(yaml_file)
101
-
102
- return yaml_settings
103
-
104
-
105
82
  def config_from_yaml(
106
83
  prefix: str = DEFAULT_CONFIG_PREFIX,
107
84
  ) -> Callable:
@@ -119,7 +96,7 @@ def config_from_yaml(
119
96
 
120
97
  Args:
121
98
  prefix: (str, optional):
122
- When defining parameters via enviroment variables, all variables
99
+ When defining parameters via environment variables, all variables
123
100
  have to be prefixed with this string following this pattern
124
101
  "{prefix}_{actual_variable_name}". Moreover, this prefix is used
125
102
  to derive the default location for the config yaml file
@@ -161,7 +138,7 @@ def config_from_yaml(
161
138
  raise ConfigYamlDoesNotExist(path=config_yaml)
162
139
 
163
140
  class ModSettings(settings):
164
- """Modifies the orginal Settings class provided by the user"""
141
+ """Modifies the original Settings class provided by the user"""
165
142
 
166
143
  model_config = SettingsConfigDict(frozen=True, env_prefix=f"{prefix}_")
167
144
 
@@ -174,12 +151,12 @@ def config_from_yaml(
174
151
  dotenv_settings: PydanticBaseSettingsSource,
175
152
  file_secret_settings: PydanticBaseSettingsSource,
176
153
  ):
177
- """Add custom yaml source"""
154
+ """Add custom yaml source, but maintain the priority of sources."""
178
155
  return (
179
156
  init_settings,
180
157
  env_settings,
181
158
  file_secret_settings,
182
- yaml_settings_factory(config_yaml),
159
+ YamlConfigSettingsSource(settings_cls, config_yaml),
183
160
  )
184
161
 
185
162
  # construct settings class:
@@ -61,7 +61,7 @@ class ResourceAlreadyExistsError(RuntimeError):
61
61
  """Raised when a resource did unexpectedly exist."""
62
62
 
63
63
  def __init__(self, *, id_: str):
64
- message = f'The resource with the id "{id_}" does already exist.'
64
+ message = f'The resource with the id "{id_}" already exists.'
65
65
  super().__init__(message)
66
66
 
67
67
 
@@ -18,7 +18,7 @@
18
18
 
19
19
  from typing import Literal
20
20
 
21
- from pydantic import Field, SecretStr
21
+ from pydantic import Field, PositiveInt, SecretStr
22
22
  from pydantic_settings import BaseSettings
23
23
 
24
24
 
@@ -76,3 +76,9 @@ class KafkaConfig(BaseSettings):
76
76
  + " newly correlation ID will be generated and used in the event header."
77
77
  ),
78
78
  )
79
+ kafka_max_message_size: PositiveInt = Field(
80
+ default=1024 * 1024, # 1 MiB
81
+ description="The largest message size that can be transmitted, in bytes. Only"
82
+ + " services that have a need to send/receive larger messages should set this.",
83
+ examples=[1024 * 1024, 16 * 1024 * 1024],
84
+ )
@@ -54,6 +54,7 @@ class KafkaProducerCompatible(Protocol):
54
54
  client_id: str,
55
55
  key_serializer: Callable[[Any], bytes],
56
56
  value_serializer: Callable[[Any], bytes],
57
+ max_request_size: int,
57
58
  ):
58
59
  """
59
60
  Initialize the producer with some config params.
@@ -67,6 +68,8 @@ class KafkaProducerCompatible(Protocol):
67
68
  Function to serialize the keys into bytes.
68
69
  value_serializer:
69
70
  Function to serialize the values into bytes.
71
+ max_request_size:
72
+ Maximum sendable message size.
70
73
  """
71
74
  ...
72
75
 
@@ -116,6 +119,7 @@ class KafkaEventPublisher(EventPublisherProtocol):
116
119
  value_serializer=lambda event_value: json.dumps(event_value).encode(
117
120
  "ascii"
118
121
  ),
122
+ max_request_size=config.kafka_max_message_size,
119
123
  )
120
124
 
121
125
  try:
@@ -90,6 +90,7 @@ class KafkaConsumerCompatible(Protocol):
90
90
  enable_auto_commit: bool,
91
91
  key_deserializer: Callable[[bytes], str],
92
92
  value_deserializer: Callable[[bytes], str],
93
+ max_partition_fetch_bytes: int,
93
94
  ):
94
95
  """
95
96
  Initialize the consumer with some config params.
@@ -108,6 +109,8 @@ class KafkaConsumerCompatible(Protocol):
108
109
  Function to deserialize the keys into strings.
109
110
  value_serializer:
110
111
  Function to deserialize the values into strings.
112
+ max_partition_fetch_bytes:
113
+ Maximum receivable message size
111
114
  """
112
115
  ...
113
116
 
@@ -176,6 +179,7 @@ class KafkaEventSubscriber(InboundProviderBase):
176
179
  value_deserializer=lambda event_value: json.loads(
177
180
  event_value.decode("ascii")
178
181
  ),
182
+ max_partition_fetch_bytes=config.kafka_max_message_size,
179
183
  )
180
184
  try:
181
185
  await consumer.start()
@@ -31,6 +31,7 @@ from motor.core import AgnosticCollection
31
31
  from motor.motor_asyncio import AsyncIOMotorClient
32
32
  from pydantic import Field, SecretStr
33
33
  from pydantic_settings import BaseSettings
34
+ from pymongo.errors import DuplicateKeyError
34
35
 
35
36
  from hexkit.protocols.dao import (
36
37
  DaoFactoryProtocol,
@@ -42,6 +43,7 @@ from hexkit.protocols.dao import (
42
43
  InvalidFindMappingError,
43
44
  MultipleHitsFoundError,
44
45
  NoHitsFoundError,
46
+ ResourceAlreadyExistsError,
45
47
  ResourceNotFoundError,
46
48
  )
47
49
  from hexkit.utils import FieldNotInModelError, validate_fields_in_model
@@ -386,7 +388,10 @@ class MongoDbDaoNaturalId(MongoDbDaoBase[Dto]):
386
388
  when a resource with the ID specified in the dto does already exist.
387
389
  """
388
390
  document = self._dto_to_document(dto)
389
- await self._collection.insert_one(document)
391
+ try:
392
+ await self._collection.insert_one(document)
393
+ except DuplicateKeyError as error:
394
+ raise ResourceAlreadyExistsError(id_=document["_id"]) from error
390
395
 
391
396
  async def upsert(self, dto: Dto) -> None:
392
397
  """Update the provided resource if it already exists, create it otherwise.
@@ -20,6 +20,7 @@ Require dependencies of the `akafka` and `mongodb` extras.
20
20
  """
21
21
 
22
22
  import json
23
+ import logging
23
24
  from collections.abc import AsyncIterator, Awaitable, Collection, Mapping
24
25
  from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
25
26
  from typing import Any, Callable, Generic, Optional
@@ -27,6 +28,7 @@ from typing import Any, Callable, Generic, Optional
27
28
  from aiokafka import AIOKafkaProducer
28
29
  from motor.core import AgnosticCollection
29
30
  from motor.motor_asyncio import AsyncIOMotorClient
31
+ from pydantic import field_validator
30
32
 
31
33
  from hexkit.correlation import get_correlation_id, set_correlation_id
32
34
  from hexkit.custom_types import JsonObject
@@ -396,6 +398,16 @@ class MongoKafkaDaoPublisher(Generic[Dto]):
396
398
  class MongoKafkaConfig(MongoDbConfig, KafkaConfig):
397
399
  """Config parameters and their defaults."""
398
400
 
401
+ @field_validator("kafka_max_message_size", mode="after")
402
+ @classmethod
403
+ def validate_max_message_size(cls, value: int) -> int:
404
+ """Validate the maximum message size."""
405
+ if value > 2**24: # 16 MiB
406
+ logging.warning(
407
+ f"Max message size ({value}) exceeds the 16 MiB document size limit for MongoDB!"
408
+ )
409
+ return value
410
+
399
411
 
400
412
  class MongoKafkaDaoPublisherFactory(DaoPublisherFactoryProtocol):
401
413
  """A provider implementing the DaoPublisherFactoryProtocol based on MongoDB and
@@ -15,7 +15,7 @@
15
15
 
16
16
  """S3-related test fixtures and support functions."""
17
17
 
18
- from ._fixtures import ( # noqa: F401
18
+ from ._fixtures import (
19
19
  LOCALSTACK_IMAGE,
20
20
  MEBIBYTE,
21
21
  TEST_FILE_DIR,
@@ -36,8 +36,8 @@ from ._fixtures import ( # noqa: F401
36
36
  tmp_file,
37
37
  upload_file,
38
38
  )
39
- from ._typical_workflow import typical_workflow # noqa: F401
40
- from ._utils import ( # noqa: F401
39
+ from ._typical_workflow import typical_workflow
40
+ from ._utils import (
41
41
  check_part_size,
42
42
  download_and_check_test_file,
43
43
  multipart_upload_file,
@@ -45,3 +45,32 @@ from ._utils import ( # noqa: F401
45
45
  upload_part_of_size,
46
46
  upload_part_via_url,
47
47
  )
48
+
49
+ __all__ = [
50
+ "LOCALSTACK_IMAGE",
51
+ "MEBIBYTE",
52
+ "TEST_FILE_DIR",
53
+ "TEST_FILE_PATHS",
54
+ "FileObject",
55
+ "S3ContainerFixture",
56
+ "S3Fixture",
57
+ "calc_md5",
58
+ "clean_s3_fixture",
59
+ "get_clean_s3_fixture",
60
+ "get_persistent_s3_fixture",
61
+ "get_s3_container_fixture",
62
+ "persistent_s3_fixture",
63
+ "populate_storage",
64
+ "s3_container_fixture",
65
+ "s3_fixture",
66
+ "temp_file_object",
67
+ "tmp_file",
68
+ "upload_file",
69
+ "typical_workflow",
70
+ "check_part_size",
71
+ "download_and_check_test_file",
72
+ "multipart_upload_file",
73
+ "upload_part",
74
+ "upload_part_of_size",
75
+ "upload_part_via_url",
76
+ ]
@@ -26,6 +26,8 @@ from typing import NamedTuple, Optional
26
26
  from hexkit.custom_types import JsonObject
27
27
  from hexkit.protocols.eventpub import EventPublisherProtocol
28
28
 
29
+ __all__ = ["InMemEventStore", "InMemEventPublisher", "TopicExhaustedError"]
30
+
29
31
 
30
32
  class TopicExhaustedError(RuntimeError):
31
33
  """Thrown when now more event are queued in a topic."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hexkit
3
- Version: 3.4.0
3
+ Version: 3.5.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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes