Skip to content

Commit 4bec2cb

Browse files
authored
Merge pull request #122 from dapper91/dev
- pydantic 2.11 compatibility. - request kwargs passed to a tracer. - pydantic validator model cached.
2 parents e2132c0 + e0147b7 commit 4bec2cb

File tree

10 files changed

+63
-22
lines changed

10 files changed

+63
-22
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
1.14.0 (2025-04-08)
5+
-------------------
6+
7+
- pydantic 2.11 compatibility.
8+
- request kwargs passed to a tracer.
9+
- pydantic validator model cached.
10+
11+
412
1.13.0 (2025-02-15)
513
------------------
614

docs/source/pjrpc/tracing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The following example illustrate opentracing integration. All you need is just i
2828
super().__init__()
2929
self._tracer = opentracing.global_tracer()
3030
31-
async def on_request_begin(self, trace_context, request):
31+
async def on_request_begin(self, trace_context, request, request_kwargs):
3232
span = self._tracer.start_active_span(f'jsonrpc.{request.method}').span
3333
span.set_tag(tags.COMPONENT, 'pjrpc.client')
3434
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)

examples/client_prometheus_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
class PrometheusTracer(tracer.Tracer):
14-
def on_request_begin(self, trace_context, request):
14+
def on_request_begin(self, trace_context, request, request_kwargs):
1515
trace_context.started_at = time.time()
1616
method_call_total.labels(request.method).inc()
1717

examples/client_tracing.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import opentracing
2-
from opentracing import tags
2+
from opentracing import propagation, tags
33

44
from pjrpc.client import tracer
55
from pjrpc.client.backend import requests as pjrpc_client
@@ -11,11 +11,18 @@ def __init__(self):
1111
super().__init__()
1212
self._tracer = opentracing.global_tracer()
1313

14-
def on_request_begin(self, trace_context, request):
14+
def on_request_begin(self, trace_context, request, request_kwargs):
1515
span = self._tracer.start_active_span(f'jsonrpc.{request.method}').span
1616
span.set_tag(tags.COMPONENT, 'pjrpc.client')
1717
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
1818

19+
http_headers = request_kwargs.setdefault('headers', {})
20+
self._tracer.inject(
21+
span_context=span,
22+
format=propagation.Format.HTTP_HEADERS,
23+
carrier=http_headers,
24+
)
25+
1926
def on_request_end(self, trace_context, request, response):
2027
span = self._tracer.active_span
2128
span.set_tag(tags.ERROR, response.is_error)

pjrpc/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
__description__ = 'Extensible JSON-RPC library'
33
__url__ = 'https://github.com/dapper91/pjrpc'
44

5-
__version__ = '1.13.0'
5+
__version__ = '1.14.0'
66

77
__author__ = 'Dmitry Pershin'
88
__email__ = '[email protected]'

