panther 4.0.0__tar.gz → 4.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {panther-4.0.0 → panther-4.1.0}/PKG-INFO +3 -22
  2. {panther-4.0.0 → panther-4.1.0}/README.md +2 -21
  3. {panther-4.0.0 → panther-4.1.0}/panther/__init__.py +1 -1
  4. {panther-4.0.0 → panther-4.1.0}/panther/app.py +1 -1
  5. {panther-4.0.0 → panther-4.1.0}/panther/base_request.py +5 -7
  6. {panther-4.0.0 → panther-4.1.0}/panther/base_websocket.py +3 -4
  7. {panther-4.0.0 → panther-4.1.0}/panther/generics.py +19 -10
  8. {panther-4.0.0 → panther-4.1.0}/panther/main.py +2 -1
  9. {panther-4.0.0 → panther-4.1.0}/panther/response.py +31 -14
  10. {panther-4.0.0 → panther-4.1.0}/panther/serializer.py +7 -4
  11. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/PKG-INFO +3 -22
  12. {panther-4.0.0 → panther-4.1.0}/tests/test_generics.py +2 -2
  13. {panther-4.0.0 → panther-4.1.0}/LICENSE +0 -0
  14. {panther-4.0.0 → panther-4.1.0}/panther/_load_configs.py +0 -0
  15. {panther-4.0.0 → panther-4.1.0}/panther/_utils.py +0 -0
  16. {panther-4.0.0 → panther-4.1.0}/panther/authentications.py +0 -0
  17. {panther-4.0.0 → panther-4.1.0}/panther/background_tasks.py +0 -0
  18. {panther-4.0.0 → panther-4.1.0}/panther/caching.py +0 -0
  19. {panther-4.0.0 → panther-4.1.0}/panther/cli/__init__.py +0 -0
  20. {panther-4.0.0 → panther-4.1.0}/panther/cli/create_command.py +0 -0
  21. {panther-4.0.0 → panther-4.1.0}/panther/cli/main.py +0 -0
  22. {panther-4.0.0 → panther-4.1.0}/panther/cli/monitor_command.py +0 -0
  23. {panther-4.0.0 → panther-4.1.0}/panther/cli/run_command.py +0 -0
  24. {panther-4.0.0 → panther-4.1.0}/panther/cli/template.py +0 -0
  25. {panther-4.0.0 → panther-4.1.0}/panther/cli/utils.py +0 -0
  26. {panther-4.0.0 → panther-4.1.0}/panther/configs.py +0 -0
  27. {panther-4.0.0 → panther-4.1.0}/panther/db/__init__.py +0 -0
  28. {panther-4.0.0 → panther-4.1.0}/panther/db/connections.py +0 -0
  29. {panther-4.0.0 → panther-4.1.0}/panther/db/cursor.py +0 -0
  30. {panther-4.0.0 → panther-4.1.0}/panther/db/models.py +0 -0
  31. {panther-4.0.0 → panther-4.1.0}/panther/db/queries/__init__.py +0 -0
  32. {panther-4.0.0 → panther-4.1.0}/panther/db/queries/base_queries.py +0 -0
  33. {panther-4.0.0 → panther-4.1.0}/panther/db/queries/mongodb_queries.py +0 -0
  34. {panther-4.0.0 → panther-4.1.0}/panther/db/queries/pantherdb_queries.py +0 -0
  35. {panther-4.0.0 → panther-4.1.0}/panther/db/queries/queries.py +0 -0
  36. {panther-4.0.0 → panther-4.1.0}/panther/db/utils.py +0 -0
  37. {panther-4.0.0 → panther-4.1.0}/panther/events.py +0 -0
  38. {panther-4.0.0 → panther-4.1.0}/panther/exceptions.py +0 -0
  39. {panther-4.0.0 → panther-4.1.0}/panther/file_handler.py +0 -0
  40. {panther-4.0.0 → panther-4.1.0}/panther/logging.py +0 -0
  41. {panther-4.0.0 → panther-4.1.0}/panther/middlewares/__init__.py +0 -0
  42. {panther-4.0.0 → panther-4.1.0}/panther/middlewares/base.py +0 -0
  43. {panther-4.0.0 → panther-4.1.0}/panther/monitoring.py +0 -0
  44. {panther-4.0.0 → panther-4.1.0}/panther/pagination.py +0 -0
  45. {panther-4.0.0 → panther-4.1.0}/panther/panel/__init__.py +0 -0
  46. {panther-4.0.0 → panther-4.1.0}/panther/panel/apis.py +0 -0
  47. {panther-4.0.0 → panther-4.1.0}/panther/panel/urls.py +0 -0
  48. {panther-4.0.0 → panther-4.1.0}/panther/panel/utils.py +0 -0
  49. {panther-4.0.0 → panther-4.1.0}/panther/permissions.py +0 -0
  50. {panther-4.0.0 → panther-4.1.0}/panther/request.py +0 -0
  51. {panther-4.0.0 → panther-4.1.0}/panther/routings.py +0 -0
  52. {panther-4.0.0 → panther-4.1.0}/panther/status.py +0 -0
  53. {panther-4.0.0 → panther-4.1.0}/panther/test.py +0 -0
  54. {panther-4.0.0 → panther-4.1.0}/panther/throttling.py +0 -0
  55. {panther-4.0.0 → panther-4.1.0}/panther/utils.py +0 -0
  56. {panther-4.0.0 → panther-4.1.0}/panther/websocket.py +0 -0
  57. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/SOURCES.txt +0 -0
  58. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/dependency_links.txt +0 -0
  59. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/entry_points.txt +0 -0
  60. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/requires.txt +0 -0
  61. {panther-4.0.0 → panther-4.1.0}/panther.egg-info/top_level.txt +0 -0
  62. {panther-4.0.0 → panther-4.1.0}/pyproject.toml +0 -0
  63. {panther-4.0.0 → panther-4.1.0}/setup.cfg +0 -0
  64. {panther-4.0.0 → panther-4.1.0}/setup.py +0 -0
  65. {panther-4.0.0 → panther-4.1.0}/tests/test_authentication.py +0 -0
  66. {panther-4.0.0 → panther-4.1.0}/tests/test_background_tasks.py +0 -0
  67. {panther-4.0.0 → panther-4.1.0}/tests/test_caching.py +0 -0
  68. {panther-4.0.0 → panther-4.1.0}/tests/test_cli.py +0 -0
  69. {panther-4.0.0 → panther-4.1.0}/tests/test_database.py +0 -0
  70. {panther-4.0.0 → panther-4.1.0}/tests/test_events.py +0 -0
  71. {panther-4.0.0 → panther-4.1.0}/tests/test_multipart.py +0 -0
  72. {panther-4.0.0 → panther-4.1.0}/tests/test_panel_apis.py +0 -0
  73. {panther-4.0.0 → panther-4.1.0}/tests/test_request.py +0 -0
  74. {panther-4.0.0 → panther-4.1.0}/tests/test_response.py +0 -0
  75. {panther-4.0.0 → panther-4.1.0}/tests/test_routing.py +0 -0
  76. {panther-4.0.0 → panther-4.1.0}/tests/test_run.py +0 -0
  77. {panther-4.0.0 → panther-4.1.0}/tests/test_serializer.py +0 -0
  78. {panther-4.0.0 → panther-4.1.0}/tests/test_status.py +0 -0
  79. {panther-4.0.0 → panther-4.1.0}/tests/test_throttling.py +0 -0
  80. {panther-4.0.0 → panther-4.1.0}/tests/test_utils.py +0 -0
  81. {panther-4.0.0 → panther-4.1.0}/tests/test_websockets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: panther
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: Fast & Friendly, Web Framework For Building Async APIs
5
5
  Home-page: https://github.com/alirn76/panther
