panther 5.0.0b3__py3-none-any.whl → 5.0.0b5__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 (57) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +46 -37
  3. panther/_utils.py +49 -34
  4. panther/app.py +96 -97
  5. panther/authentications.py +97 -50
  6. panther/background_tasks.py +98 -124
  7. panther/base_request.py +16 -10
  8. panther/base_websocket.py +8 -8
  9. panther/caching.py +16 -80
  10. panther/cli/create_command.py +17 -16
  11. panther/cli/main.py +1 -1
  12. panther/cli/monitor_command.py +11 -6
  13. panther/cli/run_command.py +5 -71
  14. panther/cli/template.py +7 -7
  15. panther/cli/utils.py +58 -69
  16. panther/configs.py +70 -72
  17. panther/db/connections.py +30 -24
  18. panther/db/cursor.py +3 -1
  19. panther/db/models.py +26 -10
  20. panther/db/queries/base_queries.py +4 -5
  21. panther/db/queries/mongodb_queries.py +21 -21
  22. panther/db/queries/pantherdb_queries.py +1 -1
  23. panther/db/queries/queries.py +26 -8
  24. panther/db/utils.py +1 -1
  25. panther/events.py +25 -14
  26. panther/exceptions.py +2 -7
  27. panther/file_handler.py +1 -1
  28. panther/generics.py +74 -100
  29. panther/logging.py +2 -1
  30. panther/main.py +12 -13
  31. panther/middlewares/cors.py +67 -0
  32. panther/middlewares/monitoring.py +5 -3
  33. panther/openapi/urls.py +2 -2
  34. panther/openapi/utils.py +3 -3
  35. panther/openapi/views.py +20 -37
  36. panther/pagination.py +4 -2
  37. panther/panel/apis.py +2 -7
  38. panther/panel/urls.py +2 -6
  39. panther/panel/utils.py +9 -5
  40. panther/panel/views.py +13 -22
  41. panther/permissions.py +2 -1
  42. panther/request.py +2 -1
  43. panther/response.py +101 -94
  44. panther/routings.py +12 -12
  45. panther/serializer.py +20 -43
  46. panther/test.py +73 -58
  47. panther/throttling.py +68 -3
  48. panther/utils.py +5 -11
  49. panther-5.0.0b5.dist-info/METADATA +188 -0
  50. panther-5.0.0b5.dist-info/RECORD +75 -0
  51. panther/monitoring.py +0 -34
  52. panther-5.0.0b3.dist-info/METADATA +0 -223
  53. panther-5.0.0b3.dist-info/RECORD +0 -75
  54. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/WHEEL +0 -0
  55. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/entry_points.txt +0 -0
  56. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/licenses/LICENSE +0 -0
  57. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/top_level.txt +0 -0
panther/generics.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import contextlib
2
2
  import logging
3
+ from abc import abstractmethod
3
4
 
4
5
  from pantherdb import Cursor as PantherDBCursor
5
6
 
@@ -7,7 +8,9 @@ from panther import status
7
8
  from panther.app import GenericAPI
8
9
  from panther.configs import config
9
10
  from panther.db import Model
11
+ from panther.db.connections import MongoDBConnection
10
12
  from panther.db.cursor import Cursor
13
+ from panther.db.models import ID
11
14
  from panther.exceptions import APIError
12
15
  from panther.pagination import Pagination
13
16
  from panther.request import Request
@@ -21,56 +24,42 @@ with contextlib.suppress(ImportError):
21
24
  logger = logging.getLogger('panther')
22
25
 
23
26
 
24
- class ObjectRequired:
25
- def _check_object(self, instance):
26
- if instance and issubclass(type(instance), Model) is False:
27
- logger.critical(f'`{self.__class__.__name__}.object()` should return instance of a Model --> `find_one()`')
28
- raise APIError
29
-
30
- async def object(self, request: Request, **kwargs):
27
+ class RetrieveAPI(GenericAPI):
28
+ @abstractmethod
29
+ async def get_instance(self, request: Request, **kwargs) -> Model:
31
30
  """
32
- Used in `RetrieveAPI`, `UpdateAPI`, `DeleteAPI`
31
+ Should return an instance of Model, e.g. `await User.find_one()`
33
32
  """
