hexkit 0.10.2__tar.gz → 0.11.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 (80) hide show
  1. {hexkit-0.10.2 → hexkit-0.11.1}/PKG-INFO +23 -7
  2. hexkit-0.11.1/pyproject.toml +52 -0
  3. hexkit-0.11.1/setup.cfg +4 -0
  4. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/__init__.py +3 -1
  5. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/config.py +30 -37
  6. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/custom_types.py +5 -1
  7. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/inject.py +13 -15
  8. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/protocols/dao.py +22 -17
  9. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/protocols/objstorage.py +8 -24
  10. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/akafka/__init__.py +5 -2
  11. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/akafka/provider.py +9 -14
  12. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/akafka/testutils.py +29 -40
  13. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/mongodb/__init__.py +1 -1
  14. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/mongodb/provider.py +30 -35
  15. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/mongodb/testutils.py +6 -7
  16. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/__init__.py +1 -1
  17. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/provider.py +23 -58
  18. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/testutils.py +36 -56
  19. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/testing/eventpub.py +1 -5
  20. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/testing/utils.py +5 -4
  21. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/utils.py +5 -5
  22. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit.egg-info/PKG-INFO +23 -7
  23. hexkit-0.11.1/src/hexkit.egg-info/SOURCES.txt +38 -0
  24. hexkit-0.11.1/src/hexkit.egg-info/requires.txt +33 -0
  25. hexkit-0.11.1/src/hexkit.egg-info/top_level.txt +1 -0
  26. hexkit-0.10.2/hexkit.egg-info/SOURCES.txt +0 -74
  27. hexkit-0.10.2/hexkit.egg-info/not-zip-safe +0 -1
  28. hexkit-0.10.2/hexkit.egg-info/requires.txt +0 -48
  29. hexkit-0.10.2/hexkit.egg-info/top_level.txt +0 -3
  30. hexkit-0.10.2/scripts/__init__.py +0 -17
  31. hexkit-0.10.2/scripts/get_package_name.py +0 -46
  32. hexkit-0.10.2/scripts/license_checker.py +0 -562
  33. hexkit-0.10.2/scripts/script_utils/__init__.py +0 -17
  34. hexkit-0.10.2/scripts/script_utils/cli.py +0 -36
  35. hexkit-0.10.2/scripts/update_template_files.py +0 -250
  36. hexkit-0.10.2/setup.cfg +0 -69
  37. hexkit-0.10.2/setup.py +0 -23
  38. hexkit-0.10.2/tests/fixtures/__init__.py +0 -20
  39. hexkit-0.10.2/tests/fixtures/config.py +0 -104
  40. hexkit-0.10.2/tests/fixtures/config_env_var_sets.yaml +0 -12
  41. hexkit-0.10.2/tests/fixtures/dummy_joint.py +0 -38
  42. hexkit-0.10.2/tests/fixtures/inject.py +0 -140
  43. hexkit-0.10.2/tests/fixtures/test_files/__init__.py +0 -14
  44. hexkit-0.10.2/tests/fixtures/test_files/test_file1.yaml +0 -3
  45. hexkit-0.10.2/tests/fixtures/test_files/test_file2.yaml +0 -3
  46. hexkit-0.10.2/tests/fixtures/test_files/test_file3.yaml +0 -3
  47. hexkit-0.10.2/tests/fixtures/test_files/test_file4.yaml +0 -3
  48. hexkit-0.10.2/tests/fixtures/utils.py +0 -35
  49. hexkit-0.10.2/tests/integration/__init__.py +0 -17
  50. hexkit-0.10.2/tests/integration/test_akafka.py +0 -97
  51. hexkit-0.10.2/tests/integration/test_akafka_testutils.py +0 -255
  52. hexkit-0.10.2/tests/integration/test_event_loop_default.py +0 -35
  53. hexkit-0.10.2/tests/integration/test_event_loop_overridden.py +0 -35
  54. hexkit-0.10.2/tests/integration/test_inject.py +0 -177
  55. hexkit-0.10.2/tests/integration/test_mongodb.py +0 -366
  56. hexkit-0.10.2/tests/integration/test_s3.py +0 -595
  57. hexkit-0.10.2/tests/unit/__init__.py +0 -17
  58. hexkit-0.10.2/tests/unit/test_akafka.py +0 -193
  59. hexkit-0.10.2/tests/unit/test_config.py +0 -128
  60. hexkit-0.10.2/tests/unit/test_dao.py +0 -164
  61. hexkit-0.10.2/tests/unit/test_eventpub.py +0 -76
  62. hexkit-0.10.2/tests/unit/test_eventsub.py +0 -67
  63. hexkit-0.10.2/tests/unit/test_inject.py +0 -146
  64. hexkit-0.10.2/tests/unit/test_testing_eventpub.py +0 -120
  65. hexkit-0.10.2/tests/unit/test_utils.py +0 -100
  66. {hexkit-0.10.2 → hexkit-0.11.1}/LICENSE +0 -0
  67. {hexkit-0.10.2 → hexkit-0.11.1}/README.md +0 -0
  68. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/__main__.py +0 -0
  69. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/base.py +0 -0
  70. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/protocols/__init__.py +0 -0
  71. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/protocols/eventpub.py +0 -0
  72. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/protocols/eventsub.py +0 -0
  73. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/__init__.py +0 -0
  74. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/test_files/__init__.py +0 -0
  75. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  76. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  77. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  78. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  79. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit/providers/testing/__init__.py +0 -0
  80. {hexkit-0.10.2 → hexkit-0.11.1/src}/hexkit.egg-info/dependency_links.txt +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hexkit
