pyrpc-django-adapter 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrpc_django_adapter-0.7.2/.gitignore +161 -0
- pyrpc_django_adapter-0.7.2/PKG-INFO +7 -0
- pyrpc_django_adapter-0.7.2/pyproject.toml +16 -0
- pyrpc_django_adapter-0.7.2/src/pyrpc_django/__init__.py +38 -0
- pyrpc_django_adapter-0.7.2/tests/conftest.py +12 -0
- pyrpc_django_adapter-0.7.2/tests/test_django.py +82 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
!docs/lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Used for packaging Python scripts into standalone executables
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.stats
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
pytestdebug.log
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# Project-specific python versions
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if Pipfile.lock (and requirements.txt) are not
|
|
93
|
+
# preferred, then add them into the ignore list.
|
|
94
|
+
# Pipfile.lock
|
|
95
|
+
|
|
96
|
+
# poetry
|
|
97
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
98
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
99
|
+
# poetry.lock
|
|
100
|
+
|
|
101
|
+
# pdm
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
103
|
+
# pdm.lock
|
|
104
|
+
# .pdm-python
|
|
105
|
+
|
|
106
|
+
# PEP 582; used by e.g. github.com/fannheyward/coc-pyright
|
|
107
|
+
__pypackages__/
|
|
108
|
+
|
|
109
|
+
# Celery stuff
|
|
110
|
+
celerybeat-schedule
|
|
111
|
+
celerybeat.pid
|
|
112
|
+
|
|
113
|
+
# SageMath parsed files
|
|
114
|
+
*.sage.py
|
|
115
|
+
|
|
116
|
+
# Environments
|
|
117
|
+
.env
|
|
118
|
+
.venv
|
|
119
|
+
env/
|
|
120
|
+
venv/
|
|
121
|
+
ENV/
|
|
122
|
+
env.bak/
|
|
123
|
+
venv.bak/
|
|
124
|
+
|
|
125
|
+
# Spyder project settings
|
|
126
|
+
.spyderproject
|
|
127
|
+
.spyderformpoint
|
|
128
|
+
|
|
129
|
+
# Rope project settings
|
|
130
|
+
.ropeproject
|
|
131
|
+
|
|
132
|
+
# mkdocs documentation
|
|
133
|
+
/site
|
|
134
|
+
|
|
135
|
+
# mypy
|
|
136
|
+
.mypy_cache/
|
|
137
|
+
.dmypy.json
|
|
138
|
+
dmypy.json
|
|
139
|
+
|
|
140
|
+
# Pyre type checker
|
|
141
|
+
.pyre/
|
|
142
|
+
|
|
143
|
+
# pytype static type analyzer
|
|
144
|
+
.pytype/
|
|
145
|
+
|
|
146
|
+
# Cython debug symbols
|
|
147
|
+
cython_debug/
|
|
148
|
+
|
|
149
|
+
# OS X
|
|
150
|
+
.DS_Store
|
|
151
|
+
|
|
152
|
+
# Node modules
|
|
153
|
+
node_modules
|
|
154
|
+
dist
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# System design docs (local developer documentation)
|
|
158
|
+
system-design/
|
|
159
|
+
|
|
160
|
+
# Scripts
|
|
161
|
+
scripts/seed_downloads.py
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrpc-django-adapter"
|
|
3
|
+
version = "0.7.2"
|
|
4
|
+
description = "Django adapter for pyRPC"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"pyrpc-core",
|
|
8
|
+
"django>=4.2.0",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["hatchling"]
|
|
13
|
+
build-backend = "hatchling.build"
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
packages = ["src/pyrpc_django"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pyrpc_core import handle_request, Router, rpc, model, default_router
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def mount_django(urlpatterns: list, router: Optional[Router] = None) -> None:
|
|
6
|
+
from django.http import HttpRequest, JsonResponse
|
|
7
|
+
from django.urls import path
|
|
8
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
resolved = router or default_router
|
|
12
|
+
|
|
13
|
+
@csrf_exempt
|
|
14
|
+
async def rpc_endpoint(request: HttpRequest) -> JsonResponse:
|
|
15
|
+
if request.method != "POST":
|
|
16
|
+
return JsonResponse({"error": "Method not allowed"}, status=405)
|
|
17
|
+
try:
|
|
18
|
+
payload = json.loads(request.body)
|
|
19
|
+
except json.JSONDecodeError:
|
|
20
|
+
return JsonResponse({"error": "Invalid JSON"}, status=400)
|
|
21
|
+
response_dict = await handle_request(payload, router=resolved)
|
|
22
|
+
return JsonResponse(response_dict)
|
|
23
|
+
|
|
24
|
+
@csrf_exempt
|
|
25
|
+
async def introspection_endpoint(request: HttpRequest) -> JsonResponse:
|
|
26
|
+
if request.method != "GET":
|
|
27
|
+
return JsonResponse({"error": "Method not allowed"}, status=405)
|
|
28
|
+
from pyrpc_core import get_registry_schema
|
|
29
|
+
schemas = get_registry_schema(resolved)
|
|
30
|
+
return JsonResponse({
|
|
31
|
+
name: schema.model_dump() if hasattr(schema, "model_dump") else schema
|
|
32
|
+
for name, schema in schemas.items()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
urlpatterns.extend([
|
|
36
|
+
path("rpc", rpc_endpoint, name="pyrpc-rpc"),
|
|
37
|
+
path("rpc", introspection_endpoint, name="pyrpc-introspection"),
|
|
38
|
+
])
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import django
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
|
|
4
|
+
settings.configure(
|
|
5
|
+
DEBUG=True,
|
|
6
|
+
SECRET_KEY="test-secret-key-for-pyrpc",
|
|
7
|
+
ROOT_URLCONF=__name__,
|
|
8
|
+
ALLOWED_HOSTS=["*"],
|
|
9
|
+
INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth"],
|
|
10
|
+
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
|
|
11
|
+
)
|
|
12
|
+
django.setup()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import anyio
|
|
3
|
+
import pytest
|
|
4
|
+
from pyrpc_core import rpc, default_router
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture(autouse=True)
|
|
8
|
+
def clear_registry():
|
|
9
|
+
default_router._procedures.clear()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_django_mount_success():
|
|
13
|
+
from pyrpc_django import mount_django
|
|
14
|
+
from django.test import RequestFactory
|
|
15
|
+
|
|
16
|
+
@rpc
|
|
17
|
+
def greet(name: str) -> str:
|
|
18
|
+
return f"Hello {name}"
|
|
19
|
+
|
|
20
|
+
urlpatterns = []
|
|
21
|
+
mount_django(urlpatterns)
|
|
22
|
+
|
|
23
|
+
factory = RequestFactory()
|
|
24
|
+
payload = {"id": "d-1", "method": "greet", "params": {"name": "Django"}}
|
|
25
|
+
request = factory.post("/rpc", json.dumps(payload), content_type="application/json")
|
|
26
|
+
|
|
27
|
+
async def run():
|
|
28
|
+
response = await urlpatterns[0].callback(request)
|
|
29
|
+
assert response.status_code == 200
|
|
30
|
+
data = json.loads(response.content)
|
|
31
|
+
assert data["id"] == "d-1"
|
|
32
|
+
assert data["result"] == "Hello Django"
|
|
33
|
+
assert data["error"] is None
|
|
34
|
+
|
|
35
|
+
anyio.run(run)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_django_async_procedure():
|
|
39
|
+
from pyrpc_django import mount_django
|
|
40
|
+
from django.test import RequestFactory
|
|
41
|
+
|
|
42
|
+
@rpc
|
|
43
|
+
async def async_greet(name: str) -> str:
|
|
44
|
+
return f"Async Hello {name}"
|
|
45
|
+
|
|
46
|
+
urlpatterns = []
|
|
47
|
+
mount_django(urlpatterns)
|
|
48
|
+
|
|
49
|
+
factory = RequestFactory()
|
|
50
|
+
payload = {"id": "d-2", "method": "async_greet", "params": {"name": "World"}}
|
|
51
|
+
request = factory.post("/rpc", json.dumps(payload), content_type="application/json")
|
|
52
|
+
|
|
53
|
+
async def run():
|
|
54
|
+
response = await urlpatterns[0].callback(request)
|
|
55
|
+
assert response.status_code == 200
|
|
56
|
+
data = json.loads(response.content)
|
|
57
|
+
assert data["result"] == "Async Hello World"
|
|
58
|
+
|
|
59
|
+
anyio.run(run)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_django_introspection():
|
|
63
|
+
from pyrpc_django import mount_django
|
|
64
|
+
from django.test import RequestFactory
|
|
65
|
+
|
|
66
|
+
@rpc
|
|
67
|
+
def add(a: int, b: int) -> int:
|
|
68
|
+
return a + b
|
|
69
|
+
|
|
70
|
+
urlpatterns = []
|
|
71
|
+
mount_django(urlpatterns)
|
|
72
|
+
|
|
73
|
+
factory = RequestFactory()
|
|
74
|
+
request = factory.get("/rpc")
|
|
75
|
+
|
|
76
|
+
async def run():
|
|
77
|
+
response = await urlpatterns[1].callback(request)
|
|
78
|
+
assert response.status_code == 200
|
|
79
|
+
data = json.loads(response.content)
|
|
80
|
+
assert "add" in data
|
|
81
|
+
|
|
82
|
+
anyio.run(run)
|