django-jsonrpc-framework 0.1.0__py3-none-any.whl
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.
- django_jsonrpc_framework-0.1.0.dist-info/METADATA +101 -0
- django_jsonrpc_framework-0.1.0.dist-info/RECORD +65 -0
- django_jsonrpc_framework-0.1.0.dist-info/WHEEL +4 -0
- jsonrpc_framework/__init__.py +8 -0
- jsonrpc_framework/controller/__init__.py +7 -0
- jsonrpc_framework/controller/_base.py +103 -0
- jsonrpc_framework/controller/_route.py +71 -0
- jsonrpc_framework/controller/decor.py +81 -0
- jsonrpc_framework/controller/openrpc/__init__.py +0 -0
- jsonrpc_framework/controller/openrpc/_openrpc.py +101 -0
- jsonrpc_framework/controller/openrpc/collectors/__init__.py +5 -0
- jsonrpc_framework/controller/openrpc/collectors/_main_collector.py +57 -0
- jsonrpc_framework/controller/openrpc/collectors/_method_collector.py +198 -0
- jsonrpc_framework/core/__init__.py +3 -0
- jsonrpc_framework/core/error.py +37 -0
- jsonrpc_framework/core/models.py +41 -0
- jsonrpc_framework/logic/__init__.py +9 -0
- jsonrpc_framework/logic/dispatcher.py +140 -0
- jsonrpc_framework/logic/responser.py +27 -0
- jsonrpc_framework/logic/validator.py +56 -0
- jsonrpc_framework/management/commands/generate_openrpc.py +49 -0
- jsonrpc_framework/openrpc/__init__.py +0 -0
- jsonrpc_framework/openrpc/builder/__init__.py +0 -0
- jsonrpc_framework/openrpc/builder/builder.py +76 -0
- jsonrpc_framework/openrpc/document/__init__.py +0 -0
- jsonrpc_framework/openrpc/document/_base.py +8 -0
- jsonrpc_framework/openrpc/document/_openrpc_document.py +71 -0
- jsonrpc_framework/openrpc/document/common/__init__.py +27 -0
- jsonrpc_framework/openrpc/document/common/_descriptor.py +68 -0
- jsonrpc_framework/openrpc/document/common/_error.py +41 -0
- jsonrpc_framework/openrpc/document/common/_example.py +55 -0
- jsonrpc_framework/openrpc/document/common/_link.py +60 -0
- jsonrpc_framework/openrpc/document/common/_link_server.py +75 -0
- jsonrpc_framework/openrpc/document/common/_pairing_object.py +52 -0
- jsonrpc_framework/openrpc/document/common/_reference.py +26 -0
- jsonrpc_framework/openrpc/document/common/_schema.py +55 -0
- jsonrpc_framework/openrpc/document/common/_tag.py +45 -0
- jsonrpc_framework/openrpc/document/common/_utils.py +15 -0
- jsonrpc_framework/openrpc/document/components/__init__.py +5 -0
- jsonrpc_framework/openrpc/document/components/_components.py +87 -0
- jsonrpc_framework/openrpc/document/external_docs/__init__.py +5 -0
- jsonrpc_framework/openrpc/document/external_docs/_external_docs.py +27 -0
- jsonrpc_framework/openrpc/document/info/__init__.py +7 -0
- jsonrpc_framework/openrpc/document/info/_info.py +110 -0
- jsonrpc_framework/openrpc/document/method/__init__.py +5 -0
- jsonrpc_framework/openrpc/document/method/_method.py +134 -0
- jsonrpc_framework/openrpc/document/server/__init__.py +6 -0
- jsonrpc_framework/openrpc/document/server/_server.py +83 -0
- jsonrpc_framework/openrpc/tests/__init__.py +0 -0
- jsonrpc_framework/openrpc/tests/_conftest/__init__.py +6 -0
- jsonrpc_framework/openrpc/tests/_conftest/builder_conftest.py +0 -0
- jsonrpc_framework/openrpc/tests/_conftest/components_conftest.py +113 -0
- jsonrpc_framework/openrpc/tests/_conftest/external_doc_conftest.py +30 -0
- jsonrpc_framework/openrpc/tests/_conftest/info_conftest.py +59 -0
- jsonrpc_framework/openrpc/tests/_conftest/method_conftest.py +261 -0
- jsonrpc_framework/openrpc/tests/_conftest/server_conftest.py +49 -0
- jsonrpc_framework/openrpc/tests/conftest.py +1 -0
- jsonrpc_framework/openrpc/tests/test_builder.py +40 -0
- jsonrpc_framework/openrpc/tests/test_components.py +11 -0
- jsonrpc_framework/openrpc/tests/test_external_doc.py +24 -0
- jsonrpc_framework/openrpc/tests/test_openrcp_info.py +24 -0
- jsonrpc_framework/openrpc/tests/test_openrcp_method.py +26 -0
- jsonrpc_framework/openrpc/tests/test_server.py +24 -0
- jsonrpc_framework/py.typed +0 -0
- jsonrpc_framework/templates/openrpc_docs.html +91 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-jsonrpc-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Django jsonrpc implementation
|
|
5
|
+
Classifier: Framework :: Django
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: django>=6.0.5
|
|
9
|
+
Requires-Dist: pydantic>=2.13.4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Django jsonrpc implementation
|
|
13
|
+
|
|
14
|
+
## Advantages
|
|
15
|
+
|
|
16
|
+
- Complete support jsonrpc 2.0 (Request, Notificatin, Batch)
|
|
17
|
+
- Auto generation openrpc.json 1.3.2 version
|
|
18
|
+
- Auto generation OpenRPC documentation (like swagger)
|
|
19
|
+
- Async support
|
|
20
|
+
|
|
21
|
+
## Create methods
|
|
22
|
+
|
|
23
|
+
We provide several methods creating methods.
|
|
24
|
+
|
|
25
|
+
- Using `method_` prefix
|
|
26
|
+
- Using `jsonrpc_method` decorator
|
|
27
|
+
- Rename existing func to new name
|
|
28
|
+
|
|
29
|
+
``` python
|
|
30
|
+
|
|
31
|
+
from jsonrpc_framework import BaseController
|
|
32
|
+
from jsonrpc_framework.controller.decor import jsonrpc_method
|
|
33
|
+
|
|
34
|
+
class EchoController(BaseController):
|
|
35
|
+
|
|
36
|
+
def method_echo_hello(self, name: str) -> str:
|
|
37
|
+
return f"hello {name}"
|
|
38
|
+
|
|
39
|
+
@jsonrpc_method
|
|
40
|
+
def echo_goodbye(self, name: str) -> str:
|
|
41
|
+
return f"goodbye {name}"
|
|
42
|
+
|
|
43
|
+
@jsonrpc_method("echo_see_you")
|
|
44
|
+
def wrong_name(self, name) -> str:
|
|
45
|
+
return "See you fron echo_see_you method"
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Adding several controllers to one controller
|
|
50
|
+
|
|
51
|
+
``` python
|
|
52
|
+
from jsonrpc_framework import RouteController
|
|
53
|
+
|
|
54
|
+
class PrintController(BaseController):
|
|
55
|
+
|
|
56
|
+
async def method_print_hello(self, name) -> None:
|
|
57
|
+
print(f"hello {name}")
|
|
58
|
+
|
|
59
|
+
@jsonrpc_method
|
|
60
|
+
async def print_goodbye(self, name) -> None:
|
|
61
|
+
print(f"goodbye {name}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
route = RouteController(
|
|
65
|
+
'jsonrpc',
|
|
66
|
+
controllers=[
|
|
67
|
+
PrintController,
|
|
68
|
+
EchoController,
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Generation openrpc.json and OpenRpc documentation
|
|
75
|
+
|
|
76
|
+
``` python
|
|
77
|
+
from jsonrpc_framework.controller.openrpc.collectors import OpenRpcCollector
|
|
78
|
+
|
|
79
|
+
collector = OpenRpcCollector(
|
|
80
|
+
PrintController,
|
|
81
|
+
EchoController,
|
|
82
|
+
title='My mini API'
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
urlpatterns = [
|
|
87
|
+
path('echorpc', EchoController,as_view),
|
|
88
|
+
path('jsonrpc', route.as_view()),
|
|
89
|
+
path('openrpc.json', OpenRpcJsonView.as_view(collector=collector)),
|
|
90
|
+
path('docs', OpenRpcDocView.as_view()),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Openrpc.json example
|
|
96
|
+
|
|
97
|
+

|
|
98
|
+
|
|
99
|
+
## Openrpc doc example
|
|
100
|
+
|
|
101
|
+

|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
jsonrpc_framework/__init__.py,sha256=X6E4p4lLRgxj719HI1OqnzXx5chipNKylno-qy7R-Vk,183
|
|
2
|
+
jsonrpc_framework/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
jsonrpc_framework/controller/__init__.py,sha256=_GdmPAgDZ35sAxn-lfSG_E5atL6trGSc1PmR2LKAI6s,129
|
|
4
|
+
jsonrpc_framework/controller/_base.py,sha256=VVixZa1WC6eRAQCPGC2djubOftO3SJ6YlKVzi5w7TxA,2980
|
|
5
|
+
jsonrpc_framework/controller/_route.py,sha256=Ts0-nfVQuzjnz1KZzE9FCAC_9PoQ3oPdKhbKHLXXMTs,2303
|
|
6
|
+
jsonrpc_framework/controller/decor.py,sha256=ULIsqmxQV0Kytvzx0n9m4ee8VrIjBpR04T8sVPWH6h8,2223
|
|
7
|
+
jsonrpc_framework/controller/openrpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
jsonrpc_framework/controller/openrpc/_openrpc.py,sha256=k-eZeQzWpf3bvoeyFxI2vMrKX3eITbavUB9x3tH2R88,3498
|
|
9
|
+
jsonrpc_framework/controller/openrpc/collectors/__init__.py,sha256=DgCgfIjnbzwO902OquiDABFUfQz-QX_sBC0v8LeQfio,84
|
|
10
|
+
jsonrpc_framework/controller/openrpc/collectors/_main_collector.py,sha256=ZDfUPaOXkoArzv5NXuZx7qybL5cJ-yNxO4ctn8xpFYY,1762
|
|
11
|
+
jsonrpc_framework/controller/openrpc/collectors/_method_collector.py,sha256=vL8a2sEQRo1onx5RwRuK64F5-_uXpJnHIGq3i_IN_0I,7189
|
|
12
|
+
jsonrpc_framework/core/__init__.py,sha256=-offSuXGPBgoDOXP5w0Rabg3agv1XgF2QZXPTFj_Np8,239
|
|
13
|
+
jsonrpc_framework/core/error.py,sha256=JiGji2BP5i8HS6KL4mEY8G_TV41gQZFFqHT6zOIvWOk,864
|
|
14
|
+
jsonrpc_framework/core/models.py,sha256=XIz5VdkhBoFm_49nLu_6_24V3yoAfcuy8UyLDt0S_sY,923
|
|
15
|
+
jsonrpc_framework/logic/__init__.py,sha256=9WpK_qxeNUkpoI-Wy5rhaHODzHDQZ9dbxK9755G10OE,200
|
|
16
|
+
jsonrpc_framework/logic/dispatcher.py,sha256=G4PwVyzoHmIurVkgnQTeXeGkMtctNBz9BG4ZS69vsJw,4377
|
|
17
|
+
jsonrpc_framework/logic/responser.py,sha256=SuROgaMTl5FUgUKGk3P2it1I2j0Sq0J7qiIxUUTrj4c,837
|
|
18
|
+
jsonrpc_framework/logic/validator.py,sha256=URxgSMtKx5Vv5JwVIzrVHDTGAMBVPH4C0uWKFfqTXU8,1869
|
|
19
|
+
jsonrpc_framework/management/commands/generate_openrpc.py,sha256=OCJqXRBKQThOpSwMIa0AwyO4VlwOfjIsHacWug4J7dM,2000
|
|
20
|
+
jsonrpc_framework/openrpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
jsonrpc_framework/openrpc/builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
jsonrpc_framework/openrpc/builder/builder.py,sha256=RChn14Y_LMXT6ocffCLZ8cGkGbNR_zFEZvyPI1sJQ8M,2545
|
|
23
|
+
jsonrpc_framework/openrpc/document/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
jsonrpc_framework/openrpc/document/_base.py,sha256=A3_YUF45ntSoC4yJvksNp6eVnwQnemrg1h06SYtthnU,174
|
|
25
|
+
jsonrpc_framework/openrpc/document/_openrpc_document.py,sha256=7kwopKwLUEsGhqjoqcWtU7NP9Vco4HgmHi6c_GHkYFs,2618
|
|
26
|
+
jsonrpc_framework/openrpc/document/common/__init__.py,sha256=f2lPC_J7ySZ-y6s-kcxZMol_je9aeGMHeIUeiSEzDJU,880
|
|
27
|
+
jsonrpc_framework/openrpc/document/common/_descriptor.py,sha256=lcjcIvYXnmmk51FUFvEca7SVPOV2ZiZ7Fb6jGtsX4nE,2185
|
|
28
|
+
jsonrpc_framework/openrpc/document/common/_error.py,sha256=_sqL49WhBSu-ENJfoJqSlJfkCLcMZ1zDODOKpMHaFSE,1253
|
|
29
|
+
jsonrpc_framework/openrpc/document/common/_example.py,sha256=7Dc16LhVrY-5OALFg2HmZKgB9vEi2zuETQZP68TD58s,1572
|
|
30
|
+
jsonrpc_framework/openrpc/document/common/_link.py,sha256=xCf-U18HrsXiaelMnGMLmg0Y-KOeXVuoofJZsbRVI-g,1868
|
|
31
|
+
jsonrpc_framework/openrpc/document/common/_link_server.py,sha256=GOkCFAZbzF2GpLeozVc9p7MbKhU7Ot7NudWx6vOy2ts,2281
|
|
32
|
+
jsonrpc_framework/openrpc/document/common/_pairing_object.py,sha256=dihAs6OGaWAvhzYrGM91nEf2rmefocDphKAFdNFUHbk,1818
|
|
33
|
+
jsonrpc_framework/openrpc/document/common/_reference.py,sha256=FW2lRN_U0soWSh0oR3XjLBhh3JJF6xrIKW8divzx3FA,489
|
|
34
|
+
jsonrpc_framework/openrpc/document/common/_schema.py,sha256=g_cglrAsSDUzojC5M7TZj_Wrv3f69ExBnSL7jJTEtvM,1546
|
|
35
|
+
jsonrpc_framework/openrpc/document/common/_tag.py,sha256=AkxN6IhImyMd8_ftqk7tV5eA4ZrHHa6UAbyvKss1SbA,1194
|
|
36
|
+
jsonrpc_framework/openrpc/document/common/_utils.py,sha256=rCuuexL7cu4aEoJdQ6GbtMdENkqTjhdtYR8p66PHtJs,403
|
|
37
|
+
jsonrpc_framework/openrpc/document/components/__init__.py,sha256=kXo5OBxjfHMWRbt8irCO8wpZG-4GVtCUE6pgF5L_nF0,127
|
|
38
|
+
jsonrpc_framework/openrpc/document/components/_components.py,sha256=0oNxA1Hbu1ge62oryTa58-inW3rTgP3SKPT-kDl8aeY,2962
|
|
39
|
+
jsonrpc_framework/openrpc/document/external_docs/__init__.py,sha256=gVyRBn9VbWR5UljuhxdYLYmFLjrMpu_8IaVq7c5LsSk,136
|
|
40
|
+
jsonrpc_framework/openrpc/document/external_docs/_external_docs.py,sha256=af7NTLK4nv9NSlsKwknID0FEg9vfS3yD_Dt3cJVrgrM,738
|
|
41
|
+
jsonrpc_framework/openrpc/document/info/__init__.py,sha256=6MNAtJ1M6yoGZjnEdYqVPpeQz3Iv-_2zhhNIQ9cIyyU,179
|
|
42
|
+
jsonrpc_framework/openrpc/document/info/_info.py,sha256=e_rmaccnyrrrE_VFSkKKQcKsRPXPvwQ3y2efg5qLPt4,2908
|
|
43
|
+
jsonrpc_framework/openrpc/document/method/__init__.py,sha256=O3TzgWKg9QpKb61TB2cLOAdLRM1IKEYkndUQVu1rops,111
|
|
44
|
+
jsonrpc_framework/openrpc/document/method/_method.py,sha256=n7kK0FHnzSAPSmyEONmpvVjVy_dVgSMtAh0afEjbglA,5404
|
|
45
|
+
jsonrpc_framework/openrpc/document/server/__init__.py,sha256=XJwc5s7oT5FiKSqm9XSLi057HNxhicb9DXC0SBSrqVg,175
|
|
46
|
+
jsonrpc_framework/openrpc/document/server/_server.py,sha256=_DYjWONMc3PaZJSxQaYZvBjkd43iQruSPpH5vGFqxa8,2788
|
|
47
|
+
jsonrpc_framework/openrpc/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
jsonrpc_framework/openrpc/tests/conftest.py,sha256=OcF-nvDZ34823vT5ViqD52n3fVuKpOlLszujHlRwjJo,68
|
|
49
|
+
jsonrpc_framework/openrpc/tests/test_builder.py,sha256=HAn2wGqBzjGwSC6BuStZVrRxIAMZE9JCY5IaJX1HMcA,1791
|
|
50
|
+
jsonrpc_framework/openrpc/tests/test_components.py,sha256=TKIGZQj3R-WFGdxk4oQg6v3qAwTDI6DrOgqKr5RuRio,454
|
|
51
|
+
jsonrpc_framework/openrpc/tests/test_external_doc.py,sha256=881BGXkEFmPMXzPsI-x1vhpml-ToLDt7SULvQPFD7SA,947
|
|
52
|
+
jsonrpc_framework/openrpc/tests/test_openrcp_info.py,sha256=we8RC5MPonQNUyP_eG3-0E-7i6LKGEUmN9wlrnwVAXU,707
|
|
53
|
+
jsonrpc_framework/openrpc/tests/test_openrcp_method.py,sha256=09oJJdRm_iCAwjQMGysCCSJlHKQ3-q3y8FaisQ2U4y4,798
|
|
54
|
+
jsonrpc_framework/openrpc/tests/test_server.py,sha256=WaH16ml1NIFCewmxOIPzUBYrMgnsplr2aJ2FCR7Ga60,784
|
|
55
|
+
jsonrpc_framework/openrpc/tests/_conftest/__init__.py,sha256=Z-mbYwidG9VCLrVjHdb58X1OwXy67wLJHX18H6m7bBs,519
|
|
56
|
+
jsonrpc_framework/openrpc/tests/_conftest/builder_conftest.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
jsonrpc_framework/openrpc/tests/_conftest/components_conftest.py,sha256=-ShdT61-KikgTGVfaksVeWfTnfidlKOlj_Gd8VapeZY,4343
|
|
58
|
+
jsonrpc_framework/openrpc/tests/_conftest/external_doc_conftest.py,sha256=w4Ag5hCQZ0qVhFucsk_JsbqZsLEmViHclaZidsMb6xs,917
|
|
59
|
+
jsonrpc_framework/openrpc/tests/_conftest/info_conftest.py,sha256=LXJf2pxCfc3EsmAjoAsBIkv221tw4bcGr-4fpE8xnDs,1562
|
|
60
|
+
jsonrpc_framework/openrpc/tests/_conftest/method_conftest.py,sha256=Hk9X4N964jX9yQE5q843rDyGfA2P7ktXeee8yO2AiAc,8604
|
|
61
|
+
jsonrpc_framework/openrpc/tests/_conftest/server_conftest.py,sha256=72vXOzmTOAfsJnYxCfO10TbGY2OgKZlVUwWUlAsCjKA,1501
|
|
62
|
+
jsonrpc_framework/templates/openrpc_docs.html,sha256=duo_IbWipuDYbUM-x2Aa3OcohiBrDkvBJMBoL4iVr6c,2670
|
|
63
|
+
django_jsonrpc_framework-0.1.0.dist-info/METADATA,sha256=jSPpKcdj_1r6vOIK1Zs5gzhYdGl-V6JtM_tzFA49nx4,2236
|
|
64
|
+
django_jsonrpc_framework-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
65
|
+
django_jsonrpc_framework-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from django.views import View
|
|
6
|
+
from django.http import HttpRequest, HttpResponse
|
|
7
|
+
|
|
8
|
+
from jsonrpc_framework.logic.dispatcher import RpcDispatcher
|
|
9
|
+
from jsonrpc_framework.logic.validator import RequestValidator, RequestType, BatchType
|
|
10
|
+
from jsonrpc_framework.logic.responser import ResponseBuilder
|
|
11
|
+
|
|
12
|
+
from jsonrpc_framework.core.error import RpcError
|
|
13
|
+
from jsonrpc_framework.core.models import MethodType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("django.server")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseController(View):
|
|
21
|
+
http_method_names = ["post"]
|
|
22
|
+
path: str = "jsonrpc"
|
|
23
|
+
|
|
24
|
+
registry: dict[MethodType, Callable[..., Any]]
|
|
25
|
+
|
|
26
|
+
dispatcher: RpcDispatcher
|
|
27
|
+
validator: RequestValidator
|
|
28
|
+
response_builder: ResponseBuilder
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def __init__(self, *args: tuple[Any], **kwargs: dict[str, Any]):
|
|
32
|
+
super().__init__(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
self.registry = self._collect_declared_methods()
|
|
35
|
+
|
|
36
|
+
self.dispatcher = RpcDispatcher()
|
|
37
|
+
self.validator = RequestValidator()
|
|
38
|
+
self.response_builder = ResponseBuilder()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _collect_declared_methods(self) -> dict[MethodType, Callable[..., Any]]:
|
|
42
|
+
registry: dict[MethodType, Callable[..., Any]] = {}
|
|
43
|
+
|
|
44
|
+
for name, value in vars(self.__class__).items():
|
|
45
|
+
if not callable(value):
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
rpc_name = getattr(value, "__rpc_method_name__", None)
|
|
49
|
+
|
|
50
|
+
if rpc_name is not None:
|
|
51
|
+
method_name = rpc_name
|
|
52
|
+
elif name.startswith("method_"):
|
|
53
|
+
method_name = name.replace("method_", "")
|
|
54
|
+
else:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
if method_name in registry:
|
|
58
|
+
raise ValueError(f"Method {method_name} already registered in {self.__class__.__name__}")
|
|
59
|
+
|
|
60
|
+
registry[method_name] = getattr(self, name)
|
|
61
|
+
|
|
62
|
+
return registry
|
|
63
|
+
|
|
64
|
+
async def post(
|
|
65
|
+
self,
|
|
66
|
+
request: HttpRequest,
|
|
67
|
+
*args: tuple[Any],
|
|
68
|
+
**kwargs: dict[str, Any],
|
|
69
|
+
) -> HttpResponse:
|
|
70
|
+
body = self.validator.validate_body(request.body)
|
|
71
|
+
|
|
72
|
+
result = await self.dispatcher.dispatch(body, registry=self.registry)
|
|
73
|
+
self._log_jsonrpc_methods(request, body)
|
|
74
|
+
|
|
75
|
+
return self.response_builder.build_response(result)
|
|
76
|
+
|
|
77
|
+
def _log_jsonrpc_methods(
|
|
78
|
+
self,
|
|
79
|
+
request: HttpRequest,
|
|
80
|
+
body: RequestType | BatchType | RpcError,
|
|
81
|
+
) -> None:
|
|
82
|
+
methods = self._extract_method_names(body)
|
|
83
|
+
|
|
84
|
+
if methods:
|
|
85
|
+
logger.info(f"JSONRPC {request.path} {methods}")
|
|
86
|
+
|
|
87
|
+
def _extract_method_names(
|
|
88
|
+
self,
|
|
89
|
+
body: RequestType | BatchType | RpcError,
|
|
90
|
+
) -> list[str] | str:
|
|
91
|
+
if isinstance(body, RpcError):
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
if isinstance(body, list):
|
|
95
|
+
methods: list[str] = []
|
|
96
|
+
|
|
97
|
+
for item in body:
|
|
98
|
+
if isinstance(item, RpcError):
|
|
99
|
+
continue
|
|
100
|
+
methods.append(item.method)
|
|
101
|
+
return methods
|
|
102
|
+
|
|
103
|
+
return body.method
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from jsonrpc_framework.controller._base import BaseController
|
|
7
|
+
|
|
8
|
+
class RouteController(BaseController):
|
|
9
|
+
"""The conroller that collects BaseControllers to merge
|
|
10
|
+
their methods into a single controller.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
path: The path to use in urlconfig.
|
|
14
|
+
controllers: The list of BaseControllers to merge.
|
|
15
|
+
"""
|
|
16
|
+
controllers: list[type[BaseController] | BaseController] = []
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
path: str,
|
|
21
|
+
controllers: list[type[BaseController] | BaseController],
|
|
22
|
+
):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.path = path
|
|
25
|
+
self.controllers = controllers
|
|
26
|
+
|
|
27
|
+
merged_registry: dict[str, Callable[..., Any]] = {}
|
|
28
|
+
key_sources: dict[str, list[str]] = defaultdict(list)
|
|
29
|
+
|
|
30
|
+
for raw_controller in controllers:
|
|
31
|
+
|
|
32
|
+
controller = (
|
|
33
|
+
raw_controller()
|
|
34
|
+
if isinstance(raw_controller, type)
|
|
35
|
+
else raw_controller
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if not isinstance(controller, BaseController):
|
|
39
|
+
raise TypeError(
|
|
40
|
+
"Each controller must be a BaseController instance or subclass"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
controller_name = controller.__class__.__name__
|
|
44
|
+
for method_name, callback in controller.registry.items():
|
|
45
|
+
key_sources[method_name].append(controller_name)
|
|
46
|
+
if method_name in merged_registry:
|
|
47
|
+
continue
|
|
48
|
+
merged_registry[method_name] = callback
|
|
49
|
+
|
|
50
|
+
conflicts = {
|
|
51
|
+
method_name: names
|
|
52
|
+
for method_name, names in key_sources.items()
|
|
53
|
+
if len(names) > 1
|
|
54
|
+
}
|
|
55
|
+
if conflicts:
|
|
56
|
+
details = "; ".join(
|
|
57
|
+
f"{method!r} -> {sources}"
|
|
58
|
+
for method, sources in sorted(conflicts.items())
|
|
59
|
+
)
|
|
60
|
+
raise ValueError(f"Duplicate JSON-RPC methods detected: {details}")
|
|
61
|
+
|
|
62
|
+
self.registry = merged_registry
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def as_view(self, **initkwargs: Any) -> Any: # type: ignore[override]
|
|
66
|
+
return BaseController.as_view.__func__( # type: ignore[attr-defined]
|
|
67
|
+
self.__class__,
|
|
68
|
+
path=self.path,
|
|
69
|
+
controllers=self.controllers,
|
|
70
|
+
**initkwargs,
|
|
71
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _decorate[R, **P](
|
|
7
|
+
func: Callable[P, R],
|
|
8
|
+
rpc_name: str,
|
|
9
|
+
summary: str | None = None,
|
|
10
|
+
description: str | None = None,
|
|
11
|
+
tags: list[str] | None = None,
|
|
12
|
+
) -> Callable[P, R]:
|
|
13
|
+
# Keep an explicit RPC alias on the callable, because class attribute
|
|
14
|
+
# names are used during registry collection and cannot be renamed here.
|
|
15
|
+
setattr(func, "__rpc_method_name__", rpc_name)
|
|
16
|
+
setattr(func, "__rpc_method_summary__", summary)
|
|
17
|
+
setattr(func, "__rpc_method_description__", description)
|
|
18
|
+
setattr(func, "__rpc_method_tags__", tags)
|
|
19
|
+
|
|
20
|
+
@wraps(func)
|
|
21
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
22
|
+
return func(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
setattr(wrapper, "__rpc_method_name__", rpc_name)
|
|
25
|
+
setattr(wrapper, "__rpc_method_summary__", summary)
|
|
26
|
+
setattr(wrapper, "__rpc_method_description__", description)
|
|
27
|
+
setattr(wrapper, "__rpc_method_tags__", tags)
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def simple_decorator[R, **P](
|
|
33
|
+
func: Callable[P, R],
|
|
34
|
+
) -> Callable[P, R]:
|
|
35
|
+
return _decorate(
|
|
36
|
+
func,
|
|
37
|
+
rpc_name=func.__name__,
|
|
38
|
+
description=func.__doc__,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parametrized_decorator[R, **P](
|
|
43
|
+
func: Callable[P, R],
|
|
44
|
+
*,
|
|
45
|
+
name: str | None = None,
|
|
46
|
+
summary: str | None = None,
|
|
47
|
+
description: str | None = None,
|
|
48
|
+
tags: list[str] | None = None,
|
|
49
|
+
) -> Callable[P, R]:
|
|
50
|
+
rpc_name = name if isinstance(name, str) else func.__name__
|
|
51
|
+
return _decorate(
|
|
52
|
+
func,
|
|
53
|
+
rpc_name,
|
|
54
|
+
summary,
|
|
55
|
+
description,
|
|
56
|
+
tags,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def jsonrpc_method(
|
|
61
|
+
name_or_func: str | Callable[..., Any] | None = None,
|
|
62
|
+
*,
|
|
63
|
+
summary: str | None = None,
|
|
64
|
+
description: str | None = None,
|
|
65
|
+
tags: list[str] | None = None,
|
|
66
|
+
) -> Callable[..., Any]:
|
|
67
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
68
|
+
return parametrized_decorator(
|
|
69
|
+
func,
|
|
70
|
+
name=name_or_func if isinstance(name_or_func, str) else None,
|
|
71
|
+
summary=summary,
|
|
72
|
+
description=description,
|
|
73
|
+
tags=tags,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if callable(name_or_func):
|
|
77
|
+
return simple_decorator(
|
|
78
|
+
name_or_func,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return decorator
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable, override, Any
|
|
3
|
+
|
|
4
|
+
from django.views.generic.base import TemplateView
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.http import HttpRequest, HttpResponse, HttpResponseBase
|
|
7
|
+
from django.http.response import FileResponse
|
|
8
|
+
|
|
9
|
+
from django.views import View
|
|
10
|
+
|
|
11
|
+
from jsonrpc_framework.controller.openrpc.collectors import OpenRpcCollector
|
|
12
|
+
|
|
13
|
+
class OpenRpcJsonView(View):
|
|
14
|
+
|
|
15
|
+
http_method_names = ["get"]
|
|
16
|
+
path = "openrpc.json"
|
|
17
|
+
collector: OpenRpcCollector = OpenRpcCollector()
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*args: tuple[Any],
|
|
22
|
+
**kwargs: dict[str, Any]
|
|
23
|
+
):
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
|
|
26
|
+
def get(self, request: HttpRequest, *args: tuple[Any], **kwargs: dict[str, Any]) -> HttpResponse | FileResponse:
|
|
27
|
+
file_path = getattr(
|
|
28
|
+
settings, "DJANGO_JSONRPC_DOCS", {}
|
|
29
|
+
).get("FILE_PATH", None)
|
|
30
|
+
|
|
31
|
+
if file_path is not None and Path(file_path).exists():
|
|
32
|
+
return FileResponse(
|
|
33
|
+
Path(file_path).open("rb"),
|
|
34
|
+
filename=Path(file_path).name,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if not self.collector.is_collected:
|
|
38
|
+
self.collector.collect()
|
|
39
|
+
|
|
40
|
+
document = self.collector.build_document()
|
|
41
|
+
|
|
42
|
+
return HttpResponse(
|
|
43
|
+
content=document,
|
|
44
|
+
content_type="application/json; charset=utf-8",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
@override
|
|
49
|
+
def as_view(
|
|
50
|
+
cls,
|
|
51
|
+
*,
|
|
52
|
+
collector: OpenRpcCollector | None = None,
|
|
53
|
+
**initkwargs: Any,
|
|
54
|
+
) -> Callable[..., HttpResponseBase]:
|
|
55
|
+
return super().as_view(collector=collector, **initkwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class OpenRpcDocView(TemplateView):
|
|
60
|
+
template_name = "openrpc_docs.html"
|
|
61
|
+
|
|
62
|
+
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
|
63
|
+
ctx = super().get_context_data(**kwargs)
|
|
64
|
+
|
|
65
|
+
docs_cfg = getattr(settings, "DJANGO_JSONRPC_DOCS", {})
|
|
66
|
+
|
|
67
|
+
schema_path = docs_cfg.get("SCHEMA_PATH", "/openrpc.json")
|
|
68
|
+
schema_url = self.request.build_absolute_uri(schema_path)
|
|
69
|
+
|
|
70
|
+
ctx.update(
|
|
71
|
+
{
|
|
72
|
+
"openrpc_schema_url": schema_url,
|
|
73
|
+
"page_title": docs_cfg.get("TITLE", "OpenRPC Docs"),
|
|
74
|
+
"docs_react_css_url": docs_cfg.get(
|
|
75
|
+
"DOCS_REACT_CSS_URL",
|
|
76
|
+
"https://cdn.jsdelivr.net/npm/@open-rpc/docs-react@2.1.1/dist/docs-react.css",
|
|
77
|
+
),
|
|
78
|
+
"esm_react_url": docs_cfg.get(
|
|
79
|
+
"ESM_REACT_URL",
|
|
80
|
+
"https://esm.sh/react@18.3.1",
|
|
81
|
+
),
|
|
82
|
+
"esm_react_dom_url": docs_cfg.get(
|
|
83
|
+
"ESM_REACT_DOM_URL",
|
|
84
|
+
"https://esm.sh/react-dom@18.3.1/client?deps=react@18.3.1",
|
|
85
|
+
),
|
|
86
|
+
"esm_openrpc_docs_url": docs_cfg.get(
|
|
87
|
+
"ESM_OPENRPC_DOCS_URL",
|
|
88
|
+
"https://esm.sh/@open-rpc/docs-react@2.1.1?deps=react@18.3.1,react-dom@18.3.1",
|
|
89
|
+
),
|
|
90
|
+
"esm_mui_styles_url": docs_cfg.get(
|
|
91
|
+
"ESM_MUI_STYLES_URL",
|
|
92
|
+
"https://esm.sh/@mui/material@6.3.1/styles?deps=react@18.3.1,react-dom@18.3.1",
|
|
93
|
+
),
|
|
94
|
+
"esm_mui_css_baseline_url": docs_cfg.get(
|
|
95
|
+
"ESM_MUI_CSS_BASELINE_URL",
|
|
96
|
+
"https://esm.sh/@mui/material@6.3.1/CssBaseline?deps=react@18.3.1,react-dom@18.3.1",
|
|
97
|
+
),
|
|
98
|
+
"openrpc_theme_mode": docs_cfg.get("THEME_MODE", "dark"),
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
return ctx
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
from jsonrpc_framework.controller._base import BaseController
|
|
3
|
+
from jsonrpc_framework.openrpc.builder.builder import OpenRpcBuilder
|
|
4
|
+
from jsonrpc_framework.openrpc.document.info import OpenRpcContact, OpenRpcLicense
|
|
5
|
+
|
|
6
|
+
from jsonrpc_framework.controller.openrpc.collectors._method_collector import MethodsCollector
|
|
7
|
+
|
|
8
|
+
type ControllerType = type[BaseController] | BaseController
|
|
9
|
+
|
|
10
|
+
class OpenRpcCollector:
|
|
11
|
+
|
|
12
|
+
controllers: tuple[type[BaseController] | BaseController, ...]
|
|
13
|
+
builder: OpenRpcBuilder
|
|
14
|
+
is_collected: bool
|
|
15
|
+
|
|
16
|
+
method_collector: MethodsCollector
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*controllers: ControllerType,
|
|
21
|
+
title: str = "Django-jsonrpc API",
|
|
22
|
+
version: str = "1.0.0",
|
|
23
|
+
description: str | None = None,
|
|
24
|
+
terms_of_service: str | None = None,
|
|
25
|
+
contact: OpenRpcContact | None = None,
|
|
26
|
+
license: OpenRpcLicense | None = None,
|
|
27
|
+
):
|
|
28
|
+
self.controllers = controllers
|
|
29
|
+
|
|
30
|
+
self.method_collector = MethodsCollector()
|
|
31
|
+
self.builder = OpenRpcBuilder(
|
|
32
|
+
title=title,
|
|
33
|
+
version=version,
|
|
34
|
+
description=description,
|
|
35
|
+
terms_of_service=terms_of_service,
|
|
36
|
+
contact=contact,
|
|
37
|
+
license=license,
|
|
38
|
+
)
|
|
39
|
+
self.is_collected = False
|
|
40
|
+
|
|
41
|
+
def collect(self) -> None:
|
|
42
|
+
for controller in self.controllers:
|
|
43
|
+
if isinstance(controller, type):
|
|
44
|
+
controller = controller()
|
|
45
|
+
|
|
46
|
+
self.method_collector.collect(controller)
|
|
47
|
+
|
|
48
|
+
for method in self.method_collector.methods:
|
|
49
|
+
self.builder.add_method(method)
|
|
50
|
+
|
|
51
|
+
self.builder.add_components(self.method_collector.components)
|
|
52
|
+
self.is_collected = True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_document(self) -> str:
|
|
56
|
+
|
|
57
|
+
return self.builder.build_json()
|