3
- Version: 0.10.2
3
+ Version: 0.11.1
4
4
  Summary: A Toolkit for Building Microservices using the Hexagonal Architecture
5
- Home-page: https://github.com/ghga-de/hexkit
6
- Author: German Human Genome Phenome Archive (GHGA)
7
- Author-email: contact@ghga.de
5
+ Author-email: "German Human Genome Phenome Archive (GHGA)" <contact@ghga.de>
8
6
  License: Apache 2.0
7
+ Project-URL: Repository, https://github.com/ghga-de/hexkit
9
8
  Classifier: Development Status :: 1 - Planning
10
9
  Classifier: Operating System :: POSIX :: Linux
11
10
  Classifier: Programming Language :: Python :: 3.9
@@ -16,16 +15,33 @@ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
16
15
  Classifier: Topic :: Software Development :: Libraries
17
16
  Classifier: Intended Audience :: Developers
18
17
  Requires-Python: >=3.9
19
- Description-Content-Type: text/markdown; charset=UTF-8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pydantic<3,>=2
21
+ Requires-Dist: pydantic_settings<3,>=2
22
+ Requires-Dist: PyYAML<7,>=6.0
23
+ Requires-Dist: dependency-injector<5,>=4.41.0
20
24
  Provides-Extra: akafka
25
+ Requires-Dist: aiokafka~=0.8.0; extra == "akafka"
26
+ Requires-Dist: jsonschema<5,>=4.17.3; extra == "akafka"
21
27
  Provides-Extra: s3
28
+ Requires-Dist: boto3<2,>=1.26.50; extra == "s3"
29
+ Requires-Dist: botocore<2,>=1.29.50; extra == "s3"
22
30
  Provides-Extra: mongodb
31
+ Requires-Dist: motor<4,>=3.1.1; extra == "mongodb"
23
32
  Provides-Extra: test-akafka
33
+ Requires-Dist: hexkit[akafka]; extra == "test-akafka"
34
+ Requires-Dist: testcontainers[kafka]<4,>=3.4.1; extra == "test-akafka"
24
35
  Provides-Extra: test-s3
36
+ Requires-Dist: hexkit[s3]; extra == "test-s3"
37
+ Requires-Dist: testcontainers<4,>=3.4.1; extra == "test-s3"
25
38
  Provides-Extra: test-mongodb
26
- Provides-Extra: test-all
39
+ Requires-Dist: hexkit[mongodb]; extra == "test-mongodb"
40
+ Requires-Dist: testcontainers[mongo]<4,>=3.4.1; extra == "test-mongodb"
41
+ Provides-Extra: test
42
+ Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
27
43
  Provides-Extra: all
28
- License-File: LICENSE
44
+ Requires-Dist: hexkit[test]; extra == "all"
29
45
 
