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.
Files changed (59) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +78 -64
  3. panther/_utils.py +1 -1
  4. panther/app.py +126 -60
  5. panther/authentications.py +26 -9
  6. panther/base_request.py +27 -2
  7. panther/base_websocket.py +26 -27
  8. panther/cli/create_command.py +1 -0
  9. panther/cli/main.py +19 -27
  10. panther/cli/monitor_command.py +8 -4
  11. panther/cli/template.py +11 -6
  12. panther/cli/utils.py +3 -2
  13. panther/configs.py +7 -9
  14. panther/db/cursor.py +23 -7
  15. panther/db/models.py +26 -19
  16. panther/db/queries/base_queries.py +1 -1
  17. panther/db/queries/mongodb_queries.py +177 -13
  18. panther/db/queries/pantherdb_queries.py +5 -5
  19. panther/db/queries/queries.py +1 -1
  20. panther/events.py +10 -4
  21. panther/exceptions.py +24 -2
  22. panther/generics.py +2 -2
  23. panther/main.py +90 -117
  24. panther/middlewares/__init__.py +1 -1
  25. panther/middlewares/base.py +15 -19
  26. panther/middlewares/monitoring.py +42 -0
  27. panther/openapi/__init__.py +1 -0
  28. panther/openapi/templates/openapi.html +27 -0
  29. panther/openapi/urls.py +5 -0
  30. panther/openapi/utils.py +167 -0
  31. panther/openapi/views.py +101 -0
  32. panther/pagination.py +1 -1
  33. panther/panel/middlewares.py +10 -0
  34. panther/panel/templates/base.html +14 -0
  35. panther/panel/templates/create.html +21 -0
  36. panther/panel/templates/create.js +1270 -0
  37. panther/panel/templates/detail.html +55 -0
  38. panther/panel/templates/home.html +9 -0
  39. panther/panel/templates/home.js +30 -0
  40. panther/panel/templates/login.html +47 -0
  41. panther/panel/templates/sidebar.html +13 -0
  42. panther/panel/templates/table.html +73 -0
  43. panther/panel/templates/table.js +339 -0
  44. panther/panel/urls.py +10 -5
  45. panther/panel/utils.py +98 -0
  46. panther/panel/views.py +143 -0
  47. panther/request.py +3 -0
  48. panther/response.py +91 -53
  49. panther/routings.py +7 -2
  50. panther/serializer.py +1 -1
  51. panther/utils.py +34 -26
  52. panther/websocket.py +3 -0
  53. {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/METADATA +19 -17
  54. panther-5.0.0b2.dist-info/RECORD +75 -0
  55. {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/WHEEL +1 -1
  56. panther-4.3.7.dist-info/RECORD +0 -57
  57. {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/entry_points.txt +0 -0
  58. {panther-4.3.7.dist-info → panther-5.0.0b2.dist-info}/licenses/LICENSE +0 -0
  59. {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
- await Event.run_startups()
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
- async def handle_ws(self, scope: dict, receive: Callable, send: Callable) -> None:
87
- from panther.websocket import GenericWebsocket, Websocket
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=temp_connection.path)
101
+ endpoint, found_path = find_endpoint(path=connection.path)
99
102
  if endpoint is None:
100
- logger.debug(f'Path `{temp_connection.path}` not found')
101
- return await temp_connection.close()
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.critical(f'You may have forgotten to inherit from `GenericWebsocket` on the `{endpoint.__name__}()`')
106
- return await temp_connection.close()
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
- del temp_connection
110
- connection = endpoint(scope=scope, receive=receive, send=send)
111
- connection._monitoring = monitoring
114
+ final_connection = endpoint(parent=connection)
115
+ del connection
112
116
 
113
117
  # Collect Path Variables
114
- connection.collect_path_variables(found_path=found_path)
118
+ final_connection.collect_path_variables(found_path=found_path)
115
119
 
116
- middlewares = [middleware(**data) for middleware, data in config.WS_MIDDLEWARES]
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
- # Listen The Connection
122
- await config.WEBSOCKET_CONNECTIONS.listen(connection=connection)
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
- # Call Middlewares .after()
125
- middlewares.reverse()
126
- await self._run_ws_middlewares_after_listen(connection=connection, middlewares=middlewares)
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
- @classmethod
129
- async def _run_ws_middlewares_before_listen(cls, *, connection, middlewares):
132
+ # Call Middlewares & Endpoint
130
133
  try:
131
- for middleware in middlewares:
132
- new_connection = await middleware.before(request=connection)
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
- return await self._raise(send, monitoring=monitoring, status_code=status.HTTP_404_NOT_FOUND)
148
+ raise NotFoundAPIError
167
149
 
168
150
  # Collect Path Variables
169
151
  request.collect_path_variables(found_path=found_path)
170
152
 
171
- middlewares = [middleware(**data) for middleware, data in config.HTTP_MIDDLEWARES]
172
- try: # They Both(middleware.before() & _endpoint()) Have The Same Exception (APIError)
173
- # Call Middlewares .before()
174
- for middleware in middlewares:
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
- # Prepare the method
182
- if not isinstance(endpoint, types.FunctionType):
183
- endpoint = endpoint().call_method
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
- # Call Endpoint
186
- response = await endpoint(request=request)
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 = self._handle_exceptions(e)
190
-
191
- except Exception as e: # noqa: BLE001
192
- # All unhandled exceptions are caught here
193
- exception = traceback_message(exception=e)
194
- logger.error(exception)
195
- return await self._raise(send, monitoring=monitoring)
196
-
197
- # Call Middlewares .after()
198
- middlewares.reverse()
199
- for middleware in middlewares:
200
- try:
201
- response = await middleware.after(response=response)
202
- if response is None:
203
- logger.critical(
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})
@@ -1 +1 @@
1
- from panther.middlewares.base import BaseMiddleware # noqa: F401
1
+ from panther.middlewares.base import HTTPMiddleware, WebsocketMiddleware # noqa: F401
@@ -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 BaseMiddleware:
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
- async def after(self, response: Response):
21
- return response
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(BaseMiddleware):
19
+ class WebsocketMiddleware:
25
20
  """Used only in ws requests"""
26
- async def before(self, request: GenericWebsocket):
27
- return request
28
21
 
29
- async def after(self, response: GenericWebsocket):
30
- return response
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>
@@ -0,0 +1,5 @@
1
+ from panther.openapi.views import OpenAPI
2
+
3
+ urls = {
4
+ '': OpenAPI,
5
+ }
@@ -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