jararaca 0.3.11a16__py3-none-any.whl → 0.4.0a5__py3-none-any.whl
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.
- README.md +121 -0
- jararaca/__init__.py +184 -12
- jararaca/__main__.py +4 -0
- jararaca/broker_backend/__init__.py +4 -0
- jararaca/broker_backend/mapper.py +4 -0
- jararaca/broker_backend/redis_broker_backend.py +9 -3
- jararaca/cli.py +272 -47
- jararaca/common/__init__.py +3 -0
- jararaca/core/__init__.py +3 -0
- jararaca/core/providers.py +4 -0
- jararaca/core/uow.py +41 -7
- jararaca/di.py +4 -0
- jararaca/files/entity.py.mako +4 -0
- jararaca/lifecycle.py +6 -2
- jararaca/messagebus/__init__.py +4 -0
- jararaca/messagebus/bus_message_controller.py +4 -0
- jararaca/messagebus/consumers/__init__.py +3 -0
- jararaca/messagebus/decorators.py +33 -67
- jararaca/messagebus/implicit_headers.py +49 -0
- jararaca/messagebus/interceptors/__init__.py +3 -0
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +13 -4
- jararaca/messagebus/interceptors/publisher_interceptor.py +4 -0
- jararaca/messagebus/message.py +4 -0
- jararaca/messagebus/publisher.py +6 -0
- jararaca/messagebus/worker.py +850 -383
- jararaca/microservice.py +110 -1
- jararaca/observability/constants.py +7 -0
- jararaca/observability/decorators.py +170 -13
- jararaca/observability/fastapi_exception_handler.py +37 -0
- jararaca/observability/hooks.py +109 -0
- jararaca/observability/interceptor.py +4 -0
- jararaca/observability/providers/__init__.py +3 -0
- jararaca/observability/providers/otel.py +202 -11
- jararaca/persistence/base.py +38 -2
- jararaca/persistence/exports.py +4 -0
- jararaca/persistence/interceptors/__init__.py +3 -0
- jararaca/persistence/interceptors/aiosqa_interceptor.py +86 -73
- jararaca/persistence/interceptors/constants.py +5 -0
- jararaca/persistence/interceptors/decorators.py +50 -0
- jararaca/persistence/session.py +3 -0
- jararaca/persistence/sort_filter.py +4 -0
- jararaca/persistence/utilities.py +50 -20
- jararaca/presentation/__init__.py +3 -0
- jararaca/presentation/decorators.py +88 -86
- jararaca/presentation/exceptions.py +23 -0
- jararaca/presentation/hooks.py +4 -0
- jararaca/presentation/http_microservice.py +4 -0
- jararaca/presentation/server.py +97 -45
- jararaca/presentation/websocket/__init__.py +3 -0
- jararaca/presentation/websocket/base_types.py +4 -0
- jararaca/presentation/websocket/context.py +4 -0
- jararaca/presentation/websocket/decorators.py +8 -41
- jararaca/presentation/websocket/redis.py +280 -53
- jararaca/presentation/websocket/types.py +4 -0
- jararaca/presentation/websocket/websocket_interceptor.py +46 -19
- jararaca/reflect/__init__.py +3 -0
- jararaca/reflect/controller_inspect.py +16 -10
- jararaca/reflect/decorators.py +238 -0
- jararaca/reflect/metadata.py +34 -25
- jararaca/rpc/__init__.py +3 -0
- jararaca/rpc/http/__init__.py +101 -0
- jararaca/rpc/http/backends/__init__.py +14 -0
- jararaca/rpc/http/backends/httpx.py +43 -9
- jararaca/rpc/http/backends/otel.py +4 -0
- jararaca/rpc/http/decorators.py +378 -113
- jararaca/rpc/http/httpx.py +3 -0
- jararaca/scheduler/__init__.py +3 -0
- jararaca/scheduler/beat_worker.py +521 -105
- jararaca/scheduler/decorators.py +15 -22
- jararaca/scheduler/types.py +4 -0
- jararaca/tools/app_config/__init__.py +3 -0
- jararaca/tools/app_config/decorators.py +7 -19
- jararaca/tools/app_config/interceptor.py +6 -2
- jararaca/tools/typescript/__init__.py +3 -0
- jararaca/tools/typescript/decorators.py +120 -0
- jararaca/tools/typescript/interface_parser.py +1074 -173
- jararaca/utils/__init__.py +3 -0
- jararaca/utils/rabbitmq_utils.py +65 -39
- jararaca/utils/retry.py +10 -3
- jararaca-0.4.0a5.dist-info/LICENSE +674 -0
- jararaca-0.4.0a5.dist-info/LICENSES/GPL-3.0-or-later.txt +232 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/METADATA +11 -7
- jararaca-0.4.0a5.dist-info/RECORD +88 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/WHEEL +1 -1
- pyproject.toml +131 -0
- jararaca-0.3.11a16.dist-info/RECORD +0 -74
- /jararaca-0.3.11a16.dist-info/LICENSE → /LICENSE +0 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Self, TypedDict, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
DECORATED_T = TypeVar("DECORATED_T", bound="Callable[..., Any] | type")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
S = TypeVar("S", bound="StackableDecorator")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DecoratorMetadata(TypedDict):
|
|
14
|
+
decorators: "list[StackableDecorator]"
|
|
15
|
+
decorators_by_type: "dict[Any, list[StackableDecorator]]"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StackableDecorator:
|
|
19
|
+
_ATTR_NAME: str = "__jararaca_stackable_decorator__"
|
|
20
|
+
|
|
21
|
+
def __call__(self, subject: DECORATED_T) -> DECORATED_T:
|
|
22
|
+
self.pre_decorated(subject)
|
|
23
|
+
self.register(subject, self)
|
|
24
|
+
self.post_decorated(subject)
|
|
25
|
+
return subject
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def decorator_key(cls) -> Any:
|
|
29
|
+
return cls
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_or_set_metadata(cls, subject: Any) -> DecoratorMetadata:
|
|
33
|
+
if cls._ATTR_NAME not in subject.__dict__:
|
|
34
|
+
setattr(
|
|
35
|
+
subject,
|
|
36
|
+
cls._ATTR_NAME,
|
|
37
|
+
DecoratorMetadata(decorators=[], decorators_by_type={}),
|
|
38
|
+
)
|
|
39
|
+
return cast(DecoratorMetadata, getattr(subject, cls._ATTR_NAME))
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def get_metadata(cls, subject: Any) -> DecoratorMetadata | None:
|
|
43
|
+
if hasattr(subject, cls._ATTR_NAME):
|
|
44
|
+
return cast(DecoratorMetadata, getattr(subject, cls._ATTR_NAME))
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def register(cls, subject: Any, decorator: "StackableDecorator") -> None:
|
|
49
|
+
if not cls._ATTR_NAME:
|
|
50
|
+
raise NotImplementedError("Subclasses must define _ATTR_NAME")
|
|
51
|
+
|
|
52
|
+
metadata = cls.get_or_set_metadata(subject)
|
|
53
|
+
metadata["decorators"].append(decorator)
|
|
54
|
+
metadata["decorators_by_type"].setdefault(cls.decorator_key(), []).append(
|
|
55
|
+
decorator
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def get(cls, subject: Any) -> list[Self]:
|
|
60
|
+
metadata = cls.get_metadata(subject)
|
|
61
|
+
if metadata is None:
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
if cls is StackableDecorator:
|
|
65
|
+
return cast(list[Self], metadata["decorators"])
|
|
66
|
+
else:
|
|
67
|
+
return cast(
|
|
68
|
+
list[Self], metadata["decorators_by_type"].get(cls.decorator_key(), [])
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def extract_list(cls, subject: Any) -> list[Self]:
|
|
73
|
+
metadata = cls.get_metadata(subject)
|
|
74
|
+
if metadata is None:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
if cls is StackableDecorator:
|
|
78
|
+
return cast(list[Self], metadata["decorators"])
|
|
79
|
+
else:
|
|
80
|
+
return cast(
|
|
81
|
+
list[Self], metadata["decorators_by_type"].get(cls.decorator_key(), [])
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get_fisrt(cls, subject: Any) -> Self | None:
|
|
86
|
+
decorators = cls.get(subject)
|
|
87
|
+
if decorators:
|
|
88
|
+
return decorators[0]
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def get_last(cls, subject: Any) -> Self | None:
|
|
93
|
+
decorators = cls.get(subject)
|
|
94
|
+
if decorators:
|
|
95
|
+
return decorators[-1]
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def pre_decorated(self, subject: DECORATED_T) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Hook method called before the subject is decorated.
|
|
101
|
+
Can be overridden by subclasses to perform additional setup.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def post_decorated(self, subject: DECORATED_T) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Hook method called after the subject has been decorated.
|
|
107
|
+
Can be overridden by subclasses to perform additional setup.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def get_all_from_type(cls, subject_type: type, inherit: bool = True) -> list[Self]:
|
|
112
|
+
"""
|
|
113
|
+
Retrieve all decorators of this type from the given class type.
|
|
114
|
+
"""
|
|
115
|
+
return resolve_class_decorators(subject_type, cls, inherit)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def get_bound_from_type(
|
|
119
|
+
cls, subject_type: type, inherit: bool = True, last: bool = False
|
|
120
|
+
) -> Self | None:
|
|
121
|
+
"""
|
|
122
|
+
Retrieve the first or last decorator of this type from the given class type.
|
|
123
|
+
"""
|
|
124
|
+
return resolve_bound_class_decorators(subject_type, cls, inherit, last=last)
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def get_all_from_method(
|
|
128
|
+
cls, cls_subject_type: type, method_name: str, inherit: bool = True
|
|
129
|
+
) -> list[Self]:
|
|
130
|
+
"""
|
|
131
|
+
Retrieve all decorators of this type from the given method.
|
|
132
|
+
"""
|
|
133
|
+
return resolve_method_decorators(cls_subject_type, method_name, cls, inherit)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def get_bound_from_method(
|
|
137
|
+
cls,
|
|
138
|
+
cls_subject_type: type,
|
|
139
|
+
method_name: str,
|
|
140
|
+
inherit: bool = True,
|
|
141
|
+
last: bool = True,
|
|
142
|
+
) -> Self | None:
|
|
143
|
+
"""
|
|
144
|
+
Retrieve the first or last decorator of this type from the given method.
|
|
145
|
+
"""
|
|
146
|
+
return resolve_bound_method_decorator(
|
|
147
|
+
cls_subject_type, method_name, cls, inherit, last=last
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def resolve_class_decorators(
|
|
152
|
+
subject: Any, decorator_cls: type[S], inherit: bool = True
|
|
153
|
+
) -> list[S]:
|
|
154
|
+
"""
|
|
155
|
+
Resolve decorators for a class or instance, optionally inheriting from base classes.
|
|
156
|
+
"""
|
|
157
|
+
if not inherit:
|
|
158
|
+
return decorator_cls.get(subject)
|
|
159
|
+
|
|
160
|
+
# If subject is an instance, get its class
|
|
161
|
+
cls = subject if isinstance(subject, type) else type(subject)
|
|
162
|
+
|
|
163
|
+
collected: list[S] = []
|
|
164
|
+
# Iterate MRO in reverse to apply base class decorators first
|
|
165
|
+
for base in reversed(cls.mro()):
|
|
166
|
+
collected.extend(decorator_cls.get(base))
|
|
167
|
+
|
|
168
|
+
return collected
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def resolve_bound_class_decorators(
|
|
172
|
+
subject: Any, decorator_cls: type[S], inherit: bool = True, last: bool = False
|
|
173
|
+
) -> S | None:
|
|
174
|
+
"""
|
|
175
|
+
Retrieve the first or last decorator of a given type from a class or instance,
|
|
176
|
+
optionally inheriting from base classes.
|
|
177
|
+
"""
|
|
178
|
+
decorators = resolve_class_decorators(subject, decorator_cls, inherit)
|
|
179
|
+
if not decorators:
|
|
180
|
+
return None
|
|
181
|
+
return decorators[-1] if last else decorators[0]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def resolve_method_decorators(
|
|
185
|
+
cls: type,
|
|
186
|
+
method_name: str,
|
|
187
|
+
decorator_cls: type[S],
|
|
188
|
+
inherit: bool = True,
|
|
189
|
+
) -> list[S]:
|
|
190
|
+
"""
|
|
191
|
+
Resolve decorators for a method, optionally inheriting from base classes.
|
|
192
|
+
"""
|
|
193
|
+
if not inherit:
|
|
194
|
+
method = getattr(cls, method_name, None)
|
|
195
|
+
if method:
|
|
196
|
+
return decorator_cls.get(method)
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
collected: list[S] = []
|
|
200
|
+
# Iterate MRO in reverse to apply base class decorators first
|
|
201
|
+
for base in reversed(cls.mro()):
|
|
202
|
+
if method_name in base.__dict__:
|
|
203
|
+
method = base.__dict__[method_name]
|
|
204
|
+
# Handle staticmethod/classmethod wrappers if necessary?
|
|
205
|
+
# Usually decorators are on the underlying function or the wrapper.
|
|
206
|
+
# getattr(cls, name) returns the bound/unbound method.
|
|
207
|
+
# base.__dict__[name] returns the raw object (function or descriptor).
|
|
208
|
+
|
|
209
|
+
# If it's a staticmethod object, it has no __dict__ usually, but we attach attributes to it?
|
|
210
|
+
# The decorator runs on the function BEFORE it becomes a staticmethod.
|
|
211
|
+
# So the attribute should be on the function object.
|
|
212
|
+
|
|
213
|
+
# However, when we access it via base.__dict__[method_name], we might get the staticmethod object.
|
|
214
|
+
# We need to unwrap it if possible.
|
|
215
|
+
|
|
216
|
+
if isinstance(method, (staticmethod, classmethod)):
|
|
217
|
+
method = method.__func__
|
|
218
|
+
|
|
219
|
+
collected.extend(decorator_cls.get(method))
|
|
220
|
+
|
|
221
|
+
return collected
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def resolve_bound_method_decorator(
|
|
225
|
+
cls: type,
|
|
226
|
+
method_name: str,
|
|
227
|
+
decorator_cls: type[S],
|
|
228
|
+
inherit: bool = True,
|
|
229
|
+
last: bool = False,
|
|
230
|
+
) -> S | None:
|
|
231
|
+
"""
|
|
232
|
+
Retrieve the first or last decorator of a given type from a method,
|
|
233
|
+
optionally inheriting from base classes.
|
|
234
|
+
"""
|
|
235
|
+
decorators = resolve_method_decorators(cls, method_name, decorator_cls, inherit)
|
|
236
|
+
if not decorators:
|
|
237
|
+
return None
|
|
238
|
+
return decorators[-1] if last else decorators[0]
|
jararaca/reflect/metadata.py
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
from contextlib import contextmanager, suppress
|
|
2
6
|
from contextvars import ContextVar
|
|
3
7
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Awaitable, Callable, Mapping, TypeVar, Union
|
|
8
|
+
from typing import Any, Awaitable, Callable, Generator, Mapping, TypeVar, Union
|
|
9
|
+
|
|
10
|
+
from jararaca.reflect.decorators import StackableDecorator
|
|
5
11
|
|
|
6
12
|
DECORATED = TypeVar("DECORATED", bound=Union[Callable[..., Awaitable[Any]], type])
|
|
7
13
|
|
|
8
14
|
|
|
9
|
-
@dataclass
|
|
10
|
-
class
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class TransactionMetadata:
|
|
11
17
|
value: Any
|
|
12
|
-
|
|
18
|
+
"""The value of the metadata."""
|
|
19
|
+
|
|
20
|
+
inherited_from_controller: bool
|
|
21
|
+
"""Whether the metadata was inherited from a parent class."""
|
|
13
22
|
|
|
14
23
|
|
|
15
|
-
metadata_context: ContextVar[Mapping[str,
|
|
24
|
+
metadata_context: ContextVar[Mapping[str, TransactionMetadata]] = ContextVar(
|
|
16
25
|
"metadata_context"
|
|
17
26
|
)
|
|
18
27
|
|
|
19
28
|
|
|
20
|
-
def get_metadata(key: str) ->
|
|
29
|
+
def get_metadata(key: str) -> TransactionMetadata | None:
|
|
21
30
|
return metadata_context.get({}).get(key)
|
|
22
31
|
|
|
23
32
|
|
|
@@ -28,12 +37,14 @@ def get_metadata_value(key: str, default: Any | None = None) -> Any:
|
|
|
28
37
|
return metadata.value
|
|
29
38
|
|
|
30
39
|
|
|
31
|
-
def get_all_metadata() -> Mapping[str,
|
|
40
|
+
def get_all_metadata() -> Mapping[str, TransactionMetadata]:
|
|
32
41
|
return metadata_context.get({})
|
|
33
42
|
|
|
34
43
|
|
|
35
44
|
@contextmanager
|
|
36
|
-
def
|
|
45
|
+
def start_transaction_metadata_context(
|
|
46
|
+
metadata: Mapping[str, TransactionMetadata],
|
|
47
|
+
) -> Generator[None, Any, None]:
|
|
37
48
|
|
|
38
49
|
current_metadata = metadata_context.get({})
|
|
39
50
|
|
|
@@ -45,23 +56,21 @@ def provide_metadata(metadata: Mapping[str, ControllerInstanceMetadata]) -> Any:
|
|
|
45
56
|
metadata_context.reset(token)
|
|
46
57
|
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
@contextmanager
|
|
60
|
+
def start_providing_metadata(
|
|
61
|
+
**metadata: Any,
|
|
62
|
+
) -> Generator[None, Any, None]:
|
|
63
|
+
|
|
64
|
+
with start_transaction_metadata_context(
|
|
65
|
+
{
|
|
66
|
+
key: TransactionMetadata(value=value, inherited_from_controller=False)
|
|
67
|
+
for key, value in metadata.items()
|
|
68
|
+
}
|
|
69
|
+
):
|
|
70
|
+
yield
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SetMetadata(StackableDecorator):
|
|
49
74
|
def __init__(self, key: str, value: Any) -> None:
|
|
50
75
|
self.key = key
|
|
51
76
|
self.value = value
|
|
52
|
-
|
|
53
|
-
METATADA_LIST = "__metadata_list__"
|
|
54
|
-
|
|
55
|
-
@staticmethod
|
|
56
|
-
def register_metadata(cls: DECORATED, value: "SetMetadata") -> None:
|
|
57
|
-
metadata_list = getattr(cls, SetMetadata.METATADA_LIST, [])
|
|
58
|
-
metadata_list.append(value)
|
|
59
|
-
setattr(cls, SetMetadata.METATADA_LIST, metadata_list)
|
|
60
|
-
|
|
61
|
-
@staticmethod
|
|
62
|
-
def get(cls: DECORATED) -> "list[SetMetadata]":
|
|
63
|
-
return cast(list[SetMetadata], getattr(cls, SetMetadata.METATADA_LIST, []))
|
|
64
|
-
|
|
65
|
-
def __call__(self, cls: DECORATED) -> DECORATED:
|
|
66
|
-
SetMetadata.register_metadata(cls, self)
|
|
67
|
-
return cls
|
jararaca/rpc/__init__.py
CHANGED
jararaca/rpc/http/__init__.py
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
# HTTP RPC Module - Complete REST Client Implementation
|
|
6
|
+
"""
|
|
7
|
+
This module provides a complete REST client implementation with support for:
|
|
8
|
+
- HTTP method decorators (@Get, @Post, @Put, @Patch, @Delete)
|
|
9
|
+
- Request parameter decorators (@Query, @Header, @PathParam, @Body, @FormData, @File)
|
|
10
|
+
- Configuration decorators (@Timeout, @Retry, @ContentType)
|
|
11
|
+
- Authentication middleware (BearerTokenAuth, BasicAuth, ApiKeyAuth)
|
|
12
|
+
- Caching and response middleware
|
|
13
|
+
- Request/response hooks for customization
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .backends.httpx import HTTPXHttpRPCAsyncBackend
|
|
17
|
+
from .decorators import ( # HTTP Method decorators; Request parameter decorators; Configuration decorators; Client builder and core classes; Authentication classes; Middleware and hooks; Configuration classes; Data structures; Error handlers; Exceptions
|
|
18
|
+
ApiKeyAuth,
|
|
19
|
+
AuthenticationMiddleware,
|
|
20
|
+
BasicAuth,
|
|
21
|
+
BearerTokenAuth,
|
|
22
|
+
Body,
|
|
23
|
+
CacheMiddleware,
|
|
24
|
+
ContentType,
|
|
25
|
+
Delete,
|
|
26
|
+
File,
|
|
27
|
+
FormData,
|
|
28
|
+
Get,
|
|
29
|
+
GlobalHttpErrorHandler,
|
|
30
|
+
Header,
|
|
31
|
+
HttpMapping,
|
|
32
|
+
HttpRpcClientBuilder,
|
|
33
|
+
HttpRPCRequest,
|
|
34
|
+
HttpRPCResponse,
|
|
35
|
+
Patch,
|
|
36
|
+
PathParam,
|
|
37
|
+
Post,
|
|
38
|
+
Put,
|
|
39
|
+
Query,
|
|
40
|
+
RequestAttribute,
|
|
41
|
+
RequestHook,
|
|
42
|
+
ResponseHook,
|
|
43
|
+
ResponseMiddleware,
|
|
44
|
+
RestClient,
|
|
45
|
+
Retry,
|
|
46
|
+
RetryConfig,
|
|
47
|
+
RouteHttpErrorHandler,
|
|
48
|
+
RPCRequestNetworkError,
|
|
49
|
+
RPCUnhandleError,
|
|
50
|
+
Timeout,
|
|
51
|
+
TimeoutException,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# HTTP Method decorators
|
|
56
|
+
"Get",
|
|
57
|
+
"Post",
|
|
58
|
+
"Put",
|
|
59
|
+
"Patch",
|
|
60
|
+
"Delete",
|
|
61
|
+
# Request parameter decorators
|
|
62
|
+
"Query",
|
|
63
|
+
"Header",
|
|
64
|
+
"PathParam",
|
|
65
|
+
"Body",
|
|
66
|
+
"FormData",
|
|
67
|
+
"File",
|
|
68
|
+
# Configuration decorators
|
|
69
|
+
"Timeout",
|
|
70
|
+
"Retry",
|
|
71
|
+
"ContentType",
|
|
72
|
+
# Client builder and core classes
|
|
73
|
+
"RestClient",
|
|
74
|
+
"HttpRpcClientBuilder",
|
|
75
|
+
"HttpMapping",
|
|
76
|
+
"RequestAttribute",
|
|
77
|
+
# Authentication classes
|
|
78
|
+
"BearerTokenAuth",
|
|
79
|
+
"BasicAuth",
|
|
80
|
+
"ApiKeyAuth",
|
|
81
|
+
"AuthenticationMiddleware",
|
|
82
|
+
# Middleware and hooks
|
|
83
|
+
"CacheMiddleware",
|
|
84
|
+
"ResponseMiddleware",
|
|
85
|
+
"RequestHook",
|
|
86
|
+
"ResponseHook",
|
|
87
|
+
# Configuration classes
|
|
88
|
+
"RetryConfig",
|
|
89
|
+
# Data structures
|
|
90
|
+
"HttpRPCRequest",
|
|
91
|
+
"HttpRPCResponse",
|
|
92
|
+
# Error handlers
|
|
93
|
+
"GlobalHttpErrorHandler",
|
|
94
|
+
"RouteHttpErrorHandler",
|
|
95
|
+
# Exceptions
|
|
96
|
+
"RPCRequestNetworkError",
|
|
97
|
+
"RPCUnhandleError",
|
|
98
|
+
"TimeoutException",
|
|
99
|
+
# Backend
|
|
100
|
+
"HTTPXHttpRPCAsyncBackend",
|
|
101
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
# HTTP RPC Backends
|
|
6
|
+
"""
|
|
7
|
+
Backend implementations for HTTP RPC clients.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .httpx import HTTPXHttpRPCAsyncBackend
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"HTTPXHttpRPCAsyncBackend",
|
|
14
|
+
]
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import time
|
|
1
6
|
from urllib.parse import urljoin
|
|
2
7
|
|
|
3
8
|
import httpx
|
|
@@ -7,34 +12,63 @@ from jararaca.rpc.http.decorators import (
|
|
|
7
12
|
HttpRPCRequest,
|
|
8
13
|
HttpRPCResponse,
|
|
9
14
|
RPCRequestNetworkError,
|
|
15
|
+
TimeoutException,
|
|
10
16
|
)
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class HTTPXHttpRPCAsyncBackend(HttpRPCAsyncBackend):
|
|
14
20
|
|
|
15
|
-
def __init__(self, prefix_url: str = ""):
|
|
21
|
+
def __init__(self, prefix_url: str = "", default_timeout: float = 30.0):
|
|
16
22
|
self.prefix_url = prefix_url
|
|
23
|
+
self.default_timeout = default_timeout
|
|
17
24
|
|
|
18
25
|
async def request(
|
|
19
26
|
self,
|
|
20
27
|
request: HttpRPCRequest,
|
|
21
28
|
) -> HttpRPCResponse:
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
start_time = time.time()
|
|
31
|
+
|
|
32
|
+
# Prepare timeout
|
|
33
|
+
timeout = (
|
|
34
|
+
request.timeout if request.timeout is not None else self.default_timeout
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Prepare request kwargs
|
|
38
|
+
request_kwargs = {
|
|
39
|
+
"method": request.method,
|
|
40
|
+
"url": urljoin(self.prefix_url, request.url),
|
|
41
|
+
"headers": request.headers,
|
|
42
|
+
"params": request.query_params,
|
|
43
|
+
"timeout": timeout,
|
|
44
|
+
}
|
|
24
45
|
|
|
46
|
+
# Handle different content types
|
|
47
|
+
if request.form_data and request.files:
|
|
48
|
+
# Multipart form data with files
|
|
49
|
+
request_kwargs["data"] = request.form_data
|
|
50
|
+
request_kwargs["files"] = request.files
|
|
51
|
+
elif request.form_data:
|
|
52
|
+
# Form data only
|
|
53
|
+
request_kwargs["data"] = request.form_data
|
|
54
|
+
elif request.body:
|
|
55
|
+
# Raw body content
|
|
56
|
+
request_kwargs["content"] = request.body
|
|
57
|
+
|
|
58
|
+
async with httpx.AsyncClient() as client:
|
|
25
59
|
try:
|
|
26
|
-
response = await client.request(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
headers=request.headers,
|
|
30
|
-
params=request.query_params,
|
|
31
|
-
content=request.body,
|
|
32
|
-
)
|
|
60
|
+
response = await client.request(**request_kwargs) # type: ignore[arg-type]
|
|
61
|
+
|
|
62
|
+
elapsed_time = time.time() - start_time
|
|
33
63
|
|
|
34
64
|
return HttpRPCResponse(
|
|
35
65
|
status_code=response.status_code,
|
|
36
66
|
data=response.content,
|
|
67
|
+
headers=dict(response.headers),
|
|
68
|
+
elapsed_time=elapsed_time,
|
|
37
69
|
)
|
|
70
|
+
except httpx.TimeoutException as err:
|
|
71
|
+
raise TimeoutException(f"Request timed out: {err}") from err
|
|
38
72
|
except httpx.NetworkError as err:
|
|
39
73
|
raise RPCRequestNetworkError(
|
|
40
74
|
request=request, backend_request=err.request
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
from opentelemetry import baggage, trace
|
|
2
6
|
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
3
7
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|