34
- logger.error(f'`object()` method is not implemented in {self.__class__} .')
33
+ logger.error(f'`get_instance()` method is not implemented in {self.__class__} .')
35
34
  raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
36
35
 
36
+ async def get(self, request: Request, **kwargs):
37
+ instance = await self.get_instance(request=request, **kwargs)
38
+ return Response(data=instance, status_code=status.HTTP_200_OK)
39
+
37
40
 
38
- class CursorRequired:
39
- def _check_cursor(self, cursor):
40
- if isinstance(cursor, (Cursor, PantherDBCursor)) is False:
41
- logger.critical(f'`{self.__class__.__name__}.cursor()` should return a Cursor --> `find()`')
42
- raise APIError
41
+ class ListAPI(GenericAPI):
42
+ sort_fields: list[str] = []
43
+ search_fields: list[str] = []
44
+ filter_fields: list[str] = []
45
+ pagination: type[Pagination] | None = None
43
46
 
44
- async def cursor(self, request: Request, **kwargs) -> Cursor | PantherDBCursor:
47
+ async def get_query(self, request: Request, **kwargs) -> Cursor | PantherDBCursor:
45
48
  """
46
- Used in `ListAPI`
47
- Should return `.find()`
49
+ Should return a Cursor, e.g. `await User.find()`
48
50
  """
49
- logger.error(f'`cursor()` method is not implemented in {self.__class__} .')
51
+ logger.error(f'`get_query()` method is not implemented in {self.__class__} .')
50
52
  raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
51
53
 
52
-
53
- class RetrieveAPI(GenericAPI, ObjectRequired):
54
- async def get(self, request: Request, **kwargs):
55
- instance = await self.object(request=request, **kwargs)
56
- self._check_object(instance)
57
-
58
- return Response(data=instance, status_code=status.HTTP_200_OK)
59
-
60
-
61
- class ListAPI(GenericAPI, CursorRequired):
62
- sort_fields: list[str]
63
- search_fields: list[str]
64
- filter_fields: list[str]
65
- pagination: type[Pagination]
66
-
67
54
  async def get(self, request: Request, **kwargs):
68
55
  cursor, pagination = await self.prepare_cursor(request=request, **kwargs)
69
56
  return Response(data=cursor, pagination=pagination, status_code=status.HTTP_200_OK)
70
57
 
71
58
  async def prepare_cursor(self, request: Request, **kwargs) -> tuple[Cursor | PantherDBCursor, Pagination | None]:
72
- cursor = await self.cursor(request=request, **kwargs)
73
- self._check_cursor(cursor)
59
+ cursor = await self.get_query(request=request, **kwargs)
60
+ if not isinstance(cursor, (Cursor, PantherDBCursor)):
61
+ logger.error(f'`{self.__class__.__name__}.get_query()` should return a Cursor, e.g. `await Model.find()`')
62
+ raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
74
63
 
75
64
  query = {}
76
65
  query |= self.process_filters(query_params=request.query_params, cursor=cursor)
@@ -89,103 +78,88 @@ class ListAPI(GenericAPI, CursorRequired):
89
78
 
90
79
  def process_filters(self, query_params: dict, cursor: Cursor | PantherDBCursor) -> dict:
91
80
  _filter = {}
92
- if hasattr(self, 'filter_fields'):
93
- for field in self.filter_fields:
94
- if field in query_params:
95
- if config.DATABASE.__class__.__name__ == 'MongoDBConnection':
96
- with contextlib.suppress(Exception):
97
- # Change type of the value if it is ObjectId
98
- if cursor.cls.model_fields[field].metadata[0].func.__name__ == 'validate_object_id':
99
- _filter[field] = bson.ObjectId(query_params[field])
100
- continue
101
- _filter[field] = query_params[field]
81
+ for field in self.filter_fields:
82
+ if field in query_params:
83
+ _filter[field] = query_params[field]
84
+ if isinstance(config.DATABASE, MongoDBConnection) and cursor.cls.model_fields[field].annotation == ID:
85
+ _filter[field] = bson.ObjectId(_filter[field])
102
86
  return _filter
