panther 4.3.7__py3-none-any.whl → 5.0.0b2__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.
- panther/__init__.py +1 -1
- panther/_load_configs.py +78 -64
- panther/_utils.py +1 -1
- panther/app.py +126 -60
- panther/authentications.py +26 -9
- panther/base_request.py +27 -2
- panther/base_websocket.py +26 -27
- panther/cli/create_command.py +1 -0
- panther/cli/main.py +19 -27
- panther/cli/monitor_command.py +8 -4
- panther/cli/template.py +11 -6
- panther/cli/utils.py +3 -2
- panther/configs.py +7 -9
- panther/db/cursor.py +23 -7
- panther/db/models.py +26 -19
- panther/db/queries/base_queries.py +1 -1
- panther/db/queries/mongodb_queries.py +177 -13
- panther/db/queries/pantherdb_queries.py +5 -5
- panther/db/queries/queries.py +1 -1
- panther/events.py +10 -4
- panther/exceptions.py +24 -2
- panther/generics.py +2 -2
- panther/main.py +90 -117
- panther/middlewares/__init__.py +1 -1
- panther/middlewares/base.py +15 -19
- panther/middlewares/monitoring.py +42 -0
- panther/openapi/__init__.py +1 -0
- panther/openapi/templates/openapi.html +27 -0
- panther/openapi/urls.py +5 -0
- panther/openapi/utils.py +167 -0
- panther/openapi/views.py +101 -0
- panther/pagination.py +1 -1
- panther/panel/middlewares.py +10 -0
- panther/panel/templates/base.html +14 -0
- panther/panel/templates/create.html +21 -0
- panther/panel/templates/create.js +1270 -0
- panther/panel/templates/detail.html +55 -0
- panther/panel/templates/home.html +9 -0
- panther/panel/templates/home.js +30 -0
- panther/panel/templates/login.html +47 -0
- panther/panel/templates/sidebar.html +13 -0
- panther/panel/templates/table.html +73 -0
- panther/panel/templates/table.js +339 -0
- panther/panel/urls.py +10 -5
- panther/panel/utils.py +98 -0
- panther/panel/views.py +143 -0
- panther/request.py +3 -0
- panther/response.py +91 -53
- panther/routings.py +7 -2
- panther/serializer.py +1 -1
- panther/utils.py +34 -26
- panther/websocket.py +3 -0
- {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/METADATA +19 -17
- panther-5.0.0b2.dist-info/RECORD +75 -0
- {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/WHEEL +1 -1
- panther-4.3.7.dist-info/RECORD +0 -57
- {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/entry_points.txt +0 -0
- {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/licenses/LICENSE +0 -0
- {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/top_level.txt +0 -0
panther/main.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import contextlib
|
2
1
|
import logging
|
3
2
|
import sys
|
4
3
|
import types
|
@@ -6,20 +5,20 @@ from collections.abc import Callable
|
|
6
5
|
from logging.config import dictConfig
|
7
6
|
from pathlib import Path
|
8
7
|
|
9
|
-
import orjson as json
|
10
|
-
|
11
8
|
import panther.logging
|
12
9
|
from panther import status
|
13
10
|
from panther._load_configs import *
|
14
11
|
from panther._utils import traceback_message, reformat_code
|
12
|
+
from panther.app import GenericAPI
|
13
|
+
from panther.base_websocket import Websocket
|
15
14
|
from panther.cli.utils import print_info
|
16
15
|
from panther.configs import config
|
17
16
|
from panther.events import Event
|
18
|
-
from panther.exceptions import APIError, PantherError
|
19
|
-
from panther.monitoring import Monitoring
|
17
|
+
from panther.exceptions import APIError, PantherError, NotFoundAPIError, BaseError, UpgradeRequiredError
|
20
18
|
from panther.request import Request
|
21
19
|
from panther.response import Response
|
22
20
|
from panther.routings import find_endpoint
|
21
|
+
from panther.websocket import GenericWebsocket
|
23
22
|
|
24
23
|
dictConfig(panther.logging.LOGGING)
|
25
24
|
logger = logging.getLogger('panther')
|
@@ -27,6 +26,16 @@ logger = logging.getLogger('panther')
|
|
27
26
|
|
28
27
|
class Panther:
|
29
28
|
def __init__(self, name: str, configs: str | None = None, urls: dict | None = None):
|
29
|
+
"""
|
30
|
+
Initialize a Panther application instance.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
name: Typically set to `__name__`; used to determine the current directory of the application.
|
34
|
+
configs: The name of the module containing your configuration.
|
35
|
+
If the configuration is defined in the current file, you can also set this to `__name__`.
|
36
|
+
urls: A dictionary containing your URL routing.
|
37
|
+
If not provided, Panther will attempt to load `URLs` from the configs module.
|
38
|
+
"""
|
30
39
|
self._configs_module_name = configs
|
31
40
|
self._urls = urls
|
32
41
|
|
@@ -53,7 +62,6 @@ class Panther:
|
|
53
62
|
load_timezone(self._configs_module)
|
54
63
|
load_database(self._configs_module)
|
55
64
|
load_secret_key(self._configs_module)
|
56
|
-
load_monitoring(self._configs_module)
|
57
65
|
load_throttling(self._configs_module)
|
58
66
|
load_user_model(self._configs_module)
|
59
67
|
load_log_queries(self._configs_module)
|
@@ -62,8 +70,8 @@ class Panther:
|
|
62
70
|
load_auto_reformat(self._configs_module)
|
63
71
|
load_background_tasks(self._configs_module)
|
64
72
|
load_default_cache_exp(self._configs_module)
|
65
|
-
load_authentication_class(self._configs_module)
|
66
73
|
load_urls(self._configs_module, urls=self._urls)
|
74
|
+
load_authentication_class(self._configs_module)
|
67
75
|
load_websocket_connections()
|
68
76
|
|
69
77
|
check_endpoints_inheritance()
|
@@ -74,7 +82,11 @@ class Panther:
|
|
74
82
|
if message["type"] == 'lifespan.startup':
|
75
83
|
if config.HAS_WS:
|
76
84
|
await config.WEBSOCKET_CONNECTIONS.start()
|
77
|
-
|
85
|
+
try:
|
86
|
+
await Event.run_startups()
|
87
|
+
except Exception as e:
|
88
|
+
logger.error(e)
|
89
|
+
raise e
|
78
90
|
elif message["type"] == 'lifespan.shutdown':
|
79
91
|
# It's not happening :\, so handle the shutdowns in __del__ ...
|
80
92
|
pass
|
@@ -83,145 +95,106 @@ class Panther:
|
|
83
95
|
func = self.handle_http if scope['type'] == 'http' else self.handle_ws
|
84
96
|
await func(scope=scope, receive=receive, send=send)
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# Monitoring
|
90
|
-
monitoring = Monitoring(is_ws=True)
|
91
|
-
|
92
|
-
# Create Temp Connection
|
93
|
-
temp_connection = Websocket(scope=scope, receive=receive, send=send)
|
94
|
-
await monitoring.before(request=temp_connection)
|
95
|
-
temp_connection._monitoring = monitoring
|
96
|
-
|
98
|
+
@classmethod
|
99
|
+
async def handle_ws_endpoint(cls, connection: Websocket):
|
97
100
|
# Find Endpoint
|
98
|
-
endpoint, found_path = find_endpoint(path=
|
101
|
+
endpoint, found_path = find_endpoint(path=connection.path)
|
99
102
|
if endpoint is None:
|
100
|
-
logger.debug(f'Path `{
|
101
|
-
|
103
|
+
logger.debug(f'Path `{connection.path}` not found')
|
104
|
+
await connection.close()
|
105
|
+
return connection
|
102
106
|
|
103
107
|
# Check Endpoint Type
|
104
108
|
if not issubclass(endpoint, GenericWebsocket):
|
105
|
-
logger.
|
106
|
-
|
109
|
+
logger.warning(f'{endpoint.__name__}() class is not a Websocket class.')
|
110
|
+
await connection.close()
|
111
|
+
return connection
|
107
112
|
|
108
113
|
# Create The Connection
|
109
|
-
|
110
|
-
connection
|
111
|
-
connection._monitoring = monitoring
|
114
|
+
final_connection = endpoint(parent=connection)
|
115
|
+
del connection
|
112
116
|
|
113
117
|
# Collect Path Variables
|
114
|
-
|
118
|
+
final_connection.collect_path_variables(found_path=found_path)
|
115
119
|
|
116
|
-
|
120
|
+
return await config.WEBSOCKET_CONNECTIONS.listen(connection=final_connection)
|
117
121
|
|
118
|
-
# Call Middlewares .before()
|
119
|
-
await self._run_ws_middlewares_before_listen(connection=connection, middlewares=middlewares)
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
+
async def handle_ws(self, scope: dict, receive: Callable, send: Callable) -> None:
|
124
|
+
# Create Temp Connection
|
125
|
+
connection = Websocket(scope=scope, receive=receive, send=send)
|
123
126
|
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
+
# Create Middlewares chain
|
128
|
+
chained_func = self.handle_ws_endpoint
|
129
|
+
for middleware in reversed(config.WS_MIDDLEWARES):
|
130
|
+
chained_func = middleware(dispatch=chained_func)
|
127
131
|
|
128
|
-
|
129
|
-
async def _run_ws_middlewares_before_listen(cls, *, connection, middlewares):
|
132
|
+
# Call Middlewares & Endpoint
|
130
133
|
try:
|
131
|
-
|
132
|
-
|
133
|
-
if new_connection is None:
|
134
|
-
logger.critical(
|
135
|
-
f'Make sure to return the `request` at the end of `{middleware.__class__.__name__}.before()`')
|
136
|
-
await connection.close()
|
137
|
-
connection = new_connection
|
138
|
-
except APIError as e:
|
134
|
+
connection = await chained_func(connection=connection)
|
135
|
+
except BaseError as e:
|
139
136
|
connection.log(e.detail)
|
140
137
|
await connection.close()
|
138
|
+
except Exception as e:
|
139
|
+
logger.error(traceback_message(exception=e))
|
140
|
+
await connection.close()
|
141
141
|
|
142
|
-
@classmethod
|
143
|
-
async def _run_ws_middlewares_after_listen(cls, *, connection, middlewares):
|
144
|
-
for middleware in middlewares:
|
145
|
-
with contextlib.suppress(APIError):
|
146
|
-
connection = await middleware.after(response=connection)
|
147
|
-
if connection is None:
|
148
|
-
logger.critical(
|
149
|
-
f'Make sure to return the `response` at the end of `{middleware.__class__.__name__}.after()`')
|
150
|
-
break
|
151
|
-
|
152
|
-
async def handle_http(self, scope: dict, receive: Callable, send: Callable) -> None:
|
153
|
-
# Monitoring
|
154
|
-
monitoring = Monitoring()
|
155
|
-
|
156
|
-
request = Request(scope=scope, receive=receive, send=send)
|
157
|
-
|
158
|
-
await monitoring.before(request=request)
|
159
|
-
|
160
|
-
# Read Request Payload
|
161
|
-
await request.read_body()
|
162
142
|
|
143
|
+
@classmethod
|
144
|
+
async def handle_http_endpoint(cls, request: Request) -> Response:
|
163
145
|
# Find Endpoint
|
164
146
|
endpoint, found_path = find_endpoint(path=request.path)
|
165
147
|
if endpoint is None:
|
166
|
-
|
148
|
+
raise NotFoundAPIError
|
167
149
|
|
168
150
|
# Collect Path Variables
|
169
151
|
request.collect_path_variables(found_path=found_path)
|
170
152
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
request = await middleware.before(request=request)
|
176
|
-
if request is None:
|
177
|
-
logger.critical(
|
178
|
-
f'Make sure to return the `request` at the end of `{middleware.__class__.__name__}.before()`')
|
179
|
-
return await self._raise(send, monitoring=monitoring)
|
153
|
+
# Prepare the method
|
154
|
+
if not isinstance(endpoint, types.FunctionType):
|
155
|
+
if not issubclass(endpoint, GenericAPI):
|
156
|
+
raise UpgradeRequiredError
|
180
157
|
|
181
|
-
|
182
|
-
|
183
|
-
|
158
|
+
endpoint = endpoint().call_method
|
159
|
+
# Call Endpoint
|
160
|
+
return await endpoint(request=request)
|
161
|
+
|
162
|
+
async def handle_http(self, scope: dict, receive: Callable, send: Callable) -> None:
|
163
|
+
# Create `Request` and its body
|
164
|
+
request = Request(scope=scope, receive=receive, send=send)
|
165
|
+
await request.read_body()
|
184
166
|
|
185
|
-
|
186
|
-
|
167
|
+
# Create Middlewares chain
|
168
|
+
chained_func = self.handle_http_endpoint
|
169
|
+
for middleware in reversed(config.HTTP_MIDDLEWARES):
|
170
|
+
chained_func = middleware(dispatch=chained_func)
|
187
171
|
|
172
|
+
# Call Middlewares & Endpoint
|
173
|
+
try:
|
174
|
+
response = await chained_func(request=request)
|
175
|
+
if response is None:
|
176
|
+
logger.error('You forgot to return `response` on the `Middlewares.__call__()`')
|
177
|
+
response = Response(
|
178
|
+
data={'detail': 'Internal Server Error'},
|
179
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
|
180
|
+
)
|
181
|
+
# Handle `APIError` Exceptions
|
188
182
|
except APIError as e:
|
189
|
-
response =
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
f'Make sure to return the `response` at the end of `{middleware.__class__.__name__}.after()`')
|
205
|
-
return await self._raise(send, monitoring=monitoring)
|
206
|
-
except APIError as e: # noqa: PERF203
|
207
|
-
response = self._handle_exceptions(e)
|
208
|
-
|
209
|
-
await response.send(send, receive, monitoring=monitoring)
|
183
|
+
response = Response(
|
184
|
+
data=e.detail if isinstance(e.detail, dict) else {'detail': e.detail},
|
185
|
+
headers=e.headers,
|
186
|
+
status_code=e.status_code,
|
187
|
+
)
|
188
|
+
# Handle Unknown Exceptions
|
189
|
+
except Exception as e: # noqa: BLE001 - Blind Exception
|
190
|
+
logger.error(traceback_message(exception=e))
|
191
|
+
response = Response(
|
192
|
+
data={'detail': 'Internal Server Error'},
|
193
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
|
194
|
+
)
|
195
|
+
|
196
|
+
# Return Response
|
197
|
+
await response.send(send, receive)
|
210
198
|
|
211
199
|
def __del__(self):
|
212
200
|
Event.run_shutdowns()
|
213
|
-
|
214
|
-
@classmethod
|
215
|
-
def _handle_exceptions(cls, e: APIError, /) -> Response:
|
216
|
-
return Response(
|
217
|
-
data=e.detail if isinstance(e.detail, dict) else {'detail': e.detail},
|
218
|
-
status_code=e.status_code,
|
219
|
-
)
|
220
|
-
|
221
|
-
@classmethod
|
222
|
-
async def _raise(cls, send, *, monitoring, status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR):
|
223
|
-
headers = [[b'Content-Type', b'application/json']]
|
224
|
-
body = json.dumps({'detail': status.status_text[status_code]})
|
225
|
-
await monitoring.after(status_code)
|
226
|
-
await send({'type': 'http.response.start', 'status': status_code, 'headers': headers})
|
227
|
-
await send({'type': 'http.response.body', 'body': body, 'more_body': False})
|
panther/middlewares/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
from panther.middlewares.base import
|
1
|
+
from panther.middlewares.base import HTTPMiddleware, WebsocketMiddleware # noqa: F401
|
panther/middlewares/base.py
CHANGED
@@ -1,30 +1,26 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
from panther.base_websocket import Websocket
|
1
4
|
from panther.request import Request
|
2
5
|
from panther.response import Response
|
3
6
|
from panther.websocket import GenericWebsocket
|
4
7
|
|
5
8
|
|
6
|
-
class
|
7
|
-
"""Used in both http & ws requests"""
|
8
|
-
async def before(self, request: Request | GenericWebsocket):
|
9
|
-
raise NotImplementedError
|
10
|
-
|
11
|
-
async def after(self, response: Response | GenericWebsocket):
|
12
|
-
raise NotImplementedError
|
13
|
-
|
14
|
-
|
15
|
-
class HTTPMiddleware(BaseMiddleware):
|
9
|
+
class HTTPMiddleware:
|
16
10
|
"""Used only in http requests"""
|
17
|
-
async def before(self, request: Request):
|
18
|
-
return request
|
19
11
|
|
20
|
-
|
21
|
-
|
12
|
+
def __init__(self, dispatch: typing.Callable):
|
13
|
+
self.dispatch = dispatch
|
14
|
+
|
15
|
+
async def __call__(self, request: Request) -> Response:
|
16
|
+
return await self.dispatch(request=request)
|
22
17
|
|
23
18
|
|
24
|
-
class WebsocketMiddleware
|
19
|
+
class WebsocketMiddleware:
|
25
20
|
"""Used only in ws requests"""
|
26
|
-
async def before(self, request: GenericWebsocket):
|
27
|
-
return request
|
28
21
|
|
29
|
-
|
30
|
-
|
22
|
+
def __init__(self, dispatch: typing.Callable):
|
23
|
+
self.dispatch = dispatch
|
24
|
+
|
25
|
+
async def __call__(self, connection: Websocket) -> GenericWebsocket:
|
26
|
+
return await self.dispatch(connection=connection)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import logging
|
2
|
+
from time import perf_counter
|
3
|
+
|
4
|
+
from panther.base_websocket import Websocket
|
5
|
+
from panther.middlewares import HTTPMiddleware, WebsocketMiddleware
|
6
|
+
from panther.request import Request
|
7
|
+
|
8
|
+
logger = logging.getLogger('monitoring')
|
9
|
+
|
10
|
+
|
11
|
+
class MonitoringMiddleware(HTTPMiddleware):
|
12
|
+
"""
|
13
|
+
Create Log Message Like Below:
|
14
|
+
date_time | method | path | ip:port | response_time(seconds) | status
|
15
|
+
"""
|
16
|
+
|
17
|
+
async def __call__(self, request: Request):
|
18
|
+
start_time = perf_counter()
|
19
|
+
method = request.scope['method']
|
20
|
+
|
21
|
+
response = await self.dispatch(request=request)
|
22
|
+
|
23
|
+
response_time = perf_counter() - start_time # Seconds
|
24
|
+
logger.info(f'{method} | {request.path} | {request.client} | {response_time} | {response.status_code}')
|
25
|
+
return response
|
26
|
+
|
27
|
+
class WebsocketMonitoringMiddleware(WebsocketMiddleware):
|
28
|
+
"""
|
29
|
+
Create Log Message Like Below:
|
30
|
+
date_time | WS | path | ip:port | connection_time(seconds) | status
|
31
|
+
"""
|
32
|
+
ConnectedConnectionTime = ' - '
|
33
|
+
|
34
|
+
async def __call__(self, connection: Websocket):
|
35
|
+
start_time = perf_counter()
|
36
|
+
|
37
|
+
logger.info(f'WS | {connection.path} | {connection.client} |{self.ConnectedConnectionTime}| {connection.state}')
|
38
|
+
connection = await self.dispatch(connection=connection)
|
39
|
+
|
40
|
+
connection_time = perf_counter() - start_time # Seconds
|
41
|
+
logger.info(f'WS | {connection.path} | {connection.client} | {connection_time} | {connection.state}')
|
42
|
+
return connection
|
@@ -0,0 +1 @@
|
|
1
|
+
from panther.openapi.utils import OutputSchema
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Swagger UI</title>
|
7
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.16.0/swagger-ui.css" />
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="swagger-ui"></div>
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.16.0/swagger-ui-bundle.js"></script>
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>
|
13
|
+
<script>
|
14
|
+
const openapiContent = {{ openapi_content | tojson }};
|
15
|
+
|
16
|
+
const ui = SwaggerUIBundle({
|
17
|
+
spec: openapiContent,
|
18
|
+
dom_id: '#swagger-ui',
|
19
|
+
presets: [
|
20
|
+
SwaggerUIBundle.presets.apis,
|
21
|
+
SwaggerUIStandalonePreset
|
22
|
+
],
|
23
|
+
layout: "StandaloneLayout",
|
24
|
+
});
|
25
|
+
</script>
|
26
|
+
</body>
|
27
|
+
</html>
|
panther/openapi/urls.py
ADDED
panther/openapi/utils.py
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
import ast
|
2
|
+
import inspect
|
3
|
+
|
4
|
+
import pydantic
|
5
|
+
|
6
|
+
from panther import status
|
7
|
+
from panther.serializer import ModelSerializer
|
8
|
+
|
9
|
+
|
10
|
+
class EmptyResponseModel(pydantic.BaseModel):
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
class OutputSchema:
|
15
|
+
"""
|
16
|
+
Its values only used in OpenAPI response schema
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
model: type[ModelSerializer] | type[pydantic.BaseModel] = EmptyResponseModel,
|
22
|
+
status_code: int = status.HTTP_200_OK,
|
23
|
+
):
|
24
|
+
self.model = model
|
25
|
+
self.status_code = status_code
|
26
|
+
|
27
|
+
|
28
|
+
class ParseEndpoint:
|
29
|
+
"""
|
30
|
+
ParseEndpoint parses the endpoint (function-base/ class-base) and finds where it returns
|
31
|
+
and extract the `data` and `status_code` values.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, endpoint, method):
|
35
|
+
self.tree = ast.parse(inspect.getsource(endpoint))
|
36
|
+
self.method = method
|
37
|
+
|
38
|
+
self.status_code = status.HTTP_200_OK
|
39
|
+
self.title = None
|
40
|
+
self.data = {}
|
41
|
+
|
42
|
+
self.parse()
|
43
|
+
|
44
|
+
def parse(self):
|
45
|
+
for branch in self.tree.body:
|
46
|
+
match branch:
|
47
|
+
case ast.ClassDef(name=name, body=body):
|
48
|
+
# class ...(GenericAPI):
|
49
|
+
# def get(...
|
50
|
+
self.title = name
|
51
|
+
for part in body:
|
52
|
+
match part:
|
53
|
+
case ast.FunctionDef(name=name, body=function_body):
|
54
|
+
# def get(...
|
55
|
+
if name == self.method:
|
56
|
+
self.parse_function(body=function_body)
|
57
|
+
break
|
58
|
+
case ast.AsyncFunctionDef(name=name, body=function_body):
|
59
|
+
# async def get(...
|
60
|
+
if name == self.method:
|
61
|
+
self.parse_function(body=function_body)
|
62
|
+
break
|
63
|
+
|
64
|
+
case ast.FunctionDef(name=name, body=body):
|
65
|
+
# def api(...
|
66
|
+
self.title = name
|
67
|
+
self.parse_function(body=body)
|
68
|
+
|
69
|
+
case ast.AsyncFunctionDef(name=name, body=body):
|
70
|
+
# async def api(...
|
71
|
+
self.title = name
|
72
|
+
self.parse_function(body=body)
|
73
|
+
|
74
|
+
def parse_function(self, body):
|
75
|
+
for part in body:
|
76
|
+
match part:
|
77
|
+
case ast.Return(value=ast.Dict(keys=keys, values=values)):
|
78
|
+
# return {...}
|
79
|
+
self.status_code = 200
|
80
|
+
self.parse_dict_response(keys=keys, values=values)
|
81
|
+
|
82
|
+
case ast.Return(value=ast.Name(id=name)):
|
83
|
+
# return my_data
|
84
|
+
self.status_code = 200
|
85
|
+
for value in self.searching_variable(body=body, name=name):
|
86
|
+
match value:
|
87
|
+
# my_data = {...}
|
88
|
+
case ast.Dict(keys=keys, values=values):
|
89
|
+
self.parse_dict_response(keys=keys, values=values)
|
90
|
+
|
91
|
+
case ast.Return(value=ast.Call(args=args, keywords=keywords, func=func)):
|
92
|
+
if func.id == 'TemplateResponse':
|
93
|
+
return
|
94
|
+
# return Response(...
|
95
|
+
self.parse_response(body=body, args=args, keywords=keywords)
|
96
|
+
|
97
|
+
def parse_dict_response(self, keys, values):
|
98
|
+
for k, v in zip(keys, values):
|
99
|
+
final_value = None
|
100
|
+
match v:
|
101
|
+
case ast.Constant(value=value):
|
102
|
+
final_value = value
|
103
|
+
self.data[k.value] = final_value
|
104
|
+
|
105
|
+
def parse_response(self, body, args, keywords):
|
106
|
+
for keyword in keywords:
|
107
|
+
if keyword.arg == 'data':
|
108
|
+
self.parse_data(body=body, value=keyword.value)
|
109
|
+
if keyword.arg == 'status_code':
|
110
|
+
self.parse_status_code(body=body, value=keyword.value)
|
111
|
+
|
112
|
+
for i, arg in enumerate(args):
|
113
|
+
if i == 0: # index 0 is `data`
|
114
|
+
self.parse_data(body=body, value=arg)
|
115
|
+
elif i == 1: # index 1 is `status_code`
|
116
|
+
self.parse_status_code(body=body, value=arg)
|
117
|
+
|
118
|
+
def parse_status_code(self, body, value):
|
119
|
+
match value:
|
120
|
+
# return Response(?, status_code=my_status)
|
121
|
+
# return Response(?, my_status)
|
122
|
+
case ast.Name():
|
123
|
+
for inner_value in self.searching_variable(body=body, name=value.id):
|
124
|
+
match inner_value:
|
125
|
+
# my_status = status.HTTP_202_ACCEPTED
|
126
|
+
case ast.Attribute(value=inner_inner_value, attr=attr):
|
127
|
+
if inner_inner_value.id == 'status':
|
128
|
+
self.status_code = getattr(status, attr)
|
129
|
+
# my_status = 202
|
130
|
+
case ast.Constant(value=inner_inner_value):
|
131
|
+
self.status_code = inner_inner_value
|
132
|
+
|
133
|
+
# return Response(?, status_code=status.HTTP_202_ACCEPTED)
|
134
|
+
# return Response(?, status.HTTP_202_ACCEPTED)
|
135
|
+
case ast.Attribute(value=value, attr=attr):
|
136
|
+
if value.id == 'status':
|
137
|
+
self.status_code = getattr(status, attr)
|
138
|
+
# return Response(?, status_code=202)
|
139
|
+
# return Response(?, 202)
|
140
|
+
case ast.Constant(value=value):
|
141
|
+
self.status_code = value
|
142
|
+
|
143
|
+
def parse_data(self, body, value):
|
144
|
+
match value:
|
145
|
+
# return Response(data=my_data, ?)
|
146
|
+
# return Response(my_data, ?)
|
147
|
+
case ast.Name():
|
148
|
+
for value in self.searching_variable(body=body, name=value.id):
|
149
|
+
match value:
|
150
|
+
# my_data = {...}
|
151
|
+
case ast.Dict(keys=keys, values=values):
|
152
|
+
self.parse_dict_response(keys=keys, values=values)
|
153
|
+
|
154
|
+
# return Response(data={...}, ?)
|
155
|
+
# return Response({...}, ?)
|
156
|
+
case ast.Dict(keys=keys, values=values):
|
157
|
+
self.parse_dict_response(keys=keys, values=values)
|
158
|
+
|
159
|
+
def searching_variable(self, body, name):
|
160
|
+
for part in body:
|
161
|
+
match part:
|
162
|
+
case ast.Assign(targets=targets, value=value):
|
163
|
+
for target in targets:
|
164
|
+
match target:
|
165
|
+
case ast.Name(id=inner_name):
|
166
|
+
if inner_name == name:
|
167
|
+
yield value
|