jararaca 0.3.18__tar.gz → 0.3.19__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.
Potentially problematic release.
This version of jararaca might be problematic. Click here for more details.
- {jararaca-0.3.18 → jararaca-0.3.19}/PKG-INFO +4 -4
- {jararaca-0.3.18 → jararaca-0.3.19}/pyproject.toml +4 -4
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/__init__.py +10 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/core/uow.py +33 -5
- jararaca-0.3.19/src/jararaca/messagebus/implicit_headers.py +45 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +6 -1
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/worker.py +6 -1
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/microservice.py +3 -1
- jararaca-0.3.19/src/jararaca/observability/decorators.py +213 -0
- jararaca-0.3.19/src/jararaca/observability/hooks.py +20 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/observability/providers/otel.py +62 -9
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/server.py +28 -4
- jararaca-0.3.18/src/jararaca/observability/decorators.py +0 -97
- {jararaca-0.3.18 → jararaca-0.3.19}/LICENSE +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/README.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/CNAME +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/architecture.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/assets/tracing_example.png +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/expose-type.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/http-rpc.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/index.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/interceptors.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/messagebus.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/retry.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/scheduler.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/stylesheets/custom.css +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/docs/websocket.md +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/__main__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/broker_backend/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/broker_backend/mapper.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/broker_backend/redis_broker_backend.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/cli.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/common/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/core/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/core/providers.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/di.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/files/entity.py.mako +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/lifecycle.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/bus_message_controller.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/consumers/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/message.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/publisher.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/observability/interceptor.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/observability/providers/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/base.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/exports.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/interceptors/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/interceptors/constants.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/interceptors/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/session.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/sort_filter.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/utilities.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/hooks.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/http_microservice.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/base_types.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/context.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/redis.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/types.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/py.typed +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/reflect/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/reflect/controller_inspect.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/reflect/metadata.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/backends/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/backends/httpx.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/backends/otel.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/rpc/http/httpx.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/scheduler/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/scheduler/beat_worker.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/scheduler/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/scheduler/types.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/app_config/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/app_config/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/app_config/interceptor.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/typescript/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/typescript/decorators.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/tools/typescript/interface_parser.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/utils/__init__.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/utils/rabbitmq_utils.py +0 -0
- {jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/utils/retry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jararaca
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.19
|
|
4
4
|
Summary: A simple and fast API framework for Python
|
|
5
5
|
Home-page: https://github.com/LuscasLeo/jararaca
|
|
6
6
|
Author: Lucas S
|
|
@@ -18,11 +18,11 @@ Requires-Dist: croniter (>=3.0.3,<4.0.0)
|
|
|
18
18
|
Requires-Dist: fastapi (>=0.113.0,<0.114.0)
|
|
19
19
|
Requires-Dist: frozendict (>=2.4.6,<3.0.0)
|
|
20
20
|
Requires-Dist: mako (>=1.3.5,<2.0.0)
|
|
21
|
-
Requires-Dist: opentelemetry-api (>=1.
|
|
22
|
-
Requires-Dist: opentelemetry-distro (>=0.
|
|
21
|
+
Requires-Dist: opentelemetry-api (>=1.38.0,<2.0.0) ; extra == "opentelemetry"
|
|
22
|
+
Requires-Dist: opentelemetry-distro (>=0.59b0,<0.60) ; extra == "opentelemetry"
|
|
23
23
|
Requires-Dist: opentelemetry-exporter-otlp (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
24
24
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
25
|
-
Requires-Dist: opentelemetry-sdk (>=1.
|
|
25
|
+
Requires-Dist: opentelemetry-sdk (>=1.38.0,<2.0.0) ; extra == "opentelemetry"
|
|
26
26
|
Requires-Dist: redis (>=5.0.8,<6.0.0)
|
|
27
27
|
Requires-Dist: sqlalchemy (>=2.0.34,<3.0.0)
|
|
28
28
|
Requires-Dist: types-croniter (>=3.0.3.20240731,<4.0.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "jararaca"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.19"
|
|
4
4
|
description = "A simple and fast API framework for Python"
|
|
5
5
|
authors = ["Lucas S <me@luscasleo.dev>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -20,9 +20,9 @@ croniter = "^3.0.3"
|
|
|
20
20
|
redis = "^5.0.8"
|
|
21
21
|
websockets = "^13.0.1"
|
|
22
22
|
opentelemetry-exporter-otlp-proto-http = { version = "^1.27.0", optional = true }
|
|
23
|
-
opentelemetry-api =
|
|
24
|
-
opentelemetry-sdk =
|
|
25
|
-
opentelemetry-distro =
|
|
23
|
+
opentelemetry-api = "^1.38.0"
|
|
24
|
+
opentelemetry-sdk = "^1.38.0"
|
|
25
|
+
opentelemetry-distro = "^0.59b0"
|
|
26
26
|
opentelemetry-exporter-otlp = { version = "^1.27.0", optional = true }
|
|
27
27
|
types-croniter = "^3.0.3.20240731"
|
|
28
28
|
types-redis = "^4.6.0.20240903"
|
|
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
|
|
|
17
17
|
AppTransactionContext,
|
|
18
18
|
use_app_type,
|
|
19
19
|
)
|
|
20
|
+
from jararaca.observability.decorators import TracedClass, TracedFunc, traced_class
|
|
21
|
+
from jararaca.observability.hooks import spawn_trace
|
|
20
22
|
from jararaca.observability.interceptor import ObservabilityInterceptor
|
|
21
23
|
from jararaca.observability.providers.otel import OtelObservabilityProvider
|
|
22
24
|
from jararaca.persistence.sort_filter import (
|
|
@@ -222,6 +224,10 @@ if TYPE_CHECKING:
|
|
|
222
224
|
"HttpPut",
|
|
223
225
|
"HttpDelete",
|
|
224
226
|
"ObservabilityInterceptor",
|
|
227
|
+
"TracedFunc",
|
|
228
|
+
"TracedClass",
|
|
229
|
+
"traced_class",
|
|
230
|
+
"spawn_trace",
|
|
225
231
|
"QueryInjector",
|
|
226
232
|
"HttpMicroservice",
|
|
227
233
|
"use_current_container",
|
|
@@ -392,6 +398,10 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
|
|
|
392
398
|
"HttpPut": (__SPEC_PARENT__, "rpc.http.decorators", "Put"),
|
|
393
399
|
"HttpDelete": (__SPEC_PARENT__, "rpc.http.decorators", "Delete"),
|
|
394
400
|
"ObservabilityInterceptor": (__SPEC_PARENT__, "observability.interceptor", None),
|
|
401
|
+
"TracedFunc": (__SPEC_PARENT__, "observability.decorators", None),
|
|
402
|
+
"TracedClass": (__SPEC_PARENT__, "observability.decorators", None),
|
|
403
|
+
"traced_class": (__SPEC_PARENT__, "observability.decorators", None),
|
|
404
|
+
"spawn_trace": (__SPEC_PARENT__, "observability.hooks", None),
|
|
395
405
|
"QueryInjector": (__SPEC_PARENT__, "persistence.utilities", None),
|
|
396
406
|
"HttpMicroservice": (__SPEC_PARENT__, "presentation.http_microservice", None),
|
|
397
407
|
"use_current_container": (__SPEC_PARENT__, "microservice", None),
|
|
@@ -33,7 +33,6 @@ class UnitOfWorkContextProvider:
|
|
|
33
33
|
self.container = container
|
|
34
34
|
self.container_interceptor = ContainerInterceptor(container)
|
|
35
35
|
|
|
36
|
-
# TODO: Guarantee that the context is closed whenever an exception is raised
|
|
37
36
|
# TODO: Guarantee a unit of work workflow for the whole request, including all the interceptors
|
|
38
37
|
|
|
39
38
|
def factory_app_interceptors(self) -> Sequence[AppInterceptor]:
|
|
@@ -65,7 +64,36 @@ class UnitOfWorkContextProvider:
|
|
|
65
64
|
for ctx in ctxs:
|
|
66
65
|
await ctx.__aenter__()
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
exc_type = None
|
|
68
|
+
exc_value = None
|
|
69
|
+
exc_traceback = None
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
yield None
|
|
73
|
+
except BaseException as e:
|
|
74
|
+
exc_type = type(e)
|
|
75
|
+
exc_value = e
|
|
76
|
+
exc_traceback = e.__traceback__
|
|
77
|
+
raise
|
|
78
|
+
finally:
|
|
79
|
+
# Exit interceptors in reverse order, propagating exception info
|
|
80
|
+
for ctx in reversed(ctxs):
|
|
81
|
+
try:
|
|
82
|
+
suppressed = await ctx.__aexit__(
|
|
83
|
+
exc_type, exc_value, exc_traceback
|
|
84
|
+
)
|
|
85
|
+
# If an interceptor returns True, it suppresses the exception
|
|
86
|
+
if suppressed and exc_type is not None:
|
|
87
|
+
exc_type = None
|
|
88
|
+
exc_value = None
|
|
89
|
+
exc_traceback = None
|
|
90
|
+
except BaseException as exit_exc:
|
|
91
|
+
# If an interceptor raises an exception during cleanup,
|
|
92
|
+
# replace the original exception with the new one
|
|
93
|
+
exc_type = type(exit_exc)
|
|
94
|
+
exc_value = exit_exc
|
|
95
|
+
exc_traceback = exit_exc.__traceback__
|
|
96
|
+
|
|
97
|
+
# Re-raise the exception if it wasn't suppressed
|
|
98
|
+
if exc_type is not None and exc_value is not None:
|
|
99
|
+
raise exc_value.with_traceback(exc_traceback)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import decimal
|
|
3
|
+
import typing
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from typing import Any, Dict, Generator
|
|
7
|
+
|
|
8
|
+
FieldArray = list["FieldValue"]
|
|
9
|
+
"""A data structure for holding an array of field values."""
|
|
10
|
+
|
|
11
|
+
FieldTable = typing.Dict[str, "FieldValue"]
|
|
12
|
+
FieldValue = (
|
|
13
|
+
bool
|
|
14
|
+
| bytes
|
|
15
|
+
| bytearray
|
|
16
|
+
| decimal.Decimal
|
|
17
|
+
| FieldArray
|
|
18
|
+
| FieldTable
|
|
19
|
+
| float
|
|
20
|
+
| int
|
|
21
|
+
| None
|
|
22
|
+
| str
|
|
23
|
+
| datetime.datetime
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
ImplicitHeaders = Dict[str, FieldValue]
|
|
27
|
+
|
|
28
|
+
implicit_headers_ctx = ContextVar[ImplicitHeaders | None](
|
|
29
|
+
"implicit_headers_ctx", default=None
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def use_implicit_headers() -> ImplicitHeaders | None:
|
|
34
|
+
return implicit_headers_ctx.get()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def provide_implicit_headers(
|
|
39
|
+
implicit_headers: ImplicitHeaders,
|
|
40
|
+
) -> Generator[None, Any, None]:
|
|
41
|
+
token = implicit_headers_ctx.set(implicit_headers)
|
|
42
|
+
try:
|
|
43
|
+
yield
|
|
44
|
+
finally:
|
|
45
|
+
implicit_headers_ctx.reset(token)
|
|
@@ -9,6 +9,7 @@ from aio_pika.abc import AbstractConnection
|
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
11
11
|
from jararaca.broker_backend import MessageBrokerBackend
|
|
12
|
+
from jararaca.messagebus import implicit_headers
|
|
12
13
|
from jararaca.messagebus.interceptors.publisher_interceptor import (
|
|
13
14
|
MessageBusConnectionFactory,
|
|
14
15
|
)
|
|
@@ -42,8 +43,12 @@ class AIOPikaMessagePublisher(MessagePublisher):
|
|
|
42
43
|
logging.warning(f"Exchange {self.exchange_name} not found")
|
|
43
44
|
return
|
|
44
45
|
routing_key = f"{topic}."
|
|
46
|
+
|
|
47
|
+
implicit_headers_data = implicit_headers.use_implicit_headers()
|
|
45
48
|
await exchange.publish(
|
|
46
|
-
aio_pika.Message(
|
|
49
|
+
aio_pika.Message(
|
|
50
|
+
body=message.model_dump_json().encode(), headers=implicit_headers_data
|
|
51
|
+
),
|
|
47
52
|
routing_key=routing_key,
|
|
48
53
|
)
|
|
49
54
|
|
|
@@ -50,6 +50,7 @@ from jararaca.messagebus.decorators import (
|
|
|
50
50
|
MessageHandlerData,
|
|
51
51
|
ScheduleDispatchData,
|
|
52
52
|
)
|
|
53
|
+
from jararaca.messagebus.implicit_headers import provide_implicit_headers
|
|
53
54
|
from jararaca.messagebus.message import Message, MessageOf
|
|
54
55
|
from jararaca.microservice import (
|
|
55
56
|
AppTransactionContext,
|
|
@@ -1130,6 +1131,8 @@ class ScheduledMessageHandlerCallback:
|
|
|
1130
1131
|
AppTransactionContext(
|
|
1131
1132
|
controller_member_reflect=scheduled_action.controller_member,
|
|
1132
1133
|
transaction_data=SchedulerTransactionData(
|
|
1134
|
+
task_name=scheduled_action.spec.name
|
|
1135
|
+
or scheduled_action.callable.__qualname__,
|
|
1133
1136
|
scheduled_to=datetime.now(UTC),
|
|
1134
1137
|
cron_expression=scheduled_action.spec.cron,
|
|
1135
1138
|
triggered_at=datetime.now(UTC),
|
|
@@ -1469,7 +1472,9 @@ class MessageHandlerCallback:
|
|
|
1469
1472
|
incoming_message_spec = MessageHandler.get_message_incoming(handler)
|
|
1470
1473
|
assert incoming_message_spec is not None
|
|
1471
1474
|
|
|
1472
|
-
with
|
|
1475
|
+
with provide_implicit_headers(aio_pika_message.headers), provide_shutdown_state(
|
|
1476
|
+
self.consumer.shutdown_state
|
|
1477
|
+
):
|
|
1473
1478
|
async with self.consumer.uow_context_provider(
|
|
1474
1479
|
AppTransactionContext(
|
|
1475
1480
|
controller_member_reflect=handler_data.controller_member,
|
|
@@ -19,7 +19,7 @@ from typing import (
|
|
|
19
19
|
runtime_checkable,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
from fastapi import Request, WebSocket
|
|
22
|
+
from fastapi import Request, Response, WebSocket
|
|
23
23
|
|
|
24
24
|
from jararaca.core.providers import ProviderSpec, T, Token
|
|
25
25
|
from jararaca.messagebus import MessageOf
|
|
@@ -34,6 +34,7 @@ if TYPE_CHECKING:
|
|
|
34
34
|
|
|
35
35
|
@dataclass
|
|
36
36
|
class SchedulerTransactionData:
|
|
37
|
+
task_name: str
|
|
37
38
|
triggered_at: datetime
|
|
38
39
|
scheduled_to: datetime
|
|
39
40
|
cron_expression: str
|
|
@@ -43,6 +44,7 @@ class SchedulerTransactionData:
|
|
|
43
44
|
@dataclass
|
|
44
45
|
class HttpTransactionData:
|
|
45
46
|
request: Request
|
|
47
|
+
response: Response
|
|
46
48
|
context_type: Literal["http"] = "http"
|
|
47
49
|
|
|
48
50
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from contextlib import contextmanager, suppress
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
AsyncContextManager,
|
|
8
|
+
Awaitable,
|
|
9
|
+
Callable,
|
|
10
|
+
ContextManager,
|
|
11
|
+
Generator,
|
|
12
|
+
Mapping,
|
|
13
|
+
Protocol,
|
|
14
|
+
Sequence,
|
|
15
|
+
TypeVar,
|
|
16
|
+
Union,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from jararaca.microservice import AppTransactionContext
|
|
20
|
+
|
|
21
|
+
F = TypeVar("F", bound=Callable[..., Awaitable[Any]])
|
|
22
|
+
|
|
23
|
+
AttributeValue = Union[
|
|
24
|
+
str,
|
|
25
|
+
bool,
|
|
26
|
+
int,
|
|
27
|
+
float,
|
|
28
|
+
Sequence[str],
|
|
29
|
+
Sequence[bool],
|
|
30
|
+
Sequence[int],
|
|
31
|
+
Sequence[float],
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
AttributeMap = Mapping[str, AttributeValue]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TracingContextProvider(Protocol):
|
|
38
|
+
|
|
39
|
+
def __call__(
|
|
40
|
+
self, trace_name: str, context_attributes: AttributeMap | None
|
|
41
|
+
) -> ContextManager[Any]: ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TracingContextProviderFactory(Protocol):
|
|
45
|
+
|
|
46
|
+
def root_setup(
|
|
47
|
+
self, app_context: AppTransactionContext
|
|
48
|
+
) -> AsyncContextManager[None]: ...
|
|
49
|
+
|
|
50
|
+
def provide_provider(
|
|
51
|
+
self, app_context: AppTransactionContext
|
|
52
|
+
) -> TracingContextProvider: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
tracing_ctx_provider_ctxv = ContextVar[TracingContextProvider]("tracing_ctx_provider")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@contextmanager
|
|
59
|
+
def provide_tracing_ctx_provider(
|
|
60
|
+
ctx_provider: TracingContextProvider,
|
|
61
|
+
) -> Generator[None, None, None]:
|
|
62
|
+
|
|
63
|
+
token = tracing_ctx_provider_ctxv.set(ctx_provider)
|
|
64
|
+
try:
|
|
65
|
+
yield
|
|
66
|
+
finally:
|
|
67
|
+
with suppress(ValueError):
|
|
68
|
+
tracing_ctx_provider_ctxv.reset(token)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_tracing_ctx_provider() -> TracingContextProvider | None:
|
|
72
|
+
return tracing_ctx_provider_ctxv.get(None)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def default_trace_mapper(*args: Any, **kwargs: Any) -> dict[str, str]:
|
|
76
|
+
return {
|
|
77
|
+
"args": str(args),
|
|
78
|
+
"kwargs": str(kwargs),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TracedFunc:
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
trace_name: str,
|
|
87
|
+
trace_mapper: Callable[..., dict[str, str]] = default_trace_mapper,
|
|
88
|
+
):
|
|
89
|
+
self.trace_name = trace_name
|
|
90
|
+
self.trace_mapper = trace_mapper
|
|
91
|
+
|
|
92
|
+
def __call__(
|
|
93
|
+
self,
|
|
94
|
+
decorated: F,
|
|
95
|
+
) -> F:
|
|
96
|
+
|
|
97
|
+
@wraps(decorated)
|
|
98
|
+
async def wrapper(
|
|
99
|
+
*args: Any,
|
|
100
|
+
**kwargs: Any,
|
|
101
|
+
) -> Any:
|
|
102
|
+
|
|
103
|
+
if ctx_provider := get_tracing_ctx_provider():
|
|
104
|
+
with ctx_provider(
|
|
105
|
+
self.trace_name,
|
|
106
|
+
self.trace_mapper(*args, **kwargs),
|
|
107
|
+
):
|
|
108
|
+
return await decorated(*args, **kwargs)
|
|
109
|
+
|
|
110
|
+
return await decorated(*args, **kwargs)
|
|
111
|
+
|
|
112
|
+
return wrapper # type: ignore[return-value]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
C = TypeVar("C", bound=type)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TracedClass:
|
|
119
|
+
"""
|
|
120
|
+
Class decorator that automatically applies tracing to all async methods in a class.
|
|
121
|
+
|
|
122
|
+
Usage:
|
|
123
|
+
@TracedClass()
|
|
124
|
+
class MyService:
|
|
125
|
+
async def method1(self) -> str:
|
|
126
|
+
return "hello"
|
|
127
|
+
|
|
128
|
+
async def method2(self, x: int) -> int:
|
|
129
|
+
return x * 2
|
|
130
|
+
|
|
131
|
+
def sync_method(self) -> str: # Not traced
|
|
132
|
+
return "sync"
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
trace_name_prefix: str | None = None,
|
|
138
|
+
trace_mapper: Callable[..., dict[str, str]] = default_trace_mapper,
|
|
139
|
+
include_private: bool = False,
|
|
140
|
+
exclude_methods: set[str] | None = None,
|
|
141
|
+
):
|
|
142
|
+
"""
|
|
143
|
+
Initialize the TracedClass decorator.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
trace_name_prefix: Prefix for trace names. If None, uses class name.
|
|
147
|
+
trace_mapper: Function to map method arguments to trace attributes.
|
|
148
|
+
include_private: Whether to trace private methods (starting with _).
|
|
149
|
+
exclude_methods: Set of method names to exclude from tracing.
|
|
150
|
+
"""
|
|
151
|
+
self.trace_name_prefix = trace_name_prefix
|
|
152
|
+
self.trace_mapper = trace_mapper
|
|
153
|
+
self.include_private = include_private
|
|
154
|
+
self.exclude_methods = exclude_methods or set()
|
|
155
|
+
|
|
156
|
+
def __call__(self, cls: C) -> C:
|
|
157
|
+
"""Apply tracing to all async methods in the class."""
|
|
158
|
+
|
|
159
|
+
# Use class name as prefix if not provided
|
|
160
|
+
trace_prefix = self.trace_name_prefix or cls.__name__
|
|
161
|
+
|
|
162
|
+
# Get all methods in the class
|
|
163
|
+
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
|
|
164
|
+
# Skip if method should be excluded
|
|
165
|
+
if name in self.exclude_methods:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
# Skip private methods unless explicitly included
|
|
169
|
+
if name.startswith("_") and not self.include_private:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Only trace async methods
|
|
173
|
+
if inspect.iscoroutinefunction(method):
|
|
174
|
+
trace_name = f"{trace_prefix}.{name}"
|
|
175
|
+
traced_method = TracedFunc(trace_name, self.trace_mapper)(method)
|
|
176
|
+
setattr(cls, name, traced_method)
|
|
177
|
+
|
|
178
|
+
return cls
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def traced_class(
|
|
182
|
+
trace_name_prefix: str | None = None,
|
|
183
|
+
trace_mapper: Callable[..., dict[str, str]] = default_trace_mapper,
|
|
184
|
+
include_private: bool = False,
|
|
185
|
+
exclude_methods: set[str] | None = None,
|
|
186
|
+
) -> Callable[[C], C]:
|
|
187
|
+
"""
|
|
188
|
+
Functional interface for TracedClass decorator.
|
|
189
|
+
|
|
190
|
+
Usage:
|
|
191
|
+
@traced_class(trace_name_prefix="MyService")
|
|
192
|
+
class MyService:
|
|
193
|
+
async def method1(self) -> str:
|
|
194
|
+
return "hello"
|
|
195
|
+
"""
|
|
196
|
+
return TracedClass(
|
|
197
|
+
trace_name_prefix=trace_name_prefix,
|
|
198
|
+
trace_mapper=trace_mapper,
|
|
199
|
+
include_private=include_private,
|
|
200
|
+
exclude_methods=exclude_methods,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = [
|
|
205
|
+
"TracingContextProvider",
|
|
206
|
+
"TracingContextProviderFactory",
|
|
207
|
+
"provide_tracing_ctx_provider",
|
|
208
|
+
"get_tracing_ctx_provider",
|
|
209
|
+
"default_trace_mapper",
|
|
210
|
+
"TracedFunc",
|
|
211
|
+
"TracedClass",
|
|
212
|
+
"traced_class",
|
|
213
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Any, Generator
|
|
3
|
+
|
|
4
|
+
from jararaca.observability.decorators import AttributeMap, get_tracing_ctx_provider
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@contextmanager
|
|
8
|
+
def spawn_trace(
|
|
9
|
+
name: str,
|
|
10
|
+
attributes: AttributeMap | None = None,
|
|
11
|
+
) -> Generator[None, Any, None]:
|
|
12
|
+
|
|
13
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
14
|
+
with trace_context_provider(trace_name=name, context_attributes=attributes):
|
|
15
|
+
yield
|
|
16
|
+
else:
|
|
17
|
+
yield
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
spawn_trace
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
|
-
from typing import AsyncGenerator, Generator, Protocol
|
|
3
|
+
from typing import Any, AsyncGenerator, Generator, Protocol
|
|
4
4
|
|
|
5
5
|
from opentelemetry import metrics, trace
|
|
6
6
|
from opentelemetry._logs import set_logger_provider
|
|
@@ -23,11 +23,21 @@ from opentelemetry.sdk.trace import TracerProvider
|
|
|
23
23
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
24
24
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
25
25
|
|
|
26
|
-
from jararaca.
|
|
26
|
+
from jararaca.messagebus.implicit_headers import (
|
|
27
|
+
ImplicitHeaders,
|
|
28
|
+
provide_implicit_headers,
|
|
29
|
+
use_implicit_headers,
|
|
30
|
+
)
|
|
31
|
+
from jararaca.microservice import (
|
|
32
|
+
AppTransactionContext,
|
|
33
|
+
Container,
|
|
34
|
+
Microservice,
|
|
35
|
+
use_app_transaction_context,
|
|
36
|
+
)
|
|
27
37
|
from jararaca.observability.decorators import (
|
|
38
|
+
AttributeMap,
|
|
28
39
|
TracingContextProvider,
|
|
29
40
|
TracingContextProviderFactory,
|
|
30
|
-
get_tracing_ctx_provider,
|
|
31
41
|
)
|
|
32
42
|
from jararaca.observability.interceptor import ObservabilityProvider
|
|
33
43
|
|
|
@@ -43,7 +53,7 @@ class OtelTracingContextProvider(TracingContextProvider):
|
|
|
43
53
|
def __call__(
|
|
44
54
|
self,
|
|
45
55
|
trace_name: str,
|
|
46
|
-
context_attributes:
|
|
56
|
+
context_attributes: AttributeMap | None,
|
|
47
57
|
) -> Generator[None, None, None]:
|
|
48
58
|
|
|
49
59
|
with tracer.start_as_current_span(trace_name, attributes=context_attributes):
|
|
@@ -63,7 +73,7 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
|
|
|
63
73
|
) -> AsyncGenerator[None, None]:
|
|
64
74
|
|
|
65
75
|
title: str = "Unmapped App Context Execution"
|
|
66
|
-
headers = {}
|
|
76
|
+
headers: dict[str, Any] = {}
|
|
67
77
|
tx_data = app_tx_ctx.transaction_data
|
|
68
78
|
if tx_data.context_type == "http":
|
|
69
79
|
|
|
@@ -72,6 +82,19 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
|
|
|
72
82
|
|
|
73
83
|
elif tx_data.context_type == "message_bus":
|
|
74
84
|
title = f"Message Bus {tx_data.topic}"
|
|
85
|
+
headers = use_implicit_headers() or {}
|
|
86
|
+
|
|
87
|
+
elif tx_data.context_type == "websocket":
|
|
88
|
+
headers = dict(tx_data.websocket.headers)
|
|
89
|
+
title = f"WebSocket {tx_data.websocket.url}"
|
|
90
|
+
|
|
91
|
+
elif tx_data.context_type == "scheduler":
|
|
92
|
+
title = f"Scheduler Task {tx_data.task_name}"
|
|
93
|
+
headers = {
|
|
94
|
+
"scheduled_to": tx_data.scheduled_to.isoformat(),
|
|
95
|
+
"cron_expression": tx_data.cron_expression,
|
|
96
|
+
"triggered_at": tx_data.triggered_at.isoformat(),
|
|
97
|
+
}
|
|
75
98
|
|
|
76
99
|
carrier = {
|
|
77
100
|
key: value
|
|
@@ -90,8 +113,23 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
|
|
|
90
113
|
|
|
91
114
|
ctx2 = W3CBaggagePropagator().extract(b2, context=ctx)
|
|
92
115
|
|
|
93
|
-
with tracer.start_as_current_span(
|
|
94
|
-
|
|
116
|
+
with tracer.start_as_current_span(
|
|
117
|
+
name=title,
|
|
118
|
+
context=ctx2,
|
|
119
|
+
attributes={
|
|
120
|
+
"app.context_type": tx_data.context_type,
|
|
121
|
+
},
|
|
122
|
+
) as root_span:
|
|
123
|
+
cx = root_span.get_span_context()
|
|
124
|
+
if app_tx_ctx.transaction_data.context_type == "http":
|
|
125
|
+
app_tx_ctx.transaction_data.response.headers["traceparent"] = hex(
|
|
126
|
+
cx.trace_id
|
|
127
|
+
)[2:].rjust(32, "0")
|
|
128
|
+
tracing_headers: ImplicitHeaders = {}
|
|
129
|
+
TraceContextTextMapPropagator().inject(tracing_headers)
|
|
130
|
+
W3CBaggagePropagator().inject(tracing_headers)
|
|
131
|
+
with provide_implicit_headers(tracing_headers):
|
|
132
|
+
yield
|
|
95
133
|
|
|
96
134
|
|
|
97
135
|
class LoggerHandlerCallback(Protocol):
|
|
@@ -99,6 +137,21 @@ class LoggerHandlerCallback(Protocol):
|
|
|
99
137
|
def __call__(self, logger_handler: logging.Handler) -> None: ...
|
|
100
138
|
|
|
101
139
|
|
|
140
|
+
class CustomLoggingHandler(LoggingHandler):
|
|
141
|
+
|
|
142
|
+
def _translate(self, record: logging.LogRecord) -> dict[str, Any]:
|
|
143
|
+
try:
|
|
144
|
+
ctx = use_app_transaction_context()
|
|
145
|
+
return {
|
|
146
|
+
**super()._translate(record),
|
|
147
|
+
"attributes": {
|
|
148
|
+
"context_type": ctx.transaction_data.context_type,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
except LookupError:
|
|
152
|
+
return super()._translate(record)
|
|
153
|
+
|
|
154
|
+
|
|
102
155
|
class OtelObservabilityProvider(ObservabilityProvider):
|
|
103
156
|
|
|
104
157
|
def __init__(
|
|
@@ -143,11 +196,11 @@ class OtelObservabilityProvider(ObservabilityProvider):
|
|
|
143
196
|
BatchLogRecordProcessor(self.logs_exporter)
|
|
144
197
|
)
|
|
145
198
|
|
|
146
|
-
logging_handler =
|
|
199
|
+
logging_handler = CustomLoggingHandler(
|
|
147
200
|
level=logging.DEBUG, logger_provider=logger_provider
|
|
148
201
|
)
|
|
149
202
|
|
|
150
|
-
logging_handler.addFilter(lambda _: get_tracing_ctx_provider() is not None)
|
|
203
|
+
# logging_handler.addFilter(lambda _: get_tracing_ctx_provider() is not None)
|
|
151
204
|
|
|
152
205
|
self.logging_handler_callback(logging_handler)
|
|
153
206
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import signal
|
|
3
4
|
import threading
|
|
@@ -5,7 +6,7 @@ from contextlib import asynccontextmanager
|
|
|
5
6
|
from signal import SIGINT, SIGTERM
|
|
6
7
|
from typing import Any, AsyncGenerator
|
|
7
8
|
|
|
8
|
-
from fastapi import Depends, FastAPI, Request, WebSocket
|
|
9
|
+
from fastapi import Depends, FastAPI, HTTPException, Request, Response, WebSocket
|
|
9
10
|
from starlette.types import ASGIApp
|
|
10
11
|
|
|
11
12
|
from jararaca.core.uow import UnitOfWorkContextProvider
|
|
@@ -23,6 +24,8 @@ from jararaca.presentation.decorators import RestController
|
|
|
23
24
|
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
24
25
|
from jararaca.reflect.controller_inspect import ControllerMemberReflect
|
|
25
26
|
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
class HttpAppLifecycle:
|
|
28
31
|
|
|
@@ -109,7 +112,7 @@ class HttpUowContextProviderDependency:
|
|
|
109
112
|
self.shutdown_state.setup_signal_handlers()
|
|
110
113
|
|
|
111
114
|
async def __call__(
|
|
112
|
-
self, websocket: WebSocket = None, request: Request = None # type: ignore
|
|
115
|
+
self, websocket: WebSocket = None, request: Request = None, response: Response = None # type: ignore
|
|
113
116
|
) -> AsyncGenerator[None, None]:
|
|
114
117
|
if request:
|
|
115
118
|
endpoint = request.scope["endpoint"]
|
|
@@ -133,13 +136,34 @@ class HttpUowContextProviderDependency:
|
|
|
133
136
|
AppTransactionContext(
|
|
134
137
|
controller_member_reflect=member,
|
|
135
138
|
transaction_data=(
|
|
136
|
-
HttpTransactionData(request=request)
|
|
139
|
+
HttpTransactionData(request=request, response=response)
|
|
137
140
|
if request
|
|
138
141
|
else WebSocketTransactionData(websocket=websocket)
|
|
139
142
|
),
|
|
140
143
|
)
|
|
141
144
|
):
|
|
142
|
-
|
|
145
|
+
try:
|
|
146
|
+
yield
|
|
147
|
+
except HTTPException:
|
|
148
|
+
raise
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.exception("Unhandled exception in request handling.")
|
|
151
|
+
raise HTTPException(
|
|
152
|
+
status_code=500,
|
|
153
|
+
detail={
|
|
154
|
+
"message": "Internal server error occurred.",
|
|
155
|
+
"x-traceparentid": (
|
|
156
|
+
response.headers.get("traceparent")
|
|
157
|
+
if response
|
|
158
|
+
else None
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
headers=(
|
|
162
|
+
{k: str(v) for k, v in response.headers.items()}
|
|
163
|
+
if response
|
|
164
|
+
else {}
|
|
165
|
+
),
|
|
166
|
+
) from e
|
|
143
167
|
|
|
144
168
|
|
|
145
169
|
def create_http_server(
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
from contextlib import contextmanager, suppress
|
|
2
|
-
from contextvars import ContextVar
|
|
3
|
-
from functools import wraps
|
|
4
|
-
from typing import (
|
|
5
|
-
Any,
|
|
6
|
-
AsyncContextManager,
|
|
7
|
-
Awaitable,
|
|
8
|
-
Callable,
|
|
9
|
-
ContextManager,
|
|
10
|
-
Generator,
|
|
11
|
-
ParamSpec,
|
|
12
|
-
Protocol,
|
|
13
|
-
TypeVar,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
from jararaca.microservice import AppTransactionContext
|
|
17
|
-
|
|
18
|
-
P = ParamSpec("P")
|
|
19
|
-
R = TypeVar("R")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class TracingContextProvider(Protocol):
|
|
23
|
-
|
|
24
|
-
def __call__(
|
|
25
|
-
self, trace_name: str, context_attributes: dict[str, str]
|
|
26
|
-
) -> ContextManager[Any]: ...
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class TracingContextProviderFactory(Protocol):
|
|
30
|
-
|
|
31
|
-
def root_setup(
|
|
32
|
-
self, app_context: AppTransactionContext
|
|
33
|
-
) -> AsyncContextManager[None]: ...
|
|
34
|
-
|
|
35
|
-
def provide_provider(
|
|
36
|
-
self, app_context: AppTransactionContext
|
|
37
|
-
) -> TracingContextProvider: ...
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tracing_ctx_provider_ctxv = ContextVar[TracingContextProvider]("tracing_ctx_provider")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@contextmanager
|
|
44
|
-
def provide_tracing_ctx_provider(
|
|
45
|
-
ctx_provider: TracingContextProvider,
|
|
46
|
-
) -> Generator[None, None, None]:
|
|
47
|
-
|
|
48
|
-
token = tracing_ctx_provider_ctxv.set(ctx_provider)
|
|
49
|
-
try:
|
|
50
|
-
yield
|
|
51
|
-
finally:
|
|
52
|
-
with suppress(ValueError):
|
|
53
|
-
tracing_ctx_provider_ctxv.reset(token)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def get_tracing_ctx_provider() -> TracingContextProvider | None:
|
|
57
|
-
return tracing_ctx_provider_ctxv.get(None)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def default_trace_mapper(*args: Any, **kwargs: Any) -> dict[str, str]:
|
|
61
|
-
return {
|
|
62
|
-
"args": str(args),
|
|
63
|
-
"kwargs": str(kwargs),
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class TracedFunc:
|
|
68
|
-
|
|
69
|
-
def __init__(
|
|
70
|
-
self,
|
|
71
|
-
trace_name: str,
|
|
72
|
-
trace_mapper: Callable[..., dict[str, str]] = default_trace_mapper,
|
|
73
|
-
):
|
|
74
|
-
self.trace_name = trace_name
|
|
75
|
-
self.trace_mapper = trace_mapper
|
|
76
|
-
|
|
77
|
-
def __call__(
|
|
78
|
-
self,
|
|
79
|
-
decorated: Callable[P, Awaitable[R]],
|
|
80
|
-
) -> Callable[P, Awaitable[R]]:
|
|
81
|
-
|
|
82
|
-
@wraps(decorated)
|
|
83
|
-
async def wrapper(
|
|
84
|
-
*args: P.args,
|
|
85
|
-
**kwargs: P.kwargs,
|
|
86
|
-
) -> R:
|
|
87
|
-
|
|
88
|
-
if ctx_provider := get_tracing_ctx_provider():
|
|
89
|
-
with ctx_provider(
|
|
90
|
-
self.trace_name,
|
|
91
|
-
self.trace_mapper(**kwargs),
|
|
92
|
-
):
|
|
93
|
-
return await decorated(*args, **kwargs)
|
|
94
|
-
|
|
95
|
-
return await decorated(*args, **kwargs)
|
|
96
|
-
|
|
97
|
-
return 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
|
{jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/messagebus/interceptors/publisher_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py
RENAMED
|
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
|
{jararaca-0.3.18 → jararaca-0.3.19}/src/jararaca/presentation/websocket/websocket_interceptor.py
RENAMED
|
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
|