103
87
 
104
88
  def process_search(self, query_params: dict) -> dict:
105
- if hasattr(self, 'search_fields') and 'search' in query_params:
106
- value = query_params['search']
107
- if config.DATABASE.__class__.__name__ == 'MongoDBConnection':
108
- if search := [{field: {'$regex': value}} for field in self.search_fields]:
109
- return {'$or': search}
110
- else:
111
- logger.warning(f'`?search={value} does not work well while using `PantherDB` as Database')
112
- return {field: value for field in self.search_fields}
113
- return {}
89
+ search_param = query_params.get('search')
90
+ if not self.search_fields or not search_param:
91
+ return {}
92
+ if isinstance(config.DATABASE, MongoDBConnection):
93
+ if search := [{field: {'$regex': search_param}} for field in self.search_fields]:
94
+ return {'$or': search}
95
+ return {field: search_param for field in self.search_fields}
114
96
 
115
97
  def process_sort(self, query_params: dict) -> list:
116
- if hasattr(self, 'sort_fields') and 'sort' in query_params:
117
- return [
118
- (field, -1 if param[0] == '-' else 1)
119
- for field in self.sort_fields for param in query_params['sort'].split(',')
120
- if field == param.removeprefix('-')
121
- ]
98
+ sort_param = query_params.get('sort')
99
+ if not self.sort_fields or not sort_param:
100
+ return []
101
+ return [
102
+ (field, -1 if param.startswith('-') else 1)
103
+ for param in sort_param.split(',')
104
+ for field in self.sort_fields
105
+ if field == param.removeprefix('-')
106
+ ]
122
107
 
123
108
  def process_pagination(self, query_params: dict, cursor: Cursor | PantherDBCursor) -> Pagination | None:
124
- if hasattr(self, 'pagination'):
109
+ if self.pagination:
125
110
  return self.pagination(query_params=query_params, cursor=cursor)
126
111
 
127
112
 
128
113
  class CreateAPI(GenericAPI):
129
- input_model: type[ModelSerializer]
114
+ input_model: type[ModelSerializer] | None = None
130
115
 
131
116
  async def post(self, request: Request, **kwargs):
132
- instance = await request.validated_data.create(validated_data={
133
- field: getattr(request.validated_data, field)
134
- for field in request.validated_data.model_fields_set
135
- if field != 'request'
136
- })
117
+ instance = await request.validated_data.model.insert_one(request.validated_data.model_dump())
137
118
  return Response(data=instance, status_code=status.HTTP_201_CREATED)
138
119
 
139
120
 
140
- class UpdateAPI(GenericAPI, ObjectRequired):
141
- input_model: type[ModelSerializer]
121
+ class UpdateAPI(GenericAPI):
122
+ input_model: type[ModelSerializer] | None = None
142
123
 
143
- async def put(self, request: Request, **kwargs):
144
- instance = await self.object(request=request, **kwargs)
145
- self._check_object(instance)
124
+ @abstractmethod
125
+ async def get_instance(self, request: Request, **kwargs) -> Model:
126
+ """
127
+ Should return an instance of Model, e.g. `await User.find_one()`
128
+ """
129
+ logger.error(f'`get_instance()` method is not implemented in {self.__class__} .')
130
+ raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
146
131
 
147
- await request.validated_data.update(
148
- instance=instance,
149
- validated_data=request.validated_data.model_dump(by_alias=True)
150
- )
132
+ async def put(self, request: Request, **kwargs):
133
+ instance = await self.get_instance(request=request, **kwargs)
134
+ await instance.update(request.validated_data.model_dump())
151
135
  return Response(data=instance, status_code=status.HTTP_200_OK)
152
136
 
