hexkit 8.4.1__tar.gz → 8.6.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 (94) hide show
  1. hexkit-8.6.0/PKG-INFO +209 -0
  2. hexkit-8.6.0/README.md +118 -0
  3. {hexkit-8.4.1 → hexkit-8.6.0}/pyproject.toml +28 -22
  4. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/log.py +1 -1
  5. hexkit-8.6.0/src/hexkit/opentelemetry/__init__.py +29 -0
  6. hexkit-8.6.0/src/hexkit/opentelemetry/instrumentation.py +122 -0
  7. hexkit-8.6.0/src/hexkit/opentelemetry/testutils.py +141 -0
  8. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/dao.py +8 -8
  9. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/objstorage.py +28 -5
  10. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/daosub.py +3 -1
  11. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/eventpub.py +5 -3
  12. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/eventsub.py +16 -14
  13. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/__init__.py +11 -2
  14. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/_manager.py +40 -71
  15. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/_utils.py +92 -3
  16. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/dao.py +10 -8
  17. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/config.py +3 -1
  18. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/daopub.py +10 -8
  19. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/objstorage.py +83 -78
  20. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_fixtures.py +2 -4
  21. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_utils.py +6 -2
  22. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/testing/dao.py +4 -6
  23. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/testing/objstorage.py +17 -3
  24. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/utils.py +1 -1
  25. hexkit-8.6.0/src/hexkit.egg-info/PKG-INFO +209 -0
  26. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit.egg-info/SOURCES.txt +3 -1
  27. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit.egg-info/requires.txt +11 -11
  28. hexkit-8.4.1/PKG-INFO +0 -223
  29. hexkit-8.4.1/README.md +0 -132
  30. hexkit-8.4.1/src/hexkit/opentelemetry.py +0 -76
  31. hexkit-8.4.1/src/hexkit.egg-info/PKG-INFO +0 -223
  32. {hexkit-8.4.1 → hexkit-8.6.0}/LICENSE +0 -0
  33. {hexkit-8.4.1 → hexkit-8.6.0}/MANIFEST.in +0 -0
  34. {hexkit-8.4.1 → hexkit-8.6.0}/setup.cfg +0 -0
  35. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/__init__.py +0 -0
  36. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/__main__.py +0 -0
  37. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/base.py +0 -0
  38. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/config.py +0 -0
  39. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/correlation.py +0 -0
  40. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/custom_types.py +0 -0
  41. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/__init__.py +0 -0
  42. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/daopub.py +0 -0
  43. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/daosub.py +0 -0
  44. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/eventpub.py +0 -0
  45. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/eventsub.py +0 -0
  46. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/protocols/kvstore.py +0 -0
  47. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/__init__.py +0 -0
  48. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  49. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/config.py +0 -0
  50. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  51. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  52. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  53. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/akafka/testutils.py +0 -0
  54. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  55. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/config.py +0 -0
  56. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/helpers.py +0 -0
  57. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/__init__.py +0 -0
  58. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/client.py +0 -0
  59. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/kvstore.py +0 -0
  60. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/utils.py +0 -0
  61. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
  62. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
  63. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
  64. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
  65. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  66. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/__init__.py +0 -0
  67. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/config.py +0 -0
  68. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/__init__.py +0 -0
  69. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/client.py +0 -0
  70. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/kvstore.py +0 -0
  71. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/redis/testutils.py +0 -0
  72. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/__init__.py +0 -0
  73. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/config.py +0 -0
  74. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/__init__.py +0 -0
  75. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/kvstore.py +0 -0
  76. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  77. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  78. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  79. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  80. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  81. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
  82. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  83. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/testing/__init__.py +0 -0
  84. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/testing/eventpub.py +0 -0
  85. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/testing/kvstore.py +0 -0
  86. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/__init__.py +0 -0
  87. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/config.py +0 -0
  88. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/__init__.py +0 -0
  89. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/client.py +0 -0
  90. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/kvstore.py +0 -0
  91. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/providers/vault/testutils.py +0 -0
  92. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit/py.typed +0 -0
  93. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  94. {hexkit-8.4.1 → hexkit-8.6.0}/src/hexkit.egg-info/top_level.txt +0 -0
