hexkit 4.5.0__tar.gz → 4.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.
- {hexkit-4.5.0/src/hexkit.egg-info → hexkit-4.6.0}/PKG-INFO +1 -1
- {hexkit-4.5.0 → hexkit-4.6.0}/pyproject.toml +2 -2
- hexkit-4.6.0/src/hexkit/opentelemetry_setup.py +170 -0
- {hexkit-4.5.0 → hexkit-4.6.0/src/hexkit.egg-info}/PKG-INFO +1 -1
- hexkit-4.5.0/src/hexkit/opentelemetry_setup.py +0 -63
- {hexkit-4.5.0 → hexkit-4.6.0}/LICENSE +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/MANIFEST.in +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/README.md +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/setup.cfg +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/__main__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/base.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/config.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/correlation.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/custom_types.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/log.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/dao.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/daopub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/daosub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/eventpub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/eventsub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/protocols/objstorage.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/config.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/akafka/testutils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/provider.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/provider/config.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/provider/daopub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/provider.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/testing/__init__.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/providers/testing/eventpub.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/py.typed +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit/utils.py +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit.egg-info/SOURCES.txt +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit.egg-info/requires.txt +0 -0
- {hexkit-4.5.0 → hexkit-4.6.0}/src/hexkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
requires = [
|
|
3
|
-
"setuptools>=
|
|
3
|
+
"setuptools>=80.3",
|
|
4
4
|
]
|
|
5
5
|
build-backend = "setuptools.build_meta"
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Intended Audience :: Developers",
|
|
25
25
|
]
|
|
26
26
|
name = "hexkit"
|
|
27
|
-
version = "4.
|
|
27
|
+
version = "4.6.0"
|
|
28
28
|
description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
|
|
29
29
|
dependencies = [
|
|
30
30
|
"opentelemetry-api >=1.31.1, <2",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Copyright 2021 - 2025 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, Callable, Literal, Optional
|
|
20
|
+
|
|
21
|
+
from opentelemetry import trace
|
|
22
|
+
from opentelemetry.environment_variables import (
|
|
23
|
+
OTEL_LOGS_EXPORTER,
|
|
24
|
+
OTEL_METRICS_EXPORTER,
|
|
25
|
+
OTEL_TRACES_EXPORTER,
|
|
26
|
+
)
|
|
27
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
28
|
+
from opentelemetry.sdk.environment_variables import (
|
|
29
|
+
OTEL_EXPORTER_OTLP_PROTOCOL,
|
|
30
|
+
OTEL_SDK_DISABLED,
|
|
31
|
+
)
|
|
32
|
+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
|
33
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
34
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
35
|
+
from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio
|
|
36
|
+
from pydantic import Field
|
|
37
|
+
from pydantic_settings import BaseSettings
|
|
38
|
+
from typing_extensions import deprecated
|
|
39
|
+
|
|
40
|
+
TRACER: Optional["SpanTracer"] = None
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SpanTracer:
|
|
46
|
+
"""Custom tracer class providing a decorator to autpopulate span names."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, name):
|
|
49
|
+
self.tracer = trace.get_tracer(name)
|
|
50
|
+
|
|
51
|
+
def start_span(
|
|
52
|
+
self, *, record_exception: bool = False, set_status_on_exception: bool = False
|
|
53
|
+
):
|
|
54
|
+
"""Decorator function starting a span populated with the __qualname__ of the wrapped function"""
|
|
55
|
+
|
|
56
|
+
def outer_wrapper(function: Callable):
|
|
57
|
+
def traced_function(*args, **kwargs):
|
|
58
|
+
name = function.__qualname__
|
|
59
|
+
with self.tracer.start_as_current_span(
|
|
60
|
+
name,
|
|
61
|
+
record_exception=record_exception,
|
|
62
|
+
set_status_on_exception=set_status_on_exception,
|
|
63
|
+
):
|
|
64
|
+
return function(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
return traced_function
|
|
67
|
+
|
|
68
|
+
return outer_wrapper
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OpenTelemetryConfig(BaseSettings):
|
|
72
|
+
"""OpenTelemetry specific configuration options"""
|
|
73
|
+
|
|
74
|
+
enable_opentelemetry: bool = Field(
|
|
75
|
+
default=False,
|
|
76
|
+
description="If set to true, this will run necessary setup code."
|
|
77
|
+
"If set to false, environment variables are set that should also effectively "
|
|
78
|
+
"disable autoinstrumentation.",
|
|
79
|
+
)
|
|
80
|
+
samplig_rate: Annotated[float, Field(strict=True, ge=0, le=1)] = Field(
|
|
81
|
+
default=1.0,
|
|
82
|
+
description="Determines which proportion of spans should be sampled. "
|
|
83
|
+
"A value of 1.0 means all and is equivalent to the previous behaviour. "
|
|
84
|
+
"Setting this to 0 will result in no spans being sampled, but this does not "
|
|
85
|
+
"automatically set `enable_opentelemetry` to False.",
|
|
86
|
+
)
|
|
87
|
+
protocol: Literal["grpc", "http/protobuf"] = Field(
|
|
88
|
+
default="http/protobuf",
|
|
89
|
+
description="Specifies which protocol should be used by exporters.",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def configure_opentelemetry(*, service_name: str, config: OpenTelemetryConfig):
|
|
94
|
+
"""Configure all needed parts of OpenTelemetry.
|
|
95
|
+
|
|
96
|
+
Setup of the TracerProvider is done programmatically, all other configuration exports
|
|
97
|
+
OpenTelemetry specific environment variables.
|
|
98
|
+
"""
|
|
99
|
+
configure_tracer(service_name=service_name, config=config)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@deprecated(
|
|
103
|
+
"This function will be removed in v5. Use `configure_opentelemetry instead.`"
|
|
104
|
+
)
|
|
105
|
+
def configure_tracer(*, service_name: str, config: OpenTelemetryConfig):
|
|
106
|
+
"""Configure all needed parts of OpenTelemetry.
|
|
107
|
+
|
|
108
|
+
Setup of the TracerProvider is done programmatically, all other configuration exports
|
|
109
|
+
OpenTelemetry specific environment variables.
|
|
110
|
+
"""
|
|
111
|
+
global TRACER
|
|
112
|
+
# opentelemetry distro sets this to grpc, but in the current context http/protobuf is preferred
|
|
113
|
+
os.environ[OTEL_EXPORTER_OTLP_PROTOCOL] = config.protocol
|
|
114
|
+
# Disable OpenTelemetry metrics and logs explicitly as they are not processed in the backend currently
|
|
115
|
+
# This overwrites the defaults of `otlp` set in opentelemetry distro
|
|
116
|
+
os.environ[OTEL_METRICS_EXPORTER] = "none"
|
|
117
|
+
os.environ[OTEL_LOGS_EXPORTER] = "none"
|
|
118
|
+
|
|
119
|
+
if config.enable_opentelemetry:
|
|
120
|
+
if TRACER is not None:
|
|
121
|
+
logger.warning(
|
|
122
|
+
"OpenTelemetry configuration code should only be run once. "
|
|
123
|
+
"If it has been run with a different service name than %s before, "
|
|
124
|
+
"the tracer and resource name will likely be wrong in some cases.",
|
|
125
|
+
service_name,
|
|
126
|
+
)
|
|
127
|
+
resource = Resource(attributes={SERVICE_NAME: service_name})
|
|
128
|
+
# Replace the default static sampler with a probabilistic one that honors parent
|
|
129
|
+
# span sampling decisions
|
|
130
|
+
# This should consistently yield full traces within a service but not necessarily
|
|
131
|
+
# across service boundaries
|
|
132
|
+
# With the default sampling rate, behaviour does not change, but this allows to
|
|
133
|
+
# introduce head sampling by adjusting a config option on the service side later on
|
|
134
|
+
sampler = ParentBasedTraceIdRatio(rate=config.samplig_rate)
|
|
135
|
+
|
|
136
|
+
# Initialize service specific TracerProvider
|
|
137
|
+
trace_provider = TracerProvider(resource=resource, sampler=sampler)
|
|
138
|
+
processor = BatchSpanProcessor(OTLPSpanExporter())
|
|
139
|
+
trace_provider.add_span_processor(processor)
|
|
140
|
+
trace.set_tracer_provider(trace_provider)
|
|
141
|
+
TRACER = SpanTracer(service_name)
|
|
142
|
+
else:
|
|
143
|
+
# Currently OTEL_SDK_DISABLED doesn't seem to be honored by all implementations yet
|
|
144
|
+
# It seems to be working well enough for the Python implementation, but to be on
|
|
145
|
+
# the safe side, let's explicitly disable the trace exporter for now
|
|
146
|
+
os.environ[OTEL_TRACES_EXPORTER] = "none"
|
|
147
|
+
os.environ[OTEL_SDK_DISABLED] = "true"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def start_span(
|
|
151
|
+
*,
|
|
152
|
+
record_exception: bool = False,
|
|
153
|
+
set_status_on_exception: bool = False,
|
|
154
|
+
) -> Callable:
|
|
155
|
+
"""Returns decorated or undecorated function depending on if TRACER is instantiated.
|
|
156
|
+
|
|
157
|
+
Should be used as a decorator.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def wrapper(function: Callable):
|
|
161
|
+
# Caller did not have any time to populate from config yet or otel is disabled
|
|
162
|
+
if TRACER is None:
|
|
163
|
+
return function
|
|
164
|
+
# Return decorated function
|
|
165
|
+
return TRACER.start_span(
|
|
166
|
+
record_exception=record_exception,
|
|
167
|
+
set_status_on_exception=set_status_on_exception,
|
|
168
|
+
)(function)
|
|
169
|
+
|
|
170
|
+
return wrapper
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Copyright 2021 - 2025 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 os
|
|
18
|
-
from typing import Callable
|
|
19
|
-
|
|
20
|
-
from opentelemetry import trace
|
|
21
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
22
|
-
from opentelemetry.sdk.environment_variables import OTEL_EXPORTER_OTLP_PROTOCOL
|
|
23
|
-
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
|
24
|
-
from opentelemetry.sdk.trace import TracerProvider
|
|
25
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def configure_tracer(service_name: str, protocol: str = "http/protobuf"):
|
|
29
|
-
"""Set up a global tracer for a specific service using the given exporter protocol."""
|
|
30
|
-
# opentelemetry distro sets this to grpc, but in the current context http/protobuf is preferred
|
|
31
|
-
os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, protocol)
|
|
32
|
-
|
|
33
|
-
resource = Resource(attributes={SERVICE_NAME: service_name})
|
|
34
|
-
trace_provider = TracerProvider(resource=resource)
|
|
35
|
-
processor = BatchSpanProcessor(OTLPSpanExporter())
|
|
36
|
-
trace_provider.add_span_processor(processor)
|
|
37
|
-
trace.set_tracer_provider(trace_provider)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class SpanTracer:
|
|
41
|
-
"""Custom tracer class providing a decorator to autpopulate span names."""
|
|
42
|
-
|
|
43
|
-
def __init__(self, name):
|
|
44
|
-
self.tracer = trace.get_tracer(name)
|
|
45
|
-
|
|
46
|
-
def start_span(
|
|
47
|
-
self, *, record_exception: bool = False, set_status_on_exception: bool = False
|
|
48
|
-
):
|
|
49
|
-
"""Decorator function starting a span populated with the __qualname__ of the wrapped function"""
|
|
50
|
-
|
|
51
|
-
def outer_wrapper(function: Callable):
|
|
52
|
-
def traced_function(*args, **kwargs):
|
|
53
|
-
name = function.__qualname__
|
|
54
|
-
with self.tracer.start_as_current_span(
|
|
55
|
-
name,
|
|
56
|
-
record_exception=record_exception,
|
|
57
|
-
set_status_on_exception=set_status_on_exception,
|
|
58
|
-
):
|
|
59
|
-
return function(*args, **kwargs)
|
|
60
|
-
|
|
61
|
-
return traced_function
|
|
62
|
-
|
|
63
|
-
return outer_wrapper
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|