6
6
  Author: Ali RajabNezhad
@@ -56,7 +56,7 @@ Requires-Dist: watchfiles~=0.21.0; extra == "full"
56
56
  - Support Custom **Middlewares**
57
57
  - Support Custom **Throttling**
58
58
  - Support **Function-Base** and **Class-Base** APIs
59
- - It's One Of The **Fastest Python Frameworks**
59
+ - It's One Of The **Fastest Python Framework**
60
60
  ---
61
61
 
62
62
  ### Supported by
@@ -68,25 +68,6 @@ Requires-Dist: watchfiles~=0.21.0; extra == "full"
68
68
 
69
69
  ---
70
70
 
71
- ### Benchmark
72
-
73
- | Framework | Throughput (Request/Second) |
74
- |------------|-----------------------------|
75
- | Blacksheep | 5,339 |
76
- | Muffin | 5,320 |
77
- | Panther | 5,112 |
78
- | Sanic | 3,660 |
79
- | FastAPI | 3,260 |
80
- | Tornado | 2,081 |
81
- | Bottle | 2,045 |
82
- | Django | 821 |
83
- | Flask | 749 |
84
-
85
-
86
- > **More Detail:** https://GitHub.com/PantherPy/frameworks-benchmark
87
-
88
- ---
89
-
90
71
  ### Installation
