hexkit 3.3.0__tar.gz → 3.4.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.3.0/src/hexkit.egg-info → hexkit-3.4.0}/PKG-INFO +1 -1
  2. {hexkit-3.3.0 → hexkit-3.4.0}/pyproject.toml +1 -1
  3. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/objstorage.py +21 -18
  4. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/testutils.py +1 -0
  5. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongodb/testutils.py +16 -10
  6. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/testutils/_fixtures.py +57 -11
  7. {hexkit-3.3.0 → hexkit-3.4.0/src/hexkit.egg-info}/PKG-INFO +1 -1
  8. {hexkit-3.3.0 → hexkit-3.4.0}/LICENSE +0 -0
  9. {hexkit-3.3.0 → hexkit-3.4.0}/MANIFEST.in +0 -0
  10. {hexkit-3.3.0 → hexkit-3.4.0}/README.md +0 -0
  11. {hexkit-3.3.0 → hexkit-3.4.0}/setup.cfg +0 -0
  12. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/__init__.py +0 -0
  13. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/__main__.py +0 -0
  14. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/base.py +0 -0
  15. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/config.py +0 -0
  16. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/correlation.py +0 -0
  17. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/custom_types.py +0 -0
  18. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/log.py +0 -0
  19. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/__init__.py +0 -0
  20. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/dao.py +0 -0
  21. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/daopub.py +0 -0
  22. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/daosub.py +0 -0
  23. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/eventpub.py +0 -0
  24. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/protocols/eventsub.py +0 -0
  25. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/__init__.py +0 -0
  26. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  27. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/config.py +0 -0
  28. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  29. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
  30. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
  31. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
  32. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  33. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  34. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  35. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongodb/provider.py +0 -0
  36. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
  37. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongokafka/provider.py +0 -0
  38. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  39. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/__init__.py +0 -0
  40. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/provider.py +0 -0
  41. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  42. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  43. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  44. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  45. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  46. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
  47. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  48. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
  49. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/testing/__init__.py +0 -0
  50. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/providers/testing/eventpub.py +0 -0
  51. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/py.typed +0 -0
  52. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit/utils.py +0 -0
  53. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit.egg-info/SOURCES.txt +0 -0
  54. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  55. {hexkit-3.3.0 → hexkit-3.4.0}/src/hexkit.egg-info/requires.txt +0 -0
  56. {hexkit-3.3.0 → hexkit-3.4.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.3.0
3
+ Version: 3.4.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.3.0"
26
+ version = "3.4.0"
27
27
  description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
28
28
  dependencies = [
29
29
  "pydantic >=2, <3",
@@ -457,7 +457,10 @@ class ObjectStorageProtocol(ABC):
457
457
 
458
458
  # Validation logic for input parameter:
459
459
  # (is typically only used by the protocol but may also be used in
460
- # provider-specific code)
460
+ # provider-specific code or overwritten by the provider)
461
+
462
+ _re_bucket_id = re.compile(r"^[a-z0-9\-]{3,63}$")
463
+ _re_bucket_id_msg = "must consist of 3-63 lowercase letters, digits or hyphens"
461
464
 
462
465
  @classmethod
463
466
  def _validate_bucket_id(cls, bucket_id: str):
@@ -466,43 +469,43 @@ class ObjectStorageProtocol(ABC):
466
469
  https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
467
470
  Raises BucketIdValidationError if not valid.
468
471
  """
469
- if not 3 <= len(bucket_id) < 64:
472
+ if not cls._re_bucket_id.match(bucket_id):
470
473
  raise cls.BucketIdValidationError(
471
474
  bucket_id=bucket_id,
472
- reason="must be between 3 and 63 characters long",
473
- )
474
- if not re.match(r"^[a-z0-9\-]*$", bucket_id):
475
- raise cls.BucketIdValidationError(
476
- bucket_id=bucket_id,
477
- reason="only lowercase letters, digits and hyphens (-) are allowed",
475
+ reason=cls._re_bucket_id_msg,
478
476
  )
479
477
  if bucket_id.startswith("-") or bucket_id.endswith("-"):
480
478
  raise cls.BucketIdValidationError(
481
479
  bucket_id=bucket_id,
482
- reason="may not start or end with a hyphen (-).",
480
+ reason="may not start or end with a hyphen",
483
481
  )
484
482
 
483
+ _re_object_id = re.compile(r"^[a-zA-Z0-9\-_.]{3,255}$")
484
+ _re_object_id_msg = (
485
+ "must consist of 3-255 letters, digits, hyphens, underscores or dots"
486
+ )
487
+
485
488
  @classmethod
486
489
  def _validate_object_id(cls, object_id: str):
487
- """Check whether a object id follows the recommended naming pattern.
490
+ """Check whether an object id follows the recommended naming pattern.
488
491
  This is roughly based on (plus some additional restrictions):
489
492
  https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
490
493
  Raises ObjectIdValidationError if not valid.
494
+
495
+ Note that the default implementation does not allow object IDs to contain
496
+ forward slashes (/) which are sometimes used to represent directories.
497
+ Providers can override this method or the above constants to allow slashes,
498
+ other characters or longer object names.
491
499
  """
492
- if not 3 <= len(object_id) < 64:
493
- raise cls.ObjectIdValidationError(
494
- object_id=object_id,
495
- reason="must be between 3 and 63 characters long",
496
- )
497
- if not re.match(r"^[a-zA-Z0-9\-\.]*$", object_id):
500
+ if not cls._re_object_id.match(object_id):
498
501
  raise cls.ObjectIdValidationError(
499
502
  object_id=object_id,
500
- reason="only letters, digits, hyphens (-) and dots (.) are allowed",
503
+ reason=cls._re_object_id_msg,
501
504
  )
502
505
  if object_id.startswith(("-", ".")) or object_id.endswith(("-", ".")):
503
506
  raise cls.ObjectIdValidationError(
504
507
  object_id=object_id,
505
- reason="may not start or end with a hyphen (-) or a dot (.).",
508
+ reason="object names may not start or end with a hyphen or a dot",
506
509
  )
507
510
 
508
511
  # Exceptions that may be used by implementation:
@@ -430,6 +430,7 @@ class KafkaFixture:
430
430
 
431
431
  async def clear_topics(
432
432
  self,
433
+ *,
433
434
  topics: Optional[Union[str, list[str]]] = None,
434
435
  exclude_internal: bool = True,
435
436
  ):
@@ -61,24 +61,30 @@ class MongoDbFixture:
61
61
 
62
62
  def empty_collections(
63
63
  self,
64
+ *,
65
+ collections: Optional[Union[str, list[str]]] = None,
64
66
  exclude_collections: Optional[Union[str, list[str]]] = None,
65
67
  ):
66
- """Drop all mongodb collections in the database.
68
+ """Drop the given mongodb collection(s) in the database.
67
69
 
70
+ If no collections are specified, all collections will be dropped.
68
71
  You can also specify collection(s) that should be excluded
69
72
  from the operation, i.e. collections that should be kept.
70
73
  """
71
74
  db_name = self.config.db_name
72
- if exclude_collections is None:
73
- exclude_collections = []
74
- if isinstance(exclude_collections, str):
75
- exclude_collections = [exclude_collections]
76
- excluded_collections = set(exclude_collections)
77
75
  try:
78
- collection_names = self.client[db_name].list_collection_names()
79
- for collection_name in collection_names:
80
- if collection_name not in excluded_collections:
81
- self.client[db_name].drop_collection(collection_name)
76
+ if collections is None:
77
+ collections = self.client[db_name].list_collection_names()
78
+ elif isinstance(collections, str):
79
+ collections = [collections]
80
+ if exclude_collections is None:
81
+ exclude_collections = []
82
+ elif isinstance(exclude_collections, str):
83
+ exclude_collections = [exclude_collections]
84
+ excluded_collections = set(exclude_collections)
85
+ for collection in collections:
86
+ if collection not in excluded_collections:
87
+ self.client[db_name].drop_collection(collection)
82
88
  except (ExecutionTimeout, OperationFailure) as error:
83
89
  raise RuntimeError(
84
90
  f"Could not drop collection(s) of Mongo database {db_name}"
@@ -24,7 +24,7 @@ from collections.abc import AsyncGenerator, Generator
24
24
  from contextlib import contextmanager
25
25
  from pathlib import Path
26
26
  from tempfile import NamedTemporaryFile
27
- from typing import Any, NamedTuple, Optional
27
+ from typing import Any, NamedTuple, Optional, Union
28
28
 
29
29
  try:
30
30
  from typing import Self
@@ -93,11 +93,10 @@ class S3Fixture:
93
93
  self.config = config
94
94
  self.storage = storage
95
95
 
96
- def get_buckets(self) -> set[str]:
96
+ def get_buckets(self) -> list[str]:
97
97
  """Return a list of the buckets currently existing in the S3 object storage."""
98
98
  response = self.storage._client.list_buckets()
99
- buckets = {bucket["Name"] for bucket in response["Buckets"]}
100
- return buckets
99
+ return [bucket["Name"] for bucket in response["Buckets"]]
101
100
 
102
101
  async def populate_buckets(self, buckets: list[str]):
103
102
  """Populate the storage with buckets."""
@@ -111,19 +110,66 @@ class S3Fixture:
111
110
  self.storage, bucket_fixtures=[], object_fixtures=file_objects
112
111
  )
113
112
 
114
- async def empty_buckets(self, buckets_to_exclude: Optional[list[str]] = None):
115
- """Clean the test artifacts or files from the populated buckets."""
116
- for bucket in self.get_buckets().difference(buckets_to_exclude or []):
113
+ async def empty_buckets(
114
+ self,
115
+ *,
116
+ buckets: Optional[Union[str, list[str]]] = None,
117
+ exclude_buckets: Optional[Union[str, list[str]]] = None,
118
+ ):
119
+ """Remove all test objects from the given bucket(s).
120
+
121
+ If no buckets are specified, all existing buckets will be emptied,
122
+ therefore we recommend to always specify a bucket list.
123
+ You can also specify bucket(s) that should be excluded
124
+ from the operation, i.e. buckets that should not be emptied.
125
+ """
126
+ if buckets is None:
127
+ buckets = self.get_buckets()
128
+ elif isinstance(buckets, str):
129
+ buckets = [buckets]
130
+ if exclude_buckets is None:
131
+ exclude_buckets = []
132
+ elif isinstance(exclude_buckets, str):
133
+ exclude_buckets = [exclude_buckets]
134
+ excluded_buckets = set(exclude_buckets)
135
+ for bucket in buckets:
136
+ if bucket in excluded_buckets:
137
+ continue
117
138
  # Get list of all objects in the bucket
118
139
  object_ids = await self.storage.list_all_object_ids(bucket_id=bucket)
119
140
  # Delete all of these objects
120
141
  for object_id in object_ids:
142
+ # Note that id validation errors can be raised here if the bucket
143
+ # was populated by other means than the S3 storage fixture.
144
+ # We intentionally do not catch these errors because they
145
+ # usually mean that you're operating on the wrong buckets.
121
146
  await self.storage.delete_object(bucket_id=bucket, object_id=object_id)
122
147
 
123
- async def delete_buckets(self, buckets_to_exclude: Optional[list[str]] = None):
124
- """Delete the populated buckets."""
125
- for bucket in self.get_buckets().difference(buckets_to_exclude or []):
126
- await self.storage.delete_bucket(bucket, delete_content=True)
148
+ async def delete_buckets(
149
+ self,
150
+ *,
151
+ buckets: Optional[Union[str, list[str]]] = None,
152
+ exclude_buckets: Optional[Union[str, list[str]]] = None,
153
+ ):
154
+ """Delete the given bucket(s).
155
+
156
+ If no buckets are specified, all existing buckets will be deleted,
157
+ therefore we recommend to always specify a bucket list.
158
+ You can also specify bucket(s) that should be excluded
159
+ from the operation, i.e. buckets that should be kept.
160
+ """
161
+ if buckets is None:
162
+ buckets = self.get_buckets()
163
+ elif isinstance(buckets, str):
164
+ buckets = [buckets]
165
+ if exclude_buckets is None:
166
+ exclude_buckets = []
167
+ elif isinstance(exclude_buckets, str):
168
+ exclude_buckets = [exclude_buckets]
169
+ excluded_buckets = set(exclude_buckets)
170
+ for bucket in buckets:
171
+ if bucket not in excluded_buckets:
172
+ await self.storage.delete_bucket(bucket, delete_content=True)
127
173
 
128
174
  async def get_initialized_upload(self) -> UploadDetails:
129
175
  """Initialize a new empty multipart upload process.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hexkit
3
- Version: 3.3.0
3
+ Version: 3.4.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
File without changes