Skip to content
Merged

Dev #129

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ Changelog
=========


2.0.1 (2025-10-28)
-------------------

- pydantic v1 validator/generator bugs fixed.
- aiohttp subapp specification wrong method path fixed.
- dispatcher middleware invocation order fixed.
- documentation fixed.


2.0.0 (2025-10-24)
-------------------

Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,9 @@ and Swagger UI web tool with basic auth:



Specification is available on http://localhost:8080/rpc/api/v1/openapi.json
Specification is available on http://localhost:8080/rpc/api/openapi.json

Web UI is running on http://localhost:8080/rpc/api/v1/swagger/ and http://localhost:8080/rpc/api/v1/redoc/
Web UI is running on http://localhost:8080/rpc/api/swagger/ and http://localhost:8080/rpc/api/redoc/

Swagger UI:
~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions docs/source/pjrpc/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,9 @@ and Swagger UI web tool with basic auth:
web.run_app(http_app, host='localhost', port=8080)


Specification is available on http://localhost:8080/rpc/api/v1/openapi.json
Specification is available on http://localhost:8080/rpc/api/openapi.json

Web UI is running on http://localhost:8080/rpc/api/v1/swagger/ and http://localhost:8080/rpc/api/v1/redoc/
Web UI is running on http://localhost:8080/rpc/api/swagger/ and http://localhost:8080/rpc/api/redoc/

Swagger UI:
~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions docs/source/pjrpc/webui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ using flask web framework:



Specification is available on http://localhost:8080/myapp/api/v1/openapi.json
Specification is available on http://localhost:8080/rpc/api/openapi.json

Web UI is running on http://localhost:8080/myapp/api/v1/ui/
Web UI is running on http://localhost:8080/rpc/api/v1/swagger/ and http://localhost:8080/rpc/api/v1/redoc/

Swagger UI
~~~~~~~~~~
Expand Down
15 changes: 8 additions & 7 deletions pjrpc/server/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ def __init__(
self._max_batch_size = max_batch_size
self._executor = executor or BasicAsyncExecutor()

self._request_handler = self._wrap_handle_request()
self._rpc_request_handler = self._wrap_handle_rpc_request()

async def dispatch(self, request_text: str, context: ContextType) -> Optional[tuple[str, tuple[int, ...]]]:
"""
Expand Down Expand Up @@ -596,12 +596,12 @@ async def dispatch(self, request_text: str, context: ContextType) -> Optional[tu
)
else:
responses = (
resp for resp in await self._executor.execute(self._request_handler, request, context)
resp for resp in await self._executor.execute(self._handle_request, request, context)
if not isinstance(resp, UnsetType)
)
response = self._batch_response(tuple(responses))
else:
response = await self._request_handler(request, context)
response = await self._handle_request(request, context)

if not isinstance(response, UnsetType):
response_text = self._json_dumper(response.to_json(), cls=self._json_encoder)
Expand All @@ -617,18 +617,19 @@ def add_middlewares(self, *middlewares: AsyncMiddlewareType[ContextType], before
else:
self._middlewares = self._middlewares + list(middlewares)

self._request_handler = self._wrap_handle_request()
self._rpc_request_handler = self._wrap_handle_rpc_request()

def _wrap_handle_rpc_request(self) -> Callable[[Request, ContextType], Awaitable[MaybeSet[Response]]]:
request_handler = self._handle_rpc_request

def _wrap_handle_request(self) -> Callable[[Request, ContextType], Awaitable[MaybeSet[Response]]]:
request_handler = self._handle_request
for middleware in reversed(self._middlewares):
request_handler = ft.partial(middleware, handler=request_handler)

return request_handler

async def _handle_request(self, request: Request, context: ContextType) -> MaybeSet[Response]:
try:
return await self._handle_rpc_request(request, context)
return await self._rpc_request_handler(request, context)
except pjrpc.exceptions.JsonRpcError as e:
logger.info("method execution error %s(%r): %r", request.method, request.params, e)
error = e
Expand Down
6 changes: 5 additions & 1 deletion pjrpc/server/integration/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def __init__(
self._endpoints: dict[str, AioHttpDispatcher] = {}
self._subapps: dict[str, Application] = {}

@property
def prefix(self) -> str:
return self._prefix

@property
def http_app(self) -> web.Application:
"""
Expand Down Expand Up @@ -149,7 +153,7 @@ def generate_spec(self, spec: specs.Specification, base_path: str = '', endpoint
app_endpoints = self._endpoints
for prefix, subapp in self._subapps.items():
for subprefix, dispatcher in subapp.endpoints.items():
app_endpoints[utils.join_path(prefix, subprefix)] = dispatcher
app_endpoints[utils.join_path(prefix, subapp._prefix, subprefix)] = dispatcher

methods = {
utils.remove_prefix(dispatcher_endpoint, endpoint): dispatcher.registry.values()
Expand Down
17 changes: 8 additions & 9 deletions pjrpc/server/specs/extractors/pydantic_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,19 @@ def extract_response_schema(
) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]:
return_model = self._build_result_model(method_name, method)

response_model: type[pd.BaseModel]
error_models = tuple(
pd.create_model(
error.__name__,
__base__=JsonRpcResponseError[JsonRpcError[Literal[error.CODE], Any]], # type: ignore[name-defined]
__config__=pydantic.config.get_config(
error_models: list[type[pd.BaseModel]] = []
for error in errors or []:
class ErrorModel(pydantic.BaseModel):
Config = pydantic.config.get_config(
dict(
self._config_args,
title=error.__name__,
json_schema_extra=dict(description=f'**{error.CODE}** {error.MESSAGE}'),
),
),
) for error in errors or []
)
)
__root__: JsonRpcResponseError[JsonRpcError[Literal[error.CODE], Any]] # type: ignore[name-defined]

error_models.append(pd.create_model(error.__name__, __base__=ErrorModel))

class ResponseModel(pydantic.BaseModel):
Config = pydantic.config.get_config(dict(title=f"{to_camel(method_name)}Response"))
Expand Down
2 changes: 1 addition & 1 deletion pjrpc/server/validators/pydantic_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def validate_params(self, params: Optional['JsonRpcParamsT']) -> dict[str, Any]:
except pydantic.ValidationError as e:
raise base.ValidationError(*e.errors()) from e

return {field_name: obj.__dict__[field_name] for field_name in fields}
return {field_name: obj.__dict__[field_name] for field_name in model_fields}

def _build_validation_model(self, method_name: str) -> type[pydantic.BaseModel]:
schema = self._build_validation_schema(self._signature)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pjrpc"
version = "2.0.0"
version = "2.0.1"
description = "Extensible JSON-RPC library"
authors = ["Dmitry Pershin <[email protected]>"]
license = "Unlicense"
Expand Down