91
72
  ```shell
92
73
  $ pip install panther
@@ -105,7 +86,7 @@ $ pip install panther
105
86
  ```shell
106
87
  $ panther run --reload
107
88
  ```
108
- _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server too_
89
+ _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server_
109
90
 
110
91
  - #### Monitoring Requests
111
92
 
@@ -25,7 +25,7 @@
25
25
  - Support Custom **Middlewares**
26
26
  - Support Custom **Throttling**
27
27
  - Support **Function-Base** and **Class-Base** APIs
28
- - It's One Of The **Fastest Python Frameworks**
28
+ - It's One Of The **Fastest Python Framework**
29
29
  ---
30
30
 
31
31
  ### Supported by
@@ -37,25 +37,6 @@
37
37
 
38
38
  ---
39
39
 
40
- ### Benchmark
41
-
42
- | Framework | Throughput (Request/Second) |
43
- |------------|-----------------------------|
44
- | Blacksheep | 5,339 |
45
- | Muffin | 5,320 |
46
- | Panther | 5,112 |
47
- | Sanic | 3,660 |
48
- | FastAPI | 3,260 |
49
- | Tornado | 2,081 |
50
- | Bottle | 2,045 |
51
- | Django | 821 |
52
- | Flask | 749 |
53
-
54
-
55
- > **More Detail:** https://GitHub.com/PantherPy/frameworks-benchmark
56
-
57
- ---
58
-
59
40
  ### Installation
60
41
  ```shell
61
42
  $ pip install panther
@@ -74,7 +55,7 @@ $ pip install panther
74
55
  ```shell
75
56
  $ panther run --reload
76
57
  ```
77
- _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server too_
58
+ _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server_
78
59
 
79
60
  - #### Monitoring Requests
80
61
 
@@ -1,6 +1,6 @@
1
1
  from panther.main import Panther # noqa: F401
2
2
 
3
- __version__ = '4.0.0'
3
+ __version__ = '4.1.0'
4
4
 
5
5
 
6
6
  def version():
@@ -95,7 +95,7 @@ class API:
95
95
  if not isinstance(response, Response):
96
96
  response = Response(data=response)
97
97
  if self.output_model and response.data:
98
- response.data = response.apply_output_model(response.data, output_model=self.output_model)
98
+ response.data = await response.apply_output_model(output_model=self.output_model)
99
99
 
100
100
  # 10. Set New Response To Cache
101
101
  if self.cache and self.request.method == 'GET':