pjrpc/client/client.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,8 @@ def traced(method: Callable[..., Any]) -> Callable[..., Any]:
486486
def wrapper(
487487
self: 'AbstractClient',
488488
request: AbstractRequest,
489+
response_class: Type[AbstractResponse],
490+
validator: Callable[..., None],
489491
_trace_ctx: Optional[SimpleNamespace] = None,
490492
**kwargs: Any,
491493
) -> Optional[AbstractResponse]:
@@ -496,10 +498,12 @@ def wrapper(
496498
trace_ctx = _trace_ctx or SimpleNamespace()
497499

498500
for tracer in self._tracers:
499-
tracer.on_request_begin(trace_ctx, request)
501+
tracer.on_request_begin(trace_ctx, request, kwargs)
500502

501503
try:
502-
response = method(self, request, _trace_ctx=trace_ctx, **kwargs)
504+
response = method(
505+
self, request, response_class=response_class, validator=validator, _trace_ctx=trace_ctx, **kwargs,
506+
)
503507
except BaseException as e:
504508
for tracer in self._tracers:
505509
tracer.on_error(trace_ctx, request, e)
@@ -620,6 +624,8 @@ def traced(method: Callable[..., Any]) -> Callable[..., Any]:
620624
async def wrapper(
621625
self: 'AbstractAsyncClient',
622626
request: Request,
627+
response_class: Type[AbstractResponse],
628+
validator: Callable[..., None],
623629
_trace_ctx: Optional[SimpleNamespace] = None,
624630
**kwargs: Any,
625631
) -> Response:
@@ -630,10 +636,12 @@ async def wrapper(
630636
trace_ctx = _trace_ctx or SimpleNamespace()
631637

632638
for tracer in self._tracers:
633-
tracer.on_request_begin(trace_ctx, request)
639+
tracer.on_request_begin(trace_ctx, request, kwargs)
634640

635641
try:
636-
response = await method(self, request, _trace_ctx=trace_ctx, **kwargs)
642+
response = await method(
643+
self, request, response_class=response_class, validator=validator, _trace_ctx=trace_ctx, **kwargs,
644+
)
637645
except BaseException as e:
638646
for tracer in self._tracers:
639647
tracer.on_error(trace_ctx, request, e)

pjrpc/client/tracer.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Generic, Optional, TypeVar
2+
from typing import Any, Generic, Optional, TypeVar
33

44
from pjrpc import AbstractRequest, AbstractResponse
55

@@ -13,12 +13,18 @@ class Tracer(Generic[ContextType]):
1313
JSON-RPC client tracer.
1414
"""
1515

16-
def on_request_begin(self, trace_context: ContextType, request: AbstractRequest) -> None:
16+
def on_request_begin(
17+
self,
18+
trace_context: ContextType,
19+
request: AbstractRequest,
20+
request_kwargs: dict[str, Any],
21+
) -> None:
1722
"""
1823
Handler called before JSON-RPC request begins.
1924
2025
:param trace_context: request trace context
2126
:param request: JSON-RPC request
27+
:param request_kwargs: additional request arguments
2228
"""
2329

2430
def on_request_end(
@@ -53,15 +59,26 @@ def __init__(self, logger: logging.Logger = client_logger, level: int = logging.
5359
self._logger = logger
5460
self._level = level
5561

56-
def on_request_begin(self, trace_context: ContextType, request: AbstractRequest) -> None:
62+
def on_request_begin(
63+
self,
64+
trace_context: ContextType,
65+
request: AbstractRequest,
66+
request_kwargs: dict[str, Any],
67+
) -> None:
5768
self._logger.log(self._level, "sending request: %r", request)
5869

5970
def on_request_end(
60-
self, trace_context: ContextType, request: AbstractRequest, response: Optional[AbstractResponse],
71+
self,
72+
trace_context: ContextType,
73+
request: AbstractRequest,
74+
response: Optional[AbstractResponse],
6175
) -> None:
6276
self._logger.log(self._level, "received response: %r", response)
6377

6478
def on_error(
65-
self, trace_context: ContextType, request: AbstractRequest, error: BaseException,
79+
self,
80+
trace_context: ContextType,
81+
request: AbstractRequest,
82+
error: BaseException,
6683
) -> None:
6784
self._logger.log(self._level, "request '%s' sending error: %r", request, error)

pjrpc/server/validators/pydantic.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools as ft
22
import inspect
3-
from typing import Any, Callable, Dict, Iterable, List, Optional
3+
from typing import Any, Callable, Dict, Iterable, List, Optional, Type
44

55
import pydantic
66

@@ -45,10 +45,7 @@ def validate_method(
4545
"""
4646

4747
signature = self.signature(method, tuple(exclude))
48-
schema = self.build_validation_schema(signature)
49-
50-
params_model = pydantic.create_model(method.__name__, **schema, model_config=self._model_config)
51-
48+
params_model = self.build_validation_model(method.__name__, signature)
5249
bound_params = self.bind(signature, params)
5350
try:
5451
obj = params_model(**bound_params.arguments)
@@ -58,6 +55,10 @@ def validate_method(
5855
return {attr: getattr(obj, attr) for attr in obj.model_fields} if self._coerce else bound_params.arguments
5956

6057
@ft.lru_cache(maxsize=None)
58+
def build_validation_model(self, method_name: str, signature: inspect.Signature) -> Type[pydantic.BaseModel]:
59+
schema = self.build_validation_schema(signature)
60+
return pydantic.create_model(method_name, **schema, __config__=self._model_config)
61+
6162
def build_validation_schema(self, signature: inspect.Signature) -> Dict[str, Any]:
6263
"""
6364
Builds pydantic model based validation schema from method signature.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pjrpc"
3-
version = "1.13.0"
3+
version = "1.14.0"
44
description = "Extensible JSON-RPC library"
55
authors = ["Dmitry Pershin <[email protected]>"]
66
license = "Unlicense"

tests/client/test_tracer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class Tracer(client.Tracer):
5454
else:
5555
cli.send(req, _trace_ctx=trace_ctx)
5656

57-
tracer.on_request_begin.assert_called_once_with(trace_ctx, req)
57+
tracer.on_request_begin.assert_called_once_with(trace_ctx, req, {})
5858
tracer.on_request_end.assert_called_once_with(trace_ctx, req, resp)
5959

6060

@@ -106,5 +106,5 @@ class Tracer(client.Tracer):
106106
else:
107107
await cli.send(req, _trace_ctx=trace_ctx)
108108

109-
tracer.on_request_begin.assert_called_once_with(trace_ctx, req)
109+
tracer.on_request_begin.assert_called_once_with(trace_ctx, req, {})
110110
tracer.on_request_end.assert_called_once_with(trace_ctx, req, resp)

0 commit comments

Comments
 (0)