153
137
  async def patch(self, request: Request, **kwargs):
154
- instance = await self.object(request=request, **kwargs)
155
- self._check_object(instance)
156
-
157
- await request.validated_data.partial_update(
158
- instance=instance,
159
- validated_data=request.validated_data.model_dump(exclude_none=True, by_alias=True)
160
- )
138
+ instance = await self.get_instance(request=request, **kwargs)
139
+ await instance.update(request.validated_data.model_dump(exclude_none=True))
161
140
  return Response(data=instance, status_code=status.HTTP_200_OK)
162
141
 
163
142
 
164
- class DeleteAPI(GenericAPI, ObjectRequired):
143
+ class DeleteAPI(GenericAPI):
144
+ @abstractmethod
145
+ async def get_instance(self, request: Request, **kwargs) -> Model:
146
+ """
147
+ Should return an instance of Model, e.g. `await User.find_one()`
148
+ """
149
+ logger.error(f'`get_instance()` method is not implemented in {self.__class__} .')
150
+ raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
151
+
165
152
  async def pre_delete(self, instance, request: Request, **kwargs):
153
+ """Hook for logic before deletion."""
166
154
  pass
167
155
 
168
156
  async def post_delete(self, instance, request: Request, **kwargs):
157
+ """Hook for logic after deletion."""
169
158
  pass
170
159
 
171
160
  async def delete(self, request: Request, **kwargs):
172
- instance = await self.object(request=request, **kwargs)
173
- self._check_object(instance)
174
-
161
+ instance = await self.get_instance(request=request, **kwargs)
175
162
  await self.pre_delete(instance, request=request, **kwargs)
176
163
  await instance.delete()
177
164
  await self.post_delete(instance, request=request, **kwargs)
178
-
179
165
  return Response(status_code=status.HTTP_204_NO_CONTENT)
180
-
181
-
182
- class ListCreateAPI(CreateAPI, ListAPI):
183
- pass
184
-
185
-
186
- class UpdateDeleteAPI(UpdateAPI, DeleteAPI):
187
- pass
188
-
189
-
190
- class RetrieveUpdateDeleteAPI(RetrieveAPI, UpdateAPI, DeleteAPI):
191
- pass
panther/logging.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from pathlib import Path
3
+
3
4
  from panther.configs import config
4
5
 
5
6
  LOGS_DIR = config.BASE_DIR / 'logs'
@@ -64,5 +65,5 @@ LOGGING = {
64
65
  'handlers': ['default', 'query_file'],
65
66
  'level': 'DEBUG',
66
67
  },
67
- }
68
+ },
68
69
  }
panther/main.py CHANGED
@@ -8,13 +8,13 @@ from pathlib import Path
8
8
  import panther.logging
9
9
  from panther import status
10
10
  from panther._load_configs import *
11
- from panther._utils import traceback_message, reformat_code
11
+ from panther._utils import reformat_code, traceback_message
12
12
  from panther.app import GenericAPI
13
13
  from panther.base_websocket import Websocket
14
14
  from panther.cli.utils import print_info
15
15
  from panther.configs import config
16
16
  from panther.events import Event
17
- from panther.exceptions import APIError, PantherError, NotFoundAPIError, BaseError, UpgradeRequiredError
17
+ from panther.exceptions import APIError, BaseError, NotFoundAPIError, PantherError, UpgradeRequiredError
18
18
  from panther.request import Request
19
19
  from panther.response import Response
20
20
  from panther.routings import find_endpoint
@@ -35,6 +35,7 @@ class Panther:
35
35
  If the configuration is defined in the current file, you can also set this to `__name__`.
36
36
  urls: A dictionary containing your URL routing.
37
37
  If not provided, Panther will attempt to load `URLs` from the configs module.
