hexkit 8.5.0__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 (91) hide show
  1. {hexkit-8.5.0/src/hexkit.egg-info → hexkit-8.6.0}/PKG-INFO +12 -12
  2. {hexkit-8.5.0 → hexkit-8.6.0}/pyproject.toml +12 -12
  3. hexkit-8.6.0/src/hexkit/opentelemetry/__init__.py +29 -0
  4. hexkit-8.6.0/src/hexkit/opentelemetry/instrumentation.py +122 -0
  5. hexkit-8.6.0/src/hexkit/opentelemetry/testutils.py +141 -0
  6. {hexkit-8.5.0 → hexkit-8.6.0/src/hexkit.egg-info}/PKG-INFO +12 -12
  7. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit.egg-info/SOURCES.txt +3 -1
  8. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit.egg-info/requires.txt +11 -11
  9. hexkit-8.5.0/src/hexkit/opentelemetry.py +0 -76
  10. {hexkit-8.5.0 → hexkit-8.6.0}/LICENSE +0 -0
  11. {hexkit-8.5.0 → hexkit-8.6.0}/MANIFEST.in +0 -0
  12. {hexkit-8.5.0 → hexkit-8.6.0}/README.md +0 -0
  13. {hexkit-8.5.0 → hexkit-8.6.0}/setup.cfg +0 -0
  14. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/__init__.py +0 -0
  15. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/__main__.py +0 -0
  16. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/base.py +0 -0
  17. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/config.py +0 -0
  18. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/correlation.py +0 -0
  19. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/custom_types.py +0 -0
  20. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/log.py +0 -0
  21. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/__init__.py +0 -0
  22. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/dao.py +0 -0
  23. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/daopub.py +0 -0
  24. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/daosub.py +0 -0
  25. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/eventpub.py +0 -0
  26. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/eventsub.py +0 -0
  27. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/kvstore.py +0 -0
  28. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/protocols/objstorage.py +0 -0
  29. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/__init__.py +0 -0
  30. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  31. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/config.py +0 -0
  32. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  33. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
  34. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
  35. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
  36. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  37. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  38. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/akafka/testutils.py +0 -0
  39. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  40. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/config.py +0 -0
  41. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
  42. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
  43. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
  44. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/migrations/helpers.py +0 -0
  45. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/__init__.py +0 -0
  46. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/client.py +0 -0
  47. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/dao.py +0 -0
  48. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/kvstore.py +0 -0
  49. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/provider/utils.py +0 -0
  50. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
  51. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
  52. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/config.py +0 -0
  53. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
  54. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/daopub.py +0 -0
  55. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
  56. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  57. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/__init__.py +0 -0
  58. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/config.py +0 -0
  59. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/__init__.py +0 -0
  60. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/client.py +0 -0
  61. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/provider/kvstore.py +0 -0
  62. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/redis/testutils.py +0 -0
  63. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/__init__.py +0 -0
  64. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/config.py +0 -0
  65. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/__init__.py +0 -0
  66. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/kvstore.py +0 -0
  67. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/provider/objstorage.py +0 -0
  68. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  69. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  70. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  71. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  72. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  73. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
  74. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
  75. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  76. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
  77. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/testing/__init__.py +0 -0
  78. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/testing/dao.py +0 -0
  79. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/testing/eventpub.py +0 -0
  80. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/testing/kvstore.py +0 -0
  81. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/testing/objstorage.py +0 -0
  82. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/__init__.py +0 -0
  83. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/config.py +0 -0
  84. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/__init__.py +0 -0
  85. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/client.py +0 -0
  86. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/provider/kvstore.py +0 -0
  87. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/providers/vault/testutils.py +0 -0
  88. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/py.typed +0 -0
  89. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit/utils.py +0 -0
  90. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  91. {hexkit-8.5.0 → hexkit-8.6.0}/src/hexkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexkit
3
- Version: 8.5.0
3
+ Version: 8.6.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
@@ -21,7 +21,7 @@ Classifier: Intended Audience :: Developers
21
21
  Requires-Python: >=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: opentelemetry-api<2,>=1.39
24
+ Requires-Dist: opentelemetry-api<2,>=1.44
25
25
  Requires-Dist: pydantic<3,>=2.12
26
26
  Requires-Dist: pydantic_settings<3,>=2.12
27
27
  Requires-Dist: PyYAML<7,>=6
@@ -38,30 +38,30 @@ Requires-Dist: redis<8,>=7.1; extra == "redis"
38
38
  Provides-Extra: vault