@@ -27,9 +27,9 @@ class Headers:
27
27
  sec_websocket_version: str
28
28
  sec_websocket_key: str
29
29
 
30
- def __init__(self, headers):
31
- self.__headers = headers
32
- self.__pythonic_headers = {k.lower().replace('-', '_'): v for k, v in headers.items()}
30
+ def __init__(self, headers: list):
31
+ self.__headers = {header[0].decode('utf-8'): header[1].decode('utf-8') for header in headers}
32
+ self.__pythonic_headers = {k.lower().replace('-', '_'): v for k, v in self.__headers.items()}
33
33
 
34
34
  def __getattr__(self, item: str):
35
35
  if result := self.__pythonic_headers.get(item):
@@ -68,8 +68,7 @@ class BaseRequest:
68
68
  @property
69
69
  def headers(self) -> Headers:
70
70
  if self._headers is None:
71
- _headers = {header[0].decode('utf-8'): header[1].decode('utf-8') for header in self.scope['headers']}
72
- self._headers = Headers(_headers)
71
+ self._headers = Headers(self.scope['headers'])
73
72
  return self._headers
74
73
 
75
74
  @property
@@ -77,8 +76,7 @@ class BaseRequest:
77
76
  if self._params is None:
78
77
  self._params = {}
79
78
  if (query_string := self.scope['query_string']) != b'':
80
- query_string = query_string.decode('utf-8').split('&')
81
- for param in query_string:
79
+ for param in query_string.decode('utf-8').split('&'):
82
80
  k, *_, v = param.split('=')
83
81
  self._params[k] = v
84
82
  return self._params
@@ -182,10 +182,9 @@ class WebsocketConnections(Singleton):
182
182
  but they have same Manager()
183
183
  """
184
184
 
185
- if config.HAS_WS:
186
- # Schedule the async function to run in the background,
187
- # We don't need to await for this task
188
- asyncio.create_task(self())
185
+ # Schedule the async function to run in the background,
186
+ # We don't need to await for this task
187
+ asyncio.create_task(self())
189
188
 
190
189
  @classmethod
191
190
  async def handle_authentication(cls, connection: Websocket):
@@ -27,7 +27,7 @@ class ObjectRequired:
27
27
  logger.critical(f'`{self.__class__.__name__}.object()` should return instance of a Model --> `find_one()`')
28
28
  raise APIError
29
29
 
30
- async def object(self, request: Request, **kwargs) -> Model:
30
+ async def object(self, request: Request, **kwargs):
31
31
  """
32
32
  Used in `RetrieveAPI`, `UpdateAPI`, `DeleteAPI`
33
33
  """
@@ -35,18 +35,18 @@ class ObjectRequired:
35
35
  raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
36
36
 
37
37
 
38
- class ObjectsRequired:
39
- def _check_objects(self, cursor):
38
+ class CursorRequired:
39
+ def _check_cursor(self, cursor):
40
40
  if isinstance(cursor, (Cursor, PantherDBCursor)) is False:
41
- logger.critical(f'`{self.__class__.__name__}.objects()` should return a Cursor --> `find()`')
41
+ logger.critical(f'`{self.__class__.__name__}.cursor()` should return a Cursor --> `find()`')
42
42
  raise APIError
43
43
 
44
- async def objects(self, request: Request, **kwargs) -> Cursor | PantherDBCursor:
44
+ async def cursor(self, request: Request, **kwargs) -> Cursor | PantherDBCursor:
45
45
  """
46
46
  Used in `ListAPI`
47
47
  Should return `.find()`
