diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a067c7..eb000a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f79ac0..02df39d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_stages: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: check-yaml - id: check-toml @@ -25,25 +25,25 @@ repos: args: - --fix=no - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.1 + rev: v3.0.0 hooks: - id: add-trailing-comma stages: - commit - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.6.0 + rev: v2.0.2 hooks: - id: autopep8 stages: - commit args: - --diff - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort name: fix import order @@ -63,7 +63,7 @@ repos: - --multi-line=9 - --project=pjrpc - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v1.4.1 hooks: - id: mypy stages: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f534918..fae5765 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -15,4 +15,4 @@ python: - method: pip path: . extra_requirements: - - docgen + - docs diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6046182..68c6f6b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +1.7.0 (2023-08-10) +------------------ + +- refactoring done +- dependencies updated +- python 3.11 support added + + 1.6.0 (2022-07-05) ------------------ diff --git a/README.rst b/README.rst index 2bf3ef8..60173d3 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ pjrpc .. image:: https://static.pepy.tech/personalized-badge/pjrpc?period=month&units=international_system&left_color=grey&right_color=orange&left_text=Downloads/month :target: https://pepy.tech/project/pjrpc :alt: Downloads/month -.. image:: https://travis-ci.org/dapper91/pjrpc.svg?branch=master - :target: https://travis-ci.org/dapper91/pjrpc +.. image:: https://github.com/dapper91/pjrpc/actions/workflows/test.yml/badge.svg?branch=master + :target: https://github.com/dapper91/pjrpc/actions/workflows/test.yml :alt: Build status .. image:: https://img.shields.io/pypi/l/pjrpc.svg :target: https://pypi.org/project/pjrpc @@ -67,7 +67,7 @@ Extra requirements Documentation ------------- -Documentation is available at `Read the Docs `_. +Documentation is available at `Read the Docs `_. Quickstart diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 611e1be..2daab60 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -1,7 +1,7 @@ -.py.class { - padding: 1.5em 0em 1.5em; +.content { + width: 55em; } -.py.data, .py.method, .py.property, .py.exception, .py.function { - padding: 0.5em 0em 0.5em; +.sidebar-drawer { + width: 15em; } diff --git a/docs/source/conf.py b/docs/source/conf.py index c764ea0..40f7d52 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,26 +10,22 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -import enum import sys from pathlib import Path -THIS_PATH = Path(__file__).parent -sys.path.insert(0, str(THIS_PATH.parent.parent)) - -import pjrpc.common.typedefs # noqa -import pjrpc.server.typedefs # noqa +import toml -# -- Project information ----------------------------------------------------- - -project = pjrpc.__title__ -author = pjrpc.__author__ -copyright = '2019, {}'.format(author) +THIS_PATH = Path(__file__).parent +ROOT_PATH = THIS_PATH.parent.parent +sys.path.insert(0, str(ROOT_PATH)) -# The full version, including alpha/beta/rc tags -release = pjrpc.__version__ -version = pjrpc.__version__ +PYPROJECT = toml.load(ROOT_PATH / 'pyproject.toml') +PROJECT_INFO = PYPROJECT['tool']['poetry'] +project = PROJECT_INFO['name'] +copyright = f"2023, {PROJECT_INFO['name']}" +author = PROJECT_INFO['authors'][0] +release = PROJECT_INFO['version'] # -- General configuration --------------------------------------------------- @@ -40,68 +36,37 @@ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.viewcode', + 'sphinx_copybutton', + 'sphinx_design', ] -html_theme_options = { - 'github_user': 'dapper91', - 'github_repo': 'pjrpc', - 'github_banner': True, -} - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -html_css_files = ['css/custom.css'] - -# The master toctree document. -master_doc = 'index' - intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'aiohttp': ('https://aiohttp.readthedocs.io/en/stable/', None), 'requests': ('https://requests.kennethreitz.org/en/master/', None), } -autodoc_mock_imports = ['attrs'] autodoc_typehints = 'description' autodoc_typehints_format = 'short' autodoc_member_order = 'bysource' autodoc_default_options = { 'show-inheritance': True, } -autodoc_type_aliases = { - type_name: f'{pjrpc.common.typedefs.__name__}.{type_name}' - for type_name in pjrpc.common.typedefs.__all__ -} | { - type_name: f'{pjrpc.server.typedefs.__name__}.{type_name}' - for type_name in pjrpc.server.typedefs.__all__ -} -def maybe_skip_member(app, what, name, obj, skip, options): - if isinstance(obj, enum.Enum): - return False +autosectionlabel_prefix_document = True - return None +html_theme_options = {} +html_title = PROJECT_INFO['name'] +templates_path = ['_templates'] +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -def setup(app): - app.connect('autodoc-skip-member', maybe_skip_member) +html_theme = 'furo' +html_static_path = ['_static'] +html_css_files = ['css/custom.css'] diff --git a/docs/source/index.rst b/docs/source/index.rst index 4e2f8db..79b2d4c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,15 +3,15 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to pjrpc's documentation! -================================= +Python JSON-RPC without boilerplate +=================================== .. image:: https://static.pepy.tech/personalized-badge/pjrpc?period=month&units=international_system&left_color=grey&right_color=orange&left_text=Downloads/month :target: https://pepy.tech/project/pjrpc :alt: Downloads/month -.. image:: https://travis-ci.org/dapper91/pjrpc.svg?branch=master - :target: https://travis-ci.org/dapper91/pjrpc +.. image:: https://github.com/dapper91/pjrpc/actions/workflows/test.yml/badge.svg?branch=master + :target: https://github.com/dapper91/pjrpc/actions/workflows/test.yml :alt: Build status .. image:: https://img.shields.io/pypi/l/pjrpc.svg :target: https://pypi.org/project/pjrpc @@ -99,6 +99,12 @@ Development pjrpc/development +Links +----- + +- `Source code `_ + + Indices and tables ================== diff --git a/docs/source/pjrpc/api/client.rst b/docs/source/pjrpc/api/client.rst index 2833db5..bfc6828 100644 --- a/docs/source/pjrpc/api/client.rst +++ b/docs/source/pjrpc/api/client.rst @@ -3,10 +3,13 @@ Client ------ +Misc +~~~~ .. automodule:: pjrpc.client :members: + Backends ~~~~~~~~ diff --git a/docs/source/pjrpc/api/common.rst b/docs/source/pjrpc/api/common.rst index f613cad..392a418 100644 --- a/docs/source/pjrpc/api/common.rst +++ b/docs/source/pjrpc/api/common.rst @@ -3,6 +3,8 @@ Common ------ +Misc +~~~~ .. automodule:: pjrpc.common :members: diff --git a/docs/source/pjrpc/api/server.rst b/docs/source/pjrpc/api/server.rst index 0942f93..bd1d80e 100644 --- a/docs/source/pjrpc/api/server.rst +++ b/docs/source/pjrpc/api/server.rst @@ -4,9 +4,13 @@ Server ------ +Misc +~~~~ + .. automodule:: pjrpc.server :members: + Types ~~~~~ diff --git a/docs/source/pjrpc/client.rst b/docs/source/pjrpc/client.rst index 4556be5..ea9eeb1 100644 --- a/docs/source/pjrpc/client.rst +++ b/docs/source/pjrpc/client.rst @@ -181,5 +181,5 @@ Id generators -------------- The library request id generator can also be customized. There are four generator types implemented in the library -see :py:mod:`pjrpc.common.generators`. You can implement your own one and pass it to a client by `id_gen` +see :py:mod:`pjrpc.common.generators`. You can implement your own one and pass it to a client by ``id_gen`` parameter. diff --git a/docs/source/pjrpc/extending.rst b/docs/source/pjrpc/extending.rst index a54c250..e6b7244 100644 --- a/docs/source/pjrpc/extending.rst +++ b/docs/source/pjrpc/extending.rst @@ -8,9 +8,9 @@ an JSON-RPC server implementation based on :py:mod:`http.server` standard python .. code-block:: python - import uuid import http.server import socketserver + import uuid import pjrpc import pjrpc.server @@ -19,20 +19,21 @@ an JSON-RPC server implementation based on :py:mod:`http.server` standard python class JsonRpcHandler(http.server.BaseHTTPRequestHandler): def do_POST(self): content_type = self.headers.get('Content-Type') - if content_type not in pjrpc.common.JSONRPC_REQUEST_CONTENT_TYPES: - self.send_response(http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE) + if content_type not in pjrpc.common.REQUEST_CONTENT_TYPES: + self.send_error(http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE) return try: content_length = int(self.headers.get('Content-Length', -1)) request_text = self.rfile.read(content_length).decode() except UnicodeDecodeError: - self.send_response(http.HTTPStatus.BAD_REQUEST) + self.send_error(http.HTTPStatus.BAD_REQUEST) return response_text = self.server.dispatcher.dispatch(request_text, context=self) if response_text is None: - self.send_response(http.HTTPStatus.OK) + self.send_response_only(http.HTTPStatus.OK) + self.end_headers() else: self.send_response(http.HTTPStatus.OK) self.send_header("Content-type", pjrpc.common.DEFAULT_CONTENT_TYPE) diff --git a/docs/source/pjrpc/specification.rst b/docs/source/pjrpc/specification.rst index 8bd230d..197d1fc 100644 --- a/docs/source/pjrpc/specification.rst +++ b/docs/source/pjrpc/specification.rst @@ -1,7 +1,7 @@ .. _specification: -Specification: -============== +Specification +============= ``pjrpc`` has built-in `OpenAPI `_ and `OpenRPC `_ diff --git a/docs/source/pjrpc/testing.rst b/docs/source/pjrpc/testing.rst index 5bf19ab..30c5156 100644 --- a/docs/source/pjrpc/testing.rst +++ b/docs/source/pjrpc/testing.rst @@ -8,6 +8,15 @@ pytest ------ ``pjrpc`` implements pytest plugin that simplifies JSON-RPC requests mocking. +To install the plugin add the following line to your pytest configuration: + +.. code-block:: python + + pytest_plugins = ("pjrpc.client.integrations.pytest ", ) + +or export the environment variable ``PYTEST_PLUGINS=pjrpc.client.integrations.pytest``. +For more information `see `_. + Look at the following test example: .. code-block:: python diff --git a/examples/aio_pika_server.py b/examples/aio_pika_server.py index 6c129f0..a2c6e88 100644 --- a/examples/aio_pika_server.py +++ b/examples/aio_pika_server.py @@ -1,40 +1,65 @@ import asyncio import logging import uuid +from dataclasses import dataclass import aio_pika +from yarl import URL import pjrpc from pjrpc.server.integration import aio_pika as integration + +@dataclass +class UserInfo: + """User information dataclass for the add_user example RPC call""" + + username: str + name: str + age: int + + +@dataclass +class AddedUser(UserInfo): + """User information dataclass (with uuid) for the add_user example RPC call""" + + uuid: uuid.UUID + + methods = pjrpc.server.MethodRegistry() @methods.add -def sum(a, b): +def sum(a: int, b: int) -> int: """RPC method implementing examples/aio_pika_client.py's calls to sum(1, 2) -> 3""" return a + b @methods.add -def tick(): +def tick() -> None: """RPC method implementing examples/aio_pika_client.py's notification 'tick'""" print("examples/aio_pika_server.py: received tick") @methods.add(context='message') -def add_user(message: aio_pika.IncomingMessage, user: dict): - user_id = uuid.uuid4().hex - - return {'id': user_id, **user} +def add_user(message: aio_pika.IncomingMessage, user_info: UserInfo) -> AddedUser: + """Simluate the creation of a user: Receive user info and return it with an uuid4. + :param UserInfo user_info: user data + :returns: user_info with a randomly generated uuid4 added + :rtype: AddedUser""" + return AddedUser(**user_info.__dict__, uuid=uuid.uuid4()) -executor = integration.Executor('amqp://guest:guest@localhost:5672/v1', queue_name='jsonrpc') +executor = integration.Executor( + broker_url=URL('amqp://guest:guest@localhost:5672/v1'), queue_name='jsonrpc' +) executor.dispatcher.add_methods(methods) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - loop = asyncio.get_event_loop() + logging.info("Example result from a local call to add_user():") + logging.info(add_user(None, UserInfo("username", "firstname lastname", 18))) + loop = asyncio.new_event_loop() loop.run_until_complete(executor.start()) try: diff --git a/examples/httpserver.py b/examples/httpserver.py index 98fdf1d..889e039 100644 --- a/examples/httpserver.py +++ b/examples/httpserver.py @@ -18,19 +18,20 @@ def do_POST(self): content_type = self.headers.get('Content-Type') if content_type not in pjrpc.common.REQUEST_CONTENT_TYPES: - self.send_response(http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE) + self.send_error(http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE) return try: content_length = int(self.headers.get('Content-Length', -1)) request_text = self.rfile.read(content_length).decode() except UnicodeDecodeError: - self.send_response(http.HTTPStatus.BAD_REQUEST) + self.send_error(http.HTTPStatus.BAD_REQUEST) return response_text = self.server.dispatcher.dispatch(request_text, context=self) if response_text is None: - self.send_response(http.HTTPStatus.OK) + self.send_response_only(http.HTTPStatus.OK) + self.end_headers() else: self.send_response(http.HTTPStatus.OK) self.send_header("Content-type", pjrpc.common.DEFAULT_CONTENT_TYPE) diff --git a/examples/openapi_aiohttp.py b/examples/openapi_aiohttp.py index 21e14d0..2538c0e 100644 --- a/examples/openapi_aiohttp.py +++ b/examples/openapi_aiohttp.py @@ -1,6 +1,7 @@ import uuid from typing import Any +import aiohttp_cors import pydantic from aiohttp import helpers, web @@ -220,5 +221,18 @@ def delete_user(request: web.Request, user_id: uuid.UUID) -> None: jsonrpc_app.dispatcher.add_methods(methods) app.add_subapp('/myapp', jsonrpc_app.app) +cors = aiohttp_cors.setup( + app, defaults={ + '*': aiohttp_cors.ResourceOptions( + allow_credentials=True, + expose_headers='*', + allow_headers='*', + ), + }, +) +for route in list(app.router.routes()): + cors.add(route) + + if __name__ == "__main__": web.run_app(app, host='localhost', port=8080) diff --git a/examples/openapi_aiohttp_subendpoints.py b/examples/openapi_aiohttp_subendpoints.py index 2c2bd71..a24ada3 100644 --- a/examples/openapi_aiohttp_subendpoints.py +++ b/examples/openapi_aiohttp_subendpoints.py @@ -1,6 +1,7 @@ import uuid from typing import Any +import aiohttp_cors import pydantic from aiohttp import web @@ -15,8 +16,6 @@ post_methods = pjrpc.server.MethodRegistry() validator = validators.PydanticValidator() -credentials = {"admin": "admin"} - class JSONEncoder(pjrpc.JSONEncoder): def default(self, o: Any) -> Any: @@ -185,5 +184,18 @@ def add_post(request: web.Request, post: PostIn) -> PostOut: jsonrpc_app.add_endpoint('/posts', json_encoder=JSONEncoder).add_methods(post_methods) +cors = aiohttp_cors.setup( + jsonrpc_app.app, defaults={ + '*': aiohttp_cors.ResourceOptions( + allow_credentials=True, + expose_headers='*', + allow_headers='*', + ), + }, +) +for route in list(jsonrpc_app.app.router.routes()): + cors.add(route) + + if __name__ == "__main__": web.run_app(jsonrpc_app.app, host='localhost', port=8080) diff --git a/pjrpc/__about__.py b/pjrpc/__about__.py index 2805763..3e20f1d 100644 --- a/pjrpc/__about__.py +++ b/pjrpc/__about__.py @@ -2,9 +2,9 @@ __description__ = 'Extensible JSON-RPC library' __url__ = 'https://github.com/dapper91/pjrpc' -__version__ = '1.6.0' +__version__ = '1.7.0' __author__ = 'Dmitry Pershin' -__email__ = 'dapper91@mail.ru' +__email__ = 'dapper1291@gmail.com' __license__ = 'Public Domain License' diff --git a/pjrpc/client/backend/aio_pika.py b/pjrpc/client/backend/aio_pika.py index cddc0c8..7a77875 100644 --- a/pjrpc/client/backend/aio_pika.py +++ b/pjrpc/client/backend/aio_pika.py @@ -72,7 +72,7 @@ async def connect(self) -> None: if self._result_queue_name: assert channel self._result_queue = await channel.declare_queue( - self._result_queue_name, **(self._result_queue_args or {}) + self._result_queue_name, **(self._result_queue_args or {}), ) self._consumer_tag = await self._result_queue.consume(self._on_result_message, no_ack=True) @@ -81,16 +81,16 @@ async def close(self) -> None: Closes current broker connection. """ - assert self._channel is not None, "client is not initialized" - assert self._connection is not None, "client is not initialized" - assert self._result_queue is not None, "client is not initialized" - - if self._consumer_tag: + if self._consumer_tag and self._result_queue: await self._result_queue.cancel(self._consumer_tag) self._consumer_tag = None - await self._channel.close() - await self._connection.close() + if self._channel: + await self._channel.close() + self._channel = None + if self._connection: + await self._connection.close() + self._connection = None for future in self._futures.values(): if future.done(): @@ -132,7 +132,7 @@ async def _request(self, request_text: str, is_notification: bool = False, **kwa async with self._connection.channel() as channel: if not self._result_queue: result_queue = await channel.declare_queue( - request_id, exclusive=True, **(self._result_queue_args or {}) + request_id, exclusive=True, **(self._result_queue_args or {}), ) await result_queue.consume(self._on_result_message, no_ack=True) else: diff --git a/pjrpc/client/backend/kombu.py b/pjrpc/client/backend/kombu.py index 1fc33ff..9125e72 100644 --- a/pjrpc/client/backend/kombu.py +++ b/pjrpc/client/backend/kombu.py @@ -5,7 +5,7 @@ import pjrpc from pjrpc.client import AbstractClient -from pjrpc.common import UNSET, UnsetType +from pjrpc.common import UNSET, MaybeSet, UnsetType logger = logging.getLogger(__package__) @@ -83,7 +83,7 @@ def _request(self, request_text: str, is_notification: bool = False, **kwargs: A request_id = kombu.uuid() result_queue = self._result_queue or kombu.Queue( - exclusive=True, name=request_id, **(self._result_queue_args or {}) + exclusive=True, name=request_id, **(self._result_queue_args or {}), ) with kombu.Producer(self._connection) as producer: @@ -98,7 +98,7 @@ def _request(self, request_text: str, is_notification: bool = False, **kwargs: A **kwargs, ) - response: Optional[Union[UnsetType, str, Exception]] = UNSET + response: MaybeSet[Union[None, str, Exception]] = UNSET def on_response(message: kombu.Message) -> None: nonlocal response diff --git a/pjrpc/client/client.py b/pjrpc/client/client.py index c768c40..00d70a6 100644 --- a/pjrpc/client/client.py +++ b/pjrpc/client/client.py @@ -7,7 +7,7 @@ from pjrpc import AbstractRequest, AbstractResponse, BatchRequest, BatchResponse, Request, Response, common from pjrpc.client import retry -from pjrpc.common import UNSET, UnsetType, exceptions, generators, v20 +from pjrpc.common import UNSET, MaybeSet, UnsetType, exceptions, generators, v20 from pjrpc.common.typedefs import JsonRpcRequestId, MethodType from .tracer import Tracer @@ -67,7 +67,7 @@ def __init__(self, client: 'BaseAbstractClient'): self._id_gen = client.id_gen_impl() self._requests = client.batch_request_class() - def __getitem__(self, requests: Iterable[Tuple]) -> Union[Awaitable[Any], Any]: + def __getitem__(self, requests: Iterable[Tuple[Any]]) -> Union[Awaitable[Any], Any]: """ Adds requests to the batch and makes a request. @@ -80,7 +80,7 @@ def __getitem__(self, requests: Iterable[Tuple]) -> Union[Awaitable[Any], Any]: method=method, params=params, id=next(self._id_gen), - ) for method, *params in requests + ) for method, *params in requests # type: ignore[var-annotated] ]) return self.call() @@ -287,7 +287,7 @@ class Proxy: def __init__(self, client: 'BaseAbstractClient'): self._client = client - def __getattr__(self, attr: str) -> Callable: + def __getattr__(self, attr: str) -> Callable[..., Any]: return ft.partial(self._client.call, attr) def __init__( @@ -298,8 +298,8 @@ def __init__( batch_response_class: Type[BatchResponse] = v20.BatchResponse, error_cls: Type[exceptions.JsonRpcError] = exceptions.JsonRpcError, id_gen_impl: Callable[..., Generator[JsonRpcRequestId, None, None]] = generators.sequential, - json_loader: Callable = json.loads, - json_dumper: Callable = json.dumps, + json_loader: Callable[..., Any] = json.loads, + json_dumper: Callable[..., str] = json.dumps, json_encoder: Type[common.JSONEncoder] = common.JSONEncoder, json_decoder: Optional[json.JSONDecoder] = None, strict: bool = True, @@ -457,7 +457,7 @@ def send( self, request: Request, _trace_ctx: SimpleNamespace = SimpleNamespace(), - _retry_strategy: Union[UnsetType, retry.RetryStrategy] = UNSET, + _retry_strategy: MaybeSet[retry.RetryStrategy] = UNSET, **kwargs: Any, ) -> Optional[Response]: """ @@ -515,7 +515,7 @@ def retried(method: Callable[..., Any]) -> Callable[..., Any]: def wrapper( self: 'AbstractClient', request: AbstractRequest, - _retry_strategy: Union[UnsetType, retry.RetryStrategy] = UNSET, + _retry_strategy: MaybeSet[retry.RetryStrategy] = UNSET, **kwargs: Any, ) -> Optional[AbstractResponse]: """ @@ -589,7 +589,7 @@ async def send( self, request: Request, _trace_ctx: SimpleNamespace = SimpleNamespace(), - _retry_strategy: Union[UnsetType, retry.RetryStrategy] = UNSET, + _retry_strategy: MaybeSet[retry.RetryStrategy] = UNSET, **kwargs: Any, ) -> Optional[Response]: """ @@ -647,7 +647,7 @@ def retried(method: Callable[..., Awaitable[Any]]) -> Callable[..., Any]: async def wrapper( self: 'AbstractClient', request: AbstractRequest, - _retry_strategy: Union[UnsetType, retry.RetryStrategy] = UNSET, + _retry_strategy: MaybeSet[retry.RetryStrategy] = UNSET, **kwargs: Any, ) -> Optional[AbstractResponse]: """ diff --git a/pjrpc/client/integrations/pytest.py b/pjrpc/client/integrations/pytest.py index b61ebc0..9640139 100644 --- a/pjrpc/client/integrations/pytest.py +++ b/pjrpc/client/integrations/pytest.py @@ -15,7 +15,7 @@ import pjrpc from pjrpc import Response -from pjrpc.common import UNSET, UnsetType +from pjrpc.common import UNSET, MaybeSet from pjrpc.common.typedefs import JsonRpcParams, JsonRpcRequestId CallType = Dict[Tuple[str, str], unittest.mock.Mock] @@ -33,7 +33,7 @@ def __init__( version: str, method_name: str, once: bool, - callback: Optional[Callable], + callback: Optional[Callable[..., Any]], **response_data: Any, ): self.endpoint = endpoint @@ -75,12 +75,12 @@ def add( self, endpoint: str, method_name: str, - result: UnsetType = UNSET, - error: UnsetType = UNSET, + result: MaybeSet[Any] = UNSET, + error: MaybeSet[Any] = UNSET, id: Optional[JsonRpcRequestId] = None, version: str = '2.0', once: bool = False, - callback: Optional[Callable] = None, + callback: Optional[Callable[..., Any]] = None, ) -> None: """ Appends response patch. If the same method patch already exists they will be used in a round-robin way. @@ -102,12 +102,12 @@ def replace( self, endpoint: str, method_name: str, - result: UnsetType = UNSET, - error: UnsetType = UNSET, + result: MaybeSet[Any] = UNSET, + error: MaybeSet[Any] = UNSET, id: Optional[JsonRpcRequestId] = None, version: str = '2.0', once: bool = False, - callback: Optional[Callable] = None, + callback: Optional[Callable[..., Any]] = None, idx: int = 0, ) -> None: """ @@ -175,7 +175,7 @@ def start(self) -> Any: if asyncio.iscoroutinefunction(patcher.temp_original): self._async_resp = True - side_effect: Callable + side_effect: Callable[..., Any] if self._async_resp: async def side_effect(*args: Any, **kwargs: Any) -> str: return await self._on_request(*args, **kwargs) diff --git a/pjrpc/common/__init__.py b/pjrpc/common/__init__.py index 8d2b05b..4c3e225 100644 --- a/pjrpc/common/__init__.py +++ b/pjrpc/common/__init__.py @@ -4,7 +4,7 @@ """ from . import generators, typedefs -from .common import UNSET, JSONEncoder, UnsetType +from .common import UNSET, JSONEncoder, MaybeSet, UnsetType from .v20 import AbstractRequest, AbstractResponse, BatchRequest, BatchResponse, Request, Response DEFAULT_CONTENT_TYPE = 'application/json' @@ -36,6 +36,7 @@ def set_default_content_type(content_type: str) -> None: 'BatchResponse', 'UNSET', 'UnsetType', + 'MaybeSet', 'JSONEncoder', 'DEFAULT_CONTENT_TYPE', 'REQUEST_CONTENT_TYPES', diff --git a/pjrpc/common/common.py b/pjrpc/common/common.py index 040a79d..21d533a 100644 --- a/pjrpc/common/common.py +++ b/pjrpc/common/common.py @@ -1,6 +1,6 @@ import json import sys -from typing import Any +from typing import Any, Dict, TypeVar, Union if sys.version_info >= (3, 8): from typing import Literal @@ -30,11 +30,14 @@ def __str__(self) -> str: def __copy__(self) -> 'UnsetType': return self - def __deepcopy__(self, memo: dict) -> 'UnsetType': + def __deepcopy__(self, memo: Dict[str, Any]) -> 'UnsetType': return self -UNSET = UnsetType() +UNSET: UnsetType = UnsetType() + +MaybeSetType = TypeVar('MaybeSetType') +MaybeSet = Union[UnsetType, MaybeSetType] class JSONEncoder(json.JSONEncoder): diff --git a/pjrpc/common/exceptions.py b/pjrpc/common/exceptions.py index 23701c7..1a6d016 100644 --- a/pjrpc/common/exceptions.py +++ b/pjrpc/common/exceptions.py @@ -3,11 +3,11 @@ """ import typing -from typing import Any, Dict, Optional, Type, Union +from typing import Any, Dict, Optional, Tuple, Type from pjrpc.common.typedefs import Json -from .common import UNSET, UnsetType +from .common import UNSET, MaybeSet class BaseError(Exception): @@ -38,7 +38,7 @@ class JsonRpcErrorMeta(type): __errors_mapping__: Dict[int, Type['JsonRpcError']] = {} - def __new__(mcs, name: str, bases: tuple, dct: dict) -> Type['JsonRpcError']: + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Type['JsonRpcError']: cls: Type['JsonRpcError'] = typing.cast(Type['JsonRpcError'], super().__new__(mcs, name, bases, dct)) if hasattr(cls, 'code') and cls.code is not None: mcs.__errors_mapping__[cls.code] = cls @@ -97,7 +97,7 @@ def from_json(cls, json_data: 'Json') -> 'JsonRpcError': def get_error_cls(cls, code: int, default: Type['JsonRpcError']) -> Type['JsonRpcError']: return type(cls).__errors_mapping__.get(code, default) - def __init__(self, code: Optional[int] = None, message: Optional[str] = None, data: Union[UnsetType, Any] = UNSET): + def __init__(self, code: Optional[int] = None, message: Optional[str] = None, data: MaybeSet[Any] = UNSET): assert code or self.code, "code is not provided" assert message or self.message, "message is not provided" diff --git a/pjrpc/common/generators.py b/pjrpc/common/generators.py index e98d3a7..95a17b1 100644 --- a/pjrpc/common/generators.py +++ b/pjrpc/common/generators.py @@ -12,6 +12,9 @@ def sequential(start: int = 1, step: int = 1) -> Generator[int, None, None]: """ Sequential id generator. Returns consecutive values starting from `start` with step `step`. + + :param start: starting number + :param step: step """ yield from it.count(start, step) @@ -20,6 +23,9 @@ def sequential(start: int = 1, step: int = 1) -> Generator[int, None, None]: def randint(a: int, b: int) -> Generator[int, None, None]: """ Random integer id generator. Returns random integers between `a` and `b`. + + :param a: from + :param b: to """ while True: @@ -29,6 +35,9 @@ def randint(a: int, b: int) -> Generator[int, None, None]: def random(length: int = 8, chars: str = string.digits + string.ascii_lowercase) -> Generator[str, None, None]: """ Random string id generator. Returns random strings of length `length` using alphabet `chars`. + + :param length: string length + :param chars: string characters """ while True: diff --git a/pjrpc/common/v20.py b/pjrpc/common/v20.py index 519a260..23ce3f4 100644 --- a/pjrpc/common/v20.py +++ b/pjrpc/common/v20.py @@ -6,11 +6,11 @@ import abc import itertools as it import operator as op -from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, Union +from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type from pjrpc.common.typedefs import Json, JsonRpcParams, JsonRpcRequestId -from .common import UNSET, UnsetType +from .common import UNSET, MaybeSet, UnsetType from .exceptions import DeserializationError, IdentityError, JsonRpcError @@ -75,7 +75,7 @@ def result(self) -> Any: @property @abc.abstractmethod - def error(self) -> Union[UnsetType, JsonRpcError]: + def error(self) -> MaybeSet[JsonRpcError]: pass @property @@ -157,8 +157,8 @@ def from_json(cls, json_data: Json, error_cls: Type[JsonRpcError] = JsonRpcError def __init__( self, id: Optional[JsonRpcRequestId], - result: Union[UnsetType, Any] = UNSET, - error: Union[UnsetType, JsonRpcError] = UNSET, + result: MaybeSet[Any] = UNSET, + error: MaybeSet[JsonRpcError] = UNSET, ): assert result is not UNSET or error is not UNSET, "result or error argument must be provided" assert result is UNSET or error is UNSET, "result and error arguments are mutually exclusive" @@ -205,7 +205,7 @@ def result(self) -> Any: return self._result @property - def error(self) -> Union[UnsetType, JsonRpcError]: + def error(self) -> MaybeSet[JsonRpcError]: """ Response error. If the response has succeeded returns :py:data:`pjrpc.common.common.UNSET`. """ @@ -228,7 +228,7 @@ def is_error(self) -> bool: return not self.is_success - @property + @property # type: ignore[override] def related(self) -> Optional['Request']: """ Returns the request related response object if the response has been @@ -437,7 +437,7 @@ def from_json(cls, json_data: Json, error_cls: Type[JsonRpcError] = JsonRpcError return cls(*(Response.from_json(item) for item in json_data)) - def __init__(self, *responses: Response, error: Union[UnsetType, JsonRpcError] = UNSET, strict: bool = True): + def __init__(self, *responses: Response, error: MaybeSet[JsonRpcError] = UNSET, strict: bool = True): self._responses: List[Response] = [] self._ids: Set[JsonRpcRequestId] = set() self._error = error @@ -479,7 +479,7 @@ def __eq__(self, other: Any) -> bool: ) @property - def error(self) -> Union[UnsetType, JsonRpcError]: + def error(self) -> MaybeSet[JsonRpcError]: """ Response error. If the response has succeeded returns :py:data:`pjrpc.common.common.UNSET`. """ @@ -531,7 +531,7 @@ def result(self) -> Tuple[Any, ...]: return tuple(result) - @property + @property # type: ignore[override] def related(self) -> Optional['BatchRequest']: """ Returns the request related response object if the response has been diff --git a/pjrpc/server/dispatcher.py b/pjrpc/server/dispatcher.py index 4aca3bd..c8b687c 100644 --- a/pjrpc/server/dispatcher.py +++ b/pjrpc/server/dispatcher.py @@ -7,7 +7,8 @@ from typing import Type, Union, ValuesView, cast import pjrpc -from pjrpc.common import UNSET, AbstractResponse, BatchRequest, BatchResponse, Request, Response, UnsetType, v20 +from pjrpc.common import UNSET, AbstractResponse, BatchRequest, BatchResponse, MaybeSet, Request, Response, UnsetType +from pjrpc.common import v20 from pjrpc.common.typedefs import JsonRpcParams, MethodType from pjrpc.server import utils from pjrpc.server.typedefs import AsyncErrorHandlerType, AsyncMiddlewareType, ErrorHandlerType, MiddlewareType @@ -39,7 +40,7 @@ def __init__(self, method: MethodType, name: Optional[str] = None, context: Opti def bind(self, params: Optional['JsonRpcParams'], context: Optional[Any] = None) -> MethodType: method_params = self.validator.validate_method( - self.method, params, exclude=(self.context,) if self.context else (), **self.validator_args + self.method, params, exclude=(self.context,) if self.context else (), **self.validator_args, ) if self.context is not None: @@ -105,7 +106,7 @@ def __init__(self, context: Optional[Any] = None): pass @classmethod - def __methods__(cls) -> Generator[Callable, None, None]: + def __methods__(cls) -> Generator[Callable[..., Any], None, None]: for attr_name in filter(lambda name: not name.startswith('_'), dir(cls)): attr = getattr(cls, attr_name) if callable(attr): @@ -183,7 +184,7 @@ def decorator(method: MethodType) -> MethodType: else: return decorator(maybe_method) - def add_methods(self, *methods: Union[Callable, Method]) -> None: + def add_methods(self, *methods: Union[Callable[..., Any], Method]) -> None: """ Adds methods to the registry. @@ -199,7 +200,7 @@ def add_methods(self, *methods: Union[Callable, Method]) -> None: def view( self, maybe_view: Optional[Type[ViewMixin]] = None, context: Optional[Any] = None, prefix: Optional[str] = None, - ) -> Union[ViewMixin, Callable]: + ) -> Union[ViewMixin, Callable[..., Any]]: """ Methods view decorator. @@ -278,12 +279,12 @@ def __init__( response_class: Type[Response] = v20.Response, batch_request: Type[BatchRequest] = v20.BatchRequest, batch_response: Type[BatchResponse] = v20.BatchResponse, - json_loader: Callable = json.loads, - json_dumper: Callable = json.dumps, + json_loader: Callable[..., Any] = json.loads, + json_dumper: Callable[..., str] = json.dumps, json_encoder: Type[JSONEncoder] = JSONEncoder, json_decoder: Optional[Type[json.JSONDecoder]] = None, - middlewares: Iterable[Callable] = (), - error_handlers: Dict[Union[None, int, Exception], List[Callable]] = {}, + middlewares: Iterable[Callable[..., Any]] = (), + error_handlers: Dict[Union[None, int, Exception], List[Callable[..., Any]]] = {}, ): self._json_loader = json_loader self._json_dumper = json_dumper @@ -302,7 +303,7 @@ def __init__( def registry(self) -> MethodRegistry: return self._registry - def add(self, method: Callable, name: Optional[str] = None, context: Optional[Any] = None) -> None: + def add(self, method: Callable[..., Any], name: Optional[str] = None, context: Optional[Any] = None) -> None: """ Adds method to the registry. @@ -313,7 +314,7 @@ def add(self, method: Callable, name: Optional[str] = None, context: Optional[An self._registry.add(method, name, context) - def add_methods(self, *methods: Union[MethodRegistry, Method, Callable]) -> None: + def add_methods(self, *methods: Union[MethodRegistry, Method, Callable[..., Any]]) -> None: """ Adds methods to the registry. @@ -351,8 +352,8 @@ def __init__( response_class: Type[Response] = v20.Response, batch_request: Type[BatchRequest] = v20.BatchRequest, batch_response: Type[BatchResponse] = v20.BatchResponse, - json_loader: Callable = json.loads, - json_dumper: Callable = json.dumps, + json_loader: Callable[..., Any] = json.loads, + json_dumper: Callable[..., str] = json.dumps, json_encoder: Type[JSONEncoder] = JSONEncoder, json_decoder: Optional[Type[json.JSONDecoder]] = None, middlewares: Iterable['MiddlewareType'] = (), @@ -382,7 +383,7 @@ def dispatch(self, request_text: str, context: Optional[Any] = None) -> Optional logger.getChild('request').debug("request received: %s", request_text) - response: Union[AbstractResponse, UnsetType] + response: MaybeSet[AbstractResponse] try: request_json = self._json_loader(request_text, cls=self._json_decoder) request: Union[Request, BatchRequest] @@ -403,7 +404,7 @@ def dispatch(self, request_text: str, context: Optional[Any] = None) -> Optional *( resp for resp in (self._handle_request(request, context) for request in request) if not isinstance(resp, UnsetType) - ) + ), ) else: response = self._handle_request(request, context) @@ -416,9 +417,9 @@ def dispatch(self, request_text: str, context: Optional[Any] = None) -> Optional return None - def _handle_request(self, request: Request, context: Optional[Any]) -> Union[UnsetType, Response]: + def _handle_request(self, request: Request, context: Optional[Any]) -> MaybeSet[Response]: try: - HandlerType = Callable[[Request, Optional[Any]], Union[UnsetType, Response]] + HandlerType = Callable[[Request, Optional[Any]], MaybeSet[Response]] handler: HandlerType = self._handle_rpc_request for middleware in reversed(self._middlewares): @@ -442,7 +443,7 @@ def _handle_request(self, request: Request, context: Optional[Any]) -> Union[Uns return self._response_class(id=request.id, error=error) - def _handle_rpc_request(self, request: Request, context: Optional[Any]) -> Union[UnsetType, Response]: + def _handle_rpc_request(self, request: Request, context: Optional[Any]) -> MaybeSet[Response]: result = self._handle_rpc_method(request.method, request.params, context) if request.id is None: return UNSET @@ -487,8 +488,8 @@ def __init__( response_class: Type[Response] = v20.Response, batch_request: Type[BatchRequest] = v20.BatchRequest, batch_response: Type[BatchResponse] = v20.BatchResponse, - json_loader: Callable = json.loads, - json_dumper: Callable = json.dumps, + json_loader: Callable[..., Any] = json.loads, + json_dumper: Callable[..., str] = json.dumps, json_encoder: Type[JSONEncoder] = JSONEncoder, json_decoder: Optional[Type[json.JSONDecoder]] = None, middlewares: Iterable['AsyncMiddlewareType'] = (), @@ -518,7 +519,7 @@ async def dispatch(self, request_text: str, context: Optional[Any] = None) -> Op logger.getChild('request').debug("request received: %s", request_text) - response: Union[AbstractResponse, UnsetType] + response: MaybeSet[AbstractResponse] try: request_json = self._json_loader(request_text, cls=self._json_decoder) request: Union[Request, BatchRequest] @@ -538,9 +539,9 @@ async def dispatch(self, request_text: str, context: Optional[Any] = None) -> Op response = self._batch_response( *filter( lambda resp: resp is not UNSET, await asyncio.gather( - *(self._handle_request(request, context) for request in request) + *(self._handle_request(request, context) for request in request), ), - ) + ), ) else: @@ -554,9 +555,9 @@ async def dispatch(self, request_text: str, context: Optional[Any] = None) -> Op return None - async def _handle_request(self, request: Request, context: Optional[Any]) -> Union[UnsetType, Response]: + async def _handle_request(self, request: Request, context: Optional[Any]) -> MaybeSet[Response]: try: - HandlerType = Callable[[Request, Optional[Any]], Awaitable[Union[UnsetType, Response]]] + HandlerType = Callable[[Request, Optional[Any]], Awaitable[MaybeSet[Response]]] handler: HandlerType = self._handle_rpc_request for middleware in reversed(self._middlewares): @@ -580,7 +581,7 @@ async def _handle_request(self, request: Request, context: Optional[Any]) -> Uni return self._response_class(id=request.id, error=error) - async def _handle_rpc_request(self, request: Request, context: Optional[Any]) -> Union[UnsetType, Response]: + async def _handle_rpc_request(self, request: Request, context: Optional[Any]) -> MaybeSet[Response]: result = await self._handle_rpc_method(request.method, request.params, context) if request.id is None: return UNSET diff --git a/pjrpc/server/integration/aiohttp.py b/pjrpc/server/integration/aiohttp.py index e328336..9d32f37 100644 --- a/pjrpc/server/integration/aiohttp.py +++ b/pjrpc/server/integration/aiohttp.py @@ -27,7 +27,7 @@ def __init__( path: str = '', spec: Optional[specs.Specification] = None, app: Optional[web.Application] = None, - **kwargs: Any + **kwargs: Any, ): self._path = path.rstrip('/') self._spec = spec diff --git a/pjrpc/server/integration/kombu.py b/pjrpc/server/integration/kombu.py index 1241600..dbacfed 100644 --- a/pjrpc/server/integration/kombu.py +++ b/pjrpc/server/integration/kombu.py @@ -34,7 +34,7 @@ def __init__( queue_args: Optional[Dict[str, Any]] = None, publish_args: Optional[Dict[str, Any]] = None, prefetch_count: int = 0, - **kwargs: Any + **kwargs: Any, ): self.connection = kombu.Connection(broker_url, **(conn_args or {})) @@ -84,7 +84,7 @@ def _rpc_handle(self, message: kombu.Message) -> None: correlation_id=message.properties.get('correlation_id'), content_type=pjrpc.common.DEFAULT_CONTENT_TYPE, content_encoding='utf8', - **(self._publish_args or {}) + **(self._publish_args or {}), ) message.ack() diff --git a/pjrpc/server/integration/starlette.py b/pjrpc/server/integration/starlette.py index 82645da..1edad88 100644 --- a/pjrpc/server/integration/starlette.py +++ b/pjrpc/server/integration/starlette.py @@ -39,7 +39,7 @@ def __init__( path: str = '', spec: Optional[specs.Specification] = None, app: Optional[Starlette] = None, - **kwargs: Any + **kwargs: Any, ): self._path = path.rstrip('/') self._spec = spec diff --git a/pjrpc/server/integration/werkzeug.py b/pjrpc/server/integration/werkzeug.py index ea53909..6c548ee 100644 --- a/pjrpc/server/integration/werkzeug.py +++ b/pjrpc/server/integration/werkzeug.py @@ -18,10 +18,10 @@ def __init__(self, path: str = '', **kwargs: Any): self._path = path self._dispatcher = pjrpc.server.Dispatcher(**kwargs) - def __call__(self, environ: Dict[str, Any], start_response: Callable) -> Iterable[bytes]: + def __call__(self, environ: Dict[str, Any], start_response: Callable[..., Any]) -> Iterable[bytes]: return self.wsgi_app(environ, start_response) - def wsgi_app(self, environ: Dict[str, Any], start_response: Callable) -> Iterable[bytes]: + def wsgi_app(self, environ: Dict[str, Any], start_response: Callable[..., Any]) -> Iterable[bytes]: environ['app'] = self request = werkzeug.Request(environ) response = self._rpc_handle(request) diff --git a/pjrpc/server/specs/__init__.py b/pjrpc/server/specs/__init__.py index 78c958d..df5626e 100644 --- a/pjrpc/server/specs/__init__.py +++ b/pjrpc/server/specs/__init__.py @@ -1,7 +1,7 @@ import abc import enum import json -from typing import Any, Iterable, Mapping, Optional +from typing import Any, Dict, Iterable, Mapping, Optional from pjrpc.server import Method @@ -18,16 +18,18 @@ def default(self, o: Any) -> Any: return super().default(o) -class BaseUI: +class BaseUI(abc.ABC): """ Base UI. """ + @abc.abstractmethod def get_static_folder(self) -> str: """ Returns ui statics folder. """ + @abc.abstractmethod def get_index_page(self, spec_url: str) -> str: """ Returns ui index webpage. @@ -80,7 +82,7 @@ def schema( path: str, methods: Iterable[Method] = (), methods_map: Mapping[str, Iterable[Method]] = {}, - ) -> dict: + ) -> Dict[str, Any]: """ Returns specification schema. diff --git a/pjrpc/server/specs/extractors/__init__.py b/pjrpc/server/specs/extractors/__init__.py index bdebe04..d8dc695 100644 --- a/pjrpc/server/specs/extractors/__init__.py +++ b/pjrpc/server/specs/extractors/__init__.py @@ -1,9 +1,9 @@ import dataclasses as dc import inspect import itertools as it -from typing import Any, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Dict, Iterable, List, Optional, Type -from pjrpc.common import UNSET, UnsetType +from pjrpc.common import UNSET, MaybeSet, UnsetType from pjrpc.common.exceptions import JsonRpcError from pjrpc.common.typedefs import MethodType @@ -16,10 +16,10 @@ class Schema: schema: Dict[str, Any] required: bool = True - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET - definitions: Union[Dict[str, Any], UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + deprecated: MaybeSet[bool] = UNSET + definitions: MaybeSet[Dict[str, Any]] = UNSET @dc.dataclass(frozen=True) @@ -31,8 +31,8 @@ class Example: params: Dict[str, Any] result: Any version: str = '2.0' - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -43,9 +43,9 @@ class ErrorExample: code: int message: str - data: Union[Optional[Any], UnsetType] = UNSET - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + data: MaybeSet[Optional[Any]] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -55,8 +55,8 @@ class Tag: """ name: str - description: Union[str, UnsetType] = UNSET - externalDocs: Union[str, UnsetType] = UNSET + description: MaybeSet[str] = UNSET + externalDocs: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -67,12 +67,12 @@ class Error: code: int message: str - data: Union[Dict[str, Any], UnsetType] = UNSET - data_required: Union[bool, UnsetType] = UNSET - title: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET - definitions: Union[Dict[str, Any], UnsetType] = UNSET + data: MaybeSet[Dict[str, Any]] = UNSET + data_required: MaybeSet[bool] = UNSET + title: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + deprecated: MaybeSet[bool] = UNSET + definitions: MaybeSet[Dict[str, Any]] = UNSET class BaseSchemaExtractor: @@ -94,12 +94,12 @@ def extract_result_schema(self, method: MethodType) -> Schema: return Schema(schema={}) - def extract_description(self, method: MethodType) -> Union[UnsetType, str]: + def extract_description(self, method: MethodType) -> MaybeSet[str]: """ Extracts method description. """ - description: Union[UnsetType, str] + description: MaybeSet[str] if method.__doc__: doc = inspect.cleandoc(method.__doc__) description = '\n'.join(it.takewhile(lambda line: line, doc.split('\n'))) @@ -108,14 +108,14 @@ def extract_description(self, method: MethodType) -> Union[UnsetType, str]: return description - def extract_summary(self, method: MethodType) -> Union[UnsetType, str]: + def extract_summary(self, method: MethodType) -> MaybeSet[str]: """ Extracts method summary. """ description = self.extract_description(method) - summary: Union[UnsetType, str] + summary: MaybeSet[str] if not isinstance(description, UnsetType): summary = description.split('.')[0] else: @@ -127,21 +127,21 @@ def extract_errors_schema( self, method: MethodType, errors: Optional[Iterable[Type[JsonRpcError]]] = None, - ) -> Union[UnsetType, List[Error]]: + ) -> MaybeSet[List[Error]]: """ Extracts method errors schema. """ return UNSET - def extract_tags(self, method: MethodType) -> Union[UnsetType, List[Tag]]: + def extract_tags(self, method: MethodType) -> MaybeSet[List[Tag]]: """ Extracts method tags. """ return UNSET - def extract_examples(self, method: MethodType) -> Union[UnsetType, List[Example]]: + def extract_examples(self, method: MethodType) -> MaybeSet[List[Example]]: """ Extracts method usage examples. """ @@ -152,7 +152,7 @@ def extract_error_examples( self, method: MethodType, errors: Optional[Iterable[Type[JsonRpcError]]] = None, - ) -> Union[UnsetType, List[ErrorExample]]: + ) -> MaybeSet[List[ErrorExample]]: """ Extracts method error examples. """ @@ -162,7 +162,7 @@ def extract_error_examples( for error in errors ] if errors else UNSET - def extract_deprecation_status(self, method: MethodType) -> Union[UnsetType, bool]: + def extract_deprecation_status(self, method: MethodType) -> MaybeSet[bool]: """ Extracts method deprecation status. """ diff --git a/pjrpc/server/specs/extractors/docstring.py b/pjrpc/server/specs/extractors/docstring.py index e2307cd..f0162e6 100644 --- a/pjrpc/server/specs/extractors/docstring.py +++ b/pjrpc/server/specs/extractors/docstring.py @@ -1,8 +1,8 @@ -from typing import Dict, Iterable, List, Optional, Type, Union +from typing import Dict, Iterable, List, Optional, Type import docstring_parser -from pjrpc.common import UNSET, UnsetType, exceptions +from pjrpc.common import UNSET, MaybeSet, exceptions from pjrpc.common.typedefs import MethodType from pjrpc.server.specs.extractors import BaseSchemaExtractor, Error, Example, JsonRpcError, Schema, Tag @@ -25,8 +25,8 @@ def extract_params_schema(self, method: MethodType, exclude: Iterable[str] = ()) parameters_schema[param.arg_name] = Schema( schema={'type': param.type_name}, required=not param.is_optional, - summary=param.description.split('.')[0], - description=param.description, + summary=param.description.split('.')[0] if param.description is not None else UNSET, + description=param.description if param.description is not None else UNSET, ) return parameters_schema @@ -40,8 +40,8 @@ def extract_result_schema(self, method: MethodType) -> Schema: result_schema = Schema( schema={'type': doc.returns.type_name}, required=True, - summary=doc.returns.description.split('.')[0], - description=doc.returns.description, + summary=doc.returns.description.split('.')[0] if doc.returns.description is not None else UNSET, + description=doc.returns.description if doc.returns.description is not None else UNSET, ) return result_schema @@ -50,7 +50,7 @@ def extract_errors_schema( self, method: MethodType, errors: Optional[Iterable[Type[JsonRpcError]]] = None, - ) -> Union[UnsetType, List[Error]]: + ) -> MaybeSet[List[Error]]: errors_schema = [] if method.__doc__: @@ -72,7 +72,7 @@ def extract_errors_schema( return errors_schema or UNSET - def extract_description(self, method: MethodType) -> Union[UnsetType, str]: + def extract_description(self, method: MethodType) -> MaybeSet[str]: if method.__doc__: doc = docstring_parser.parse(method.__doc__) description = doc.long_description or UNSET @@ -81,7 +81,7 @@ def extract_description(self, method: MethodType) -> Union[UnsetType, str]: return description - def extract_summary(self, method: MethodType) -> Union[UnsetType, str]: + def extract_summary(self, method: MethodType) -> MaybeSet[str]: if method.__doc__: doc = docstring_parser.parse(method.__doc__) description = doc.short_description or UNSET @@ -90,14 +90,14 @@ def extract_summary(self, method: MethodType) -> Union[UnsetType, str]: return description - def extract_tags(self, method: MethodType) -> Union[UnsetType, List[Tag]]: + def extract_tags(self, method: MethodType) -> MaybeSet[List[Tag]]: return UNSET - def extract_examples(self, method: MethodType) -> Union[UnsetType, List[Example]]: + def extract_examples(self, method: MethodType) -> MaybeSet[List[Example]]: return UNSET - def extract_deprecation_status(self, method: MethodType) -> Union[UnsetType, bool]: - is_deprecated: Union[UnsetType, bool] + def extract_deprecation_status(self, method: MethodType) -> MaybeSet[bool]: + is_deprecated: MaybeSet[bool] if method.__doc__: doc = docstring_parser.parse(method.__doc__) is_deprecated = bool(doc.deprecation) diff --git a/pjrpc/server/specs/extractors/pydantic.py b/pjrpc/server/specs/extractors/pydantic.py index 02ecbc2..aa300f9 100644 --- a/pjrpc/server/specs/extractors/pydantic.py +++ b/pjrpc/server/specs/extractors/pydantic.py @@ -1,9 +1,9 @@ import inspect -from typing import Any, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Dict, Iterable, List, Optional, Type import pydantic as pd -from pjrpc.common import UNSET, UnsetType +from pjrpc.common import UNSET, MaybeSet from pjrpc.common.exceptions import JsonRpcError from pjrpc.common.typedefs import MethodType from pjrpc.server.specs.extractors import BaseSchemaExtractor, Error, Schema @@ -83,7 +83,7 @@ def extract_errors_schema( self, method: MethodType, errors: Optional[Iterable[Type[JsonRpcError]]] = None, - ) -> Union[UnsetType, List[Error]]: + ) -> MaybeSet[List[Error]]: if errors: errors_schema = [] for error in errors: @@ -126,7 +126,7 @@ def _extract_field_schema(model_schema: Dict[str, Any], field_name: str) -> Dict return field_schema @staticmethod - def _get_annotations(cls: Type) -> Dict[str, Any]: + def _get_annotations(cls: Type[Any]) -> Dict[str, Any]: annotations: Dict[str, Any] = {} for patent in cls.mro(): annotations.update(**getattr(patent, '__annotations__', {})) diff --git a/pjrpc/server/specs/openapi.py b/pjrpc/server/specs/openapi.py index da64162..3d8972f 100644 --- a/pjrpc/server/specs/openapi.py +++ b/pjrpc/server/specs/openapi.py @@ -12,9 +12,9 @@ import functools as ft import pathlib import re -from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Type -from pjrpc.common import UNSET, UnsetType, exceptions +from pjrpc.common import UNSET, MaybeSet, UnsetType, exceptions from pjrpc.common.typedefs import Func from pjrpc.server import Method, utils @@ -123,9 +123,9 @@ class Contact: :param email: the email address of the contact person/organization """ - name: Union[str, UnsetType] = UNSET - url: Union[str, UnsetType] = UNSET - email: Union[str, UnsetType] = UNSET + name: MaybeSet[str] = UNSET + url: MaybeSet[str] = UNSET + email: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -138,7 +138,7 @@ class License: """ name: str - url: Union[str, UnsetType] = UNSET + url: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -156,10 +156,10 @@ class Info: title: str version: str - description: Union[str, UnsetType] = UNSET - contact: Union[Contact, UnsetType] = UNSET - license: Union[License, UnsetType] = UNSET - termsOfService: Union[str, UnsetType] = UNSET + description: MaybeSet[str] = UNSET + contact: MaybeSet[Contact] = UNSET + license: MaybeSet[License] = UNSET + termsOfService: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -173,8 +173,8 @@ class ServerVariable: """ default: str - enum: Union[List[str], UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + enum: MaybeSet[List[str]] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -187,8 +187,8 @@ class Server: """ url: str - description: Union[str, UnsetType] = UNSET - variables: Union[Dict[str, ServerVariable], UnsetType] = UNSET + description: MaybeSet[str] = UNSET + variables: MaybeSet[Dict[str, ServerVariable]] = UNSET @dc.dataclass(frozen=True) @@ -201,7 +201,7 @@ class ExternalDocumentation: """ url: str - description: Union[str, UnsetType] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -216,8 +216,8 @@ class Tag: """ name: str - description: Union[str, UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET + description: MaybeSet[str] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET class SecuritySchemeType(str, enum.Enum): @@ -255,7 +255,7 @@ class OAuthFlow: authorizationUrl: str tokenUrl: str scopes: Dict[str, str] - refreshUrl: Union[str, UnsetType] = UNSET + refreshUrl: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -269,10 +269,10 @@ class OAuthFlows: :param authorizationCode: configuration for the OAuth Authorization Code flow """ - implicit: Union[OAuthFlow, UnsetType] = UNSET - password: Union[OAuthFlow, UnsetType] = UNSET - clientCredentials: Union[OAuthFlow, UnsetType] = UNSET - authorizationCode: Union[OAuthFlow, UnsetType] = UNSET + implicit: MaybeSet[OAuthFlow] = UNSET + password: MaybeSet[OAuthFlow] = UNSET + clientCredentials: MaybeSet[OAuthFlow] = UNSET + authorizationCode: MaybeSet[OAuthFlow] = UNSET @dc.dataclass(frozen=True) @@ -292,12 +292,12 @@ class SecurityScheme: type: SecuritySchemeType scheme: str - name: Union[str, UnsetType] = UNSET - location: Union[ApiKeyLocation, UnsetType] = UNSET # `in` field - bearerFormat: Union[str, UnsetType] = UNSET - flows: Union[OAuthFlows, UnsetType] = UNSET - openIdConnectUrl: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + name: MaybeSet[str] = UNSET + location: MaybeSet[ApiKeyLocation] = UNSET # `in` field + bearerFormat: MaybeSet[str] = UNSET + flows: MaybeSet[OAuthFlows] = UNSET + openIdConnectUrl: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET def __post_init__(self) -> None: # `in` field name is not allowed in python @@ -314,7 +314,7 @@ class Components: :param schemas: the definition of input and output data types """ - securitySchemes: Union[Dict[str, SecurityScheme], UnsetType] = UNSET + securitySchemes: MaybeSet[Dict[str, SecurityScheme]] = UNSET schemas: Dict[str, Dict[str, Any]] = dc.field(default_factory=dict) @@ -333,8 +333,8 @@ class MethodExample: params: Dict[str, Any] result: Any version: str = '2.0' - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -349,9 +349,9 @@ class ExampleObject: """ value: Any - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - externalValue: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + externalValue: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -364,7 +364,7 @@ class MediaType: """ schema: Dict[str, Any] - examples: Union[Dict[str, ExampleObject], UnsetType] = UNSET + examples: MaybeSet[Dict[str, ExampleObject]] = UNSET @dc.dataclass(frozen=True) @@ -377,7 +377,7 @@ class Response: """ description: str - content: Union[Dict[str, MediaType], UnsetType] = UNSET + content: MaybeSet[Dict[str, MediaType]] = UNSET @dc.dataclass(frozen=True) @@ -391,8 +391,8 @@ class RequestBody: """ content: Dict[str, MediaType] - required: Union[bool, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + required: MaybeSet[bool] = UNSET + description: MaybeSet[str] = UNSET class ParameterLocation(str, enum.Enum): @@ -443,16 +443,16 @@ class Parameter: name: str location: ParameterLocation # `in` field - description: Union[str, UnsetType] = UNSET - required: Union[bool, UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET - allowEmptyValue: Union[bool, UnsetType] = UNSET - style: Union[StyleType, UnsetType] = UNSET - explode: Union[bool, UnsetType] = UNSET - allowReserved: Union[bool, UnsetType] = UNSET - schema: Union[Dict[str, Any], UnsetType] = UNSET - examples: Union[Dict[str, ExampleObject], UnsetType] = UNSET - content: Union[Dict[str, MediaType], UnsetType] = UNSET + description: MaybeSet[str] = UNSET + required: MaybeSet[bool] = UNSET + deprecated: MaybeSet[bool] = UNSET + allowEmptyValue: MaybeSet[bool] = UNSET + style: MaybeSet[StyleType] = UNSET + explode: MaybeSet[bool] = UNSET + allowReserved: MaybeSet[bool] = UNSET + schema: MaybeSet[Dict[str, Any]] = UNSET + examples: MaybeSet[Dict[str, ExampleObject]] = UNSET + content: MaybeSet[Dict[str, MediaType]] = UNSET def __post_init__(self) -> None: # `in` field name is not allowed in python @@ -477,15 +477,15 @@ class Operation: """ responses: Dict[str, Response] - requestBody: Union[RequestBody, UnsetType] = UNSET - tags: Union[List[str], UnsetType] = UNSET - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET - servers: Union[List[Server], UnsetType] = UNSET - security: Union[List[Dict[str, List[str]]], UnsetType] = UNSET - parameters: Union[List[Parameter], UnsetType] = UNSET + requestBody: MaybeSet[RequestBody] = UNSET + tags: MaybeSet[List[str]] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET + deprecated: MaybeSet[bool] = UNSET + servers: MaybeSet[List[Server]] = UNSET + security: MaybeSet[List[Dict[str, List[str]]]] = UNSET + parameters: MaybeSet[List[Parameter]] = UNSET @dc.dataclass(frozen=True) @@ -498,33 +498,33 @@ class Path: :param servers: an alternative server array to service all operations in this path """ - get: Union[Operation, UnsetType] = UNSET - put: Union[Operation, UnsetType] = UNSET - post: Union[Operation, UnsetType] = UNSET - delete: Union[Operation, UnsetType] = UNSET - options: Union[Operation, UnsetType] = UNSET - head: Union[Operation, UnsetType] = UNSET - patch: Union[Operation, UnsetType] = UNSET - trace: Union[Operation, UnsetType] = UNSET - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - servers: Union[List[Server], UnsetType] = UNSET + get: MaybeSet[Operation] = UNSET + put: MaybeSet[Operation] = UNSET + post: MaybeSet[Operation] = UNSET + delete: MaybeSet[Operation] = UNSET + options: MaybeSet[Operation] = UNSET + head: MaybeSet[Operation] = UNSET + patch: MaybeSet[Operation] = UNSET + trace: MaybeSet[Operation] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + servers: MaybeSet[List[Server]] = UNSET def annotate( - params_schema: Union[Dict[str, Schema], UnsetType] = UNSET, - result_schema: Union[Schema, UnsetType] = UNSET, - errors_schema: Union[List[Error], UnsetType] = UNSET, - errors: Union[List[Type[exceptions.JsonRpcError]], UnsetType] = UNSET, - examples: Union[List[MethodExample], UnsetType] = UNSET, - error_examples: Union[List[ErrorExample], UnsetType] = UNSET, - tags: Union[List[str], UnsetType] = UNSET, - summary: Union[str, UnsetType] = UNSET, - description: Union[str, UnsetType] = UNSET, - external_docs: Union[ExternalDocumentation, UnsetType] = UNSET, - deprecated: Union[bool, UnsetType] = UNSET, - security: Union[List[Dict[str, List[str]]], UnsetType] = UNSET, - parameters: Union[List[Parameter], UnsetType] = UNSET, + params_schema: MaybeSet[Dict[str, Schema]] = UNSET, + result_schema: MaybeSet[Schema] = UNSET, + errors_schema: MaybeSet[List[Error]] = UNSET, + errors: MaybeSet[List[Type[exceptions.JsonRpcError]]] = UNSET, + examples: MaybeSet[List[MethodExample]] = UNSET, + error_examples: MaybeSet[List[ErrorExample]] = UNSET, + tags: MaybeSet[List[str]] = UNSET, + summary: MaybeSet[str] = UNSET, + description: MaybeSet[str] = UNSET, + external_docs: MaybeSet[ExternalDocumentation] = UNSET, + deprecated: MaybeSet[bool] = UNSET, + security: MaybeSet[List[Dict[str, List[str]]]] = UNSET, + parameters: MaybeSet[List[Parameter]] = UNSET, ) -> Callable[[Func], Func]: """ Adds Open Api specification annotation to the method. @@ -592,21 +592,21 @@ class OpenAPI(Specification): info: Info paths: Dict[str, Path] components: Components - servers: Union[List[Server], UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET - tags: Union[List[Tag], UnsetType] = UNSET - security: Union[List[Dict[str, List[str]]], UnsetType] = UNSET + servers: MaybeSet[List[Server]] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET + tags: MaybeSet[List[Tag]] = UNSET + security: MaybeSet[List[Dict[str, List[str]]]] = UNSET openapi: str = '3.0.0' def __init__( self, info: Info, path: str = '/openapi.json', - servers: Union[List[Server], UnsetType] = UNSET, - external_docs: Union[ExternalDocumentation, UnsetType] = UNSET, - tags: Union[List[Tag], UnsetType] = UNSET, - security: Union[List[Dict[str, List[str]]], UnsetType] = UNSET, - security_schemes: Union[Dict[str, SecurityScheme], UnsetType] = UNSET, + servers: MaybeSet[List[Server]] = UNSET, + external_docs: MaybeSet[ExternalDocumentation] = UNSET, + tags: MaybeSet[List[Tag]] = UNSET, + security: MaybeSet[List[Dict[str, List[str]]]] = UNSET, + security_schemes: MaybeSet[Dict[str, SecurityScheme]] = UNSET, openapi: str = '3.0.0', schema_extractor: Optional[extractors.BaseSchemaExtractor] = None, schema_extractors: Iterable[extractors.BaseSchemaExtractor] = (), @@ -631,7 +631,7 @@ def schema( path: str, methods: Iterable[Method] = (), methods_map: Mapping[str, Iterable[Method]] = {}, - ) -> dict: + ) -> Dict[str, Any]: methods_list: List[Tuple[str, Method]] = [] methods_list.extend((path, method) for method in methods) methods_list.extend( diff --git a/pjrpc/server/specs/openrpc.py b/pjrpc/server/specs/openrpc.py index aa0bec8..43a4dba 100644 --- a/pjrpc/server/specs/openrpc.py +++ b/pjrpc/server/specs/openrpc.py @@ -11,13 +11,13 @@ import itertools as it from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Type, Union -from pjrpc.common import UNSET, UnsetType, exceptions +from pjrpc.common import UNSET, MaybeSet, UnsetType, exceptions from pjrpc.common.typedefs import Func from pjrpc.server import Method, utils from . import Specification, extractors -Json = Union[str, int, float, dict, bool, list, tuple, set, None] +Json = Union[str, int, float, dict, bool, list, tuple, set, None] # type: ignore[type-arg] @dc.dataclass(frozen=True) @@ -30,9 +30,9 @@ class Contact: :param email: the email address of the contact person/organization """ - name: Union[str, UnsetType] = UNSET - url: Union[str, UnsetType] = UNSET - email: Union[str, UnsetType] = UNSET + name: MaybeSet[str] = UNSET + url: MaybeSet[str] = UNSET + email: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -45,7 +45,7 @@ class License: """ name: str - url: Union[str, UnsetType] = UNSET + url: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -63,10 +63,10 @@ class Info: title: str version: str - description: Union[str, UnsetType] = UNSET - contact: Union[Contact, UnsetType] = UNSET - license: Union[License, UnsetType] = UNSET - termsOfService: Union[str, UnsetType] = UNSET + description: MaybeSet[str] = UNSET + contact: MaybeSet[Contact] = UNSET + license: MaybeSet[License] = UNSET + termsOfService: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -82,8 +82,8 @@ class Server: name: str url: str - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -96,7 +96,7 @@ class ExternalDocumentation: """ url: str - description: Union[str, UnsetType] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -112,9 +112,9 @@ class Tag: """ name: str - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET @dc.dataclass(frozen=True) @@ -130,8 +130,8 @@ class ExampleObject: value: Json name: str - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -149,8 +149,8 @@ class MethodExample: name: str params: List[ExampleObject] result: ExampleObject - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET @dc.dataclass(frozen=True) @@ -170,10 +170,10 @@ class ContentDescriptor: name: str schema: Dict[str, Any] - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - required: Union[bool, UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + required: MaybeSet[bool] = UNSET + deprecated: MaybeSet[bool] = UNSET @dc.dataclass(frozen=True) @@ -188,7 +188,7 @@ class Error: code: int message: str - data: Union[Dict[str, Any], UnsetType] = UNSET + data: MaybeSet[Dict[str, Any]] = UNSET class ParamStructure(str, enum.Enum): @@ -221,17 +221,17 @@ class MethodInfo: """ name: str - params: List[Union[ContentDescriptor, dict]] - result: Union[ContentDescriptor, dict] - errors: Union[List[Error], UnsetType] = UNSET - paramStructure: Union[ParamStructure, UnsetType] = UNSET - examples: Union[List[MethodExample], UnsetType] = UNSET - summary: Union[str, UnsetType] = UNSET - description: Union[str, UnsetType] = UNSET - tags: Union[List[Tag], UnsetType] = UNSET - deprecated: Union[bool, UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET - servers: Union[List[Server], UnsetType] = UNSET + params: List[Union[ContentDescriptor, Dict[str, Any]]] + result: Union[ContentDescriptor, Dict[str, Any]] + errors: MaybeSet[List[Error]] = UNSET + paramStructure: MaybeSet[ParamStructure] = UNSET + examples: MaybeSet[List[MethodExample]] = UNSET + summary: MaybeSet[str] = UNSET + description: MaybeSet[str] = UNSET + tags: MaybeSet[List[Tag]] = UNSET + deprecated: MaybeSet[bool] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET + servers: MaybeSet[List[Server]] = UNSET @dc.dataclass(frozen=True) @@ -246,14 +246,14 @@ class Components: def annotate( - params_schema: Union[List[ContentDescriptor], UnsetType] = UNSET, - result_schema: Union[ContentDescriptor, UnsetType] = UNSET, - errors: Union[List[Union[Error, Type[exceptions.JsonRpcError]]], UnsetType] = UNSET, - examples: Union[List[MethodExample], UnsetType] = UNSET, - summary: Union[str, UnsetType] = UNSET, - description: Union[str, UnsetType] = UNSET, - tags: Union[List[Union[Tag, str]], UnsetType] = UNSET, - deprecated: Union[bool, UnsetType] = UNSET, + params_schema: MaybeSet[List[ContentDescriptor]] = UNSET, + result_schema: MaybeSet[ContentDescriptor] = UNSET, + errors: MaybeSet[List[Union[Error, Type[exceptions.JsonRpcError]]]] = UNSET, + examples: MaybeSet[List[MethodExample]] = UNSET, + summary: MaybeSet[str] = UNSET, + description: MaybeSet[str] = UNSET, + tags: MaybeSet[List[Union[Tag, str]]] = UNSET, + deprecated: MaybeSet[bool] = UNSET, ) -> Callable[[Func], Func]: """ Adds JSON-RPC method to the API specification. @@ -310,16 +310,16 @@ class OpenRPC(Specification): info: Info components: Components methods: List[MethodInfo] = dc.field(default_factory=list) - servers: Union[List[Server], UnsetType] = UNSET - externalDocs: Union[ExternalDocumentation, UnsetType] = UNSET + servers: MaybeSet[List[Server]] = UNSET + externalDocs: MaybeSet[ExternalDocumentation] = UNSET openrpc: str = '1.0.0' def __init__( self, info: Info, path: str = '/openrpc.json', - servers: Union[List[Server], UnsetType] = UNSET, - external_docs: Union[ExternalDocumentation, UnsetType] = UNSET, + servers: MaybeSet[List[Server]] = UNSET, + external_docs: MaybeSet[ExternalDocumentation] = UNSET, openrpc: str = '1.0.0', schema_extractor: Optional[extractors.BaseSchemaExtractor] = None, ): diff --git a/pjrpc/server/validators/pydantic.py b/pjrpc/server/validators/pydantic.py index a413e2b..6d107d5 100644 --- a/pjrpc/server/validators/pydantic.py +++ b/pjrpc/server/validators/pydantic.py @@ -27,7 +27,7 @@ def __init__(self, coerce: bool = True, **config_args: Any): self._model_config = type('ModelConfig', (pydantic.BaseConfig,), config_args) def validate_method( - self, method: Callable, params: Optional['JsonRpcParams'], exclude: Iterable[str] = (), **kwargs: Any, + self, method: Callable[..., Any], params: Optional['JsonRpcParams'], exclude: Iterable[str] = (), **kwargs: Any, ) -> Dict[str, Any]: """ Validates params against method using ``pydantic`` validator. diff --git a/pyproject.toml b/pyproject.toml index aba2b6f..13a6ae5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [tool.poetry] name = "pjrpc" -version = "1.6.0" +version = "1.7.0" description = "Extensible JSON-RPC library" authors = ["Dmitry Pershin "] license = "Unlicense" readme = "README.rst" homepage = "https://github.com/dapper91/pjrpc" repository = "https://github.com/dapper91/pjrpc" -documentation = "https://pjrpc.readthedocs.io/en/latest/" +documentation = "https://pjrpc.readthedocs.io" keywords = [ 'json-rpc', 'rpc', 'jsonrpc-client', 'jsonrpc-server', 'requests', 'aiohttp', 'flask', 'httpx', 'aio-pika', 'kombu', @@ -26,31 +26,37 @@ classifiers = [ "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] [tool.poetry.dependencies] -python = "^3.7" -aio-pika = { version = "^8.0", optional = true } -aiofiles = { version = "^0.7", optional = true } -aiohttp = { version = "^3.7", optional = true } -django = { version = "^3.0", optional = true } -docstring-parser = { version = "^0.8", optional = true } +python = ">=3.8,<4.0" +aio-pika = { version = ">=8.0", optional = true } +aiofiles = { version = ">=0.7", optional = true } +aiohttp = { version = ">=3.7", optional = true } +django = { version = ">=3.0", optional = true } +docstring-parser = { version = ">=0.8", optional = true } flask = { version = ">=1.1.3", optional = true } -httpx = { version = "^0.23.0", optional = true } -jsonschema = { version = "^3.0", optional = true } -kombu = { version = "^5.1", optional = true } +httpx = { version = ">=0.23.0", optional = true } +jsonschema = {version = ">=3.0,<4.0", optional = true} +kombu = { version = ">=5.1", optional = true } markupsafe = { version = "==2.0.1", optional = true } -openapi-ui-bundles = { version = "^0.1", optional = true } -pydantic = { version = "^1.7.0", optional = true } -requests = { version = "^2.0", optional = true } -starlette = { version = "^0.12.0", optional = true } -werkzeug = { version = "~=2.0", optional = true} -sphinx = { version = "~=4.5", optional = true} +openapi-ui-bundles = { version = ">=0.1", optional = true } +pydantic = {version = ">=1.7.0,<2.0", optional = true} +requests = { version = ">=2.0", optional = true } +starlette = { version = ">=0.25.0", optional = true } +werkzeug = { version = ">=2.0", optional = true} + +furo = {version = "^2022.12.7", optional = true} +Sphinx = {version = "^5.3.0", optional = true} +sphinx-copybutton = {version = "^0.5.1", optional = true} +sphinx_design = {version = "^0.3.0", optional = true} +toml = {version = "^0.10.2", optional = true} + [tool.poetry.extras] aio-pika = ['aio-pika'] @@ -67,19 +73,22 @@ requests = ['requests'] starlette = ['starlette', 'aiofiles'] test = ['docstring-parser', 'flask', 'jsonschema', 'openapi-ui-bundles', 'pydantic', 'werkzeug'] werkzeug = ['werkzeug'] -docgen = ['sphinx', 'aiohttp', 'aio-pika', 'flask', 'jsonschema', 'pydantic', 'requests', 'kombu'] +docs = [ + 'sphinx', 'sphinx-copybutton', 'sphinx_design', 'furo', 'toml', + 'aiohttp', 'aio-pika', 'flask', 'jsonschema', 'pydantic', 'requests', 'kombu' +] [tool.poetry.dev-dependencies] -aioresponses = "^0.7" -asynctest = "^0.12" -codecov = "^2.0" -pytest = "^6.0" -pytest-aiohttp = "^0.3" -pytest-cov = "^2.0" -pytest-mock = "^1.0" -responses = "^0.14" -respx = "^0.19.2" -mypy = "^0.942" +aioresponses = "^0.7.4" +asynctest = "^0.13.0" +codecov = "^2.1.13" +pytest = "^7.4.0" +pytest-aiohttp = "^1.0.4" +pytest-cov = "^4.1.0" +pytest-mock = "^3.11.1" +responses = "^0.23.3" +respx = "^0.20.2" +mypy = "^1.4.1" pre-commit = "^2.19" [build-system] @@ -88,6 +97,7 @@ build-backend = "poetry.core.masonry.api" [tool.mypy] allow_redefinition = true +disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_decorators = false disallow_untyped_defs = true diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..4088045 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode=auto