39
39
  Requires-Dist: hvac<3,>=2.4; extra == "vault"
40
40
  Provides-Extra: opentelemetry-base
41
- Requires-Dist: opentelemetry-sdk<2,>=1.39.1; extra == "opentelemetry-base"
42
- Requires-Dist: opentelemetry-exporter-otlp<2,>=1.39.1; extra == "opentelemetry-base"
43
- Requires-Dist: opentelemetry-instrumentation>=0.60b1; extra == "opentelemetry-base"
44
- Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "opentelemetry-base"
45
- Requires-Dist: opentelemetry-distro>=0.60b1; 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
46
  Provides-Extra: opentelemetry-akafka
47
47
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-akafka"
48
48
  Requires-Dist: hexkit[akafka]; extra == "opentelemetry-akafka"
49
- Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.60b1; extra == "opentelemetry-akafka"
49
+ Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.65b0; extra == "opentelemetry-akafka"
50
50
  Provides-Extra: opentelemetry-mongodb
51
51
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-mongodb"
52
52
  Requires-Dist: hexkit[mongodb]; extra == "opentelemetry-mongodb"
53
- Requires-Dist: opentelemetry-instrumentation-pymongo>=0.60b1; extra == "opentelemetry-mongodb"
53
+ Requires-Dist: opentelemetry-instrumentation-pymongo>=0.65b0; extra == "opentelemetry-mongodb"
54
54
  Provides-Extra: opentelemetry-fastapi
55
55
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-fastapi"
56
- Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "opentelemetry-fastapi"
56
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.65b0; extra == "opentelemetry-fastapi"
57
57
  Provides-Extra: opentelemetry-s3
58
58
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-s3"
59
59
  Requires-Dist: hexkit[s3]; extra == "opentelemetry-s3"
60
- Requires-Dist: opentelemetry-instrumentation-botocore>=0.60b1; extra == "opentelemetry-s3"
60
+ Requires-Dist: opentelemetry-instrumentation-botocore>=0.65b0; extra == "opentelemetry-s3"
61
61
  Provides-Extra: opentelemetry-redis
62
62
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-redis"
63
63
  Requires-Dist: hexkit[redis]; extra == "opentelemetry-redis"
64
- Requires-Dist: opentelemetry-instrumentation-redis>=0.60b1; extra == "opentelemetry-redis"
64
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.65b0; extra == "opentelemetry-redis"
65
65
  Provides-Extra: opentelemetry
66
66
  Requires-Dist: hexkit[opentelemetry-akafka]; extra == "opentelemetry"
67
67
  Requires-Dist: hexkit[opentelemetry-mongodb]; extra == "opentelemetry"
@@ -23,10 +23,10 @@ classifiers = [
23
23
  "Intended Audience :: Developers",
24
24
  ]
25
25
  name = "hexkit"