48
48
  """
49
- logger.error(f'`objects()` method is not implemented in {self.__class__} .')
49
+ logger.error(f'`cursor()` method is not implemented in {self.__class__} .')
50
50
  raise APIError(status_code=status.HTTP_501_NOT_IMPLEMENTED)
51
51
 
52
52
 
@@ -58,15 +58,19 @@ class RetrieveAPI(GenericAPI, ObjectRequired):
58
58
  return Response(data=instance, status_code=status.HTTP_200_OK)
59
59
 
60
60
 
61
- class ListAPI(GenericAPI, ObjectsRequired):
61
+ class ListAPI(GenericAPI, CursorRequired):
62
62
  sort_fields: list[str]
63
63
  search_fields: list[str]
64
64
  filter_fields: list[str]
65
65
  pagination: type[Pagination]
66
66
 
67
67
  async def get(self, request: Request, **kwargs):
68
- cursor = await self.objects(request=request, **kwargs)
69
- self._check_objects(cursor)
68
+ cursor = await self.prepare_cursor(request=request, **kwargs)
69
+ return Response(data=cursor, status_code=status.HTTP_200_OK)
70
+
71
+ async def prepare_cursor(self, request: Request, **kwargs):
72
+ cursor = await self.cursor(request=request, **kwargs)
73
+ self._check_cursor(cursor)
70
74
 
71
75
  query = {}
72
76
  query |= self.process_filters(query_params=request.query_params, cursor=cursor)
@@ -81,7 +85,7 @@ class ListAPI(GenericAPI, ObjectsRequired):
81
85
  if pagination := self.process_pagination(query_params=request.query_params, cursor=cursor):
82
86
  cursor = await pagination.paginate()
83
87
 
84
- return Response(data=cursor, status_code=status.HTTP_200_OK)
88
+ return cursor
85
89
 
86
90
  def process_filters(self, query_params: dict, cursor: Cursor | PantherDBCursor) -> dict:
87
91
  _filter = {}
@@ -90,6 +94,7 @@ class ListAPI(GenericAPI, ObjectsRequired):
90
94
  if field in query_params:
91
95
  if config.DATABASE.__class__.__name__ == 'MongoDBConnection':
92
96
  with contextlib.suppress(Exception):
97
+ # Change type of the value if it is ObjectId
93
98
  if cursor.cls.model_fields[field].metadata[0].func.__name__ == 'validate_object_id':
94
99
  _filter[field] = bson.ObjectId(query_params[field])
95
100
  continue
@@ -161,3 +166,7 @@ class DeleteAPI(GenericAPI, ObjectRequired):
161
166
 
162
167
  await instance.delete()
163
168
  return Response(status_code=status.HTTP_204_NO_CONTENT)
169
+
170
+
171
+ class ListCreateAPI(CreateAPI, ListAPI):
172
+ pass
@@ -70,7 +70,8 @@ class Panther:
70
70
  if scope['type'] == 'lifespan':
71
71
  message = await receive()
72
72
  if message["type"] == 'lifespan.startup':
73
- await config.WEBSOCKET_CONNECTIONS.start()
73
+ if config.HAS_WS:
74
+ await config.WEBSOCKET_CONNECTIONS.start()
74
75
  await Event.run_startups()
75
76
  elif message["type"] == 'lifespan.shutdown':
76
77
  # It's not happening :\, so handle the shutdowns in __del__ ...
@@ -1,10 +1,9 @@
1
1
  import asyncio
2
2
  from types import NoneType
3
- from typing import Generator, AsyncGenerator
3
+ from typing import Generator, AsyncGenerator, Any, Type
4
4
 
5
5
  import orjson as json
6
- from pydantic import BaseModel as PydanticBaseModel
7
- from pydantic._internal._model_construction import ModelMetaclass
6
+ from pydantic import BaseModel
8
7
 
9
8
  from panther import status
10
9
  from panther._utils import to_async_generator
@@ -12,7 +11,7 @@ from panther.db.cursor import Cursor
12
11
  from pantherdb import Cursor as PantherDBCursor
13
12
  from panther.monitoring import Monitoring
14
13
 
15
- ResponseDataTypes = list | tuple | set | Cursor | PantherDBCursor | dict | int | float | str | bool | bytes | NoneType | ModelMetaclass
14
+ ResponseDataTypes = list | tuple | set | Cursor | PantherDBCursor | dict | int | float | str | bool | bytes | NoneType | Type[BaseModel]
16
15
  IterableDataTypes = list | tuple | set | Cursor | PantherDBCursor
17
16
  StreamingDataTypes = Generator | AsyncGenerator
18
17
 
@@ -32,6 +31,7 @@ class Response:
32
31
  :param status_code: should be int
33
32
  """