38
+
38
39
  """
39
40
  self._configs_module_name = configs
40
41
  self._urls = urls
@@ -45,7 +46,7 @@ class Panther:
45
46
  self.load_configs()
46
47
  if config.AUTO_REFORMAT:
47
48
  reformat_code(base_dir=config.BASE_DIR)
48
- except Exception as e: # noqa: BLE001
49
+ except Exception as e:
49
50
  logger.error(e.args[0] if isinstance(e, PantherError) else traceback_message(exception=e))
50
51
  sys.exit()
51
52
 
@@ -69,7 +70,7 @@ class Panther:
69
70
  load_middlewares(self._configs_module)
70
71
  load_auto_reformat(self._configs_module)
71
72
  load_background_tasks(self._configs_module)
72
- load_default_cache_exp(self._configs_module)
73
+ load_other_configs(self._configs_module)
73
74
  load_urls(self._configs_module, urls=self._urls)
74
75
  load_authentication_class(self._configs_module)
75
76
  load_websocket_connections()
@@ -79,15 +80,15 @@ class Panther:
79
80
  async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
80
81
  if scope['type'] == 'lifespan':
81
82
  message = await receive()
82
- if message["type"] == 'lifespan.startup':
83
+ if message['type'] == 'lifespan.startup':
83
84
  if config.HAS_WS:
84
85
  await config.WEBSOCKET_CONNECTIONS.start()
85
86
  try:
86
87
  await Event.run_startups()
87
88
  except Exception as e:
88
89
  logger.error(e)
89
- raise e
90
- elif message["type"] == 'lifespan.shutdown':
90
+ raise
91
+ elif message['type'] == 'lifespan.shutdown':
91
92
  # It's not happening :\, so handle the shutdowns in __del__ ...
92
93
  pass
93
94
  return
@@ -119,7 +120,6 @@ class Panther:
119
120
 
120
121
  return await config.WEBSOCKET_CONNECTIONS.listen(connection=final_connection)
121
122
 
122
-
123
123
  async def handle_ws(self, scope: dict, receive: Callable, send: Callable) -> None:
124
124
  # Create Temp Connection
125
125
  connection = Websocket(scope=scope, receive=receive, send=send)
@@ -139,7 +139,6 @@ class Panther:
139
139
  logger.error(traceback_message(exception=e))
140
140
  await connection.close()
141
141
 
142
-
143
142
  @classmethod
144
143
  async def handle_http_endpoint(cls, request: Request) -> Response:
145
144
  # Find Endpoint
@@ -176,7 +175,7 @@ class Panther:
176
175
  logger.error('You forgot to return `response` on the `Middlewares.__call__()`')
177
176
  response = Response(
178
177
  data={'detail': 'Internal Server Error'},
179
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
178
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
180
179
  )
181
180
  # Handle `APIError` Exceptions
182
181
  except APIError as e:
@@ -186,15 +185,15 @@ class Panther:
186
185
  status_code=e.status_code,
187
186
  )
188
187
  # Handle Unknown Exceptions
189
- except Exception as e: # noqa: BLE001 - Blind Exception
188
+ except Exception as e:
190
189
  logger.error(traceback_message(exception=e))
191
190
  response = Response(
192
191
  data={'detail': 'Internal Server Error'},
193
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
192
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
194
193
  )
195
194
 
196
195
  # Return Response
197
- await response.send(send, receive)
196
+ await response.send(send=send, receive=receive)
198
197
 
199
198
  def __del__(self):
200
199
  Event.run_shutdowns()
@@ -0,0 +1,67 @@
1
+ from panther.configs import config
2
+ from panther.middlewares import HTTPMiddleware
3
+ from panther.request import Request
4
+ from panther.response import Response
5
+
6
+
7
+ class CORSMiddleware(HTTPMiddleware):
8
+ """
9
+ Middleware to handle Cross-Origin Resource Sharing (CORS) for Panther applications.
10
+
11
+ This middleware automatically adds the appropriate CORS headers to all HTTP responses
12
+ based on configuration variables defined in your Panther config file (e.g., core/configs.py).
13
+ It also handles preflight (OPTIONS) requests.
14
+
15
+ Configuration attributes (set these in your config):
16
+ ---------------------------------------------------
17
+ ALLOW_ORIGINS: list[str]
18
+ List of allowed origins. Use ["*"] to allow all origins. Default: ["*"]
19
+ ALLOW_METHODS: list[str]
20
+ List of allowed HTTP methods. Default: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
21
+ ALLOW_HEADERS: list[str]
22
+ List of allowed request headers. Use ["*"] to allow all headers. Default: ["*"]
23
+ ALLOW_CREDENTIALS: bool
24
+ Whether to allow credentials (cookies, authorization headers, etc.). Default: False
25
+ EXPOSE_HEADERS: list[str]
26
+ List of headers that can be exposed to the browser. Default: []
27
+ CORS_MAX_AGE: int
28
+ Number of seconds browsers are allowed to cache preflight responses. Default: 600
29
+
30
+ Usage:
31
+ ------
32
+ 1. Set the above config variables in your config file as needed.
33
+ 2. Add 'panther.middlewares.cors.CORSMiddleware' to your MIDDLEWARES list.
34
+ """
35
+
36
+ async def __call__(self, request: Request) -> Response:
37
+ # Fetch CORS settings from config, with defaults
38
+ allow_origins = config.ALLOW_ORIGINS or ['*']
39
+ allow_methods = config.ALLOW_METHODS or ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
40
+ allow_headers = config.ALLOW_HEADERS or ['*']
41
+ allow_credentials = config.ALLOW_CREDENTIALS or False
42
+ expose_headers = config.EXPOSE_HEADERS or []
43
+ max_age = config.CORS_MAX_AGE or 600
44
+
45
+ # Handle preflight (OPTIONS) requests
46
+ if request.method == 'OPTIONS':
47
+ response = Response(status_code=204)
48
+ else:
49
+ response = await self.dispatch(request=request)
50
+
51
+ origin = request.headers['origin'] or '*'
52
+ if '*' in allow_origins:
53
+ allow_origin = '*'
54
+ elif origin in allow_origins:
55
+ allow_origin = origin
56
+ else:
57
+ allow_origin = allow_origins[0] if allow_origins else '*'
58
+
59
+ response.headers['Access-Control-Allow-Origin'] = allow_origin
60
+ response.headers['Access-Control-Allow-Methods'] = ', '.join(allow_methods)
61
+ response.headers['Access-Control-Allow-Headers'] = ', '.join(allow_headers)
62
+ response.headers['Access-Control-Max-Age'] = str(max_age)
63
+ if allow_credentials:
64
+ response.headers['Access-Control-Allow-Credentials'] = 'true'
65
+ if expose_headers:
66
+ response.headers['Access-Control-Expose-Headers'] = ', '.join(expose_headers)
67
+ return response
@@ -11,7 +11,7 @@ logger = logging.getLogger('monitoring')
11
11
  class MonitoringMiddleware(HTTPMiddleware):
12
12
  """