26
- version = "8.5.0"
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]",
@@ -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()
@@ -0,0 +1,141 @@
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
+ """Utilities for testing code that relies on OpenTelemetry instrumentation.
18
+
19
+ Please note, only use for testing purposes.
20
+ """
21
+
22
+ from collections.abc import Generator
23
+ from dataclasses import dataclass
24
+
25
+ import pytest
26
+ from opentelemetry import trace
27
+ from opentelemetry.sdk.trace import ReadableSpan, TracerProvider
28
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
29
+ from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
30
+ InMemorySpanExporter,
31
+ )
32
+
33
+ from hexkit.custom_types import PytestScope
34
+ from hexkit.opentelemetry import OpenTelemetryConfig, configure_opentelemetry
35
+
36
+ __all__ = [
37
+ "OpenTelemetryFixture",
38
+ "get_otel_fixture",
39
+ "get_otel_provider_fixture",
40
+ "otel_fixture",
41
+ "otel_provider_fixture",
42
+ ]
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class OpenTelemetryFixture:
47
+ """A fixture with utility methods for tests that verify emitted spans."""
48
+
49
+ exporter: InMemorySpanExporter
50
+
51
+ def reset(self) -> None:
52
+ """Clear all previously captured spans."""
53
+ self.exporter.clear()
54
+
55
+ def get_finished_spans(self) -> tuple[ReadableSpan, ...]:
56
+ """Return all spans finished so far, in the order they completed."""
57
+ return tuple(self.exporter.get_finished_spans())
58
+
59
+ def get_span_names(self) -> list[str]:
60
+ """Return the names of all finished spans, in the order they completed."""
61
+ return [span.name for span in self.get_finished_spans()]
62
+
63
+ def assert_has_span(self, name: str) -> ReadableSpan:
64
+ """Assert that a finished span with the given name exists and return it."""
65
+ for span in self.get_finished_spans():
66
+ if span.name == name:
67
+ return span
68
+ raise AssertionError(
69
+ f"No span named {name!r} found. Captured spans: {self.get_span_names()}"
70
+ )
71
+
72
+
73
+ def _otel_provider_fixture(
74
+ service_name: str = "test",
75
+ ) -> Generator[OpenTelemetryFixture, None, None]:
76
+ """Fixture function attaching an in-memory span exporter for the test session.
77
+
78
+ If OpenTelemetry has already been configured (e.g. because the app under test
79
+ calls `configure_opentelemetry()` on startup), this attaches to the resulting
80
+ TracerProvider so the exact same setup (sampler, resource, instrumented
81
+ libraries) is exercised. Otherwise, it configures one itself via
82
+ `configure_opentelemetry()` so the fixture also works standalone.
83
+
84
+ A `SimpleSpanProcessor` is used instead of the `BatchSpanProcessor` used for real
85
+ exports, so spans show up as soon as they end instead of on a batching interval.
86
+ """
87
+ provider = trace.get_tracer_provider()
88
+ exporter = InMemorySpanExporter()
89
+
90
+ if not isinstance(provider, TracerProvider):
91
+ # No app under test has configured OpenTelemetry yet, so do it here, wiring
92
+ # up the in-memory exporter directly instead of the default OTLP exporter -
93
+ # this avoids pointless connection attempts against a collector in tests.
94
+ configure_opentelemetry(
95
+ service_name=service_name,
96
+ config=OpenTelemetryConfig(enable_opentelemetry=True),
97
+ span_processor=SimpleSpanProcessor(exporter),
98
+ )
99
+ else:
100
+ # OpenTelemetry has already been configured (e.g. by the app under test),
101
+ # so attach to its existing TracerProvider instead of replacing it -
102
+ # OpenTelemetry only allows the global TracerProvider to be set once.
103
+ # Detaching after the test is not possible, as there's no stable public API for that
104
+ provider.add_span_processor(SimpleSpanProcessor(exporter))
105
+
106
+ yield OpenTelemetryFixture(exporter=exporter)
107
+
108
+
109
+ def get_otel_provider_fixture(
110
+ scope: PytestScope = "session", name: str = "otel_provider"
111
+ ):
112
+ """Get a fixture with desired scope and name that sets up span capturing.
113
+
114
+ By default, the session scope is used since only one `TracerProvider` can be
115
+ configured per process.
116
+ """
117
+ return pytest.fixture(_otel_provider_fixture, scope=scope, name=name)
118
+
119
+
120
+ otel_provider_fixture = get_otel_provider_fixture()
121
+
122
+
123
+ def _otel_fixture(
124
+ otel_provider: OpenTelemetryFixture,
125
+ ) -> Generator[OpenTelemetryFixture, None, None]:
126
+ """Fixture function that gets a clean slate of captured spans for a single test."""
127
+ otel_provider.reset()
128
+ yield otel_provider
129
+ otel_provider.reset()
130
+
131
+
132
+ def get_otel_fixture(scope: PytestScope = "function", name: str = "otel"):
133
+ """Get a fixture with desired scope and name for asserting on emitted spans.
134
+
135
+ By default, the function scope is used so captured spans don't leak between
136
+ tests, while the session scope is used for the underlying span capturing setup.
137
+ """
138
+ return pytest.fixture(_otel_fixture, scope=scope, name=name)
139
+
140
+
141
+ otel_fixture = get_otel_fixture()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexkit
3
- Version: 8.5.0
3
+ Version: 8.6.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
@@ -21,7 +21,7 @@ Classifier: Intended Audience :: Developers
21
21
  Requires-Python: >=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: opentelemetry-api<2,>=1.39
24
+ Requires-Dist: opentelemetry-api<2,>=1.44
25
25
  Requires-Dist: pydantic<3,>=2.12
26
26
  Requires-Dist: pydantic_settings<3,>=2.12
27
27
  Requires-Dist: PyYAML<7,>=6
@@ -38,30 +38,30 @@ Requires-Dist: redis<8,>=7.1; extra == "redis"
38
38
  Provides-Extra: vault