hexkit-8.6.0/PKG-INFO ADDED
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexkit
3
+ Version: 8.6.0
4
+ Summary: A Toolkit for Building Microservices using the Hexagonal Architecture
5
+ Author-email: "German Human Genome Phenome Archive (GHGA)" <contact@ghga.de>
6
+ License: Apache 2.0
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
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: License :: OSI Approved :: Apache Software License
18
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Intended Audience :: Developers
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: opentelemetry-api<2,>=1.44
25
+ Requires-Dist: pydantic<3,>=2.12
26
+ Requires-Dist: pydantic_settings<3,>=2.12
27
+ Requires-Dist: PyYAML<7,>=6
28
+ Provides-Extra: akafka
29
+ Requires-Dist: aiokafka[lz4,snappy,zstd]~=0.14.0; extra == "akafka"
30
+ Requires-Dist: jsonschema<5,>=4.25; extra == "akafka"
31
+ Provides-Extra: s3
32
+ Requires-Dist: boto3<2,>=1.42; extra == "s3"
33
+ Requires-Dist: botocore<2,>=1.42; extra == "s3"
34
+ Provides-Extra: mongodb
35
+ Requires-Dist: pymongo<5,>=4.16; extra == "mongodb"
36
+ Provides-Extra: redis
37
+ Requires-Dist: redis<8,>=7.1; extra == "redis"
38
+ Provides-Extra: vault
39
+ Requires-Dist: hvac<3,>=2.4; extra == "vault"
40
+ Provides-Extra: opentelemetry-base
41
+ Requires-Dist: opentelemetry-sdk<2,>=1.44; extra == "opentelemetry-base"
42
+ Requires-Dist: opentelemetry-exporter-otlp<2,>=1.44; extra == "opentelemetry-base"
43
+ Requires-Dist: opentelemetry-instrumentation>=0.65b0; extra == "opentelemetry-base"
44
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.65b0; extra == "opentelemetry-base"
45
+ Requires-Dist: opentelemetry-distro>=0.65b0; extra == "opentelemetry-base"
46
+ Provides-Extra: opentelemetry-akafka
47
+ Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-akafka"
48
+ Requires-Dist: hexkit[akafka]; extra == "opentelemetry-akafka"
49
+ Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.65b0; extra == "opentelemetry-akafka"
50
+ Provides-Extra: opentelemetry-mongodb
51
+ Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-mongodb"
52
+ Requires-Dist: hexkit[mongodb]; extra == "opentelemetry-mongodb"
53
+ Requires-Dist: opentelemetry-instrumentation-pymongo>=0.65b0; extra == "opentelemetry-mongodb"
54
+ Provides-Extra: opentelemetry-fastapi
55
+ Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-fastapi"
56
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.65b0; extra == "opentelemetry-fastapi"
57
+ Provides-Extra: opentelemetry-s3
58
+ Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-s3"
59
+ Requires-Dist: hexkit[s3]; extra == "opentelemetry-s3"
60
+ Requires-Dist: opentelemetry-instrumentation-botocore>=0.65b0; extra == "opentelemetry-s3"
61
+ Provides-Extra: opentelemetry-redis
62
+ Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-redis"
63
+ Requires-Dist: hexkit[redis]; extra == "opentelemetry-redis"
64
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.65b0; extra == "opentelemetry-redis"
65
+ Provides-Extra: opentelemetry
66
+ Requires-Dist: hexkit[opentelemetry-akafka]; extra == "opentelemetry"
67
+ Requires-Dist: hexkit[opentelemetry-mongodb]; extra == "opentelemetry"
68
+ Requires-Dist: hexkit[opentelemetry-fastapi]; extra == "opentelemetry"
69
+ Requires-Dist: hexkit[opentelemetry-s3]; extra == "opentelemetry"
70
+ Provides-Extra: test-akafka
71
+ Requires-Dist: hexkit[akafka]; extra == "test-akafka"
72
+ Requires-Dist: testcontainers[kafka]<5,>=4.14; extra == "test-akafka"
73
+ Provides-Extra: test-s3
74
+ Requires-Dist: hexkit[s3]; extra == "test-s3"
75
+ Requires-Dist: testcontainers<5,>=4.14; extra == "test-s3"
76
+ Provides-Extra: test-mongodb
77
+ Requires-Dist: hexkit[mongodb]; extra == "test-mongodb"
78
+ Requires-Dist: testcontainers[mongo]<5,>=4.14; extra == "test-mongodb"
79
+ Provides-Extra: test-redis
80
+ Requires-Dist: hexkit[redis]; extra == "test-redis"
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"
85
+ Provides-Extra: test
86
+ Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3,test-vault]; extra == "test"
87
+ Provides-Extra: all
88
+ Requires-Dist: hexkit[test]; extra == "all"
89
+ Requires-Dist: hexkit[opentelemetry]; extra == "all"
90
+ Dynamic: license-file
91
+
92
+ [![PyPI version shields.io](https://img.shields.io/pypi/v/hexkit.svg)](https://pypi.python.org/pypi/hexkit/)
93
+ [![PyPI pyversions](https://img.shields.io/pypi/pyversions/hexkit.svg)](https://pypi.python.org/pypi/hexkit/)
94
+ ![tests](https://github.com/ghga-de/hexkit/actions/workflows/tests.yaml/badge.svg)
95
+ [![Coverage Status](https://coveralls.io/repos/github/ghga-de/hexkit/badge.svg?branch=main)](https://coveralls.io/github/ghga-de/hexkit?branch=main)
96
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
97
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
98
+
99
+ # hexkit
100
+
101
+ *A chassis library for building domain-focused, infrastructure-agnostic,
102
+ and event-driven microservices in Python*
103
+
104
+ Read the short summary below, or jump straight to our full
105
+ 📖 **[User Guide](https://ghga-de.github.io/hexkit/user-guide/)** to learn the abstract
106
+ concepts and patterns as well as the concrete infrastructure integrations and testing
107
+ tools that hexkit provides for building better microservices.
108
+
109
+ <!--
110
+ NOTE: This README doubles as the landing page of the documentation site built with
111
+ great-docs. Therefore, all links must be absolute URLs — repo-relative links like
112
+ ./examples or ./src would resolve correctly on GitHub but break on the site.
113
+ -->
114
+
115
+ ## In a Nutshell
116
+
117
+ hexkit implements the *Triple Hexagonal Architecture* pattern, an optimization of the
118
+ ordinary
119
+ [Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/) for
120
+ use with microservices: adapters (as per the hexagonal architecture) are divided into
121
+ two parts. One part called *translator* is specific to one individual microservice
122
+ and its domain-oriented ports. The other part called *provider* is service-agnostic
123
+ but specific to one technology of the surrounding infrastructure. Both parts interact
124
+ through a high-level interface called *protocol* (not to be confused with Python's
125
+ typing.Protocol) which is neither specific to the technology nor to the microservice.
126
+
127
+ As a chassis lib, hexkit reduces redundancy and boilerplate across microservices by
128
+ providing the service-independent building blocks — protocols and providers — ready
129
+ to use. The only task that remains for an individual service is to implement
130
+ service-specific translators between the service's ports and the general-purpose
131
+ protocols (in addition to implementing the domain functionality of the service, of
132
+ course). For an in-depth introduction to the pattern, please read the
133
+ [Architectural Concepts](https://ghga-de.github.io/hexkit/user-guide/arch_concepts/)
134
+ chapter of the User Guide.
135
+
136
+ The following protocols and providers are currently available:
137
+
138
+ | Protocol | Providers |
139
+ | ------------------ | -------------------------------------- |
140
+ | Event Publishing | Apache Kafka, MongoDB + Kafka (outbox) |
141
+ | Event Subscription | Apache Kafka |
142
+ | Data Access Object | MongoDB |
143
+ | Object Storage | S3(-compatible) |
144
+ | Key-Value Store | MongoDB, Redis, S3, HashiCorp Vault |
145
+
146
+ In-memory implementations and per-backend test utilities are also included to
147
+ support testing without real infrastructure.
148
+
149
+ You are not forced to go all-in on the idea of Triple Hexagonal Architecture.
150
+ You can use it just for the technologies where you see benefits and use another
151
+ approach for the rest. For example, you could use hexkit for simplifying the exchange
152
+ of events between microservices but use a classical web framework such as FastAPI for
153
+ designing REST APIs and an ORM like SQLAlchemy for interacting with databases.
154
+
155
+ ## Getting Started
156
+
157
+ The 📖 [User Guide](https://ghga-de.github.io/hexkit/user-guide/) covers all
158
+ protocols, providers, testing utilities, and observability tools with example code,
159
+ and the [API Reference](https://ghga-de.github.io/hexkit/reference/) documents all
160
+ public classes and functions.
161
+
162
+ For a complete example service built with hexkit, have a look at the
163
+ [stream_calc](https://github.com/ghga-de/hexkit/tree/main/examples/stream_calc)
164
+ example application. We also put a lot of effort into making the code
165
+ self-documenting: you can find the protocols being defined at
166
+ [src/hexkit/protocols](https://github.com/ghga-de/hexkit/tree/main/src/hexkit/protocols)
167
+ and the providers being implemented at
168
+ [src/hexkit/providers](https://github.com/ghga-de/hexkit/tree/main/src/hexkit/providers).
169
+
170
+ ## Installation
171
+
172
+ This package is available at PyPI:
173
+ <https://pypi.org/project/hexkit>
174
+
175
+ You can install it from there using:
176
+
177
+ ```sh
178
+ pip install hexkit
179
+ ```
180
+
181
+ The following extras are available:
182
+
183
+ - `akafka`: when using the Apache Kafka-based event publishing or subscription
184
+ - `mongodb`: when using MongoDB as backend for the DAO protocol or key-value stores
185
+ - `s3`: when interacting with S3-compatible object storages or key-value stores
186
+ - `redis`: when using Redis-based key-value stores
187
+ - `vault`: when using HashiCorp Vault-based key-value stores
188
+ - `opentelemetry`: observability instrumentation (also available per backend, e.g.
189
+ `opentelemetry-akafka`, `opentelemetry-mongodb`, `opentelemetry-fastapi`)
190
+ - `test-akafka`, `test-mongodb`, `test-s3`, `test-redis`, `test-vault`: testing
191
+ utils for the respective backend (`test` is a union of all of them)
192
+ - `all`: a union of all the above
193
+
194
+ ## Contributing
195
+
196
+ Contributions are welcome! Please see the
197
+ [contribution guide](https://github.com/ghga-de/hexkit/blob/main/CONTRIBUTING.md)
198
+ for how to set up the development environment and build the documentation locally.
199
+
200
+ ## Credits
201
+
202
+ The hexkit library is developed and maintained by the developer team of GHGA. We would especially
203
+ like to thank its original author, Kersten Breuer, who came up with the idea for the
204
+ library and designed and coded its first versions.
205
+
206
+ ## License
207
+
208
+ This repository is free to use and modify according to the
209
+ [Apache 2.0 License](https://github.com/ghga-de/hexkit/blob/main/LICENSE).
hexkit-8.6.0/README.md ADDED
@@ -0,0 +1,118 @@
1
+ [![PyPI version shields.io](https://img.shields.io/pypi/v/hexkit.svg)](https://pypi.python.org/pypi/hexkit/)
2
+ [![PyPI pyversions](https://img.shields.io/pypi/pyversions/hexkit.svg)](https://pypi.python.org/pypi/hexkit/)
3
+ ![tests](https://github.com/ghga-de/hexkit/actions/workflows/tests.yaml/badge.svg)
4
+ [![Coverage Status](https://coveralls.io/repos/github/ghga-de/hexkit/badge.svg?branch=main)](https://coveralls.io/github/ghga-de/hexkit?branch=main)
5
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
7
+
8
+ # hexkit
9
+
10
+ *A chassis library for building domain-focused, infrastructure-agnostic,
11
+ and event-driven microservices in Python*
12
+
13
+ Read the short summary below, or jump straight to our full
14
+ 📖 **[User Guide](https://ghga-de.github.io/hexkit/user-guide/)** to learn the abstract
15
+ concepts and patterns as well as the concrete infrastructure integrations and testing
16
+ tools that hexkit provides for building better microservices.
17
+
18
+ <!--
19
+ NOTE: This README doubles as the landing page of the documentation site built with
20
+ great-docs. Therefore, all links must be absolute URLs — repo-relative links like
21
+ ./examples or ./src would resolve correctly on GitHub but break on the site.
22
+ -->
23
+
24
+ ## In a Nutshell
25
+
26
+ hexkit implements the *Triple Hexagonal Architecture* pattern, an optimization of the
27
+ ordinary
28
+ [Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/) for
29
+ use with microservices: adapters (as per the hexagonal architecture) are divided into
30
+ two parts. One part called *translator* is specific to one individual microservice
31
+ and its domain-oriented ports. The other part called *provider* is service-agnostic
32
+ but specific to one technology of the surrounding infrastructure. Both parts interact
33
+ through a high-level interface called *protocol* (not to be confused with Python's
34
+ typing.Protocol) which is neither specific to the technology nor to the microservice.
35
+
36
+ As a chassis lib, hexkit reduces redundancy and boilerplate across microservices by
37
+ providing the service-independent building blocks — protocols and providers — ready
38
+ to use. The only task that remains for an individual service is to implement
39
+ service-specific translators between the service's ports and the general-purpose
40
+ protocols (in addition to implementing the domain functionality of the service, of
41
+ course). For an in-depth introduction to the pattern, please read the
42
+ [Architectural Concepts](https://ghga-de.github.io/hexkit/user-guide/arch_concepts/)
43
+ chapter of the User Guide.
44
+
45
+ The following protocols and providers are currently available:
46
+
47
+ | Protocol | Providers |
48
+ | ------------------ | -------------------------------------- |
49
+ | Event Publishing | Apache Kafka, MongoDB + Kafka (outbox) |
50
+ | Event Subscription | Apache Kafka |
51
+ | Data Access Object | MongoDB |
52
+ | Object Storage | S3(-compatible) |
53
+ | Key-Value Store | MongoDB, Redis, S3, HashiCorp Vault |
54
+
55
+ In-memory implementations and per-backend test utilities are also included to
56
+ support testing without real infrastructure.
57
+
58
+ You are not forced to go all-in on the idea of Triple Hexagonal Architecture.
59
+ You can use it just for the technologies where you see benefits and use another
60
+ approach for the rest. For example, you could use hexkit for simplifying the exchange
61
+ of events between microservices but use a classical web framework such as FastAPI for
62
+ designing REST APIs and an ORM like SQLAlchemy for interacting with databases.
63
+
64
+ ## Getting Started
65
+
66
+ The 📖 [User Guide](https://ghga-de.github.io/hexkit/user-guide/) covers all
67
+ protocols, providers, testing utilities, and observability tools with example code,
68
+ and the [API Reference](https://ghga-de.github.io/hexkit/reference/) documents all
69
+ public classes and functions.
70
+
71
+ For a complete example service built with hexkit, have a look at the
72
+ [stream_calc](https://github.com/ghga-de/hexkit/tree/main/examples/stream_calc)
73
+ example application. We also put a lot of effort into making the code
74
+ self-documenting: you can find the protocols being defined at
75
+ [src/hexkit/protocols](https://github.com/ghga-de/hexkit/tree/main/src/hexkit/protocols)
76
+ and the providers being implemented at
77
+ [src/hexkit/providers](https://github.com/ghga-de/hexkit/tree/main/src/hexkit/providers).
78
+
79
+ ## Installation
80
+
81
+ This package is available at PyPI:
82
+ <https://pypi.org/project/hexkit>
83
+
84
+ You can install it from there using:
85
+
86
+ ```sh
87
+ pip install hexkit
88
+ ```
89
+
90
+ The following extras are available:
91
+
92
+ - `akafka`: when using the Apache Kafka-based event publishing or subscription
93
+ - `mongodb`: when using MongoDB as backend for the DAO protocol or key-value stores
94
+ - `s3`: when interacting with S3-compatible object storages or key-value stores
95
+ - `redis`: when using Redis-based key-value stores
96
+ - `vault`: when using HashiCorp Vault-based key-value stores
97
+ - `opentelemetry`: observability instrumentation (also available per backend, e.g.
98
+ `opentelemetry-akafka`, `opentelemetry-mongodb`, `opentelemetry-fastapi`)
99
+ - `test-akafka`, `test-mongodb`, `test-s3`, `test-redis`, `test-vault`: testing
100
+ utils for the respective backend (`test` is a union of all of them)
101
+ - `all`: a union of all the above
102
+
103
+ ## Contributing
104
+
105
+ Contributions are welcome! Please see the
106
+ [contribution guide](https://github.com/ghga-de/hexkit/blob/main/CONTRIBUTING.md)
107
+ for how to set up the development environment and build the documentation locally.
108
+
109
+ ## Credits
110
+
111
+ The hexkit library is developed and maintained by the developer team of GHGA. We would especially
112
+ like to thank its original author, Kersten Breuer, who came up with the idea for the
113
+ library and designed and coded its first versions.
114
+
115
+ ## License
116
+
117
+ This repository is free to use and modify according to the
118
+ [Apache 2.0 License](https://github.com/ghga-de/hexkit/blob/main/LICENSE).
@@ -23,10 +23,10 @@ classifiers = [
23
23
  "Intended Audience :: Developers",
24
24
  ]
25
25
  name = "hexkit"
26
- version = "8.4.1"
26
+ version = "8.6.0"
27
27
  description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
28
28
  dependencies = [
29
- "opentelemetry-api >=1.39, <2",
29
+ "opentelemetry-api >=1.44, <2",
30
30
  "pydantic >=2.12, <3",
31
31
  "pydantic_settings >=2.12, <3",
32
32
  "PyYAML >=6, <7",
@@ -54,35 +54,35 @@ vault = [
54
54
  "hvac >=2.4, <3",
55
55
  ]
56
56
  opentelemetry-base = [
57
- "opentelemetry-sdk >=1.39.1, <2",
58
- "opentelemetry-exporter-otlp >=1.39.1, <2",
59
- "opentelemetry-instrumentation >=0.60b1",
60
- "opentelemetry-instrumentation-httpx >=0.60b1",
61
- "opentelemetry-distro >=0.60b1",
57
+ "opentelemetry-sdk >=1.44, <2",
58
+ "opentelemetry-exporter-otlp >=1.44, <2",
59
+ "opentelemetry-instrumentation >=0.65b0",
60
+ "opentelemetry-instrumentation-httpx >=0.65b0",
61
+ "opentelemetry-distro >=0.65b0",
62
62
  ]
63
63
  opentelemetry-akafka = [
64
64
  "hexkit[opentelemetry-base]",
65
65
  "hexkit[akafka]",
66
- "opentelemetry-instrumentation-aiokafka >=0.60b1",
66
+ "opentelemetry-instrumentation-aiokafka >=0.65b0",
67
67
  ]
68
68
  opentelemetry-mongodb = [
69
69
  "hexkit[opentelemetry-base]",
70
70
  "hexkit[mongodb]",
71
- "opentelemetry-instrumentation-pymongo >=0.60b1",
71
+ "opentelemetry-instrumentation-pymongo >=0.65b0",
72
72
  ]
73
73
  opentelemetry-fastapi = [
74
74
  "hexkit[opentelemetry-base]",
75
- "opentelemetry-instrumentation-fastapi >=0.60b1",
75
+ "opentelemetry-instrumentation-fastapi >=0.65b0",
76
76
  ]
77
77
  opentelemetry-s3 = [
78
78
  "hexkit[opentelemetry-base]",
79
79
  "hexkit[s3]",
80
- "opentelemetry-instrumentation-botocore >=0.60b1",
80
+ "opentelemetry-instrumentation-botocore >=0.65b0",
81
81
  ]
82
82
  opentelemetry-redis = [
83
83
  "hexkit[opentelemetry-base]",
84
84
  "hexkit[redis]",
85
- "opentelemetry-instrumentation-redis >=0.60b1",
85
+ "opentelemetry-instrumentation-redis >=0.65b0",
86
86
  ]
87
87
  opentelemetry = [
88
88
  "hexkit[opentelemetry-akafka]",
@@ -157,6 +157,9 @@ ignore = [
157
157
  "E111",
158
158
  "E114",
159
159
  "E116",
160
+ "E117",
161
+ "E501",
162
+ "W191",
160
163
  "PLW",
161
164
  "RUF001",
162
165
  "RUF010",
@@ -185,6 +188,13 @@ select = [
185
188
  "RUF",
186
189
  "SIM",
187
190
  "D",
191
+ "E",
192
+ "W",
193
+ "ASYNC",
194
+ "LOG",
195
+ "PERF",
196
+ "RET",
197
+ "C4",
188
198
  ]
189
199
 
190
200
  [tool.ruff.lint.mccabe]
@@ -196,6 +206,7 @@ max-complexity = 10
196
206
  "S",
197
207
  "SIM",
198
208
  "D",
209
+ "E402",
199
210
  ]
200
211
  "tests/*" = [
201
212
  "S",
@@ -256,19 +267,14 @@ legacy_tox_ini = """
256
267
 
257
268
  [testenv:docs]
258
269
  basepython = python3.13
259
- allowlist_externals =
260
- mv
261
- rm
262
270
  deps =
263
271
  --no-deps -r ./lock/requirements-dev.txt
272
+ commands_pre =
273
+ # great-docs (Python >= 3.11 only) is kept out of the shared dev lock so it
274
+ # does not break the py310 test env; install it here where docs are built.
275
+ pip install great-docs==0.14.1
264
276
  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
277
+ great-docs build
272
278
 
273
279
  [testenv]
274
280
  pass_env =
@@ -105,7 +105,7 @@ class JsonFormatter(Formatter):
105
105
  """
106
106
  # Create a log record dictionary
107
107
  log_record = record.__dict__
108
- output: dict[str, Any] = dict()
108
+ output: dict[str, Any] = {}
109
109
 
110
110
  output["timestamp"] = log_record["timestamp"]
111
111
  output["service"] = log_record["service"]
@@ -0,0 +1,29 @@
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 OpenTelemetry instrumentation and related utilities."""
18
+
19
+ from .instrumentation import (
20
+ OpenTelemetryConfig,
21
+ configure_opentelemetry,
22
+ instrument_installed_libraries,
23
+ )
24
+
25
+ __all__ = [
26
+ "OpenTelemetryConfig",
27
+ "configure_opentelemetry",
28
+ "instrument_installed_libraries",
29
+ ]
@@ -0,0 +1,122 @@
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
+ """OpenTelemetry specific configuration code. This is gated behind the opentelemetry extra."""
17
+
18
+ import importlib
19
+ import logging
20
+ from typing import Annotated
21
+
22
+ from opentelemetry import trace
23
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
24
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
25
+ from opentelemetry.sdk.trace import SpanProcessor, TracerProvider
26
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
27
+ from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio
28
+ from pydantic import Field
29
+ from pydantic_settings import BaseSettings
30
+
31
+ __all__ = [
32
+ "OpenTelemetryConfig",
33
+ "configure_opentelemetry",
34
+ "instrument_installed_libraries",
35
+ ]
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ # Instrumentation modules corresponding to the `opentelemetry-*` optional extras.
40
+ # Each entry is only instrumented if its module is installed.
41
+ _INSTRUMENTOR_MODULES: tuple[tuple[str, str], ...] = (
42
+ ("opentelemetry.instrumentation.httpx", "HTTPXClientInstrumentor"),
43
+ ("opentelemetry.instrumentation.aiokafka", "AIOKafkaInstrumentor"),
44
+ ("opentelemetry.instrumentation.pymongo", "PymongoInstrumentor"),
45
+ ("opentelemetry.instrumentation.fastapi", "FastAPIInstrumentor"),
46
+ ("opentelemetry.instrumentation.botocore", "BotocoreInstrumentor"),
47
+ ("opentelemetry.instrumentation.redis", "RedisInstrumentor"),
48
+ )
49
+
50
+
51
+ def instrument_installed_libraries() -> None:
52
+ """Run `.instrument()` for every instrumentation library that is installed."""
53
+ for module_name, class_name in _INSTRUMENTOR_MODULES:
54
+ try:
55
+ module = importlib.import_module(module_name)
56
+ except ImportError:
57
+ logger.debug("'%s' not installed, skipping instrumentation.", module_name)
58
+ continue
59
+ try:
60
+ instrumentor_class = getattr(module, class_name)
61
+ except AttributeError:
62
+ logger.warning(
63
+ "Could not instrument '%s' for '%s'.", class_name, module_name
64
+ )
65
+ instrumentor_class().instrument()
66
+ logger.info("Instrumented '%s' for '%s'.", class_name, module_name)
67
+
68
+
69
+ class OpenTelemetryConfig(BaseSettings):
70
+ """OpenTelemetry specific configuration options"""
71
+
72
+ enable_opentelemetry: bool = Field(
73
+ default=False,
74
+ description="If set to true, this will run necessary setup code."
75
+ "If set to false, no setup code is run, which leaves tracing disabled.",
76
+ )
77
+ otel_trace_sampling_rate: Annotated[float, Field(strict=True, ge=0, le=1)] = Field(
78
+ default=1.0,
79
+ description="Determines which proportion of spans should be sampled. "
80
+ "A value of 1.0 means all and is equivalent to the previous behaviour. "
81
+ "Setting this to 0 will result in no spans being sampled, but this does not "
82
+ "automatically set `enable_opentelemetry` to False.",
83
+ )
84
+
85
+
86
+ def configure_opentelemetry(
87
+ *,
88
+ service_name: str,
89
+ config: OpenTelemetryConfig,
90
+ span_processor: SpanProcessor | None = None,
91
+ ):
92
+ """Configure all needed parts of OpenTelemetry.
93
+
94
+ This needs to be called before constructing any objects that are instrumented, e.g.
95
+ construction a FastAPI app before running this, will result in missing instrumentation.
96
+
97
+ Setup of the TracerProvider is done programmatically. If disabled, this is a no-op,
98
+ leaving the default no-op TracerProvider from the OpenTelemetry API in place.
99
+
100
+ By default, spans are exported via OTLP over HTTP using a `BatchSpanProcessor`.
101
+ """
102
+ if not config.enable_opentelemetry:
103
+ logger.info("OpenTelemetry disabled via config.")
104
+ return
105
+
106
+ resource = Resource(attributes={SERVICE_NAME: service_name})
107
+ # Replace the default static sampler with a probabilistic one that honors parent
108
+ # span sampling decisions
109
+ # This should consistently yield full traces within a service but not necessarily
110
+ # across service boundaries
111
+ # With the default sampling rate, behaviour does not change, but this allows to
112
+ # introduce head sampling by adjusting a config option on the service side later on
113
+ sampler = ParentBasedTraceIdRatio(rate=config.otel_trace_sampling_rate)
114
+
115
+ # Initialize service specific TracerProvider
116
+ trace_provider = TracerProvider(resource=resource, sampler=sampler)
117
+ trace_provider.add_span_processor(
118
+ span_processor or BatchSpanProcessor(OTLPSpanExporter())
119
+ )
120
+ trace.set_tracer_provider(trace_provider)
121
+
122
+ instrument_installed_libraries()