13
13
  Create Log Message Like Below:
14
- date_time | method | path | ip:port | response_time(seconds) | status
14
+ datetime | method | path | ip:port | response_time(seconds) | status
15
15
  """
16
16
 
17
17
  async def __call__(self, request: Request):
@@ -24,11 +24,13 @@ class MonitoringMiddleware(HTTPMiddleware):
24
24
  logger.info(f'{method} | {request.path} | {request.client} | {response_time} | {response.status_code}')
25
25
  return response
26
26
 
27
+
27
28
  class WebsocketMonitoringMiddleware(WebsocketMiddleware):
28
29
  """
29
30
  Create Log Message Like Below:
30
- date_time | WS | path | ip:port | connection_time(seconds) | status
31
+ datetime | WS | path | ip:port | connection_time(seconds) | status
31
32
  """
33
+
32
34
  ConnectedConnectionTime = ' - '
33
35
 
34
36
  async def __call__(self, connection: Websocket):
@@ -39,4 +41,4 @@ class WebsocketMonitoringMiddleware(WebsocketMiddleware):
39
41
 
40
42
  connection_time = perf_counter() - start_time # Seconds
41
43
  logger.info(f'WS | {connection.path} | {connection.client} | {connection_time} | {connection.state}')
42
- return connection
44
+ return connection
panther/openapi/urls.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from panther.openapi.views import OpenAPI
2
2
 
3
- urls = {
3
+ url_routing = {
4
4
  '': OpenAPI,
5
- }
5
+ }
panther/openapi/utils.py CHANGED
@@ -17,9 +17,9 @@ class OutputSchema:
17
17
  """