39
39
  Requires-Dist: hvac<3,>=2.4; extra == "vault"
40
40
  Provides-Extra: opentelemetry-base
41
- Requires-Dist: opentelemetry-sdk<2,>=1.39.1; extra == "opentelemetry-base"
42
- Requires-Dist: opentelemetry-exporter-otlp<2,>=1.39.1; extra == "opentelemetry-base"
43
- Requires-Dist: opentelemetry-instrumentation>=0.60b1; extra == "opentelemetry-base"
44
- Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "opentelemetry-base"
45
- Requires-Dist: opentelemetry-distro>=0.60b1; 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
46
  Provides-Extra: opentelemetry-akafka
47
47
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-akafka"
48
48
  Requires-Dist: hexkit[akafka]; extra == "opentelemetry-akafka"
49
- Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.60b1; extra == "opentelemetry-akafka"
49
+ Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.65b0; extra == "opentelemetry-akafka"
50
50
  Provides-Extra: opentelemetry-mongodb
51
51
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-mongodb"
52
52
  Requires-Dist: hexkit[mongodb]; extra == "opentelemetry-mongodb"
53
- Requires-Dist: opentelemetry-instrumentation-pymongo>=0.60b1; extra == "opentelemetry-mongodb"
53
+ Requires-Dist: opentelemetry-instrumentation-pymongo>=0.65b0; extra == "opentelemetry-mongodb"
54
54
  Provides-Extra: opentelemetry-fastapi
55
55
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-fastapi"
56
- Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "opentelemetry-fastapi"
56
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.65b0; extra == "opentelemetry-fastapi"
57
57
  Provides-Extra: opentelemetry-s3
58
58
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-s3"
59
59
  Requires-Dist: hexkit[s3]; extra == "opentelemetry-s3"
60
- Requires-Dist: opentelemetry-instrumentation-botocore>=0.60b1; extra == "opentelemetry-s3"
60
+ Requires-Dist: opentelemetry-instrumentation-botocore>=0.65b0; extra == "opentelemetry-s3"
61
61
  Provides-Extra: opentelemetry-redis
62
62
  Requires-Dist: hexkit[opentelemetry-base]; extra == "opentelemetry-redis"
63
63
  Requires-Dist: hexkit[redis]; extra == "opentelemetry-redis"
64
- Requires-Dist: opentelemetry-instrumentation-redis>=0.60b1; extra == "opentelemetry-redis"
64
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.65b0; extra == "opentelemetry-redis"
65
65
  Provides-Extra: opentelemetry
66
66
  Requires-Dist: hexkit[opentelemetry-akafka]; extra == "opentelemetry"
67
67
  Requires-Dist: hexkit[opentelemetry-mongodb]; extra == "opentelemetry"
@@ -9,7 +9,6 @@ src/hexkit/config.py
9
9
  src/hexkit/correlation.py
10
10
  src/hexkit/custom_types.py
11
11
  src/hexkit/log.py
12
- src/hexkit/opentelemetry.py
13
12
  src/hexkit/py.typed
14
13
  src/hexkit/utils.py
15
14
  src/hexkit.egg-info/PKG-INFO
@@ -17,6 +16,9 @@ src/hexkit.egg-info/SOURCES.txt
17
16
  src/hexkit.egg-info/dependency_links.txt
18
17
  src/hexkit.egg-info/requires.txt
19
18
  src/hexkit.egg-info/top_level.txt
19
+ src/hexkit/opentelemetry/__init__.py
20
+ src/hexkit/opentelemetry/instrumentation.py
21
+ src/hexkit/opentelemetry/testutils.py
20
22
  src/hexkit/protocols/__init__.py
21
23
  src/hexkit/protocols/dao.py
22
24
  src/hexkit/protocols/daopub.py
@@ -1,4 +1,4 @@
1
- opentelemetry-api<2,>=1.39
1
+ opentelemetry-api<2,>=1.44
2
2
  pydantic<3,>=2.12
3
3
  pydantic_settings<3,>=2.12
4
4
  PyYAML<7,>=6
@@ -23,33 +23,33 @@ hexkit[opentelemetry-s3]
23
23
  [opentelemetry-akafka]
24
24
  hexkit[opentelemetry-base]
25
25
  hexkit[akafka]
26
- opentelemetry-instrumentation-aiokafka>=0.60b1
26
+ opentelemetry-instrumentation-aiokafka>=0.65b0
27
27
 
28
28
  [opentelemetry-base]