30
46
  ![tests](https://github.com/ghga-de/hexkit/actions/workflows/unit_and_int_tests.yaml/badge.svg)
31
47
  [![PyPI version shields.io](https://img.shields.io/pypi/v/hexkit.svg)](https://pypi.python.org/pypi/hexkit/)
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=67.7.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hexkit"
7
+ version = "0.11.1"
8
+ description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "German Human Genome Phenome Archive (GHGA)", email = "contact@ghga.de" },
12
+ ]
13
+ requires-python = ">=3.9"
14
+ license = { text = "Apache 2.0" }
15
+ classifiers = [
16
+ "Development Status :: 1 - Planning",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "License :: OSI Approved :: Apache Software License",
22
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Intended Audience :: Developers",
25
+ ]
26
+ dependencies = [
27
+ "pydantic >=2, <3",
28
+ "pydantic_settings >=2, <3",
29
+ "PyYAML >=6.0, <7",
30
+ "dependency-injector >=4.41.0, <5",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ akafka = ["aiokafka~=0.8.0", "jsonschema >=4.17.3, <5"]
35
+ s3 = ["boto3 >=1.26.50, <2", "botocore >=1.29.50, <2"]
36
+ mongodb = ["motor >=3.1.1, <4"]
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"]
41
+ test = ["hexkit[test-akafka,test-s3,test-mongodb]"]
42
+
43
+ all = ["hexkit[test]"]
44
+
45
+ [project.urls]
46
+ Repository = "https://github.com/ghga-de/hexkit"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ "hexkit.providers.s3.test_files" = ["*.yaml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -15,4 +15,6 @@
15
15
 
16
16
  """A Toolkit for Building Microservices using the Hexagonal Architecture"""
17
17
 
18
- __version__ = "0.10.2"
18
+ from importlib.metadata import version
19
+
20
+ __version__ = version(__package__)
@@ -17,10 +17,14 @@
17
17
 
18
18
  import os
19
19
  from pathlib import Path
20
- from typing import Any, Callable, Dict, Final, Optional
20
+ from typing import Any, Callable, Final, Optional
21
21
 
22
22
  import yaml
23
- from pydantic import BaseSettings
23
+ from pydantic_settings import (
24
+ BaseSettings,
25
+ PydanticBaseSettingsSource,
26
+ SettingsConfigDict,
27
+ )
24
28
 
25
29
  # Default config prefix:
26
30
  DEFAULT_CONFIG_PREFIX: Final = "ghga_services"
@@ -77,7 +81,7 @@ def get_default_config_yaml(prefix: str) -> Optional[Path]:
77
81
 
78
82
  def yaml_settings_factory(
79
83
  config_yaml: Optional[Path] = None,
80
- ) -> Callable[[BaseSettings], Dict[str, Any]]:
84
+ ) -> Callable[[], dict[str, Any]]:
81
85
  """
82
86
  A factory of source methods for pydantic's BaseSettings Config that load
83
87
  settings from a yaml file.
@@ -87,14 +91,12 @@ def yaml_settings_factory(
87
91
  Path to the yaml file to read from.
88
92
  """
89
93
 
90
- def yaml_settings( # pylint: disable=unused-argument
91
- settings: BaseSettings,
92
- ) -> Dict[str, Any]:
93
- """source method for loading pydantic BaseSettings from a yaml file"""
94
+ def yaml_settings() -> dict[str, Any]:
95
+ """Source method for loading pydantic BaseSettings from a yaml file"""
94
96
  if config_yaml is None:
95
97
  return {}
96
98
 
97
- with open(config_yaml, "r", encoding="utf8") as yaml_file:
99
+ with open(config_yaml, encoding="utf8") as yaml_file:
98
100
  return yaml.safe_load(yaml_file)
99
101
 
100
102
  return yaml_settings
@@ -105,8 +107,8 @@ def config_from_yaml(
105
107
  ) -> Callable:
106
108
  """A factory that returns decorator functions which extends a
107
109
  pydantic BaseSettings class to read in parameters from a config yaml.
108
- It replaces (or adds) a Config subclass to the BaseSettings class that configures
109
- the priorities for parameter sources as follows (highest Priority first):
110
+ It replaces (or adds) config settings to the BaseSettings class that configures
111
+ the priorities for parameter sources as follows (highest priority first):
110
112
  - parameters passed using **kwargs
111
113
  - environment variables
112
114
  - file secrets
@@ -136,11 +138,10 @@ def config_from_yaml(
136
138
  settings (BaseSettings):
137
139
  A pydantic BaseSettings class to be modified.
138
140
  """
139
-
140
141
  # check if settings inherits from pydantic BaseSettings:
141
142
  if not issubclass(settings, BaseSettings):
142
143
  raise TypeError(
143
- "The specified settings class is not a subclass of pydantic.BaseSettings"
144
+ "The specified settings class is not a subclass of pydantic_settings.BaseSettings"
144
145
  )
145
146
 
146
147
  def constructor_wrapper(
@@ -153,41 +154,33 @@ def config_from_yaml(
153
154
  config_yaml (str, optional):
154
155
  Path to a config yaml. Overwrites the default location.
155
156
  """
156
-
157
157
  # get default path if config_yaml not specified:
158
158
  if config_yaml is None:
159
159
  config_yaml = get_default_config_yaml(prefix)
160
- else:
161
- if not config_yaml.is_file():
162
- raise ConfigYamlDoesNotExist(path=config_yaml)
160
+ elif not config_yaml.is_file():
161
+ raise ConfigYamlDoesNotExist(path=config_yaml)
163
162
 
164
163
  class ModSettings(settings):
165
164
  """Modifies the orginal Settings class provided by the user"""
166
165
 
167
- # pylint: disable=too-few-public-methods
168
- class Config:
169
- """pydantic Config subclass"""
170
-
171
- frozen = True
172
-
173
- # add this prefix to all variable names to
174
- # define them as environment variables:
175
- env_prefix = f"{prefix}_"
176
-
177
- @classmethod
178
- def customise_sources(
179
- cls,
166
+ model_config = SettingsConfigDict(frozen=True, env_prefix=f"{prefix}_")
167
+
168
+ @classmethod
169
+ def settings_customise_sources( # noqa: PLR0913
170
+ cls,
171
+ settings_cls: type[BaseSettings],
172
+ init_settings: PydanticBaseSettingsSource,
173
+ env_settings: PydanticBaseSettingsSource,
174
+ dotenv_settings: PydanticBaseSettingsSource,
175
+ file_secret_settings: PydanticBaseSettingsSource,
176
+ ):
177
+ """Add custom yaml source"""
178
+ return (
180
179
  init_settings,
181
180
  env_settings,
182
181
  file_secret_settings,
183
- ):
184
- """add custom yaml source"""
185
- return (
186
- init_settings,
187
- env_settings,
188
- file_secret_settings,
189
- yaml_settings_factory(config_yaml),
190
- )
182
+ yaml_settings_factory(config_yaml),
183
+ )
191
184
 
192
185
  # construct settings class:
193
186
  return ModSettings(**kwargs)
@@ -17,7 +17,7 @@
17
17
  """Module collecting custom types."""
18
18
 
19
19
  from collections.abc import Mapping, Sequence
20
- from typing import Any, Union
20
+ from typing import Any, Literal, Union
21
21
 
22
22
  # This is intended to type objects that could be a potential output of `json.loads`.
23
23
  # (Scalar types as well as arrays are excluded from the above assumption.)
@@ -74,3 +74,7 @@ AsyncConstructable = Any
74
74
  # ```
75
75
  # Thus using a type alias for now:
76
76
  AsyncContextConstructable = Any
77
+
78
+
79
+ # The possible scopes for pytest fixtures
80
+ PytestScope = Literal["session", "package", "module", "class", "function"]
@@ -33,7 +33,7 @@ from typing import Any, Callable, Generic, Optional, TypeVar, Union
33
33
 
34
34
  import dependency_injector.containers
35
35
  import dependency_injector.providers
36
- from pydantic import BaseSettings
36
+ from pydantic_settings import BaseSettings
37
37
 
38
38
  from hexkit.custom_types import AsyncConstructable, AsyncContextConstructable
39
39
 
@@ -55,7 +55,8 @@ class NotConstructableError(TypeError):
55
55
 
56
56
  class AsyncInitShutdownError(TypeError):
57
57
  """Thrown when a container has sync `init_resource` or `shutdown_resource` methods
58
- but coroutines are needed."""
58
+ but coroutines are needed.
59
+ """
59
60
 
60
61
 
61
62
  def assert_async_constructable(
@@ -68,7 +69,6 @@ def assert_async_constructable(
68
69
  not check whether `construct` really returns an awaitable or an async context
69
70
  manager.
70
71
  """
71
-
72
72
  if not callable(getattr(constructable, "construct", None)):
73
73
  raise NotConstructableError(
74
74
  "Async(Context)Constructable class must have a callable `construct` attribute."
@@ -77,7 +77,8 @@ def assert_async_constructable(
77
77
 
78
78
  class AsyncConstructor(dependency_injector.providers.Resource):
79
79
  """Maps an Async(Context)Constructable onto the Resource class from the
80
- `dependency_injector` framework."""
80
+ `dependency_injector` framework.
81
+ """
81
82
 
82
83
  @staticmethod
83
84
  def constructable_to_resource(
@@ -87,7 +88,6 @@ class AsyncConstructor(dependency_injector.providers.Resource):
87
88
  Converts an Async(Context)Constructable to an async generator that is compatible
88
89
  with the Resource definition of the `dependency_injector` framework.
89
90
  """
90
-
91
91
  assert_async_constructable(constructable)
92
92
 
93
93
  async def resource(*args: Any, **kwargs: Any) -> AsyncIterator[Any]:
@@ -120,7 +120,6 @@ class AsyncConstructor(dependency_injector.providers.Resource):
120
120
  **kwargs: dependency_injector.providers.Injection,
121
121
  ):
122
122
  """Initialize `dependency_injector`'s Resource with an AbstractAsyncContextManager."""
123
-
124
123
  if provides is None:
125
124
  super().__init__()
126
125
  else:
@@ -130,8 +129,8 @@ class AsyncConstructor(dependency_injector.providers.Resource):
130
129
 
131
130
  def get_constructor(provides: type, *args, **kwargs):
132
131
  """Automatically selects and applies the right constructor for the class given to
133
- `provides`."""
134
-
132
+ `provides`.
133
+ """
135
134
  constructor_cls: type
136
135
 
137
136
  try:
@@ -148,11 +147,11 @@ def get_constructor(provides: type, *args, **kwargs):
148
147
 
149
148
  class CMDynamicContainer(dependency_injector.containers.DynamicContainer):
150
149
  """Adds a async context manager interface to the DynamicContainer base class from
151
- the `dependency_injector` framework."""
150
+ the `dependency_injector` framework.
151
+ """
152
152
 
153
153
  async def __aenter__(self):
154
154
  """Init/setup resources."""
155
-
156
155
  init_future = self.init_resources()
157
156
 
158
157
  if not inspect.isawaitable(init_future):
@@ -165,7 +164,6 @@ class CMDynamicContainer(dependency_injector.containers.DynamicContainer):
165
164
 
166
165
  async def __aexit__(self, exc_type, exc_value, exc_trace):
167
166
  """Shutdown/teardown resources"""
168
-
169
167
  shutdown_future = self.shutdown_resources()
170
168
 
171
169
  if not inspect.isawaitable(shutdown_future):
@@ -206,11 +204,11 @@ PydanticConfig = TypeVar("PydanticConfig", bound=BaseSettings)
206
204
 
207
205
  class Configurator(dependency_injector.providers.Factory, Generic[PydanticConfig]):
208
206
  """A configuration constructor that holds configuration parameters using a pydantic
209
- model."""
207
+ model.
208
+ """
210
209
 
211
210
  def load_config(self, config: PydanticConfig):
212
211
  """Loading config parameters form an pydantic config instance."""
213
-
214
212
  self.override(dependency_injector.providers.Callable(lambda: config))
215
213
 
216
214
 
@@ -220,6 +218,6 @@ def get_configurator(
220
218
  """Initializes a configuration provider.
221
219
 
222
220
  This helper function is necessary because the __init__ of Providers used by the
223
- dependency_injector framework need to always use the same signature."""
224
-
221
+ dependency_injector framework need to always use the same signature.
222
+ """
225
223
  return Configurator[PydanticConfig](pydantic_cls)
@@ -15,7 +15,10 @@
15
15
  #
16
16
 
17
17
  """Protocol for creating Data Access Objects to perform CRUD (plus find) interactions
18
- with the database."""
18
+ with the database.
19
+ """
20
+
21
+ # ruff: noqa: PLR0913
19
22
 
20
23
  import typing
21
24
  from abc import ABC, abstractmethod
@@ -72,7 +75,8 @@ class InvalidFindMappingError(FindError):
72
75
 
73
76
  class MultipleHitsFoundError(FindError):
74
77
  """Raised when a DAO find operation did result in multiple hits while only a
75
- single hit was expected."""
78
+ single hit was expected.
79
+ """
76
80
 
77
81
  def __init__(self, *, mapping: Mapping[str, str]):
78
82
  message = (
@@ -84,7 +88,8 @@ class MultipleHitsFoundError(FindError):
84
88
 
85
89
  class NoHitsFoundError(FindError):
86
90
  """Raised when a DAO find operation did result in no hits while a
87
- single hit was expected."""
91
+ single hit was expected.
92
+ """
88
93
 
89
94
  def __init__(self, *, mapping: Mapping[str, str]):
90
95
  message = (
@@ -259,7 +264,6 @@ async def uuid4_id_generator() -> AsyncGenerator[str, None]:
259
264
  This is an AsyncGenerator to be compliant with the id_generator requirements of the
260
265
  DaoFactoryProtocol.
261
266
  """
262
-
263
267
  while True:
264
268
  yield str(uuid4())
265
269
 
@@ -274,7 +278,8 @@ class DaoFactoryProtocol(ABC):
274
278
 
275
279
  class CreationModelInvalidError(ValueError):
276
280
  """Raised when the DtoCreationModel was invalid in relation to the main
277
- DTO model."""
281
+ DTO model.
282
+ """
278
283
 
279
284
  class IndexFieldsInvalidError(ValueError):
280
285
  """Raised when providing an invalid list of fields to index."""
@@ -282,9 +287,9 @@ class DaoFactoryProtocol(ABC):
282
287
  @classmethod
283
288
  def _validate_dto_model_id(cls, *, dto_model: type[Dto], id_field: str) -> None:
284
289
  """Checks whether the dto_model contains the expected id_field.
285
- Raises IdFieldNotFoundError otherwise."""
286
-
287
- if id_field not in dto_model.schema()["properties"]:
290
+ Raises IdFieldNotFoundError otherwise.
291
+ """
292
+ if id_field not in dto_model.model_json_schema()["properties"]:
288
293
  raise cls.IdFieldNotFoundError()
289
294
 
290
295
  @classmethod
@@ -296,16 +301,16 @@ class DaoFactoryProtocol(ABC):
296
301
  id_field: str,
297
302
  ) -> None:
298
303
  """Checks that the dto_creation_model has the same fields as the dto_model
299
- except missing the ID. Raises CreationModelInvalidError otherwise."""
300
-
304
+ except missing the ID. Raises CreationModelInvalidError otherwise.
305
+ """
301
306
  if dto_creation_model is None:
302
307
  return
303
308
 
304
- expected_properties = copy(dto_model.schema()["properties"])
309
+ expected_properties = copy(dto_model.model_json_schema()["properties"])
305
310
  # (the schema method returns an attribute of the class, making a copy to not
306
311
  # alter the class)
307
312
  del expected_properties[id_field]
308
- observed_properties = dto_creation_model.schema()["properties"]
313
+ observed_properties = dto_creation_model.model_json_schema()["properties"]
309
314
 
310
315
  if observed_properties != expected_properties:
311
316
  raise cls.CreationModelInvalidError()
@@ -318,8 +323,8 @@ class DaoFactoryProtocol(ABC):
318
323
  fields_to_index: Optional[Collection[str]],
319
324
  ) -> None:
320
325
  """Checks that all provided fields are present in the dto_model.
321
- Raises IndexFieldsInvalidError otherwise."""
322
-
326
+ Raises IndexFieldsInvalidError otherwise.
327
+ """
323
328
  if fields_to_index is None:
324
329
  return
325
330
 
@@ -403,7 +408,6 @@ class DaoFactoryProtocol(ABC):
403
408
  self.IdFieldNotFoundError:
404
409
  Raised when the dto_model did not contain the expected id_field.
405
410
  """
406
-
407
411
  self._validate_dto_model_id(dto_model=dto_model, id_field=id_field)
408
412
 
409
413
  self._validate_dto_creation_model(
@@ -417,7 +421,7 @@ class DaoFactoryProtocol(ABC):
417
421
  )
418
422
 
419
423
  if id_generator is None:
420
- # instanciate the default ID generator:
424
+ # instantiate the default ID generator:
421
425
  id_generator = uuid4_id_generator()
422
426
 
423
427
  return await self._get_dao(
@@ -470,5 +474,6 @@ class DaoFactoryProtocol(ABC):
470
474
  id_generator: AsyncGenerator[str, None],
471
475
  ) -> Union[DaoSurrogateId[Dto, DtoCreation], DaoNaturalId[Dto]]:
472
476
  """*To be implemented by the provider. Input validation is done outside of this
473
- method.*"""
477
+ method.*
478
+ """
474
479
  ...
@@ -16,6 +16,7 @@
16
16
 
17
17
  """Protocol for interacting with S3-like Object Storages."""
18
18
 
19
+ # ruff: noqa: PLR0913
19
20
 
20
21
  import re
21
22
  from abc import ABC, abstractmethod
@@ -28,16 +29,15 @@ DEFAULT_URL_EXPIRATION_PERIOD = 24 * 60 * 60 # default expiration time 24 hours
28
29
 
29
30
  class PresignedPostURL(NamedTuple):
30
31
  """Container for presigned POST URLs along with additional metadata fields that
31
- should be attached as body data when sending the POST request."""
32
+ should be attached as body data when sending the POST request.
33
+ """
32
34
 
33
35
  url: str
34
36
  fields: dict[str, str]
35
37
 
36
38
 
37
39
  class ObjectStorageProtocol(ABC):
38
- """
39
- Protocol for interacting with S3-like Object Storages.
40
- """
40
+ """Protocol for interacting with S3-like Object Storages."""
41
41
 
42
42
  # constants for multipart uploads:
43
43
  # (shall not be changed by provider implementations)
@@ -51,7 +51,6 @@ class ObjectStorageProtocol(ABC):
51
51
  """Check whether a bucket with the specified ID (`bucket_id`) exists.
52
52
  Returns `True` if it exists and `False` otherwise.
53
53
  """
54
-
55
54
  self._validate_bucket_id(bucket_id)
56
55
  return await self._does_bucket_exist(bucket_id)
57
56
 
@@ -60,7 +59,6 @@ class ObjectStorageProtocol(ABC):
60
59
  Create a bucket (= a structure that can hold multiple file objects) with the
61
60
  specified unique ID.
62
61
  """
63
-
64
62
  self._validate_bucket_id(bucket_id)
65
63
  await self._create_bucket(bucket_id)
66
64
 
@@ -73,14 +71,11 @@ class ObjectStorageProtocol(ABC):
73
71
  will be deleted, if False (the default) a BucketNotEmptyError will be raised if
74
72
  the bucket is not empty.
75
73
  """
76
-
77
74
  self._validate_bucket_id(bucket_id)
78
75
  await self._delete_bucket(bucket_id, delete_content=delete_content)
79
76
 
80
77
  async def list_all_object_ids(self, bucket_id: str) -> list[str]:
81
- """
82
- Retrieve a list of IDs for all objects currently present in the specified bucket
83
- """
78
+ """Retrieve a list of IDs for all objects currently present in the specified bucket"""
84
79
  self._validate_bucket_id(bucket_id)
85
80
  return await self._list_all_object_ids(bucket_id=bucket_id)
86
81
 
@@ -97,7 +92,6 @@ class ObjectStorageProtocol(ABC):
97
92
  You may also specify a custom expiry duration in seconds (`expires_after`) and
98
93
  a maximum size (bytes) for uploads (`max_upload_size`).
99
94
  """
100
-
101
95
  self._validate_bucket_id(bucket_id)
102
96
  self._validate_object_id(object_id)
103
97
  return await self._get_object_upload_url(
@@ -114,7 +108,6 @@ class ObjectStorageProtocol(ABC):
114
108
  object_id: str,
115
109
  ) -> str:
116
110
  """Initiates a multipart upload procedure. Returns the upload ID."""
117
-
118
111
  self._validate_bucket_id(bucket_id)
119
112
  self._validate_object_id(object_id)
120
113
  return await self._init_multipart_upload(
@@ -136,7 +129,6 @@ class ObjectStorageProtocol(ABC):
136
129
  Please note: the part number must be a non-zero, positive integer and parts
137
130
  should be uploaded in sequence.
138
131
  """
139
-
140
132
  self._validate_bucket_id(bucket_id)
141
133
  self._validate_object_id(object_id)
142
134
  return await self._get_part_upload_url(
@@ -157,7 +149,6 @@ class ObjectStorageProtocol(ABC):
157
149
  """Cancel a multipart upload with the specified ID. All uploaded content is
158
150
  deleted.
159
151
  """
160
-
161
152
  self._validate_bucket_id(bucket_id)
162
153
  self._validate_object_id(object_id)
163
154
  await self._abort_multipart_upload(
@@ -180,7 +171,6 @@ class ObjectStorageProtocol(ABC):
180
171
  This ensures that exactly the specified number of parts exist and that all parts
181
172
  (except the last one) have the specified size.
182
173
  """
183
-
184
174
  self._validate_bucket_id(bucket_id)
185
175
  self._validate_object_id(object_id)
186
176
  await self._complete_multipart_upload(
@@ -198,7 +188,6 @@ class ObjectStorageProtocol(ABC):
198
188
  the specified ID (`object_id`) from bucket with the specified id (`bucket_id`).
199
189
  You may also specify a custom expiry duration in seconds (`expires_after`).
200
190
  """
201
-
202
191
  self._validate_bucket_id(bucket_id)
203
192
  self._validate_object_id(object_id)
204
193
  return await self._get_object_download_url(
@@ -213,7 +202,6 @@ class ObjectStorageProtocol(ABC):
213
202
  may be provided to check the objects content.
214
203
  Returns `True` if checks succeed and `False` otherwise.
215
204
  """
216
-
217
205
  self._validate_bucket_id(bucket_id)
218
206
  self._validate_object_id(object_id)
219
207
  return await self._does_object_exist(
@@ -221,10 +209,7 @@ class ObjectStorageProtocol(ABC):
221
209
  )
222
210
 
223
211
  async def get_object_size(self, *, bucket_id: str, object_id: str) -> int:
224
- """
225
- Returns the size of an object in bytes.
226
- """
227
-
212
+ """Returns the size of an object in bytes."""
228
213
  self._validate_bucket_id(bucket_id)
229
214
  self._validate_object_id(object_id)
230
215
  return await self._get_object_size(bucket_id=bucket_id, object_id=object_id)
@@ -240,7 +225,6 @@ class ObjectStorageProtocol(ABC):
240
225
  """Copy an object from one bucket (`source_bucket_id` and `source_object_id`) to
241
226
  another bucket (`dest_bucket_id` and `dest_object_id`).
242
227
  """
243
-
244
228
  self._validate_bucket_id(source_bucket_id)
245
229
  self._validate_object_id(source_object_id)
246
230
  self._validate_bucket_id(dest_bucket_id)
@@ -256,7 +240,6 @@ class ObjectStorageProtocol(ABC):
256
240
  """Delete an object with the specified id (`object_id`) in the bucket with the
257
241
  specified id (`bucket_id`).
258
242
  """
259
-
260
243
  self._validate_bucket_id(bucket_id)
261
244
  self._validate_object_id(object_id)
262
245
  await self._delete_object(bucket_id=bucket_id, object_id=object_id)
@@ -601,7 +584,8 @@ class ObjectStorageProtocol(ABC):
601
584
 
602
585
  class MultiPartUploadAlreadyExistsError(MultiPartUploadError):
603
586
  """Thrown when trying to create a multipart upload for an object for which another
604
- upload is already active."""
587
+ upload is already active.
588
+ """
605
589
 
606
590
  def __init__(self, bucket_id: str, object_id: str):
607
591
  message = (
@@ -19,7 +19,10 @@ They correspond to the `EventPublisherProtocol` and `EventSubscriberProtocol`,
19
19
  respectively.
20
20
  """
21
21
 
22
- from .provider import KafkaEventPublisher # noqa: F401
23
- from .provider import KafkaConfig, KafkaEventSubscriber
22
+ from .provider import (
23
+ KafkaConfig,
24
+ KafkaEventPublisher,
25
+ KafkaEventSubscriber,
26
+ )
24
27
 
25
28
  __all__ = ["KafkaEventPublisher", "KafkaEventSubscriber", "KafkaConfig"]