Skip to content

Commit

Permalink
Merge pull request #114 from dapper91/dev
Browse files Browse the repository at this point in the history
- method context passing customized.
  • Loading branch information
dapper91 authored Dec 19, 2024
2 parents 4bee6ba + 3f4f73b commit 2e1dad3
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 14 deletions.
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

0 comments on commit 2e1dad3

Please sign in to comment.