You've already forked bthlabs-jsonrpc
Initial public releases
* `bthlabs-jsonrpc-aiohttp` v1.0.0 * `bthlabs-jsonrpc-core` v1.0.0 * `bthlabs-jsonrpc-django` v1.0.0
This commit is contained in:
19
packages/bthlabs-jsonrpc-aiohttp/LICENSE
Normal file
19
packages/bthlabs-jsonrpc-aiohttp/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2022-present Tomek Wójcik <contact@bthlabs.pl>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
55
packages/bthlabs-jsonrpc-aiohttp/README.rst
Normal file
55
packages/bthlabs-jsonrpc-aiohttp/README.rst
Normal file
@@ -0,0 +1,55 @@
|
||||
bthlabs-jsonrpc-aiohttp
|
||||
=======================
|
||||
|
||||
BTHLabs JSONRPC - aiohttp integration
|
||||
|
||||
`Docs`_ | `Source repository`_
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
BTHLabs JSONRPC is a set of Python libraries that provide extensible framework
|
||||
for adding JSONRPC interfaces to existing Python Web applications.
|
||||
|
||||
The *aiohttp* package provides aiohttp integration.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pip install bthlabs_jsonrpc_aiohttp
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# app.py
|
||||
from aiohttp import web
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp import JSONRPCView
|
||||
|
||||
@register_method('hello')
|
||||
async def hello(request, who='World'):
|
||||
return f'Hello, {who}!'
|
||||
|
||||
app = web.Application()
|
||||
app.add_routes([
|
||||
web.post('/rpc', JSONRPCView()),
|
||||
])
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
*bthlabs-jsonrpc-aiohttp* is developed by `Tomek Wójcik`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
*bthlabs-jsonrpc-aiohttp* is licensed under the MIT License.
|
||||
|
||||
.. _Docs: https://projects.bthlabs.pl/bthlabs-jsonrpc/aiohttp/
|
||||
.. _Source repository: https://git.bthlabs.pl/tomekwojcik/bthlabs-jsonrpc/
|
||||
.. _Tomek Wójcik: https://www.bthlabs.pl/
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-aiohttp | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from .views import JSONRPCView # noqa
|
||||
|
||||
__version__ = '1.0.0'
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-aiohttp | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import logging
|
||||
|
||||
from bthlabs_jsonrpc_core import Executor, JSONRPCAccessDeniedError
|
||||
from bthlabs_jsonrpc_core.exceptions import JSONRPCParseError
|
||||
|
||||
LOGGER = logging.getLogger('bthlabs_jsonrpc.aiohttp.executor')
|
||||
|
||||
|
||||
class AioHttpExecutor(Executor):
|
||||
def __init__(self, request, can_call, namespace=None):
|
||||
super().__init__(namespace=namespace)
|
||||
self.request = request
|
||||
self.can_call = can_call
|
||||
|
||||
async def list_methods(self, *args, **kwargs):
|
||||
return super().list_methods()
|
||||
|
||||
async def deserialize_data(self, request):
|
||||
try:
|
||||
return await request.json()
|
||||
except Exception as exception:
|
||||
LOGGER.error('Error deserializing RPC call!', exc_info=exception)
|
||||
raise JSONRPCParseError()
|
||||
|
||||
def enrich_args(self, args):
|
||||
return [self.request, *super().enrich_args(args)]
|
||||
|
||||
async def before_call(self, method, args, kwargs):
|
||||
can_call = await self.can_call(self.request, method, args, kwargs)
|
||||
if can_call is False:
|
||||
raise JSONRPCAccessDeniedError(data='can_call')
|
||||
|
||||
async def execute(self):
|
||||
with self.execute_context() as execute_context:
|
||||
data = await self.deserialize_data(self.request)
|
||||
|
||||
calls = self.get_calls(data)
|
||||
for call in calls:
|
||||
with self.call_context(execute_context, call) as call_context:
|
||||
if call_context.is_valid is True:
|
||||
await self.before_call(
|
||||
call_context.method,
|
||||
call_context.args,
|
||||
call_context.kwargs,
|
||||
)
|
||||
|
||||
call_context.result = await call_context.handler(
|
||||
*call_context.args, **call_context.kwargs,
|
||||
)
|
||||
|
||||
return execute_context.serializer
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-aiohttp | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import typing
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp.executor import AioHttpExecutor
|
||||
|
||||
|
||||
class JSONRPCView:
|
||||
"""
|
||||
The JSONRPC View. This is the main JSONRPC entry point. Use it to register
|
||||
your JSONRPC endpoints.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp import JSONRPCView
|
||||
|
||||
app.add_routes([
|
||||
web.post('/rpc', JSONRPCView()),
|
||||
web.post('/example/rpc', JSONRPCView(namespace='examnple')),
|
||||
])
|
||||
"""
|
||||
|
||||
# pragma mark - Public interface
|
||||
|
||||
def __init__(self, namespace: typing.Optional[str] = None):
|
||||
self.namespace: typing.Optional[str] = namespace
|
||||
|
||||
async def can_call(self,
|
||||
request: web.Request,
|
||||
method: str,
|
||||
args: list,
|
||||
kwargs: dict) -> bool:
|
||||
"""
|
||||
Hook for subclasses to perform additional per-call permissions checks
|
||||
etc. The default implementation returns ``True``.
|
||||
"""
|
||||
return True
|
||||
|
||||
async def __call__(self, request: web.Request) -> web.Response:
|
||||
"""The request handler."""
|
||||
executor = AioHttpExecutor(
|
||||
request, self.can_call, namespace=self.namespace,
|
||||
)
|
||||
|
||||
serializer = await executor.execute()
|
||||
if serializer is None:
|
||||
return web.Response(body='')
|
||||
|
||||
return web.json_response(serializer.data)
|
||||
20
packages/bthlabs-jsonrpc-aiohttp/docs/Makefile
Normal file
20
packages/bthlabs-jsonrpc-aiohttp/docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
13
packages/bthlabs-jsonrpc-aiohttp/docs/source/api.rst
Normal file
13
packages/bthlabs-jsonrpc-aiohttp/docs/source/api.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
.. module:: bthlabs_jsonrpc_aiohttp
|
||||
|
||||
This section provides the API documentation for BTHLabs JSONRPC - aiohttp.
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
.. autoclass:: JSONRPCView
|
||||
:members:
|
||||
:special-members: __call__
|
||||
57
packages/bthlabs-jsonrpc-aiohttp/docs/source/conf.py
Normal file
57
packages/bthlabs-jsonrpc-aiohttp/docs/source/conf.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# 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 os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../../'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'BTHLabs JSONRPC - aiohttp'
|
||||
copyright = '2022-present Tomek Wójcik'
|
||||
author = 'Tomek Wójcik'
|
||||
version = '1.0.0'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '1.0.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
|
||||
# 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 = 'sphinx_rtd_theme'
|
||||
|
||||
# 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']
|
||||
17
packages/bthlabs-jsonrpc-aiohttp/docs/source/index.rst
Normal file
17
packages/bthlabs-jsonrpc-aiohttp/docs/source/index.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
BTHLabs JSONRPC - aiohttp
|
||||
=========================
|
||||
|
||||
BTHLabs JSONRPC is a set of Python libraries that provide extensible framework
|
||||
for adding JSONRPC interfaces to existing Python Web applications.
|
||||
|
||||
The *aiohttp* package provides aiohttp integration.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
36
packages/bthlabs-jsonrpc-aiohttp/docs/source/overview.rst
Normal file
36
packages/bthlabs-jsonrpc-aiohttp/docs/source/overview.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
This section provides the general overview of the integration.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pip install bthlabs_jsonrpc_aiohttp
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
First, you'll need to add a JSONRPC view to your project's URLs:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# app.py
|
||||
app = web.Application()
|
||||
|
||||
app.add_routes([
|
||||
web.post('/rpc', JSONRPCView()),
|
||||
])
|
||||
|
||||
Then, you'll need to implement the RPC method modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# your_app/rpc_methods.py
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
|
||||
@register_method(name='hello')
|
||||
async def hello(request, who='World'):
|
||||
return f'Hello, {who}!'
|
||||
63
packages/bthlabs-jsonrpc-aiohttp/example/example.py
Normal file
63
packages/bthlabs-jsonrpc-aiohttp/example/example.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-aiohttp | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from aiohttp import web
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp import JSONRPCView
|
||||
|
||||
logger = logging.getLogger('bthlabs_jsonrpc_aiohttp_example')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s %(name)s: %(levelname)s: %(message)s',
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
app_logger = logging.getLogger('bthlabs_jsonrpc_aiohttp_example.app')
|
||||
|
||||
jsonrpc_logger = logger = logging.getLogger('bthlabs_jsonrpc')
|
||||
jsonrpc_logger.setLevel(logging.DEBUG)
|
||||
jsonrpc_logger.addHandler(handler)
|
||||
|
||||
|
||||
async def app_on_startup(app):
|
||||
logger.info('BTHLabs JSONRPC aiohttp integration example')
|
||||
logger.debug('My PID = {pid}'.format(pid=os.getpid()))
|
||||
|
||||
|
||||
@register_method('hello')
|
||||
async def hello(request):
|
||||
return 'Hello, World!'
|
||||
|
||||
|
||||
@register_method('async_test')
|
||||
async def async_test(request, delay):
|
||||
await asyncio.sleep(delay)
|
||||
return 'It works!'
|
||||
|
||||
|
||||
@register_method('hello', namespace='example')
|
||||
async def hello_example(request):
|
||||
return 'Hello, Example!'
|
||||
|
||||
|
||||
def create_app(loop=None):
|
||||
app = web.Application(logger=app_logger, loop=loop)
|
||||
app.on_startup.append(app_on_startup)
|
||||
|
||||
app.add_routes([
|
||||
web.post('/rpc', JSONRPCView()),
|
||||
web.post('/example/rpc', JSONRPCView(namespace='example')),
|
||||
])
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
3
packages/bthlabs-jsonrpc-aiohttp/example/start.sh
Executable file
3
packages/bthlabs-jsonrpc-aiohttp/example/start.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# django-jsonrpc-aiohttp | (c) 2022-present Tomek Wójcik | MIT License
|
||||
exec adev runserver example.py
|
||||
1213
packages/bthlabs-jsonrpc-aiohttp/poetry.lock
generated
Normal file
1213
packages/bthlabs-jsonrpc-aiohttp/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
packages/bthlabs-jsonrpc-aiohttp/poetry.toml
Normal file
3
packages/bthlabs-jsonrpc-aiohttp/poetry.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[virtualenvs]
|
||||
create = true
|
||||
in-project = true
|
||||
32
packages/bthlabs-jsonrpc-aiohttp/pyproject.toml
Normal file
32
packages/bthlabs-jsonrpc-aiohttp/pyproject.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[tool.poetry]
|
||||
name = "bthlabs-jsonrpc-aiohttp"
|
||||
version = "1.0.0"
|
||||
description = "BTHLabs JSONRPC - aiohttp integration"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
maintainers = ["BTHLabs <contact@bthlabs.pl>"]
|
||||
license = "MIT License"
|
||||
readme = "README.rst"
|
||||
homepage = "https://projects.bthlabs.pl/bthlabs-jsonrpc/"
|
||||
repository = "https://git.bthlabs.pl/tomekwojcik/bthlabs-jsonrpc/"
|
||||
documentation = "https://projects.bthlabs.pl/bthlabs-jsonrpc/aiohttp/"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
aiohttp = ">=3.6,<4.0"
|
||||
bthlabs-jsonrpc-core = "1.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
bthlabs-jsonrpc-core = { path = "../bthlabs-jsonrpc-core", develop = true }
|
||||
aiohttp-devtools = "1.0.post0"
|
||||
flake8 = "4.0.1"
|
||||
flake8-commas = "2.1.0"
|
||||
mypy = "0.950"
|
||||
pytest = "7.1.2"
|
||||
pytest-aiohttp = "1.0.4"
|
||||
pytest-asyncio = "0.18.3"
|
||||
sphinx = "4.5.0"
|
||||
sphinx-rtd-theme = "1.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
7
packages/bthlabs-jsonrpc-aiohttp/setup.cfg
Normal file
7
packages/bthlabs-jsonrpc-aiohttp/setup.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[flake8]
|
||||
exclude = .venv/,.pytest_cache/
|
||||
ignore = E402
|
||||
max-line-length = 119
|
||||
|
||||
[tool:pytest]
|
||||
asyncio_mode = auto
|
||||
2
packages/bthlabs-jsonrpc-aiohttp/skel/envrc
Normal file
2
packages/bthlabs-jsonrpc-aiohttp/skel/envrc
Normal file
@@ -0,0 +1,2 @@
|
||||
export VIRTUAL_ENV="`realpath .venv`"
|
||||
export PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
0
packages/bthlabs-jsonrpc-aiohttp/tests/__init__.py
Normal file
0
packages/bthlabs-jsonrpc-aiohttp/tests/__init__.py
Normal file
10
packages/bthlabs-jsonrpc-aiohttp/tests/conftest.py
Normal file
10
packages/bthlabs-jsonrpc-aiohttp/tests/conftest.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from aiohttp.web import Request
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_request():
|
||||
return mock.Mock(spec=Request)
|
||||
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from bthlabs_jsonrpc_core import exceptions, serializer
|
||||
import pytest
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp import executor
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_can_call():
|
||||
result = mock.AsyncMock()
|
||||
result.return_value = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def test_init(fake_request, fake_can_call):
|
||||
# When
|
||||
result = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# Then
|
||||
assert result.request == fake_request
|
||||
assert result.can_call == fake_can_call
|
||||
|
||||
|
||||
async def test_list_methods(fake_request, fake_can_call):
|
||||
# Given
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
result = await the_executor.list_methods()
|
||||
|
||||
# Then
|
||||
assert result == ['system.list_methods']
|
||||
|
||||
|
||||
async def test_deserialize_data(fake_request, fake_can_call):
|
||||
# Given
|
||||
fake_request.json.return_value = 'spam'
|
||||
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
result = await the_executor.deserialize_data(fake_request)
|
||||
|
||||
# Then
|
||||
assert result == 'spam'
|
||||
|
||||
|
||||
async def test_deserialize_data_error(fake_request, fake_can_call):
|
||||
# Given
|
||||
fake_request.json.side_effect = RuntimeError('I HAZ FAIL')
|
||||
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
try:
|
||||
_ = await the_executor.deserialize_data(fake_request)
|
||||
except Exception as exception:
|
||||
assert isinstance(exception, exceptions.JSONRPCParseError)
|
||||
else:
|
||||
assert False, 'No exception raised?'
|
||||
|
||||
|
||||
def test_enrich_args(fake_request, fake_can_call):
|
||||
# Given
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
result = the_executor.enrich_args(['spam'])
|
||||
|
||||
# Then
|
||||
assert result == [fake_request, 'spam']
|
||||
|
||||
|
||||
async def test_before_call(fake_request, fake_can_call):
|
||||
# Given
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
await the_executor.before_call('test', ['spam'], {'spam': True})
|
||||
|
||||
# Then
|
||||
fake_can_call.assert_called_with(
|
||||
fake_request, 'test', ['spam'], {'spam': True},
|
||||
)
|
||||
|
||||
|
||||
async def test_before_call_access_denied(fake_request, fake_can_call):
|
||||
# Given
|
||||
fake_can_call.return_value = False
|
||||
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
# When
|
||||
try:
|
||||
await the_executor.before_call('test', ['spam'], {'spam': True})
|
||||
except Exception as exception:
|
||||
assert isinstance(exception, exceptions.JSONRPCAccessDeniedError)
|
||||
else:
|
||||
assert False, 'No exception raised?'
|
||||
|
||||
|
||||
@mock.patch('bthlabs_jsonrpc_core.registry.MethodRegistry.shared_registry')
|
||||
async def test_execute(mock_shared_registry, fake_request, fake_can_call):
|
||||
# Given
|
||||
fake_method_registry = mock.Mock()
|
||||
fake_method_registry.get_handler.return_value = None
|
||||
fake_method_registry.get_methods.return_value = []
|
||||
mock_shared_registry.return_value = fake_method_registry
|
||||
|
||||
batch = [
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'method': 'system.list_methods',
|
||||
'params': ['spam'],
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_2',
|
||||
'method': 'idontexist',
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'system.list_methods',
|
||||
'params': {'spam': True},
|
||||
},
|
||||
]
|
||||
|
||||
fake_request.json.return_value = batch
|
||||
|
||||
the_executor = executor.AioHttpExecutor(fake_request, fake_can_call)
|
||||
|
||||
with mock.patch.object(the_executor, 'before_call') as mock_before_call:
|
||||
# When
|
||||
result = await the_executor.execute()
|
||||
|
||||
# Then
|
||||
assert isinstance(result, serializer.JSONRPCSerializer)
|
||||
|
||||
expected_result_data = [
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'result': ['system.list_methods'],
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_2',
|
||||
'error': {
|
||||
'code': exceptions.JSONRPCMethodNotFoundError.ERROR_CODE,
|
||||
'message': exceptions.JSONRPCMethodNotFoundError.ERROR_MESSAGE,
|
||||
},
|
||||
},
|
||||
]
|
||||
assert result.data == expected_result_data
|
||||
|
||||
fake_method_registry.get_handler.assert_called_with(
|
||||
'jsonrpc', 'idontexist',
|
||||
)
|
||||
|
||||
assert mock_before_call.call_count == 2
|
||||
mock_before_call.assert_any_call(
|
||||
'system.list_methods', [fake_request, 'spam'], {},
|
||||
)
|
||||
mock_before_call.assert_any_call(
|
||||
'system.list_methods', [fake_request], {'spam': True},
|
||||
)
|
||||
148
packages/bthlabs-jsonrpc-aiohttp/tests/views/test_JSONRPCView.py
Normal file
148
packages/bthlabs-jsonrpc-aiohttp/tests/views/test_JSONRPCView.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from aiohttp import web
|
||||
from bthlabs_jsonrpc_core import exceptions
|
||||
|
||||
from bthlabs_jsonrpc_aiohttp import views
|
||||
|
||||
|
||||
def test_init():
|
||||
# When
|
||||
result = views.JSONRPCView()
|
||||
|
||||
# Then
|
||||
assert result.namespace is None
|
||||
|
||||
|
||||
def test_init_with_namespace():
|
||||
# When
|
||||
result = views.JSONRPCView(namespace='testing')
|
||||
|
||||
# Then
|
||||
assert result.namespace == 'testing'
|
||||
|
||||
|
||||
async def test_can_call(fake_request):
|
||||
# Given
|
||||
view = views.JSONRPCView()
|
||||
|
||||
# When
|
||||
result = await view.can_call(fake_request, 'test', [], {})
|
||||
|
||||
# Then
|
||||
assert result is True
|
||||
|
||||
|
||||
async def test_view(aiohttp_client):
|
||||
# Given
|
||||
view = views.JSONRPCView()
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_post('/', view)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
batch = [
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'method': 'system.list_methods',
|
||||
'params': ['spam'],
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_2',
|
||||
'method': 'idontexist',
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'system.list_methods',
|
||||
'params': {'spam': True},
|
||||
},
|
||||
]
|
||||
|
||||
# When
|
||||
response = await client.post('/', json=batch)
|
||||
|
||||
# Then
|
||||
assert response.status == 200
|
||||
|
||||
data = await response.json()
|
||||
expected_result_data = [
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'result': ['system.list_methods'],
|
||||
},
|
||||
{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_2',
|
||||
'error': {
|
||||
'code': exceptions.JSONRPCMethodNotFoundError.ERROR_CODE,
|
||||
'message': exceptions.JSONRPCMethodNotFoundError.ERROR_MESSAGE,
|
||||
},
|
||||
},
|
||||
]
|
||||
assert data == expected_result_data
|
||||
|
||||
|
||||
async def test_view_empty_response(aiohttp_client):
|
||||
# Given
|
||||
view = views.JSONRPCView()
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_post('/', view)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
call = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'system.list_methods',
|
||||
}
|
||||
|
||||
# When
|
||||
response = await client.post('/', json=call)
|
||||
|
||||
# Then
|
||||
assert response.status == 200
|
||||
|
||||
data = await response.content.read()
|
||||
assert data == b''
|
||||
|
||||
|
||||
async def test_view_permission_denied(aiohttp_client):
|
||||
# Given
|
||||
view = views.JSONRPCView()
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_post('/', view)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
call = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'method': 'system.list_methods',
|
||||
}
|
||||
|
||||
with mock.patch.object(view, 'can_call') as mock_can_call:
|
||||
mock_can_call.return_value = False
|
||||
|
||||
# When
|
||||
response = await client.post('/', json=call)
|
||||
|
||||
# Then
|
||||
assert response.status == 200
|
||||
|
||||
data = await response.json()
|
||||
expected_result_data = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'call_1',
|
||||
'error': {
|
||||
'code': exceptions.JSONRPCAccessDeniedError.ERROR_CODE,
|
||||
'message': exceptions.JSONRPCAccessDeniedError.ERROR_MESSAGE,
|
||||
'data': 'can_call',
|
||||
},
|
||||
}
|
||||
assert data == expected_result_data
|
||||
Reference in New Issue
Block a user