29
- opentelemetry-sdk<2,>=1.39.1
30
- opentelemetry-exporter-otlp<2,>=1.39.1
31
- opentelemetry-instrumentation>=0.60b1
32
- opentelemetry-instrumentation-httpx>=0.60b1
33
- opentelemetry-distro>=0.60b1
29
+ opentelemetry-sdk<2,>=1.44
30
+ opentelemetry-exporter-otlp<2,>=1.44
31
+ opentelemetry-instrumentation>=0.65b0
32
+ opentelemetry-instrumentation-httpx>=0.65b0
33
+ opentelemetry-distro>=0.65b0
34
34
 
35
35
  [opentelemetry-fastapi]
36
36
  hexkit[opentelemetry-base]
37
- opentelemetry-instrumentation-fastapi>=0.60b1
37
+ opentelemetry-instrumentation-fastapi>=0.65b0
38
38
 
39
39
  [opentelemetry-mongodb]
40
40
  hexkit[opentelemetry-base]
41
41
  hexkit[mongodb]
42
- opentelemetry-instrumentation-pymongo>=0.60b1
42
+ opentelemetry-instrumentation-pymongo>=0.65b0
43
43
 
44
44
  [opentelemetry-redis]
45
45
  hexkit[opentelemetry-base]
46
46
  hexkit[redis]
47
- opentelemetry-instrumentation-redis>=0.60b1
47
+ opentelemetry-instrumentation-redis>=0.65b0
48
48
 
49
49
  [opentelemetry-s3]
50
50
  hexkit[opentelemetry-base]
51
51
  hexkit[s3]
52
- opentelemetry-instrumentation-botocore>=0.60b1
52
+ opentelemetry-instrumentation-botocore>=0.65b0
53
53
 
54
54
  [redis]
55
55
  redis<8,>=7.1
@@ -1,76 +0,0 @@
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
- """OpenTelemetry specific configuration code. This is gated behind the opentelemetry extra."""
16
-
17
- import logging
18
- import os
19
- from typing import Annotated
20
-
21
- from opentelemetry import trace
22
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
23
- from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED
24
- from opentelemetry.sdk.resources import SERVICE_NAME, Resource
25
- from opentelemetry.sdk.trace import 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
- logger = logging.getLogger(__name__)
32
-
33
-
34
- class OpenTelemetryConfig(BaseSettings):
35
- """OpenTelemetry specific configuration options"""
36
-
37
- enable_opentelemetry: bool = Field(
38
- default=False,
39
- description="If set to true, this will run necessary setup code."
40
- "If set to false, environment variables are set that should also effectively "
41
- "disable autoinstrumentation.",
42
- )
43
- otel_trace_sampling_rate: Annotated[float, Field(strict=True, ge=0, le=1)] = Field(
44
- default=1.0,
45
- description="Determines which proportion of spans should be sampled. "
46
- "A value of 1.0 means all and is equivalent to the previous behaviour. "
47
- "Setting this to 0 will result in no spans being sampled, but this does not "
48
- "automatically set `enable_opentelemetry` to False.",
49
- )
50
-
51
-
52
- def configure_opentelemetry(*, service_name: str, config: OpenTelemetryConfig):
53
- """Configure all needed parts of OpenTelemetry.
54
-
55
- Setup of the TracerProvider is done programmatically, while disabling OpenTelemetry
56
- sets the corresponding environment variable.
57
- """
58
- if config.enable_opentelemetry:
59
- resource = Resource(attributes={SERVICE_NAME: service_name})
60
- # Replace the default static sampler with a probabilistic one that honors parent
61
- # span sampling decisions
62
- # This should consistently yield full traces within a service but not necessarily
63
- # across service boundaries
64
- # With the default sampling rate, behaviour does not change, but this allows to
65
- # introduce head sampling by adjusting a config option on the service side later on
66
- sampler = ParentBasedTraceIdRatio(rate=config.otel_trace_sampling_rate)
67
-
68
- # Initialize service specific TracerProvider
69
- trace_provider = TracerProvider(resource=resource, sampler=sampler)
70
- processor = BatchSpanProcessor(OTLPSpanExporter())
71
- trace_provider.add_span_processor(processor)
72
- trace.set_tracer_provider(trace_provider)
73
- else:
74
- # Currently OTEL_SDK_DISABLED doesn't seem to be honored by all implementations yet
75
- # It seems to be working well enough for the Python implementation.
76
- os.environ[OTEL_SDK_DISABLED] = "true"
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