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.
Files changed (63) hide show
  1. {hexkit-4.3.1/src/hexkit.egg-info → hexkit-4.5.0}/PKG-INFO +12 -1
  2. {hexkit-4.3.1 → hexkit-4.5.0}/pyproject.toml +14 -2
  3. hexkit-4.5.0/src/hexkit/opentelemetry_setup.py +63 -0
  4. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/testutils.py +117 -16
  5. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/provider.py +12 -6
  6. {hexkit-4.3.1 → hexkit-4.5.0/src/hexkit.egg-info}/PKG-INFO +12 -1
  7. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/SOURCES.txt +1 -0
  8. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/requires.txt +12 -0
  9. {hexkit-4.3.1 → hexkit-4.5.0}/LICENSE +0 -0
  10. {hexkit-4.3.1 → hexkit-4.5.0}/MANIFEST.in +0 -0
  11. {hexkit-4.3.1 → hexkit-4.5.0}/README.md +0 -0
  12. {hexkit-4.3.1 → hexkit-4.5.0}/setup.cfg +0 -0
  13. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/__init__.py +0 -0
  14. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/__main__.py +0 -0
  15. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/base.py +0 -0
  16. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/config.py +0 -0
  17. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/correlation.py +0 -0
  18. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/custom_types.py +0 -0
  19. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/log.py +0 -0
  20. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/__init__.py +0 -0
  21. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/dao.py +0 -0
  22. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/daopub.py +0 -0
  23. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/daosub.py +0 -0
  24. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/eventpub.py +0 -0
  25. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/eventsub.py +0 -0
  26. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/protocols/objstorage.py +0 -0
  27. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/__init__.py +0 -0
  28. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/__init__.py +0 -0
  29. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/config.py +0 -0
  30. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
  31. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
  32. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
  33. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
  34. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
  35. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
  36. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
  37. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
  38. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
  39. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
  40. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/provider.py +0 -0
  41. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
  42. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
  43. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
  44. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/config.py +0 -0
  45. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/daopub.py +0 -0
  46. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
  47. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
  48. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/__init__.py +0 -0
  49. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
  50. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
  51. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
  52. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
  53. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
  54. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
  55. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
  56. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
  57. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
  58. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/testing/__init__.py +0 -0
  59. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/providers/testing/eventpub.py +0 -0
  60. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/py.typed +0 -0
  61. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit/utils.py +0 -0
  62. {hexkit-4.3.1 → hexkit-4.5.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
  63. {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.1
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
  ![tests](https://github.com/ghga-de/hexkit/actions/workflows/tests.yaml/badge.svg)
@@ -24,10 +24,10 @@ classifiers = [
24
24
  "Intended Audience :: Developers",
25
25
  ]
26
26
  name = "hexkit"
27
- version = "4.3.1"
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
- admin_client = AIOKafkaAdminClient(bootstrap_servers=self.kafka_servers)
457
- await admin_client.start()
458
- try:
459
- if topics is None:
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
- records_to_delete = {
467
- TopicPartition(
468
- topic=topic_info["topic"], partition=partition_info["partition"]
469
- ): RecordsToDelete(before_offset=-1)
470
- for topic_info in topics_info
471
- for partition_info in topic_info["partitions"]
472
- }
473
- await admin_client.delete_records(records_to_delete, timeout_ms=10000)
474
- finally:
475
- await admin_client.close()
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
- uploads_info = await asyncio.to_thread(
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
- upload_list = uploads_info.get("Uploads", [])
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.1
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
  ![tests](https://github.com/ghga-de/hexkit/actions/workflows/tests.yaml/badge.svg)
@@ -9,6 +9,7 @@ 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_setup.py
12
13
  src/hexkit/py.typed
13
14
  src/hexkit/utils.py
14
15
  src/hexkit.egg-info/PKG-INFO
@@ -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