Skip to content

Dev #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 19, 2024
Merged

Dev #114

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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

1.12.0 (2024-12-19)
------------------

- method context passing customized.


1.11.0 (2024-12-08)
------------------

Expand Down
86 changes: 86 additions & 0 deletions examples/aiohttp_dishka_di.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import uuid
from typing import Any, Optional, Type

import pydantic
from aiohttp import web
from dishka import FromDishka, Provider, Scope, make_async_container, provide
from dishka.integrations.aiohttp import inject, setup_dishka

import pjrpc.server
import pjrpc.server.specs.extractors.pydantic
from pjrpc.server.integration import aiohttp
from pjrpc.server.specs import extractors
from pjrpc.server.specs import openapi as specs
from pjrpc.server.validators import pydantic as validators


def is_di_injected(name: str, annotation: Type[Any], default: Any) -> bool:
return annotation is FromDishka


methods = pjrpc.server.MethodRegistry()
validator = validators.PydanticValidator(exclude_param=is_di_injected)


class UserService:
def __init__(self):
self._users = {}

def add_user(self, user: dict) -> str:
user_id = uuid.uuid4().hex
self._users[user_id] = user

return user_id

def get_user(self, user_id: uuid.UUID) -> Optional[dict]:
return self._users.get(user_id)


class User(pydantic.BaseModel):
name: str
surname: str
age: int


@specs.annotate(summary='Creates a user', tags=['users'])
@methods.add(context='request', positional=True)
@validator.validate
@inject
async def add_user(
request: web.Request,
user_service: FromDishka[UserService],
user: User,
) -> dict:
user_dict = user.model_dump()
user_id = user_service.add_user(user_dict)

return {'id': user_id, **user_dict}


jsonrpc_app = aiohttp.Application(
'/api/v1',
specs=[
specs.OpenAPI(
info=specs.Info(version="1.0.0", title="User storage"),
schema_extractor=extractors.pydantic.PydanticSchemaExtractor(exclude_param=is_di_injected),
ui=specs.SwaggerUI(),
),
],
)
jsonrpc_app.dispatcher.add_methods(methods)
jsonrpc_app.app['users'] = {}


class ServiceProvider(Provider):
user_service = provide(UserService, scope=Scope.APP)


setup_dishka(
app=jsonrpc_app.app,
container=make_async_container(
ServiceProvider(),
),
)

if __name__ == "__main__":
web.run_app(jsonrpc_app.app, host='localhost', port=8080)
2 changes: 1 addition & 1 deletion pjrpc/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__description__ = 'Extensible JSON-RPC library'
__url__ = 'https://github.com/dapper91/pjrpc'

__version__ = '1.11.0'
__version__ = '1.12.0'

__author__ = 'Dmitry Pershin'
__email__ = '[email protected]'
Expand Down
30 changes: 23 additions & 7 deletions pjrpc/server/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,41 @@ class Method:
:param context: context name
"""

def __init__(self, method: MethodType, name: Optional[str] = None, context: Optional[str] = None):
def __init__(
self,
method: MethodType,
name: Optional[str] = None,
context: Optional[str] = None,
positional: bool = False,
):
self.method = method
self.name = name or method.__name__
self.context = context
self.positional = positional

meta = utils.set_meta(method, method_name=self.name, context_name=context)

self.validator, self.validator_args = meta.get('validator', default_validator), meta.get('validator_args', {})

def bind(self, params: Optional['JsonRpcParams'], context: Optional[Any] = None) -> MethodType:
method_params = self.validator.validate_method(
method_args = []
method_kwargs = self.validator.validate_method(
self.method, params, exclude=(self.context,) if self.context else (), **self.validator_args,
)

if self.context is not None:
method_params[self.context] = context
if self.positional:
method_args.append(context)
else:
method_kwargs[self.context] = context

return ft.partial(self.method, **method_params)
return ft.partial(self.method, *method_args, **method_kwargs)

def copy(self, **kwargs: Any) -> 'Method':
cls_kwargs = dict(name=self.name, context=self.context)
cls_kwargs.update(kwargs)

return Method(method=self.method, **cls_kwargs)
return Method(method=self.method, **cls_kwargs) # type: ignore[arg-type]

def __eq__(self, other: Any) -> bool:
if not isinstance(other, Method):
Expand Down Expand Up @@ -169,20 +180,25 @@ def get(self, item: str) -> Optional[Method]:
return self._registry.get(item)

def add(
self, maybe_method: Optional[MethodType] = None, name: Optional[str] = None, context: Optional[Any] = None,
self,
maybe_method: Optional[MethodType] = None,
name: Optional[str] = None,
context: Optional[Any] = None,
positional: bool = False,
) -> MethodType:
"""
Decorator adding decorated method to the registry.

:param maybe_method: method or `None`
:param name: method name to be used instead of `__name__` attribute
:param context: parameter name to be used as an application context
:param positional: pass context as first positional argument
:returns: decorated method or decorator
"""

def decorator(method: MethodType) -> MethodType:
full_name = '.'.join(filter(None, (self._prefix, name or method.__name__)))
self.add_methods(Method(method, full_name, context))
self.add_methods(Method(method, full_name, context, positional))

return method

Expand Down
3 changes: 0 additions & 3 deletions pjrpc/server/specs/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ def build_request_schema(method_name: str, parameters_schema: Dict[str, Any]) ->
reqeust_schema = copy.deepcopy(REQUEST_SCHEMA)

reqeust_schema['properties']['method']['const'] = method_name
reqeust_schema['properties']['method']['enum'] = [method_name]
reqeust_schema['properties']['params'] = {
'title': 'Parameters',
'description': 'Reqeust parameters',
Expand All @@ -137,9 +136,7 @@ def build_response_schema(result_schema: Dict[str, Any], errors: Iterable[Type[J
error_schema = copy.deepcopy(ERROR_SCHEMA)
error_props = error_schema['properties']['error']['properties']
error_props['code']['const'] = error.code
error_props['code']['enum'] = [error.code]
error_props['message']['const'] = error.message
error_props['message']['enum'] = [error.message]
error_schemas.append(error_schema)

response_schema = {'oneOf': [response_schema] + error_schemas}
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 = "1.11.0"
version = "1.12.0"
description = "Extensible JSON-RPC library"
authors = ["Dmitry Pershin <[email protected]>"]
license = "Unlicense"
Expand Down
2 changes: 0 additions & 2 deletions tests/server/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def jsonrpc_request_schema(method_name: str, params_schema: Dict[str, Any]) -> D
'description': 'Method name',
'type': 'string',
'const': method_name,
'enum': [method_name],
},
'params': params_schema,
},
Expand Down Expand Up @@ -168,7 +167,6 @@ def error_component(code: int) -> Dict[str, Any]:
'description': 'Error code',
'type': 'integer',
'const': code,
'enum': [code],
},
'data': {
'title': 'Data',
Expand Down
Loading