34
33
  self.headers = headers or {}
34
+ self.initial_data = data
35
35
  self.data = self.prepare_data(data=data)
36
36
  self.status_code = self.check_status_code(status_code=status_code)
37
37
 
@@ -68,7 +68,7 @@ class Response:
68
68
  elif isinstance(data, dict):
69
69
  return {key: self.prepare_data(value) for key, value in data.items()}
70
70
 
71
- elif issubclass(type(data), PydanticBaseModel):
71
+ elif issubclass(type(data), BaseModel):
72
72
  return data.model_dump()
73
73
 
74
74
  elif isinstance(data, IterableDataTypes):
@@ -79,25 +79,42 @@ class Response:
79
79
  raise TypeError(msg)
80
80
 
81
81
  @classmethod
82
- def check_status_code(cls, status_code: any):
82
+ def check_status_code(cls, status_code: Any):
83
83
  if not isinstance(status_code, int):
84
84
  error = f'Response `status_code` Should Be `int`. (`{status_code}` is {type(status_code)})'
85
85
  raise TypeError(error)
86
86
  return status_code
87
87
 
88
- @classmethod
89
- def apply_output_model(cls, data: any, /, output_model: ModelMetaclass):
88
+ async def apply_output_model(self, output_model: Type[BaseModel]):
90
89
  """This method is called in API.__call__"""
90
+
91
91
  # Dict
92
- if isinstance(data, dict):
92
+ if isinstance(self.data, dict):
93
+ # Apply `validation_alias` (id -> _id)
93
94
  for field_name, field in output_model.model_fields.items():
94
- if field.validation_alias and field_name in data:
95
- data[field.validation_alias] = data.pop(field_name)
96
- return output_model(**data).model_dump()
95
+ if field.validation_alias and field_name in self.data:
96
+ self.data[field.validation_alias] = self.data.pop(field_name)
97
+ output = output_model(**self.data)
98
+ if hasattr(output_model, 'prepare_response'):
99
+ return await output.prepare_response(instance=self.initial_data, data=output.model_dump())
100
+ return output.model_dump()
97
101
 
98
102
  # Iterable
99
- if isinstance(data, IterableDataTypes):
100
- return [cls.apply_output_model(d, output_model=output_model) for d in data]
103
+ results = []
104
+ if isinstance(self.data, IterableDataTypes):
105
+ for i, d in enumerate(self.data):
106
+ # Apply `validation_alias` (id -> _id)
107
+ for field_name, field in output_model.model_fields.items():
108
+ if field.validation_alias and field_name in d:
109
+ d[field.validation_alias] = d.pop(field_name)
110
+
111
+ output = output_model(**d)
112
+ if hasattr(output_model, 'prepare_response'):
113
+ result = await output.prepare_response(instance=self.initial_data[i], data=output.model_dump())
114
+ else:
115
+ result = output.model_dump()
116
+ results.append(result)
117
+ return results
101
118
 
102
119
  # Str | Bool | Bytes
103
120
  msg = 'Type of Response data is not match with `output_model`.\n*hint: You may want to remove `output_model`'
@@ -1,5 +1,5 @@
1
1
  import typing
2
- from typing import TypeVar, Type
2
+ from typing import Any
3
3
 
4
4
  from pydantic import create_model, BaseModel, ConfigDict
5
5
  from pydantic.fields import FieldInfo, Field
