You've already forked bthlabs-jsonrpc
v1.1.0b1
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from .auth_checks import ( # noqa
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from .auth_checks import ( # noqa: F401
|
||||
has_perms,
|
||||
is_authenticated,
|
||||
is_staff,
|
||||
)
|
||||
from .views import JSONRPCView # noqa
|
||||
from .codecs import DjangoJSONCodec # noqa: F401
|
||||
from .executor import DjangoExecutor # noqa: F401
|
||||
from .serializer import DjangoJSONRPCSerializer # noqa: F401
|
||||
from .views import JSONRPCView # noqa: F401
|
||||
|
||||
__version__ = '1.0.0'
|
||||
__version__ = '1.1.0b1'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import JSONCodec
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
|
||||
class DjangoJSONCodec(JSONCodec):
|
||||
"""Django-specific JSON codec"""
|
||||
|
||||
# pragma mark - Public interface
|
||||
|
||||
def encode(self, payload: typing.Any, **encoder_kwargs) -> str:
|
||||
"""
|
||||
Before handing off control to the superclass, this method will default
|
||||
the *cls* encoder kwarg to
|
||||
:py:class:`django.core.serializers.json.DjangoJSONEncoder`.
|
||||
"""
|
||||
effective_encoder_kwargs = {
|
||||
'cls': DjangoJSONEncoder,
|
||||
**encoder_kwargs,
|
||||
}
|
||||
|
||||
return super().encode(payload, **effective_encoder_kwargs)
|
||||
@@ -1,28 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import Executor, JSONRPCAccessDeniedError
|
||||
from bthlabs_jsonrpc_core import Codec, Executor, JSONRPCAccessDeniedError
|
||||
from django.http import HttpRequest
|
||||
|
||||
from bthlabs_jsonrpc_django.serializer import DjangoJSONRPCSerializer
|
||||
|
||||
TCanCall = typing.Callable[[HttpRequest, str, list, dict], bool]
|
||||
|
||||
|
||||
class DjangoExecutor(Executor):
|
||||
"""Django-specific executor"""
|
||||
|
||||
serializer = DjangoJSONRPCSerializer
|
||||
|
||||
def __init__(self,
|
||||
request: HttpRequest,
|
||||
can_call: typing.Callable,
|
||||
namespace: typing.Optional[str] = None):
|
||||
super().__init__(namespace=namespace)
|
||||
self.request: HttpRequest = request
|
||||
self.can_call: typing.Callable = can_call
|
||||
can_call: TCanCall,
|
||||
namespace: str | None = None,
|
||||
codec: Codec | None = None):
|
||||
super().__init__(namespace=namespace, codec=codec)
|
||||
self.request = request
|
||||
self.can_call = can_call
|
||||
|
||||
def enrich_args(self, args):
|
||||
# pragma mark - Public interface
|
||||
|
||||
def enrich_args(self, args: list) -> list:
|
||||
"""
|
||||
Injects the current :py:class:`django.http.HttpRequest` as the first
|
||||
argument.
|
||||
"""
|
||||
return [self.request, *super().enrich_args(args)]
|
||||
|
||||
def before_call(self, method, args, kwargs):
|
||||
def before_call(self, method: str, args: list, kwargs: dict):
|
||||
"""
|
||||
Executes *can_call* and raises :py:exc:`JSONRPCAccessDeniedError`
|
||||
accordingly.
|
||||
"""
|
||||
can_call = self.can_call(self.request, method, args, kwargs)
|
||||
if can_call is False:
|
||||
raise JSONRPCAccessDeniedError(data='can_call')
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import JSONRPCSerializer
|
||||
from django.db.models import QuerySet
|
||||
|
||||
|
||||
class DjangoJSONRPCSerializer(JSONRPCSerializer):
|
||||
SEQUENCE_TYPES = (QuerySet, *JSONRPCSerializer.SEQUENCE_TYPES)
|
||||
"""Django-specific serializer"""
|
||||
SEQUENCE_TYPES: typing.Any = (QuerySet, *JSONRPCSerializer.SEQUENCE_TYPES)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import Executor
|
||||
from bthlabs_jsonrpc_core.codecs import Codec
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.decorators import classonlymethod
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic.base import View
|
||||
|
||||
from bthlabs_jsonrpc_django.codecs import DjangoJSONCodec
|
||||
from bthlabs_jsonrpc_django.executor import DjangoExecutor
|
||||
|
||||
|
||||
@@ -35,18 +38,19 @@ class JSONRPCView(View):
|
||||
]
|
||||
"""
|
||||
|
||||
# pragma mark - Private class attributes
|
||||
|
||||
# The executor class.
|
||||
executor: Executor = DjangoExecutor
|
||||
|
||||
# pragma mark - Public class attributes
|
||||
|
||||
#: The executor class.
|
||||
executor: type[DjangoExecutor] = DjangoExecutor
|
||||
|
||||
#: List of auth check functions.
|
||||
auth_checks: list[typing.Callable] = []
|
||||
|
||||
#: Namespace of this endpoint.
|
||||
namespace: typing.Optional[str] = None
|
||||
namespace: str | None = None
|
||||
|
||||
#: The codec class.
|
||||
codec: type[Codec] = DjangoJSONCodec
|
||||
|
||||
# pragma mark - Private interface
|
||||
|
||||
@@ -69,6 +73,19 @@ class JSONRPCView(View):
|
||||
if has_auth is False:
|
||||
raise PermissionDenied('This RPC endpoint requires auth.')
|
||||
|
||||
def get_executor(self, request: HttpRequest) -> DjangoExecutor:
|
||||
"""
|
||||
Returns an executor configured for the *request*.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return self.executor(
|
||||
request,
|
||||
self.can_call,
|
||||
self.namespace,
|
||||
codec=self.get_codec(request),
|
||||
)
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""
|
||||
Dispatches the *request*.
|
||||
@@ -84,7 +101,7 @@ class JSONRPCView(View):
|
||||
|
||||
self.ensure_auth(request)
|
||||
|
||||
return handler(request, *args, **kwargs)
|
||||
return handler(request, *args, **kwargs) # type: ignore[misc]
|
||||
|
||||
def post(self, request: HttpRequest) -> HttpResponse:
|
||||
"""
|
||||
@@ -92,15 +109,18 @@ class JSONRPCView(View):
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
executor = self.executor(
|
||||
request, self.can_call, self.namespace,
|
||||
)
|
||||
executor = self.get_executor(request)
|
||||
|
||||
serializer = executor.execute(request.body)
|
||||
if serializer is None:
|
||||
return HttpResponse('')
|
||||
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
codec = self.get_codec(request)
|
||||
|
||||
return HttpResponse(
|
||||
content=codec.encode(serializer.data),
|
||||
content_type=codec.get_content_type(),
|
||||
)
|
||||
|
||||
# pragma mark - Public interface
|
||||
|
||||
@@ -120,3 +140,7 @@ class JSONRPCView(View):
|
||||
etc. The default implementation returns ``True``.
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_codec(self, request: HttpRequest) -> Codec:
|
||||
"""Returns a codec configured for the *request*."""
|
||||
return self.codec()
|
||||
|
||||
@@ -3,7 +3,7 @@ API Documentation
|
||||
|
||||
.. module:: bthlabs_jsonrpc_django
|
||||
|
||||
This section provides the API documentation for BTHLabs JSONRPC - Core.
|
||||
This section provides the API documentation for BTHLabs JSONRPC - Django.
|
||||
|
||||
Auth checks
|
||||
-----------
|
||||
@@ -14,8 +14,26 @@ Auth checks
|
||||
|
||||
.. autofunction:: is_staff
|
||||
|
||||
Codecs
|
||||
------
|
||||
|
||||
.. autoclass:: DjangoJSONCodec
|
||||
:members:
|
||||
|
||||
Executors
|
||||
---------
|
||||
|
||||
.. autoclass:: DjangoExecutor
|
||||
:members:
|
||||
|
||||
Serializers
|
||||
-----------
|
||||
|
||||
.. autoclass:: DjangoJSONRPCSerializer
|
||||
:members:
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
.. autoclass:: JSONRPCView
|
||||
:members: as_view, auth_checks, can_call, namespace
|
||||
:members:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# type: ignore
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
@@ -20,10 +21,10 @@ sys.path.insert(0, os.path.abspath('../../'))
|
||||
project = 'BTHLabs JSONRPC - Django'
|
||||
copyright = '2022-present Tomek Wójcik'
|
||||
author = 'Tomek Wójcik'
|
||||
version = '1.0.0'
|
||||
version = '1.1.0'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '1.0.0'
|
||||
release = '1.1.0b1'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ASGI config for django_jsonrpc_django_example project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
|
||||
"""
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# type: ignore
|
||||
import pathlib
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
SECRET_KEY = None
|
||||
DEBUG = False
|
||||
@@ -71,6 +73,10 @@ STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
|
||||
JSONRPC_METHOD_MODULES = [
|
||||
'bthlabs_jsonrpc_django_example.things.rpc_methods',
|
||||
]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from django.contrib import admin
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from bthlabs_jsonrpc_django_example.things import models
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from bthlabs_jsonrpc_core import JSONRPCAccessDeniedError, register_method
|
||||
|
||||
from bthlabs_jsonrpc_django_example.things.models import Thing
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated, is_staff
|
||||
from bthlabs_jsonrpc_core.ext.jwt import ALGORITHMS, HMACKey, JWTCodec, KeyPair
|
||||
|
||||
|
||||
class JWTRPCView(JSONRPCView):
|
||||
codec = JWTCodec
|
||||
|
||||
def get_codec(self, request):
|
||||
return JWTCodec(
|
||||
KeyPair(
|
||||
decode_key=HMACKey('thisisntasecrurekeydontuseitpls=', ALGORITHMS.HS256),
|
||||
encode_key=HMACKey('thisisntasecrurekeydontuseitpls=', ALGORITHMS.HS256),
|
||||
),
|
||||
issuer='bthlabs_jsonrpc_django_example',
|
||||
ttl=datetime.timedelta(seconds=3600),
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path(
|
||||
'rpc/admin',
|
||||
JSONRPCView.as_view(
|
||||
JWTRPCView.as_view(
|
||||
auth_checks=[is_authenticated, is_staff],
|
||||
namespace='admin',
|
||||
),
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
WSGI config for django_jsonrpc_django_example project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
||||
"""
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault(
|
||||
'DJANGO_SETTINGS_MODULE',
|
||||
'bthlabs_jsonrpc_django_example.settings.local',
|
||||
)
|
||||
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
@@ -20,6 +23,7 @@ def main():
|
||||
"forget to activate a virtual environment?"
|
||||
),
|
||||
) from exc
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
|
||||
1014
packages/bthlabs-jsonrpc-django/poetry.lock
generated
1014
packages/bthlabs-jsonrpc-django/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "bthlabs-jsonrpc-django"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0b1"
|
||||
description = "BTHLabs JSONRPC - Django integration"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
maintainers = ["BTHLabs <contact@bthlabs.pl>"]
|
||||
@@ -13,20 +13,20 @@ documentation = "https://projects.bthlabs.pl/bthlabs-jsonrpc/aiohttp/"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
django = ">=3.2,<5.0"
|
||||
bthlabs-jsonrpc-core = "1.0.0"
|
||||
bthlabs-jsonrpc-core = "1.1.0b1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
bthlabs-jsonrpc-core = { path = "../bthlabs-jsonrpc-core", develop = true }
|
||||
django = "3.2.13"
|
||||
factory-boy = "3.2.1"
|
||||
flake8 = "4.0.1"
|
||||
django = "3.2.23"
|
||||
factory-boy = "3.3.0"
|
||||
flake8 = "6.1.0"
|
||||
flake8-commas = "2.1.0"
|
||||
mypy = "0.950"
|
||||
pytest = "7.1.2"
|
||||
pytest-django = "4.5.2"
|
||||
sphinx = "4.5.0"
|
||||
sphinx-rtd-theme = "1.0.0"
|
||||
mypy = "1.8.0"
|
||||
pytest = "7.4.3"
|
||||
pytest-django = "4.7.0"
|
||||
sphinx = "7.2.6"
|
||||
sphinx-rtd-theme = "2.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
[flake8]
|
||||
exclude = .venv/,.pytest_cache/,example/*/migrations/*.py,testing/migrations/*.py
|
||||
exclude = .venv/,.mypy_cache/,.pytest_cache/,example/*/migrations/*.py,testing/migrations/*.py
|
||||
ignore = E402
|
||||
max-line-length = 119
|
||||
|
||||
[tool:pytest]
|
||||
DJANGO_SETTINGS_MODULE = testing.settings
|
||||
|
||||
[mypy]
|
||||
|
||||
[mypy-django.*]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[mypy-factory.*]
|
||||
ignore_missing_imports = true
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import factory
|
||||
|
||||
from testing.models import Thing
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
# type: ignore
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# bthlabs-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated
|
||||
|
||||
0
packages/bthlabs-jsonrpc-django/tests/__init__.py
Normal file
0
packages/bthlabs-jsonrpc-django/tests/__init__.py
Normal file
@@ -1,10 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import RequestFactory
|
||||
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_has_perms_regular_user(rf, user):
|
||||
def test_has_perms_regular_user(rf: RequestFactory, user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
@@ -18,7 +22,7 @@ def test_has_perms_regular_user(rf, user):
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_has_perms_ok(rf, user):
|
||||
def test_has_perms_ok(rf: RequestFactory, user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
@@ -37,7 +41,7 @@ def test_has_perms_ok(rf, user):
|
||||
mock_has_perms.assert_called_with(['can_use_rpc'])
|
||||
|
||||
|
||||
def test_has_perms_ok_super_user(rf, super_user):
|
||||
def test_has_perms_ok_super_user(rf: RequestFactory, super_user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = super_user
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import RequestFactory
|
||||
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_is_authenticated_anonymous_user(rf):
|
||||
def test_is_authenticated_anonymous_user(rf: RequestFactory):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = AnonymousUser()
|
||||
@@ -16,7 +19,7 @@ def test_is_authenticated_anonymous_user(rf):
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_authenticated_inactive(rf, inactive_user):
|
||||
def test_is_authenticated_inactive(rf: RequestFactory, inactive_user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = inactive_user
|
||||
@@ -28,7 +31,7 @@ def test_is_authenticated_inactive(rf, inactive_user):
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_authenticated_ok(rf, user):
|
||||
def test_is_authenticated_ok(rf: RequestFactory, user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import RequestFactory
|
||||
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_is_staff_regular_user(rf, user):
|
||||
def test_is_staff_regular_user(rf: RequestFactory, user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
@@ -14,7 +18,7 @@ def test_is_staff_regular_user(rf, user):
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_staff_ok(rf, staff_user):
|
||||
def test_is_staff_ok(rf: RequestFactory, staff_user: User):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = staff_user
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
import decimal
|
||||
import datetime
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from bthlabs_jsonrpc_django import codecs
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def payload() -> dict:
|
||||
return {
|
||||
'str': 'This is a string',
|
||||
'int': 42,
|
||||
'float': 3.14,
|
||||
'decimal': decimal.Decimal('2.71828'),
|
||||
'datetime': datetime.datetime(2021, 1, 19, 8, 0, 0),
|
||||
'date': datetime.date(2022, 8, 25),
|
||||
'uuid': uuid.UUID('{ab3eacec-e205-413d-b900-940e14f61518}'),
|
||||
}
|
||||
|
||||
|
||||
def test_encode(payload: dict):
|
||||
# Given
|
||||
codec = codecs.DjangoJSONCodec()
|
||||
|
||||
# When
|
||||
result = codec.encode(payload)
|
||||
|
||||
# Then
|
||||
expected_result = (
|
||||
'{'
|
||||
'"str": "This is a string", '
|
||||
'"int": 42, '
|
||||
'"float": 3.14, '
|
||||
'"decimal": "2.71828", '
|
||||
'"datetime": "2021-01-19T08:00:00", '
|
||||
'"date": "2022-08-25", '
|
||||
'"uuid": "ab3eacec-e205-413d-b900-940e14f61518"'
|
||||
'}'
|
||||
)
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
def test_encode_super_encode_call(payload: dict):
|
||||
# Given
|
||||
codec = codecs.DjangoJSONCodec()
|
||||
|
||||
with mock.patch.object(codecs.JSONCodec, 'encode') as mock_super_encode:
|
||||
# When
|
||||
_ = codec.encode(payload)
|
||||
|
||||
# Then
|
||||
mock_super_encode.assert_called_once_with(
|
||||
payload, cls=DjangoJSONEncoder,
|
||||
)
|
||||
|
||||
|
||||
def test_encode_super_encode_call_encoder_kwargs(payload: dict):
|
||||
# Given
|
||||
codec = codecs.DjangoJSONCodec()
|
||||
|
||||
with mock.patch.object(codecs.JSONCodec, 'encode') as mock_super_encode:
|
||||
# When
|
||||
_ = codec.encode(payload, cls=None)
|
||||
|
||||
# Then
|
||||
mock_super_encode.assert_called_once_with(
|
||||
payload, cls=None,
|
||||
)
|
||||
@@ -1,46 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from django.contrib.auth.models import User
|
||||
import factory
|
||||
import pytest
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
username = factory.Faker('email')
|
||||
first_name = factory.Faker('first_name')
|
||||
last_name = factory.Faker('last_name')
|
||||
email = factory.Faker('email')
|
||||
is_staff = False
|
||||
is_superuser = False
|
||||
is_active = True
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
from .factories import UserFactory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user(db):
|
||||
def user(db) -> User:
|
||||
return UserFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inactive_user(db):
|
||||
def inactive_user(db) -> User:
|
||||
return UserFactory(is_active=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def staff_user(db):
|
||||
def staff_user(db) -> User:
|
||||
return UserFactory(is_staff=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def super_user(db):
|
||||
def super_user(db) -> User:
|
||||
return UserFactory(is_superuser=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call():
|
||||
return {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'system.list_methods',
|
||||
'method': 'system.list_methods',
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from unittest import mock
|
||||
|
||||
from bthlabs_jsonrpc_core import exceptions
|
||||
from django.test import RequestFactory
|
||||
import pytest
|
||||
|
||||
from bthlabs_jsonrpc_django import executor
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_can_call():
|
||||
def fake_can_call() -> mock.Mock:
|
||||
return mock.Mock()
|
||||
|
||||
|
||||
def test_init(rf, fake_can_call):
|
||||
def test_init(rf: RequestFactory, fake_can_call: mock.Mock):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
@@ -24,7 +26,7 @@ def test_init(rf, fake_can_call):
|
||||
assert result.can_call == fake_can_call
|
||||
|
||||
|
||||
def test_enrich_args(rf, fake_can_call):
|
||||
def test_enrich_args(rf: RequestFactory, fake_can_call: mock.Mock):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
@@ -37,7 +39,7 @@ def test_enrich_args(rf, fake_can_call):
|
||||
assert result == [request, 'spam']
|
||||
|
||||
|
||||
def test_before_call(rf, fake_can_call):
|
||||
def test_before_call(rf: RequestFactory, fake_can_call: mock.Mock):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
@@ -50,7 +52,8 @@ def test_before_call(rf, fake_can_call):
|
||||
fake_can_call.assert_called_with(request, 'test', ['spam'], {'spam': True})
|
||||
|
||||
|
||||
def test_before_call_access_denied(rf, fake_can_call):
|
||||
def test_before_call_access_denied(rf: RequestFactory,
|
||||
fake_can_call: mock.Mock):
|
||||
# Given
|
||||
fake_can_call.return_value = False
|
||||
|
||||
|
||||
19
packages/bthlabs-jsonrpc-django/tests/factories.py
Normal file
19
packages/bthlabs-jsonrpc-django/tests/factories.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
import factory
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
username = factory.Faker('email')
|
||||
first_name = factory.Faker('first_name')
|
||||
last_name = factory.Faker('last_name')
|
||||
email = factory.Faker('email')
|
||||
is_staff = False
|
||||
is_superuser = False
|
||||
is_active = True
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
import pytest
|
||||
|
||||
from bthlabs_jsonrpc_django import serializer
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from bthlabs_jsonrpc_core import exceptions
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client
|
||||
|
||||
|
||||
def test_view(client):
|
||||
def test_view(client: Client):
|
||||
# Given
|
||||
batch = [
|
||||
{
|
||||
@@ -48,25 +51,27 @@ def test_view(client):
|
||||
assert data == expected_result_data
|
||||
|
||||
|
||||
def test_view_empty_response(client, call):
|
||||
def test_view_empty_response(client: Client, single_call: dict):
|
||||
# Given
|
||||
call.pop('id')
|
||||
single_call.pop('id')
|
||||
|
||||
# When
|
||||
response = client.post('/rpc', data=call, content_type='application/json')
|
||||
response = client.post(
|
||||
'/rpc', data=single_call, content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert response.status_code == 200
|
||||
assert response.content == b''
|
||||
|
||||
|
||||
def test_view_with_auth_checks(client, user, call):
|
||||
def test_view_with_auth_checks(client: Client, user: User, single_call: dict):
|
||||
# Given
|
||||
client.force_login(user)
|
||||
|
||||
# When
|
||||
response = client.post(
|
||||
'/rpc/private', data=call, content_type='application/json',
|
||||
'/rpc/private', data=single_call, content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
@@ -75,16 +80,17 @@ def test_view_with_auth_checks(client, user, call):
|
||||
data = response.json()
|
||||
expected_result_data = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'system.list_methods',
|
||||
'id': 'test',
|
||||
'result': ['system.list_methods'],
|
||||
}
|
||||
assert data == expected_result_data
|
||||
|
||||
|
||||
def test_view_with_auth_checks_permission_denied(client, call):
|
||||
def test_view_with_auth_checks_permission_denied(client: Client,
|
||||
single_call: dict):
|
||||
# When
|
||||
response = client.post(
|
||||
'/rpc/private', data=call, content_type='application/json',
|
||||
'/rpc/private', data=single_call, content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
|
||||
Reference in New Issue
Block a user