18
18
 
19
19
  def __init__(
20
- self,
21
- model: type[ModelSerializer] | type[pydantic.BaseModel] = EmptyResponseModel,
22
- status_code: int = status.HTTP_200_OK,
20
+ self,
21
+ model: type[ModelSerializer] | type[pydantic.BaseModel] = EmptyResponseModel,
22
+ status_code: int = status.HTTP_200_OK,
23
23
  ):
24
24
  self.model = model
25
25
  self.status_code = status_code
panther/openapi/views.py CHANGED
@@ -16,43 +16,31 @@ class OpenAPI(GenericAPI):
16
16
  schema = endpoint.output_schema.model.schema()
17
17
  else:
18
18
  status_code = parsed.status_code
19
- schema = {
20
- 'properties': {
21
- k: {'default': v} for k, v in parsed.data.items()
22
- }
23
- }
19
+ schema = {'properties': {k: {'default': v} for k, v in parsed.data.items()}}
24
20
 
25
21
  responses = {}
26
22
  if schema:
27
- responses = {
28
- 'responses': {
29
- status_code: {
30
- 'content': {
31
- 'application/json': {
32
- 'schema': schema
33
- }
34
- }
35
- }
36
- }
37
- }
23
+ responses = {'responses': {status_code: {'content': {'application/json': {'schema': schema}}}}}
38
24
  request_body = {}
39
25
  if endpoint.input_model and method in ['post', 'put', 'patch']:
40
26
  request_body = {
41
27
  'requestBody': {
42
28
  'required': True,
43
29
  'content': {
44
- 'application/json': {
45
- 'schema': endpoint.input_model.schema() if endpoint.input_model else {}
46
- }
47
- }
48
- }
30
+ 'application/json': {'schema': endpoint.input_model.schema() if endpoint.input_model else {}},
31
+ },
32
+ },
49
33
  }
50
34
 
51
- content = {
52
- 'title': parsed.title,
53
- 'summary': endpoint.__doc__,
54
- 'tags': ['.'.join(endpoint.__module__.rsplit('.')[:-1]) or endpoint.__module__],
55
- } | responses | request_body
35
+ content = (
36
+ {
37
+ 'title': parsed.title,
38
+ 'summary': endpoint.__doc__,
39
+ 'tags': ['.'.join(endpoint.__module__.rsplit('.')[:-1]) or endpoint.__module__],
40
+ }
41
+ | responses
42
+ | request_body
43
+ )
56
44
  return {method: content}
57
45
 
58
46
  def get(self):
@@ -70,15 +58,15 @@ class OpenAPI(GenericAPI):
70
58
 
71
59
  if isinstance(endpoint, types.FunctionType):
72
60
  methods = endpoint.methods
73
- if methods is None or 'POST' in methods:
61
+ if 'POST' in methods:
74
62
  paths[url] |= self.get_content(endpoint, 'post')
75
- if methods is None or 'GET' in methods:
63
+ if 'GET' in methods:
76
64
  paths[url] |= self.get_content(endpoint, 'get')
77
- if methods is None or 'PUT' in methods:
65
+ if 'PUT' in methods:
78
66
  paths[url] |= self.get_content(endpoint, 'put')
79
- if methods is None or 'PATCH' in methods:
67
+ if 'PATCH' in methods:
80
68
  paths[url] |= self.get_content(endpoint, 'patch')
81
- if methods is None or 'DELETE' in methods:
69
+ if 'DELETE' in methods:
82
70
  paths[url] |= self.get_content(endpoint, 'delete')
83
71
  else:
84
72
  if endpoint.post is not GenericAPI.post:
@@ -92,10 +80,5 @@ class OpenAPI(GenericAPI):
92
80
  if endpoint.delete is not GenericAPI.delete:
93
81
  paths[url] |= self.get_content(endpoint, 'delete')
94
82
 
95
- openapi_content = {
96
- 'openapi': '3.0.0',
97
- 'paths': paths,
98
- 'components': {}
99
- }
100
- print(f'{openapi_content=}')
83
+ openapi_content = {'openapi': '3.0.0', 'paths': paths, 'components': {}}
101
84
  return TemplateResponse(name='openapi.html', context={'openapi_content': openapi_content})
panther/pagination.py CHANGED
@@ -1,6 +1,7 @@
1
- from panther.db.cursor import Cursor
2
1
  from pantherdb import Cursor as PantherDBCursor
3
2
 
3
+ from panther.db.cursor import Cursor
4
+
4
5
 
5
6
  class Pagination:
6
7
  """
@@ -14,6 +15,7 @@ class Pagination:
14
15
  'results': [...]
15
16
  }
16
17
  """
18
+
17
19
  DEFAULT_LIMIT = 20
18
20
  DEFAULT_SKIP = 0
19
21
 
@@ -47,5 +49,5 @@ class Pagination:
47
49
  'count': count,
48
50
  'next': self.build_next_params() if has_next else None,
49
51
  'previous': self.build_previous_params() if self.skip else None,
50
- 'results': response
52
+ 'results': response,
51
53
  }
panther/panel/apis.py CHANGED
@@ -3,8 +3,7 @@ import contextlib
3
3
  from panther import status
4
4
  from panther.app import API
5
5
  from panther.configs import config
6
- from panther.db.connections import db
7
- from panther.db.connections import redis
6
+ from panther.db.connections import db, redis
8
7
  from panther.panel.utils import get_model_fields
9
8
  from panther.request import Request
10
9
  from panther.response import Response
@@ -16,11 +15,7 @@ with contextlib.suppress(ImportError):
16
15
 
17
16
  @API(methods=['GET'])
18
17
  async def models_api():
19
- return [{
20
- 'name': model.__name__,
21
- 'module': model.__module__,
22
- 'index': i
23
- } for i, model in enumerate(config.MODELS)]
18
+ return [{'name': model.__name__, 'module': model.__module__, 'index': i} for i, model in enumerate(config.MODELS)]
24
19
 
25
20
 
26
21
  @API(methods=['GET', 'POST'])
panther/panel/urls.py CHANGED
@@ -1,10 +1,6 @@
1
- from panther.panel.views import TableView, CreateView, LoginView, DetailView, HomeView
1
+ from panther.panel.views import CreateView, DetailView, HomeView, LoginView, TableView
2
2
 
3
- urls = {
4
- # '': models_api,
5
- # '<index>/': documents_api,
6
- # '<index>/<document_id>/': single_document_api,
7
- # 'health': healthcheck_api,
3
+ url_routing = {
8
4
  '': HomeView,
9
5
  '<index>/': TableView,
10
6
  '<index>/create/': CreateView,
panther/panel/utils.py CHANGED
@@ -51,6 +51,7 @@ def clean_model_schema(schema: dict) -> dict:
51
51
  'is_male': {'title': 'Is Male', 'type': ['boolean', 'null'], 'required': True}
52
52
  }
53
53
  }
54
+
54
55
  """
55
56
 
56
57
  result = defaultdict(dict)
@@ -108,8 +109,11 @@ def get_model_fields(model):
108
109
 
109
110
 
110
111
  def get_models():
111
- return [{
112
- 'index': i,
113
- 'name': model.__name__,
114
- 'module': model.__module__,
115
- } for i, model in enumerate(config.MODELS)]
112
+ return [
113
+ {
114
+ 'index': i,
115
+ 'name': model.__name__,
116
+ 'module': model.__module__,
117
+ }
118
+ for i, model in enumerate(config.MODELS)
119
+ ]