@@ -205,13 +205,13 @@ class ModelSerializer(metaclass=MetaModelSerializer):
205
205
  model: type[BaseModel]
206
206
  request: Request
207
207
 
208
- async def create(self, validated_data: dict) -> Model:
208
+ async def create(self, validated_data: dict):
209
209
  """
210
210
  validated_data = ModelSerializer.model_dump()
211
211
  """
212
212
  return await self.model.insert_one(validated_data)
213
213
 
214
- async def update(self, instance: Model, validated_data: dict) -> Model:
214
+ async def update(self, instance: Model, validated_data: dict):
215
215
  """
216
216
  instance = UpdateAPI.object()
217
217
  validated_data = ModelSerializer.model_dump()
@@ -219,10 +219,13 @@ class ModelSerializer(metaclass=MetaModelSerializer):
219
219
  await instance.update(validated_data)
220
220
  return instance
221
221
 
222
- async def partial_update(self, instance: Model, validated_data: dict) -> Model:
222
+ async def partial_update(self, instance: Model, validated_data: dict):
223
223
  """
224
224
  instance = UpdateAPI.object()
225
225
  validated_data = ModelSerializer.model_dump(exclude_none=True)
226
226
  """
227
227
  await instance.update(validated_data)
228
228
  return instance
229
+
230
+ async def prepare_response(self, instance: Any, data: dict) -> dict:
231
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: panther
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: Fast & Friendly, Web Framework For Building Async APIs
5
5
  Home-page: https://github.com/alirn76/panther
6
6
  Author: Ali RajabNezhad
@@ -56,7 +56,7 @@ Requires-Dist: watchfiles~=0.21.0; extra == "full"
56
56
  - Support Custom **Middlewares**
57
57
  - Support Custom **Throttling**
58
58
  - Support **Function-Base** and **Class-Base** APIs
59
- - It's One Of The **Fastest Python Frameworks**
59
+ - It's One Of The **Fastest Python Framework**
60
60
  ---
61
61
 
62
62
  ### Supported by
@@ -68,25 +68,6 @@ Requires-Dist: watchfiles~=0.21.0; extra == "full"
68
68
 
69
69
  ---
70
70
 
71
- ### Benchmark
72
-
73
- | Framework | Throughput (Request/Second) |
74
- |------------|-----------------------------|
75
- | Blacksheep | 5,339 |
76
- | Muffin | 5,320 |
77
- | Panther | 5,112 |
78
- | Sanic | 3,660 |
79
- | FastAPI | 3,260 |
80
- | Tornado | 2,081 |
81
- | Bottle | 2,045 |
82
- | Django | 821 |
83
- | Flask | 749 |
84
-
85
-
86
- > **More Detail:** https://GitHub.com/PantherPy/frameworks-benchmark
87
-
88
- ---
89
-
90
71
  ### Installation
91
72
  ```shell
92
73
  $ pip install panther
@@ -105,7 +86,7 @@ $ pip install panther
105
86
  ```shell
106
87
  $ panther run --reload
107
88
  ```
108
- _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server too_
89
+ _* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server_
109
90
 
110
91
  - #### Monitoring Requests
111
92
 
@@ -24,7 +24,7 @@ class RetrieveAPITest(RetrieveAPI):
24
24
 
25
25
 
26
26
  class ListAPITest(ListAPI):
27
- async def objects(self, request: Request, **kwargs):
27
+ async def cursor(self, request: Request, **kwargs):
28
28
  return await User.find()
29
29
 
30
30
 
@@ -34,7 +34,7 @@ class FullListAPITest(ListAPI):
34
34
  filter_fields = ['name', 'age']
35
35
  pagination = Pagination
36
36
 
37
- async def objects(self, request: Request, **kwargs):
37
+ async def cursor(self, request: Request, **kwargs):
38
38
  return await Person.find()
39
39
 
40
40
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes