hexkit 4.3.1__tar.gz → 4.5.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.3.1/src/hexkit.egg-info → hexkit-4.5.0}/PKG-INFO +12 -1
- {hexkit-4.3.1 → hexkit-4.5.0}/pyproject.toml +14 -2
- hexkit-4.5.0/src/hexkit/opentelemetry_setup.py +63 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/testutils.py +117 -16
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/provider.py +12 -6
- {hexkit-4.3.1 → hexkit-4.5.0/src/hexkit.egg-info}/PKG-INFO +12 -1
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/SOURCES.txt +1 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/requires.txt +12 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/LICENSE +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/MANIFEST.in +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/README.md +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/setup.cfg +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/__main__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/base.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/config.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/correlation.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/custom_types.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/log.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/dao.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/daopub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/daosub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/eventpub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/eventsub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/objstorage.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/config.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/provider.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/config.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/daopub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/testing/__init__.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/testing/eventpub.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/py.typed +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/utils.py +0 -0
- {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
- {hexkit-4.3.1 → hexkit-4.5.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: 4.
|
|
3
|
+
Version: 4.5.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
|
|
@@ -31,6 +31,16 @@ Requires-Dist: boto3<2,>=1.37; extra == "s3"
|
|
|
31
31
|
Requires-Dist: botocore<2,>=1.37; extra == "s3"
|
|
32
32
|
Provides-Extra: mongodb
|
|
33
33
|
Requires-Dist: motor<4,>=3.7; extra == "mongodb"
|
|
34
|
+
Provides-Extra: opentelemetry
|
|
35
|
+
Requires-Dist: opentelemetry-sdk<2,>=1.31.1; extra == "opentelemetry"
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.31.1; extra == "opentelemetry"
|
|
37
|
+
Requires-Dist: opentelemetry-distro>=0.52b1; extra == "opentelemetry"
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation>=0.52b1; extra == "opentelemetry"
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.52b1; extra == "opentelemetry"
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-botocore>=0.52b1; extra == "opentelemetry"
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == "opentelemetry"
|
|
42
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == "opentelemetry"
|
|
43
|
+
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.52b1; extra == "opentelemetry"
|
|
34
44
|
Provides-Extra: test-akafka
|
|
35
45
|
Requires-Dist: hexkit[akafka]; extra == "test-akafka"
|
|
36
46
|
Requires-Dist: testcontainers[kafka]<5,>=4.9; extra == "test-akafka"
|
|
@@ -44,6 +54,7 @@ Provides-Extra: test
|
|
|
44
54
|
Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
|
|
45
55
|
Provides-Extra: all
|
|
46
56
|
Requires-Dist: hexkit[test]; extra == "all"
|
|
57
|
+
Requires-Dist: hexkit[opentelemetry]; extra == "all"
|
|
47
58
|
Dynamic: license-file
|
|
48
59
|
|
|
49
60
|

|
|
@@ -24,10 +24,10 @@ classifiers = [
|
|
|
24
24
|
"Intended Audience :: Developers",
|
|
25
25
|
]
|
|
26
26
|
name = "hexkit"
|
|
27
|
-
version = "4.
|
|
27
|
+
version = "4.5.0"
|
|
28
28
|
description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
|
|
29
29
|
dependencies = [
|
|
30
|
-
"opentelemetry-api>=1.31.1, <2",
|
|
30
|
+
"opentelemetry-api >=1.31.1, <2",
|
|
31
31
|
"pydantic >=2.10, <3",
|
|
32
32
|
"pydantic_settings >=2.8, <3",
|
|
33
33
|
"PyYAML >=6, <7",
|
|
@@ -48,6 +48,17 @@ s3 = [
|
|
|
48
48
|
mongodb = [
|
|
49
49
|
"motor >=3.7, <4",
|
|
50
50
|
]
|
|
51
|
+
opentelemetry = [
|
|
52
|
+
"opentelemetry-sdk >=1.31.1, <2",
|
|
53
|
+
"opentelemetry-exporter-otlp-proto-http >=1.31.1, <2",
|
|
54
|
+
"opentelemetry-distro >=0.52b1",
|
|
55
|
+
"opentelemetry-instrumentation >=0.52b1",
|
|
56
|
+
"opentelemetry-instrumentation-aiokafka >=0.52b1",
|
|
57
|
+
"opentelemetry-instrumentation-botocore >=0.52b1",
|
|
58
|
+
"opentelemetry-instrumentation-fastapi >=0.52b1",
|
|
59
|
+
"opentelemetry-instrumentation-httpx >=0.52b1",
|
|
60
|
+
"opentelemetry-instrumentation-pymongo >=0.52b1",
|
|
61
|
+
]
|
|
51
62
|
test-akafka = [
|
|
52
63
|
"hexkit[akafka]",
|
|
53
64
|
"testcontainers[kafka] >=4.9, <5",
|
|
@@ -65,6 +76,7 @@ test = [
|
|
|
65
76
|
]
|
|
66
77
|
all = [
|
|
67
78
|
"hexkit[test]",
|
|
79
|
+
"hexkit[opentelemetry]",
|
|
68
80
|
]
|
|
69
81
|
|
|
70
82
|
[project.urls]
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
|
@@ -19,12 +19,15 @@
|
|
|
19
19
|
Please note, only use for testing purposes.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import asyncio
|
|
22
23
|
import json
|
|
23
24
|
from collections.abc import AsyncGenerator, Generator, Mapping, Sequence
|
|
24
25
|
from contextlib import asynccontextmanager
|
|
25
26
|
from dataclasses import dataclass
|
|
26
27
|
from functools import partial
|
|
27
|
-
from typing import Any, Optional, Union
|
|
28
|
+
from typing import Any, Optional, Union, cast
|
|
29
|
+
|
|
30
|
+
from aiokafka.admin.config_resource import ConfigResource, ConfigResourceType
|
|
28
31
|
|
|
29
32
|
try:
|
|
30
33
|
from typing import Self
|
|
@@ -411,6 +414,7 @@ class KafkaFixture:
|
|
|
411
414
|
self.config = config
|
|
412
415
|
self.kafka_servers = kafka_servers
|
|
413
416
|
self.publisher = publisher
|
|
417
|
+
self.admin_client: Optional[AIOKafkaAdminClient] = None
|
|
414
418
|
|
|
415
419
|
async def publish_event(
|
|
416
420
|
self,
|
|
@@ -442,6 +446,80 @@ class KafkaFixture:
|
|
|
442
446
|
capture_headers=capture_headers,
|
|
443
447
|
)
|
|
444
448
|
|
|
449
|
+
@asynccontextmanager
|
|
450
|
+
async def get_admin_client(self):
|
|
451
|
+
"""An async context manager that provides a running AIOKafkaAdminClient
|
|
452
|
+
as a singleton instance.
|
|
453
|
+
|
|
454
|
+
If a running client already exists, it is yielded directly.
|
|
455
|
+
If not, this is considered the root use of the CM, so we start a new client,
|
|
456
|
+
yield it, then close the client within a `finally` block.
|
|
457
|
+
"""
|
|
458
|
+
if self.admin_client:
|
|
459
|
+
yield self.admin_client
|
|
460
|
+
return
|
|
461
|
+
|
|
462
|
+
# No running client exists, so create one and make sure to clean up afterward.
|
|
463
|
+
self.admin_client = AIOKafkaAdminClient(bootstrap_servers=self.kafka_servers)
|
|
464
|
+
await self.admin_client.start()
|
|
465
|
+
try:
|
|
466
|
+
yield self.admin_client
|
|
467
|
+
finally:
|
|
468
|
+
await self.admin_client.close()
|
|
469
|
+
self.admin_client = None
|
|
470
|
+
|
|
471
|
+
async def get_cleanup_policy(self, *, topic: str) -> Optional[str]:
|
|
472
|
+
"""Get the current cleanup policy for a topic or None if the topic doesn't exist"""
|
|
473
|
+
return await self.get_topic_config(topic=topic, config_name="cleanup.policy")
|
|
474
|
+
|
|
475
|
+
async def get_topic_config(self, *, topic: str, config_name: str) -> Optional[str]:
|
|
476
|
+
"""Get the current config for a topic or None if the topic/config doesn't exist"""
|
|
477
|
+
async with self.get_admin_client() as admin_client:
|
|
478
|
+
# fetch a list containing a response for each requested topic
|
|
479
|
+
config_list = await admin_client.describe_configs(
|
|
480
|
+
[ConfigResource(ConfigResourceType.TOPIC, name=topic)]
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# We only requested one topic, so get the first/only element from the list
|
|
484
|
+
config_response = config_list[0]
|
|
485
|
+
|
|
486
|
+
# Each config response has a 'resources' attribute
|
|
487
|
+
resources = config_response.resources
|
|
488
|
+
|
|
489
|
+
# The resources attr is a list containing one tuple for each requested topic.
|
|
490
|
+
# We only requested one topic, so our target is the first element.
|
|
491
|
+
requested_resource = resources[0]
|
|
492
|
+
|
|
493
|
+
# The resource itself is a list, and the topic config is at the end
|
|
494
|
+
configs = requested_resource[-1]
|
|
495
|
+
value = None
|
|
496
|
+
config_name = config_name.lower()
|
|
497
|
+
|
|
498
|
+
# The `configs` item is a list of tuples, where each tuple is a config item
|
|
499
|
+
# The first item is the config name, the second is the value, and the rest are ancillary
|
|
500
|
+
for item in configs:
|
|
501
|
+
if item[0].lower() == config_name:
|
|
502
|
+
value = item[1]
|
|
503
|
+
break
|
|
504
|
+
return value
|
|
505
|
+
|
|
506
|
+
async def set_cleanup_policy(self, *, topic: str, policy: str):
|
|
507
|
+
"""Set the cleanup policy for the given topic. The topic must already exist."""
|
|
508
|
+
await self.set_topic_config(topic=topic, config={"cleanup.policy": policy})
|
|
509
|
+
|
|
510
|
+
async def set_topic_config(self, *, topic: str, config: dict[str, Any]):
|
|
511
|
+
"""Set an arbitrary config value(s) for a topic"""
|
|
512
|
+
async with self.get_admin_client() as admin_client:
|
|
513
|
+
await admin_client.alter_configs(
|
|
514
|
+
config_resources=[
|
|
515
|
+
ConfigResource(
|
|
516
|
+
ConfigResourceType.TOPIC,
|
|
517
|
+
name=topic,
|
|
518
|
+
configs=config,
|
|
519
|
+
)
|
|
520
|
+
]
|
|
521
|
+
)
|
|
522
|
+
|
|
445
523
|
async def clear_topics(
|
|
446
524
|
self,
|
|
447
525
|
*,
|
|
@@ -453,26 +531,49 @@ class KafkaFixture:
|
|
|
453
531
|
If no topics are specified, all topics will be cleared, except internal topics
|
|
454
532
|
unless otherwise specified.
|
|
455
533
|
"""
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
topics = await admin_client.list_topics()
|
|
534
|
+
async with self.get_admin_client() as admin_client:
|
|
535
|
+
available_topics = await admin_client.list_topics()
|
|
536
|
+
if not topics:
|
|
537
|
+
topics = available_topics
|
|
461
538
|
elif isinstance(topics, str):
|
|
462
539
|
topics = [topics]
|
|
540
|
+
topics = cast(list, topics)
|
|
463
541
|
if exclude_internal:
|
|
464
542
|
topics = [topic for topic in topics if not topic.startswith("__")]
|
|
543
|
+
|
|
544
|
+
# Filter out any topics that don't exist
|
|
545
|
+
topics = [topic for topic in topics if topic in available_topics]
|
|
546
|
+
if not topics:
|
|
547
|
+
return
|
|
548
|
+
|
|
549
|
+
# Record the current cleanup policies before modifying them
|
|
465
550
|
topics_info = await admin_client.describe_topics(topics)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
551
|
+
for topic_info in topics_info:
|
|
552
|
+
topic = topic_info["topic"]
|
|
553
|
+
original_policy = await self.get_cleanup_policy(topic=topic)
|
|
554
|
+
if original_policy and "compact" in original_policy.split(","):
|
|
555
|
+
await self.set_cleanup_policy(topic=topic, policy="delete")
|
|
556
|
+
await asyncio.sleep(0.2) # brief pause to wait for policy changes
|
|
557
|
+
|
|
558
|
+
records_to_delete = {
|
|
559
|
+
TopicPartition(
|
|
560
|
+
topic=topic, partition=partition_info["partition"]
|
|
561
|
+
): RecordsToDelete(before_offset=-1)
|
|
562
|
+
for partition_info in topic_info["partitions"]
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
# Delete any events in the topic
|
|
566
|
+
try:
|
|
567
|
+
await admin_client.delete_records(
|
|
568
|
+
records_to_delete, timeout_ms=10000
|
|
569
|
+
)
|
|
570
|
+
finally:
|
|
571
|
+
# Always set compacted topics back to their original policy
|
|
572
|
+
if original_policy:
|
|
573
|
+
await self.set_cleanup_policy(
|
|
574
|
+
topic=topic,
|
|
575
|
+
policy=original_policy,
|
|
576
|
+
)
|
|
476
577
|
|
|
477
578
|
@asynccontextmanager
|
|
478
579
|
async def expect_events(
|
|
@@ -409,20 +409,26 @@ class S3ObjectStorage(ObjectStorageProtocol):
|
|
|
409
409
|
|
|
410
410
|
(S3 allows multiple ongoing multi-part uploads.)
|
|
411
411
|
"""
|
|
412
|
+
uploads = []
|
|
412
413
|
try:
|
|
413
|
-
|
|
414
|
-
self._client.list_multipart_uploads,
|
|
414
|
+
response_iter = await asyncio.to_thread(
|
|
415
|
+
self._client.get_paginator("list_multipart_uploads").paginate,
|
|
415
416
|
Bucket=bucket_id,
|
|
416
417
|
)
|
|
418
|
+
for response_page in response_iter:
|
|
419
|
+
uploads.extend(
|
|
420
|
+
[
|
|
421
|
+
upload["UploadId"]
|
|
422
|
+
for upload in response_page.get("Uploads", [])
|
|
423
|
+
if upload["Key"] == object_id
|
|
424
|
+
]
|
|
425
|
+
)
|
|
417
426
|
except botocore.exceptions.ClientError as error:
|
|
418
427
|
raise self._translate_s3_client_errors(
|
|
419
428
|
error, bucket_id=bucket_id, object_id=object_id
|
|
420
429
|
) from error
|
|
421
430
|
|
|
422
|
-
|
|
423
|
-
return [
|
|
424
|
-
upload["UploadId"] for upload in upload_list if upload["Key"] == object_id
|
|
425
|
-
]
|
|
431
|
+
return uploads
|
|
426
432
|
|
|
427
433
|
async def _assert_no_multipart_upload(self, *, bucket_id: str, object_id: str):
|
|
428
434
|
"""Ensure that there are no active multi-part uploads for the given object."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexkit
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.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
|
|
@@ -31,6 +31,16 @@ Requires-Dist: boto3<2,>=1.37; extra == "s3"
|
|
|
31
31
|
Requires-Dist: botocore<2,>=1.37; extra == "s3"
|
|
32
32
|
Provides-Extra: mongodb
|
|
33
33
|
Requires-Dist: motor<4,>=3.7; extra == "mongodb"
|
|
34
|
+
Provides-Extra: opentelemetry
|
|
35
|
+
Requires-Dist: opentelemetry-sdk<2,>=1.31.1; extra == "opentelemetry"
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.31.1; extra == "opentelemetry"
|
|
37
|
+
Requires-Dist: opentelemetry-distro>=0.52b1; extra == "opentelemetry"
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation>=0.52b1; extra == "opentelemetry"
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.52b1; extra == "opentelemetry"
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-botocore>=0.52b1; extra == "opentelemetry"
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == "opentelemetry"
|
|
42
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == "opentelemetry"
|
|
43
|
+
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.52b1; extra == "opentelemetry"
|
|
34
44
|
Provides-Extra: test-akafka
|
|
35
45
|
Requires-Dist: hexkit[akafka]; extra == "test-akafka"
|
|
36
46
|
Requires-Dist: testcontainers[kafka]<5,>=4.9; extra == "test-akafka"
|
|
@@ -44,6 +54,7 @@ Provides-Extra: test
|
|
|
44
54
|
Requires-Dist: hexkit[test-akafka,test-mongodb,test-s3]; extra == "test"
|
|
45
55
|
Provides-Extra: all
|
|
46
56
|
Requires-Dist: hexkit[test]; extra == "all"
|
|
57
|
+
Requires-Dist: hexkit[opentelemetry]; extra == "all"
|
|
47
58
|
Dynamic: license-file
|
|
48
59
|
|
|
49
60
|

|
|
@@ -9,10 +9,22 @@ jsonschema<5,>=4.23
|
|
|
9
9
|
|
|
10
10
|
[all]
|
|
11
11
|
hexkit[test]
|
|
12
|
+
hexkit[opentelemetry]
|
|
12
13
|
|
|
13
14
|
[mongodb]
|
|
14
15
|
motor<4,>=3.7
|
|
15
16
|
|
|
17
|
+
[opentelemetry]
|
|
18
|
+
opentelemetry-sdk<2,>=1.31.1
|
|
19
|
+
opentelemetry-exporter-otlp-proto-http<2,>=1.31.1
|
|
20
|
+
opentelemetry-distro>=0.52b1
|
|
21
|
+
opentelemetry-instrumentation>=0.52b1
|
|
22
|
+
opentelemetry-instrumentation-aiokafka>=0.52b1
|
|
23
|
+
opentelemetry-instrumentation-botocore>=0.52b1
|
|
24
|
+
opentelemetry-instrumentation-fastapi>=0.52b1
|
|
25
|
+
opentelemetry-instrumentation-httpx>=0.52b1
|
|
26
|
+
opentelemetry-instrumentation-pymongo>=0.52b1
|
|
27
|
+
|
|
16
28
|
[s3]
|
|
17
29
|
boto3<2,>=1.37
|
|
18
30
|
botocore<2,>=1.37
|
|
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
|