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-django/LICENSE
Normal file
19
packages/bthlabs-jsonrpc-django/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2023-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.
|
||||
71
packages/bthlabs-jsonrpc-django/README.rst
Normal file
71
packages/bthlabs-jsonrpc-django/README.rst
Normal file
@@ -0,0 +1,71 @@
|
||||
bthlabs-jsonrpc-django
|
||||
======================
|
||||
|
||||
BTHLabs JSONRPC - django 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 *django* package provides Django integration.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pip install bthlabs_jsonrpc_django
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# settings.py
|
||||
INSTALLED_APPS = [
|
||||
# ...
|
||||
'bthlabs_jsonrpc_django',
|
||||
]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# settings.py
|
||||
JSONRPC_METHOD_MODULES = [
|
||||
# ...
|
||||
'your_app.rpc_methods',
|
||||
]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# urls.py
|
||||
urlpatterns = [
|
||||
# ...
|
||||
path('rpc', JSONRPCView.as_view()),
|
||||
]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# your_app/rpc_methods.py
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
|
||||
@register_method(name='hello')
|
||||
def hello(request, who='World'):
|
||||
return f'Hello, {who}!'
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
*bthlabs-jsonrpc-django* is developed by `Tomek Wójcik`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
*bthlabs-jsonrpc-django* is licensed under the MIT License.
|
||||
|
||||
.. _Docs: https://projects.bthlabs.pl/bthlabs-jsonrpc/django/
|
||||
.. _Source repository: https://git.bthlabs.pl/tomekwojcik/bthlabs-jsonrpc/
|
||||
.. _Tomek Wójcik: https://www.bthlabs.pl/
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from .auth_checks import ( # noqa
|
||||
has_perms,
|
||||
is_authenticated,
|
||||
is_staff,
|
||||
)
|
||||
from .views import JSONRPCView # noqa
|
||||
|
||||
__version__ = '1.0.0'
|
||||
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import importlib
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BTHLabsJSONRPCConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'bthlabs_jsonrpc_django'
|
||||
verbose_name = 'BTHLabs JSONRPC'
|
||||
|
||||
def ready(self):
|
||||
from django.conf import settings
|
||||
|
||||
for module_path in settings.JSONRPC_METHOD_MODULES:
|
||||
_ = importlib.import_module(module_path)
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import typing
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
def is_authenticated(request: HttpRequest) -> bool:
|
||||
"""Checks if the request user is authenticated and active."""
|
||||
return all((
|
||||
request.user.is_anonymous is False,
|
||||
request.user.is_active is True,
|
||||
))
|
||||
|
||||
|
||||
def is_staff(request: HttpRequest) -> bool:
|
||||
"""Checks if the request user is a staff user."""
|
||||
return request.user.is_staff
|
||||
|
||||
|
||||
def has_perms(perms: list[str]) -> typing.Callable:
|
||||
"""Checks if the request user has the specified permissions."""
|
||||
|
||||
def internal_has_perms(request: HttpRequest) -> bool:
|
||||
return request.user.has_perms(perms)
|
||||
|
||||
return internal_has_perms
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import Executor, JSONRPCAccessDeniedError
|
||||
from django.http import HttpRequest
|
||||
|
||||
from bthlabs_jsonrpc_django.serializer import DjangoJSONRPCSerializer
|
||||
|
||||
|
||||
class DjangoExecutor(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
|
||||
|
||||
def enrich_args(self, args):
|
||||
return [self.request, *super().enrich_args(args)]
|
||||
|
||||
def before_call(self, method, args, kwargs):
|
||||
can_call = self.can_call(self.request, method, args, kwargs)
|
||||
if can_call is False:
|
||||
raise JSONRPCAccessDeniedError(data='can_call')
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from bthlabs_jsonrpc_core import JSONRPCSerializer
|
||||
from django.db.models import QuerySet
|
||||
|
||||
|
||||
class DjangoJSONRPCSerializer(JSONRPCSerializer):
|
||||
SEQUENCE_TYPES = (QuerySet, *JSONRPCSerializer.SEQUENCE_TYPES)
|
||||
122
packages/bthlabs-jsonrpc-django/bthlabs_jsonrpc_django/views.py
Normal file
122
packages/bthlabs-jsonrpc-django/bthlabs_jsonrpc_django/views.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_core import Executor
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
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.executor import DjangoExecutor
|
||||
|
||||
|
||||
class JSONRPCView(View):
|
||||
"""
|
||||
The JSONRPC View. This is the main JSONRPC entry point. Use it to register
|
||||
your JSONRPC endpoints.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'rpc/private',
|
||||
JSONRPCView.as_view(
|
||||
auth_checks=[is_authenticated],
|
||||
namespace='admin',
|
||||
),
|
||||
)
|
||||
path('rpc', JSONRPCView.as_view()),
|
||||
]
|
||||
"""
|
||||
|
||||
# pragma mark - Private class attributes
|
||||
|
||||
# The executor class.
|
||||
executor: Executor = DjangoExecutor
|
||||
|
||||
# pragma mark - Public class attributes
|
||||
|
||||
#: List of auth check functions.
|
||||
auth_checks: list[typing.Callable] = []
|
||||
|
||||
#: Namespace of this endpoint.
|
||||
namespace: typing.Optional[str] = None
|
||||
|
||||
# pragma mark - Private interface
|
||||
|
||||
def ensure_auth(self, request: HttpRequest) -> None:
|
||||
"""
|
||||
Runs auth checks (if any) and raises
|
||||
:py:exc:`django.core.exceptions.PermissionDenied` if any of them
|
||||
returns ``False``.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
if len(self.auth_checks) == []:
|
||||
return
|
||||
|
||||
has_auth = all((
|
||||
auth_check(request)
|
||||
for auth_check
|
||||
in self.auth_checks
|
||||
))
|
||||
if has_auth is False:
|
||||
raise PermissionDenied('This RPC endpoint requires auth.')
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""
|
||||
Dispatches the *request*.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
if request.method.lower() in self.http_method_names:
|
||||
handler = getattr(
|
||||
self, request.method.lower(), self.http_method_not_allowed,
|
||||
)
|
||||
else:
|
||||
handler = self.http_method_not_allowed
|
||||
|
||||
self.ensure_auth(request)
|
||||
|
||||
return handler(request, *args, **kwargs)
|
||||
|
||||
def post(self, request: HttpRequest) -> HttpResponse:
|
||||
"""
|
||||
The POST handler.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
executor = self.executor(
|
||||
request, self.can_call, self.namespace,
|
||||
)
|
||||
|
||||
serializer = executor.execute(request.body)
|
||||
if serializer is None:
|
||||
return HttpResponse('')
|
||||
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
# pragma mark - Public interface
|
||||
|
||||
@classonlymethod
|
||||
def as_view(cls, **initkwargs):
|
||||
result = super().as_view(**initkwargs)
|
||||
|
||||
return csrf_exempt(result)
|
||||
|
||||
def can_call(self,
|
||||
request: HttpRequest,
|
||||
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
|
||||
20
packages/bthlabs-jsonrpc-django/docs/Makefile
Normal file
20
packages/bthlabs-jsonrpc-django/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)
|
||||
21
packages/bthlabs-jsonrpc-django/docs/source/api.rst
Normal file
21
packages/bthlabs-jsonrpc-django/docs/source/api.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
.. module:: bthlabs_jsonrpc_django
|
||||
|
||||
This section provides the API documentation for BTHLabs JSONRPC - Core.
|
||||
|
||||
Auth checks
|
||||
-----------
|
||||
|
||||
.. autofunction:: has_perms
|
||||
|
||||
.. autofunction:: is_authenticated
|
||||
|
||||
.. autofunction:: is_staff
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
.. autoclass:: JSONRPCView
|
||||
:members: as_view, auth_checks, can_call, namespace
|
||||
57
packages/bthlabs-jsonrpc-django/docs/source/conf.py
Normal file
57
packages/bthlabs-jsonrpc-django/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 - Django'
|
||||
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-django/docs/source/index.rst
Normal file
17
packages/bthlabs-jsonrpc-django/docs/source/index.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
BTHLabs JSONRPC - Django
|
||||
========================
|
||||
|
||||
BTHLabs JSONRPC is a set of Python libraries that provide extensible framework
|
||||
for adding JSONRPC interfaces to existing Python Web applications.
|
||||
|
||||
The *django* package provides Django integration.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
57
packages/bthlabs-jsonrpc-django/docs/source/overview.rst
Normal file
57
packages/bthlabs-jsonrpc-django/docs/source/overview.rst
Normal file
@@ -0,0 +1,57 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
This section provides the general overview of the integration.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pip install bthlabs_jsonrpc_django
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
First, you'll need to enable the application by adding it to
|
||||
``INSTALLED_APPS``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# settings.py
|
||||
INSTALLED_APPS = [
|
||||
# ...
|
||||
'bthlabs_jsonrpc_django',
|
||||
]
|
||||
|
||||
Then, you'll need to add your RPC method modules to ``JSONRPC_METHOD_MODULES``
|
||||
setting:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# settings.py
|
||||
JSONRPC_METHOD_MODULES = [
|
||||
# ...
|
||||
'your_app.rpc_methods',
|
||||
]
|
||||
|
||||
After that, you'll need to add a JSONRPC view to your project's URLs:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# urls.py
|
||||
urlpatterns = [
|
||||
# ...
|
||||
path('rpc', JSONRPCView.as_view()),
|
||||
]
|
||||
|
||||
Last but not least, 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')
|
||||
def hello(request, who='World'):
|
||||
return f'Hello, {who}!'
|
||||
1
packages/bthlabs-jsonrpc-django/example/.gitignore
vendored
Normal file
1
packages/bthlabs-jsonrpc-django/example/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/db.sqlite3
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- 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/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault(
|
||||
'DJANGO_SETTINGS_MODULE',
|
||||
'bthlabs_jsonrpc_django_example.settings.production',
|
||||
)
|
||||
|
||||
application = get_asgi_application()
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||
|
||||
|
||||
class CustomHeaderRemoteUserMiddleware(RemoteUserMiddleware):
|
||||
header = 'HTTP_X_USER'
|
||||
1
packages/bthlabs-jsonrpc-django/example/bthlabs_jsonrpc_django_example/settings/.gitignore
vendored
Normal file
1
packages/bthlabs-jsonrpc-django/example/bthlabs_jsonrpc_django_example/settings/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/local.py
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
SECRET_KEY = None
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# Django apps
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# 3rd party apps
|
||||
'bthlabs_jsonrpc_django',
|
||||
|
||||
# Project apps
|
||||
'bthlabs_jsonrpc_django_example.things',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'bthlabs_jsonrpc_django_example.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'bthlabs_jsonrpc_django_example.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
JSONRPC_METHOD_MODULES = [
|
||||
'bthlabs_jsonrpc_django_example.things.rpc_methods',
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
from bthlabs_jsonrpc_django_example.things import models
|
||||
|
||||
|
||||
class ThingAdmin(admin.ModelAdmin):
|
||||
list_display = ('pk', 'name', 'created_at', 'owner', 'is_active')
|
||||
|
||||
|
||||
admin.site.register(models.Thing, ThingAdmin)
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ThingsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'bthlabs_jsonrpc_django_example.things'
|
||||
label = 'things'
|
||||
verbose_name = 'Things'
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-12 06:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Thing',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('content', models.TextField()),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'thing',
|
||||
'verbose_name_plural': 'things',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Thing(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
content = models.TextField()
|
||||
created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
|
||||
modified_at = models.DateTimeField(auto_now=True, auto_now_add=False)
|
||||
is_active = models.BooleanField(default=True, db_index=True)
|
||||
|
||||
owner = models.ForeignKey(
|
||||
'auth.User', null=True, blank=True, on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'thing'
|
||||
verbose_name_plural = 'things'
|
||||
|
||||
def to_rpc(self):
|
||||
return {
|
||||
'id': self.pk,
|
||||
'name': self.name,
|
||||
'content': self.content,
|
||||
'created_at': self.created_at,
|
||||
'modified_at': self.modified_at,
|
||||
'is_active': self.is_active,
|
||||
'owner_id': self.owner_id,
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
|
||||
from bthlabs_jsonrpc_core import JSONRPCAccessDeniedError, register_method
|
||||
|
||||
from bthlabs_jsonrpc_django_example.things.models import Thing
|
||||
|
||||
|
||||
@register_method('things.list')
|
||||
def list_things(request):
|
||||
return Thing.objects.filter(
|
||||
is_active=True,
|
||||
owner__isnull=True,
|
||||
)
|
||||
|
||||
|
||||
@register_method('things.list', namespace='private')
|
||||
def private_list_things(request):
|
||||
return Thing.objects.filter(
|
||||
is_active=True,
|
||||
owner=request.user,
|
||||
)
|
||||
|
||||
|
||||
@register_method('things.create', namespace='private')
|
||||
def private_create_thing(request, name, content):
|
||||
return Thing.objects.create(
|
||||
name=name,
|
||||
content=content,
|
||||
is_active=True,
|
||||
owner=request.user,
|
||||
)
|
||||
|
||||
|
||||
@register_method('things.update', namespace='private')
|
||||
def private_update_thing(request, thing_id, name=None, content=None):
|
||||
thing = Thing.objects.get(pk=thing_id, is_active=True)
|
||||
if thing.owner != request.user:
|
||||
raise JSONRPCAccessDeniedError("You can't access this thing.")
|
||||
|
||||
if name is not None:
|
||||
thing.name = name
|
||||
|
||||
if content is not None:
|
||||
thing.content = content
|
||||
|
||||
thing.save()
|
||||
|
||||
return thing
|
||||
|
||||
|
||||
@register_method('things.list', namespace='admin')
|
||||
def admin_list_things(request):
|
||||
return Thing.objects.all()
|
||||
|
||||
|
||||
@register_method('things.create', namespace='admin')
|
||||
def admin_create_thing(request, name, content, is_active, owner_id):
|
||||
return Thing.objects.create(
|
||||
name=name,
|
||||
content=content,
|
||||
is_active=is_active,
|
||||
owner_id=owner_id,
|
||||
)
|
||||
|
||||
|
||||
@register_method('things.update', namespace='admin')
|
||||
def admin_update_thing(request,
|
||||
thing_id,
|
||||
name=None,
|
||||
content=None,
|
||||
is_active=None,
|
||||
owner_id=None):
|
||||
thing = Thing.objects.get(pk=thing_id)
|
||||
|
||||
if name is not None:
|
||||
thing.name = name
|
||||
|
||||
if content is not None:
|
||||
thing.content = content
|
||||
|
||||
if is_active is not None:
|
||||
thing.is_active = is_active
|
||||
|
||||
if owner_id is not None:
|
||||
thing.owner_id = owner_id
|
||||
|
||||
thing.save()
|
||||
|
||||
return thing
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated, is_staff
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path(
|
||||
'rpc/admin',
|
||||
JSONRPCView.as_view(
|
||||
auth_checks=[is_authenticated, is_staff],
|
||||
namespace='admin',
|
||||
),
|
||||
),
|
||||
path(
|
||||
'rpc/private',
|
||||
JSONRPCView.as_view(
|
||||
auth_checks=[is_authenticated],
|
||||
namespace='private',
|
||||
),
|
||||
),
|
||||
path('rpc', JSONRPCView.as_view()),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- 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/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault(
|
||||
'DJANGO_SETTINGS_MODULE',
|
||||
'bthlabs_jsonrpc_django_example.settings.production',
|
||||
)
|
||||
|
||||
application = get_wsgi_application()
|
||||
27
packages/bthlabs-jsonrpc-django/example/manage.py
Executable file
27
packages/bthlabs-jsonrpc-django/example/manage.py
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
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:
|
||||
raise ImportError(
|
||||
(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
),
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
814
packages/bthlabs-jsonrpc-django/poetry.lock
generated
Normal file
814
packages/bthlabs-jsonrpc-django/poetry.lock
generated
Normal file
@@ -0,0 +1,814 @@
|
||||
[[package]]
|
||||
name = "alabaster"
|
||||
version = "0.7.12"
|
||||
description = "A configurable sidebar-enabled Sphinx theme"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.5.2"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.4.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.10.1"
|
||||
description = "Internationalization utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pytz = ">=2015.7"
|
||||
|
||||
[[package]]
|
||||
name = "bthlabs-jsonrpc-core"
|
||||
version = "1.0.0"
|
||||
description = "BTHLabs JSONRPC - Core"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "^3.10"
|
||||
develop = true
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
url = "../bthlabs-jsonrpc-core"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.5.18.1"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.12"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
|
||||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "3.2.13"
|
||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.3.2,<4"
|
||||
pytz = "*"
|
||||
sqlparse = ">=0.2.2"
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "docutils"
|
||||
version = "0.17.1"
|
||||
description = "Docutils -- Python Documentation Utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "factory-boy"
|
||||
version = "3.2.1"
|
||||
description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
Faker = ">=0.7.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage", "django", "flake8", "isort", "pillow", "sqlalchemy", "mongoengine", "wheel (>=0.32.0)", "tox", "zest.releaser"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "13.12.0"
|
||||
description = "Faker is a Python package that generates fake data for you."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.4"
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "4.0.1"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
mccabe = ">=0.6.0,<0.7.0"
|
||||
pycodestyle = ">=2.8.0,<2.9.0"
|
||||
pyflakes = ">=2.4.0,<2.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-commas"
|
||||
version = "2.1.0"
|
||||
description = "Flake8 lint for trailing commas."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
flake8 = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "1.3.0"
|
||||
description = "Getting image size from png/jpeg/jpeg2000/gif file"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "mccabe"
|
||||
version = "0.6.1"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.950"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.8.0"
|
||||
description = "Python style guide checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "2.4.0"
|
||||
description = "passive checker of Python programs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.12.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.1.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
py = ">=1.8.2"
|
||||
tomli = ">=1.0.0"
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
version = "4.5.2"
|
||||
description = "A Django plugin for pytest."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=5.4.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||
testing = ["django", "django-configurations (>=2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.27.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
|
||||
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "snowballstemmer"
|
||||
version = "2.2.0"
|
||||
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "4.5.0"
|
||||
description = "Python documentation generator"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
alabaster = ">=0.7,<0.8"
|
||||
babel = ">=1.3"
|
||||
colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
|
||||
docutils = ">=0.14,<0.18"
|
||||
imagesize = "*"
|
||||
Jinja2 = ">=2.3"
|
||||
packaging = "*"
|
||||
Pygments = ">=2.0"
|
||||
requests = ">=2.5.0"
|
||||
snowballstemmer = ">=1.1"
|
||||
sphinxcontrib-applehelp = "*"
|
||||
sphinxcontrib-devhelp = "*"
|
||||
sphinxcontrib-htmlhelp = ">=2.0.0"
|
||||
sphinxcontrib-jsmath = "*"
|
||||
sphinxcontrib-qthelp = "*"
|
||||
sphinxcontrib-serializinghtml = ">=1.1.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinxcontrib-websupport"]
|
||||
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"]
|
||||
test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-rtd-theme"
|
||||
version = "1.0.0"
|
||||
description = "Read the Docs theme for Sphinx"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
docutils = "<0.18"
|
||||
sphinx = ">=1.6"
|
||||
|
||||
[package.extras]
|
||||
dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-applehelp"
|
||||
version = "1.0.2"
|
||||
description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-devhelp"
|
||||
version = "1.0.2"
|
||||
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-htmlhelp"
|
||||
version = "2.0.0"
|
||||
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||
test = ["pytest", "html5lib"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-jsmath"
|
||||
version = "1.0.1"
|
||||
description = "A sphinx extension which renders display math in HTML via JavaScript"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-qthelp"
|
||||
version = "1.0.3"
|
||||
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-serializinghtml"
|
||||
version = "1.1.5"
|
||||
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.4.2"
|
||||
description = "A non-validating SQL parser."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.2.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.9"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "0d2fc52b8eaf0c5363f865d3c32b6a3f804e6429c798c5841d2ec45f5d56d222"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
||||
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
||||
]
|
||||
asgiref = [
|
||||
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
|
||||
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
babel = [
|
||||
{file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"},
|
||||
{file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"},
|
||||
]
|
||||
bthlabs-jsonrpc-core = []
|
||||
certifi = [
|
||||
{file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
|
||||
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
django = [
|
||||
{file = "Django-3.2.13-py3-none-any.whl", hash = "sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf"},
|
||||
{file = "Django-3.2.13.tar.gz", hash = "sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6"},
|
||||
]
|
||||
docutils = [
|
||||
{file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
|
||||
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
|
||||
]
|
||||
factory-boy = [
|
||||
{file = "factory_boy-3.2.1-py2.py3-none-any.whl", hash = "sha256:eb02a7dd1b577ef606b75a253b9818e6f9eaf996d94449c9d5ebb124f90dc795"},
|
||||
{file = "factory_boy-3.2.1.tar.gz", hash = "sha256:a98d277b0c047c75eb6e4ab8508a7f81fb03d2cb21986f627913546ef7a2a55e"},
|
||||
]
|
||||
faker = [
|
||||
{file = "Faker-13.12.0-py3-none-any.whl", hash = "sha256:5cbb89fc6a16793b2bd98252c03a86098c7426beab0a20382709a815651b8804"},
|
||||
{file = "Faker-13.12.0.tar.gz", hash = "sha256:1f6478011ac8a8273e0f9cd6da03d9ea6391c622db340eca015339512e9cde29"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
|
||||
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
|
||||
]
|
||||
flake8-commas = [
|
||||
{file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"},
|
||||
{file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
imagesize = [
|
||||
{file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"},
|
||||
{file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22"},
|
||||
{file = "mypy-0.950-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb"},
|
||||
{file = "mypy-0.950-cp310-cp310-win_amd64.whl", hash = "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334"},
|
||||
{file = "mypy-0.950-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f"},
|
||||
{file = "mypy-0.950-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc"},
|
||||
{file = "mypy-0.950-cp36-cp36m-win_amd64.whl", hash = "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2"},
|
||||
{file = "mypy-0.950-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed"},
|
||||
{file = "mypy-0.950-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075"},
|
||||
{file = "mypy-0.950-cp37-cp37m-win_amd64.whl", hash = "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605"},
|
||||
{file = "mypy-0.950-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2"},
|
||||
{file = "mypy-0.950-cp38-cp38-win_amd64.whl", hash = "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2"},
|
||||
{file = "mypy-0.950-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519"},
|
||||
{file = "mypy-0.950-cp39-cp39-win_amd64.whl", hash = "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef"},
|
||||
{file = "mypy-0.950-py3-none-any.whl", hash = "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb"},
|
||||
{file = "mypy-0.950.tar.gz", hash = "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pyflakes = [
|
||||
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
|
||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
|
||||
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
||||
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
|
||||
]
|
||||
pytest-django = [
|
||||
{file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"},
|
||||
{file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
|
||||
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
snowballstemmer = [
|
||||
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
|
||||
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
|
||||
]
|
||||
sphinx = [
|
||||
{file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
|
||||
{file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
|
||||
]
|
||||
sphinx-rtd-theme = [
|
||||
{file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
|
||||
{file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
|
||||
]
|
||||
sphinxcontrib-applehelp = [
|
||||
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
|
||||
{file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
|
||||
]
|
||||
sphinxcontrib-devhelp = [
|
||||
{file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
|
||||
{file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
|
||||
]
|
||||
sphinxcontrib-htmlhelp = [
|
||||
{file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
|
||||
{file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
|
||||
]
|
||||
sphinxcontrib-jsmath = [
|
||||
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
|
||||
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
|
||||
]
|
||||
sphinxcontrib-qthelp = [
|
||||
{file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
|
||||
{file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
|
||||
]
|
||||
sphinxcontrib-serializinghtml = [
|
||||
{file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
|
||||
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
|
||||
]
|
||||
sqlparse = [
|
||||
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
|
||||
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
||||
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
|
||||
]
|
||||
3
packages/bthlabs-jsonrpc-django/poetry.toml
Normal file
3
packages/bthlabs-jsonrpc-django/poetry.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[virtualenvs]
|
||||
create = true
|
||||
in-project = true
|
||||
32
packages/bthlabs-jsonrpc-django/pyproject.toml
Normal file
32
packages/bthlabs-jsonrpc-django/pyproject.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[tool.poetry]
|
||||
name = "bthlabs-jsonrpc-django"
|
||||
version = "1.0.0"
|
||||
description = "BTHLabs JSONRPC - Django 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"
|
||||
django = ">=3.2,<5.0"
|
||||
bthlabs-jsonrpc-core = "1.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
bthlabs-jsonrpc-core = { path = "../bthlabs-jsonrpc-core", develop = true }
|
||||
django = "3.2.13"
|
||||
factory-boy = "3.2.1"
|
||||
flake8 = "4.0.1"
|
||||
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"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
7
packages/bthlabs-jsonrpc-django/setup.cfg
Normal file
7
packages/bthlabs-jsonrpc-django/setup.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[flake8]
|
||||
exclude = .venv/,.pytest_cache/,example/*/migrations/*.py,testing/migrations/*.py
|
||||
ignore = E402
|
||||
max-line-length = 119
|
||||
|
||||
[tool:pytest]
|
||||
DJANGO_SETTINGS_MODULE = testing.settings
|
||||
2
packages/bthlabs-jsonrpc-django/skel/envrc
Normal file
2
packages/bthlabs-jsonrpc-django/skel/envrc
Normal file
@@ -0,0 +1,2 @@
|
||||
export VIRTUAL_ENV="`realpath .venv`"
|
||||
export PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
1
packages/bthlabs-jsonrpc-django/testing/.gitignore
vendored
Normal file
1
packages/bthlabs-jsonrpc-django/testing/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/db.sqlite3
|
||||
0
packages/bthlabs-jsonrpc-django/testing/__init__.py
Normal file
0
packages/bthlabs-jsonrpc-django/testing/__init__.py
Normal file
6
packages/bthlabs-jsonrpc-django/testing/apps.py
Normal file
6
packages/bthlabs-jsonrpc-django/testing/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TestingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'testing'
|
||||
14
packages/bthlabs-jsonrpc-django/testing/factories.py
Normal file
14
packages/bthlabs-jsonrpc-django/testing/factories.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import factory
|
||||
|
||||
from testing.models import Thing
|
||||
|
||||
|
||||
class ThingFactory(factory.django.DjangoModelFactory):
|
||||
name = factory.Faker('name')
|
||||
content = factory.Faker('sentence')
|
||||
is_active = True
|
||||
owner = None
|
||||
|
||||
class Meta:
|
||||
model = Thing
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-13 06:44
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Thing',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('content', models.TextField()),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('modified_at', models.DateTimeField(auto_now=True)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'thing',
|
||||
'verbose_name_plural': 'things',
|
||||
},
|
||||
),
|
||||
]
|
||||
29
packages/bthlabs-jsonrpc-django/testing/models.py
Normal file
29
packages/bthlabs-jsonrpc-django/testing/models.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Thing(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
content = models.TextField()
|
||||
created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
|
||||
modified_at = models.DateTimeField(auto_now=True, auto_now_add=False)
|
||||
is_active = models.BooleanField(default=True, db_index=True)
|
||||
|
||||
owner = models.ForeignKey(
|
||||
'auth.User', null=True, blank=True, on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'thing'
|
||||
verbose_name_plural = 'things'
|
||||
|
||||
def to_rpc(self):
|
||||
return {
|
||||
'id': self.pk,
|
||||
'name': self.name,
|
||||
'content': self.content,
|
||||
'created_at': self.created_at,
|
||||
'modified_at': self.modified_at,
|
||||
'is_active': self.is_active,
|
||||
'owner_id': self.owner_id,
|
||||
}
|
||||
77
packages/bthlabs-jsonrpc-django/testing/settings.py
Normal file
77
packages/bthlabs-jsonrpc-django/testing/settings.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
SECRET_KEY = 'bthlabs_jsonrpc_django'
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# Django apps
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# 3rd party apps
|
||||
'bthlabs_jsonrpc_django',
|
||||
|
||||
# Project apps
|
||||
'testing',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'testing.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
},
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
JSONRPC_METHOD_MODULES = [
|
||||
]
|
||||
15
packages/bthlabs-jsonrpc-django/testing/urls.py
Normal file
15
packages/bthlabs-jsonrpc-django/testing/urls.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.urls import path
|
||||
|
||||
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'rpc/private',
|
||||
JSONRPCView.as_view(
|
||||
auth_checks=[is_authenticated],
|
||||
namespace='private',
|
||||
),
|
||||
),
|
||||
path('rpc', JSONRPCView.as_view()),
|
||||
]
|
||||
0
packages/bthlabs-jsonrpc-django/tests/__init_.py
Normal file
0
packages/bthlabs-jsonrpc-django/tests/__init_.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_has_perms_regular_user(rf, user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
|
||||
check = auth_checks.has_perms(['can_use_rpc'])
|
||||
|
||||
# When
|
||||
result = check(request)
|
||||
|
||||
# Then
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_has_perms_ok(rf, user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
|
||||
check = auth_checks.has_perms(['can_use_rpc'])
|
||||
|
||||
with mock.patch.object(user, 'has_perms') as mock_has_perms:
|
||||
mock_has_perms.return_value = True
|
||||
|
||||
# When
|
||||
result = check(request)
|
||||
|
||||
# Then
|
||||
assert result is True
|
||||
|
||||
mock_has_perms.assert_called_with(['can_use_rpc'])
|
||||
|
||||
|
||||
def test_has_perms_ok_super_user(rf, super_user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = super_user
|
||||
|
||||
check = auth_checks.has_perms(['can_use_rpc'])
|
||||
|
||||
# When
|
||||
result = check(request)
|
||||
|
||||
# Then
|
||||
assert result is True
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_is_authenticated_anonymous_user(rf):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = AnonymousUser()
|
||||
|
||||
# When
|
||||
result = auth_checks.is_authenticated(request)
|
||||
|
||||
# Then
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_authenticated_inactive(rf, inactive_user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = inactive_user
|
||||
|
||||
# When
|
||||
result = auth_checks.is_authenticated(request)
|
||||
|
||||
# Then
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_authenticated_ok(rf, user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
|
||||
# When
|
||||
result = auth_checks.is_authenticated(request)
|
||||
|
||||
# Then
|
||||
assert result is True
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from bthlabs_jsonrpc_django import auth_checks
|
||||
|
||||
|
||||
def test_is_staff_regular_user(rf, user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = user
|
||||
|
||||
# When
|
||||
result = auth_checks.is_staff(request)
|
||||
|
||||
# Then
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_staff_ok(rf, staff_user):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
request.user = staff_user
|
||||
|
||||
# When
|
||||
result = auth_checks.is_staff(request)
|
||||
|
||||
# Then
|
||||
assert result is True
|
||||
46
packages/bthlabs-jsonrpc-django/tests/conftest.py
Normal file
46
packages/bthlabs-jsonrpc-django/tests/conftest.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user(db):
|
||||
return UserFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inactive_user(db):
|
||||
return UserFactory(is_active=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def staff_user(db):
|
||||
return UserFactory(is_staff=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def super_user(db):
|
||||
return UserFactory(is_superuser=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call():
|
||||
return {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'system.list_methods',
|
||||
'method': 'system.list_methods',
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from bthlabs_jsonrpc_core import exceptions
|
||||
import pytest
|
||||
|
||||
from bthlabs_jsonrpc_django import executor
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_can_call():
|
||||
return mock.Mock()
|
||||
|
||||
|
||||
def test_init(rf, fake_can_call):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
# When
|
||||
result = executor.DjangoExecutor(request, fake_can_call)
|
||||
|
||||
# Then
|
||||
assert result.request == request
|
||||
assert result.can_call == fake_can_call
|
||||
|
||||
|
||||
def test_enrich_args(rf, fake_can_call):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
the_executor = executor.DjangoExecutor(request, fake_can_call)
|
||||
|
||||
# When
|
||||
result = the_executor.enrich_args(['spam'])
|
||||
|
||||
# Then
|
||||
assert result == [request, 'spam']
|
||||
|
||||
|
||||
def test_before_call(rf, fake_can_call):
|
||||
# Given
|
||||
request = rf.get('/')
|
||||
|
||||
the_executor = executor.DjangoExecutor(request, fake_can_call)
|
||||
|
||||
# When
|
||||
the_executor.before_call('test', ['spam'], {'spam': True})
|
||||
|
||||
# Then
|
||||
fake_can_call.assert_called_with(request, 'test', ['spam'], {'spam': True})
|
||||
|
||||
|
||||
def test_before_call_access_denied(rf, fake_can_call):
|
||||
# Given
|
||||
fake_can_call.return_value = False
|
||||
|
||||
request = rf.get('/')
|
||||
|
||||
the_executor = executor.DjangoExecutor(request, fake_can_call)
|
||||
|
||||
# When
|
||||
try:
|
||||
the_executor.before_call('test', ['spam'], {'spam': True})
|
||||
except Exception as exception:
|
||||
assert isinstance(exception, exceptions.JSONRPCAccessDeniedError)
|
||||
else:
|
||||
assert False, 'No exception raised?'
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
from bthlabs_jsonrpc_django import serializer
|
||||
from testing.factories import ThingFactory
|
||||
from testing.models import Thing
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_serialize_value_query_set():
|
||||
# Given
|
||||
things = [ThingFactory() for _ in range(0, 3)]
|
||||
|
||||
query_set = Thing.objects.\
|
||||
filter(pk__in=[thing.pk for thing in things]).\
|
||||
order_by('pk')
|
||||
|
||||
the_serializer = serializer.DjangoJSONRPCSerializer('spam')
|
||||
|
||||
# When
|
||||
result = the_serializer.serialize_value(query_set)
|
||||
|
||||
# Then
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 3
|
||||
|
||||
expected_serialized_thing = things[0].to_rpc()
|
||||
expected_serialized_thing.update({
|
||||
'created_at': expected_serialized_thing['created_at'].isoformat(),
|
||||
'modified_at': expected_serialized_thing['modified_at'].isoformat(),
|
||||
})
|
||||
assert result[0] == expected_serialized_thing
|
||||
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from bthlabs_jsonrpc_core import exceptions
|
||||
|
||||
|
||||
def test_view(client):
|
||||
# Given
|
||||
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 = client.post('/rpc', data=batch, content_type='application/json')
|
||||
|
||||
# Then
|
||||
assert response.status_code == 200
|
||||
|
||||
data = 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
|
||||
|
||||
|
||||
def test_view_empty_response(client, call):
|
||||
# Given
|
||||
call.pop('id')
|
||||
|
||||
# When
|
||||
response = client.post('/rpc', data=call, content_type='application/json')
|
||||
|
||||
# Then
|
||||
assert response.status_code == 200
|
||||
assert response.content == b''
|
||||
|
||||
|
||||
def test_view_with_auth_checks(client, user, call):
|
||||
# Given
|
||||
client.force_login(user)
|
||||
|
||||
# When
|
||||
response = client.post(
|
||||
'/rpc/private', data=call, content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
expected_result_data = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'system.list_methods',
|
||||
'result': ['system.list_methods'],
|
||||
}
|
||||
assert data == expected_result_data
|
||||
|
||||
|
||||
def test_view_with_auth_checks_permission_denied(client, call):
|
||||
# When
|
||||
response = client.post(
|
||||
'/rpc/private', data=call, content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert response.status_code == 403
|
||||
Reference in New Issue
Block a user