panther 4.2.6__py3-none-any.whl → 4.3.1__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 CHANGED
@@ -1,6 +1,6 @@
1
1
  from panther.main import Panther # noqa: F401
2
2
 
3
- __version__ = '4.2.6'
3
+ __version__ = '4.3.1'
4
4
 
5
5
 
6
6
  def version():
panther/_load_configs.py CHANGED
@@ -1,9 +1,10 @@
1
1
  import logging
2
2
  import sys
3
+ import types
3
4
  from importlib import import_module
4
5
  from multiprocessing import Manager
5
6
 
6
- from panther._utils import import_class
7
+ from panther._utils import import_class, check_function_type_endpoint, check_class_type_endpoint
7
8
  from panther.background_tasks import background_tasks
8
9
  from panther.base_websocket import WebsocketConnections
9
10
  from panther.cli.utils import import_error
@@ -29,12 +30,14 @@ __all__ = (
29
30
  'load_user_model',
30
31
  'load_log_queries',
31
32
  'load_middlewares',
33
+ 'load_templates_dir',
32
34
  'load_auto_reformat',
33
35
  'load_background_tasks',
34
36
  'load_default_cache_exp',
35
37
  'load_authentication_class',
36
38
  'load_urls',
37
39
  'load_websocket_connections',
40
+ 'check_endpoints_inheritance',
38
41
  )
39
42
 
40
43
  logger = logging.getLogger('panther')
@@ -82,6 +85,11 @@ def load_timezone(_configs: dict, /) -> None:
82
85
  config.TIMEZONE = timezone
83
86
 
84
87
 
88
+ def load_templates_dir(_configs: dict, /) -> None:
89
+ if templates_dir := _configs.get('TEMPLATES_DIR'):
90
+ config.TEMPLATES_DIR = templates_dir
91
+
92
+
85
93
  def load_database(_configs: dict, /) -> None:
86
94
  database_config = _configs.get('DATABASE', {})
87
95
  if 'engine' in database_config:
@@ -259,5 +267,14 @@ def load_websocket_connections():
259
267
  config.WEBSOCKET_CONNECTIONS = WebsocketConnections(pubsub_connection=pubsub_connection)
260
268
 
261
269
 
270
+ def check_endpoints_inheritance():
271
+ """Should be after `load_urls()`"""
272
+ for _, endpoint in config.FLAT_URLS.items():
273
+ if isinstance(endpoint, types.FunctionType):
274
+ check_function_type_endpoint(endpoint=endpoint)
275
+ else:
276
+ check_class_type_endpoint(endpoint=endpoint)
277
+
278
+
262
279
  def _exception_handler(field: str, error: str | Exception) -> PantherError:
263
280
  return PantherError(f"Invalid '{field}': {error}")
panther/_utils.py CHANGED
@@ -4,12 +4,13 @@ import logging
4
4
  import re
5
5
  import subprocess
6
6
  import types
7
- from typing import Any, Generator, Iterator, AsyncGenerator
8
7
  from collections.abc import Callable
9
8
  from traceback import TracebackException
9
+ from typing import Any, Generator, Iterator, AsyncGenerator
10
10
 
11
11
  from panther.exceptions import PantherError
12
12
  from panther.file_handler import File
13
+ from panther.websocket import GenericWebsocket
13
14
 
14
15
  logger = logging.getLogger('panther')
15
16
 
@@ -99,19 +100,18 @@ def reformat_code(base_dir):
99
100
  def check_function_type_endpoint(endpoint: types.FunctionType) -> Callable:
100
101
  # Function Doesn't Have @API Decorator
101
102
  if not hasattr(endpoint, '__wrapped__'):
102
- logger.critical(f'You may have forgotten to use @API() on the {endpoint.__name__}()')
103
- raise TypeError
104
- return endpoint
103
+ raise PantherError(
104
+ f'You may have forgotten to use `@API()` on the `{endpoint.__module__}.{endpoint.__name__}()`')
105
105
 
106
106
 
107
107
  def check_class_type_endpoint(endpoint: Callable) -> Callable:
108
108
  from panther.app import GenericAPI
109
109
 
110
- if not issubclass(endpoint, GenericAPI):
111
- logger.critical(f'You may have forgotten to inherit from GenericAPI on the {endpoint.__name__}()')
112
- raise TypeError
113
-
114
- return endpoint().call_method
110
+ if not issubclass(endpoint, (GenericAPI, GenericWebsocket)):
111
+ raise PantherError(
112
+ f'You may have forgotten to inherit from `panther.app.GenericAPI` or `panther.app.GenericWebsocket` '
113
+ f'on the `{endpoint.__module__}.{endpoint.__name__}()`'
114
+ )
115
115
 
116
116
 
117
117
  def async_next(iterator: Iterator):
panther/configs.py CHANGED
@@ -67,6 +67,7 @@ class Config:
67
67
  STARTUPS: list[Callable]
68
68
  SHUTDOWNS: list[Callable]
69
69
  TIMEZONE: str
70
+ TEMPLATES_DIR: str | list[str]
70
71
  AUTO_REFORMAT: bool
71
72
  QUERY_ENGINE: typing.Callable | None
72
73
  DATABASE: typing.Callable | None
@@ -110,6 +111,7 @@ default_configs = {
110
111
  'STARTUPS': [],
111
112
  'SHUTDOWNS': [],
112
113
  'TIMEZONE': 'UTC',
114
+ 'TEMPLATES_DIR': 'templates',
113
115
  'AUTO_REFORMAT': False,
114
116
  'QUERY_ENGINE': None,
115
117
  'DATABASE': None,
panther/main.py CHANGED
@@ -58,6 +58,7 @@ class Panther:
58
58
  load_throttling(self._configs_module)
59
59
  load_user_model(self._configs_module)
60
60
  load_log_queries(self._configs_module)
61
+ load_templates_dir(self._configs_module)
61
62
  load_middlewares(self._configs_module)
62
63
  load_auto_reformat(self._configs_module)
63
64
  load_background_tasks(self._configs_module)
@@ -66,6 +67,8 @@ class Panther:
66
67
  load_urls(self._configs_module, urls=self._urls)
67
68
  load_websocket_connections()
68
69
 
70
+ check_endpoints_inheritance()
71
+
69
72
  async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
70
73
  if scope['type'] == 'lifespan':
71
74
  message = await receive()
@@ -159,19 +162,10 @@ class Panther:
159
162
  await request.read_body()
160
163
 
161
164
  # Find Endpoint
162
- _endpoint, found_path = find_endpoint(path=request.path)
163
- if _endpoint is None:
165
+ endpoint, found_path = find_endpoint(path=request.path)
166
+ if endpoint is None:
164
167
  return await self._raise(send, monitoring=monitoring, status_code=status.HTTP_404_NOT_FOUND)
165
168
 
166
- # Check Endpoint Type
167
- try:
168
- if isinstance(_endpoint, types.FunctionType):
169
- endpoint = check_function_type_endpoint(endpoint=_endpoint)
170
- else:
171
- endpoint = check_class_type_endpoint(endpoint=_endpoint)
172
- except TypeError:
173
- return await self._raise(send, monitoring=monitoring, status_code=status.HTTP_501_NOT_IMPLEMENTED)
174
-
175
169
  # Collect Path Variables
176
170
  request.collect_path_variables(found_path=found_path)
177
171
 
@@ -185,6 +179,10 @@ class Panther:
185
179
  f'Make sure to return the `request` at the end of `{middleware.__class__.__name__}.before()`')
186
180
  return await self._raise(send, monitoring=monitoring)
187
181
 
182
+ # Prepare the method
183
+ if not isinstance(endpoint, types.FunctionType):
184
+ endpoint = endpoint().call_method
185
+
188
186
  # Call Endpoint
189
187
  response = await endpoint(request=request)
190
188
 
panther/response.py CHANGED
@@ -1,11 +1,22 @@
1
1
  import asyncio
2
+ from sys import version_info
2
3
  from types import NoneType
3
4
  from typing import Generator, AsyncGenerator, Any, Type
4
5
 
6
+ if version_info >= (3, 11):
7
+ from typing import LiteralString
8
+ else:
9
+ from typing import TypeVar
10
+
11
+ LiteralString = TypeVar('LiteralString')
12
+
13
+
5
14
  import orjson as json
6
15
  from pydantic import BaseModel
16
+ from jinja2 import Environment, FileSystemLoader
7
17
 
8
18
  from panther import status
19
+ from panther.configs import config
9
20
  from panther._utils import to_async_generator
10
21
  from panther.db.cursor import Cursor
11
22
  from pantherdb import Cursor as PantherDBCursor
@@ -215,3 +226,29 @@ class PlainTextResponse(Response):
215
226
  if isinstance(self.data, bytes):
216
227
  return self.data
217
228
  return self.data.encode()
229
+
230
+
231
+ class TemplateResponse(HTMLResponse):
232
+ environment = Environment(loader=FileSystemLoader(config.TEMPLATES_DIR))
233
+
234
+ def __init__(
235
+ self,
236
+ source: str | LiteralString | NoneType = None,
237
+ path: str | NoneType = None,
238
+ context: dict | NoneType = None,
239
+ headers: dict | NoneType = None,
240
+ status_code: int = status.HTTP_200_OK,
241
+ pagination: Pagination | NoneType = None,
242
+ ):
243
+ """
244
+ :param source: should be a string
245
+ :param path: should be path of template file
246
+ :param context: should be dict of items
247
+ :param headers: should be dict of headers
248
+ :param status_code: should be int
249
+ :param pagination: instance of Pagination or None
250
+ Its template() method will be used
251
+ """
252
+
253
+ template = self.environment.get_template(path) if path is not None else self.environment.from_string(source)
254
+ super().__init__(template.render(context), headers, status_code, pagination=pagination)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: panther
3
- Version: 4.2.6
3
+ Version: 4.3.1
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
@@ -13,21 +13,22 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Python: >=3.10
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: httptools ~=0.6.1
17
- Requires-Dist: pantherdb ~=2.1.0
18
- Requires-Dist: pydantic ~=2.7.4
19
- Requires-Dist: rich ~=13.7.1
20
- Requires-Dist: uvicorn ~=0.27.1
21
- Requires-Dist: pytz ~=2024.1
16
+ Requires-Dist: httptools~=0.6.1
17
+ Requires-Dist: pantherdb~=2.1.0
18
+ Requires-Dist: pydantic~=2.7.4
19
+ Requires-Dist: rich~=13.7.1
20
+ Requires-Dist: uvicorn~=0.27.1
21
+ Requires-Dist: pytz~=2024.1
22
+ Requires-Dist: Jinja2~=3.1
22
23
  Provides-Extra: full
23
- Requires-Dist: redis ==5.0.1 ; extra == 'full'
24
- Requires-Dist: motor ~=3.5.0 ; extra == 'full'
25
- Requires-Dist: bpython ~=0.24 ; extra == 'full'
26
- Requires-Dist: ruff ~=0.1.9 ; extra == 'full'
27
- Requires-Dist: python-jose ~=3.3.0 ; extra == 'full'
28
- Requires-Dist: websockets ~=12.0 ; extra == 'full'
29
- Requires-Dist: cryptography ~=42.0.8 ; extra == 'full'
30
- Requires-Dist: watchfiles ~=0.21.0 ; extra == 'full'
24
+ Requires-Dist: redis==5.0.1; extra == "full"
25
+ Requires-Dist: motor~=3.5.0; extra == "full"
26
+ Requires-Dist: bpython~=0.24; extra == "full"
27
+ Requires-Dist: ruff~=0.1.9; extra == "full"
28
+ Requires-Dist: python-jose~=3.3.0; extra == "full"
29
+ Requires-Dist: websockets~=12.0; extra == "full"
30
+ Requires-Dist: cryptography~=42.0.8; extra == "full"
31
+ Requires-Dist: watchfiles~=0.21.0; extra == "full"
31
32
 
32
33
 
33
34
  [![PyPI](https://img.shields.io/pypi/v/panther?label=PyPI)](https://pypi.org/project/panther/) [![PyVersion](https://img.shields.io/pypi/pyversions/panther.svg)](https://pypi.org/project/panther/) [![codecov](https://codecov.io/github/AliRn76/panther/graph/badge.svg?token=YWFQA43GSP)](https://codecov.io/github/AliRn76/panther) [![Downloads](https://static.pepy.tech/badge/panther/month)](https://pepy.tech/project/panther) [![license](https://img.shields.io/github/license/alirn76/panther.svg)](https://github.com/alirn76/panther/blob/main/LICENSE)
@@ -1,24 +1,24 @@
1
- panther/__init__.py,sha256=tmDiShQr_-JjYBeI3TswawX-WkQFwh8Hmd0pmBS_6-8,110
2
- panther/_load_configs.py,sha256=AVkoixkUFkBQiTmrLrwCmg0eiPW2U_Uw2EGNEGQRfnI,9281
3
- panther/_utils.py,sha256=j0rwIxTf0rtcZAAD-1nGE-_bWpvinyKtnwt3uO0hMmY,4330
1
+ panther/__init__.py,sha256=hMDOHZroAMX-dAiBlxUfhmaKrxK2GYCD-0hCQf25VJU,110
2
+ panther/_load_configs.py,sha256=NZEwp3s7Mq_gU6w5FFN-3JfJImjZtf4_q-qengDQ6Y0,9875
3
+ panther/_utils.py,sha256=R1tsUde1zA5iJtk-ZvWe8hp6JaNhKgNoH2rxpdiQDMc,4449
4
4
  panther/app.py,sha256=e2eb4sXIaBje5vpcm4pbvvEO_sj83pLfBHCIZJFFX38,8222
5
5
  panther/authentications.py,sha256=gf7BVyQ8vXKhiumJAtD0aAK7uIHWx_snbOKYAKrYuVw,5677
6
6
  panther/background_tasks.py,sha256=HBYubDIiO_673cl_5fqCUP9zzimzRgRkDSkag9Msnbs,7656
7
7
  panther/base_request.py,sha256=XD2v1gLWcCKHePowRxT6_fYnS4tdKFxTLINMX0HQu8M,3880
8
8
  panther/base_websocket.py,sha256=L0tiQQjg7E3462cd91PMf_SoVMMK4YiwW45yTFdTLhY,10973
9
9
  panther/caching.py,sha256=ltuJYdjNiAaKIs3jpO5EBpL8Y6CF1vAIQqh8J_Np10g,4098
10
- panther/configs.py,sha256=EaLApT6nYcguBoNXBG_8n6DU6HTNxsulI2943j8UAkE,3174
10
+ panther/configs.py,sha256=Z8bo--VqspXJKk1_LR2zovDi3oG2lITMJ9brgo5lNog,3243
11
11
  panther/events.py,sha256=bxDqrfiNNBlvD03vEk2LDK4xbMzTMFVcgAjx2ein7mI,1158
12
12
  panther/exceptions.py,sha256=7rHdJIES2__kqOStIqbHl3Uxask2lzKgLQlkZvvDwFA,1591
13
13
  panther/file_handler.py,sha256=I94tpbtTVniBnnUMkFr3Eis6kPDt8sLzS5u8TzFrR5I,1323
14
14
  panther/generics.py,sha256=D2ia7M4ML15kMZiuCIMpL7ZfQhMmKpqE4wCmuRE-q4Y,7233
15
15
  panther/logging.py,sha256=SGgF9faQM1QmbmMPVc6m1DY-TbV329kTD8BuzGLx3I0,2073
16
- panther/main.py,sha256=UbIxwaojvY_vH9nYfBpkulRBqVEj4Lbl81Er4XW_KCY,9334
16
+ panther/main.py,sha256=SnDN_aqpg5qG7jqwrb9JEiBs9bSmUUDursGuXs3FJbQ,9159
17
17
  panther/monitoring.py,sha256=y1F3c8FJlnmooM-m1nSyOTa9eWq0v1nHnmw9zz-4Kls,1314
18
18
  panther/pagination.py,sha256=ANJrEF0q1nVAfD33I4nZfUUxFcETzJb01gIhbZX3HEw,1639
19
19
  panther/permissions.py,sha256=9-J5vzvEKa_PITwEVQbZZv8PG2FOu05YBlD5yMrKcfc,348
20
20
  panther/request.py,sha256=F9ZiAWSse7_6moAzqdoFInUN4zTKlzijh9AdU9w3Jfw,1673
21
- panther/response.py,sha256=Njp4zJozNic8J4ucG8Sgh-xeBZOgtoz2cfdDkJlGOWU,7582
21
+ panther/response.py,sha256=uQ41lmBhdFUu3mmkftmbiZEvVgGoenEbt7zVwIDodbk,8851
22
22
  panther/routings.py,sha256=1eqbjubLnUUEQRlz8mIF464ImvCMjyasiekHBtxEQoQ,6218
23
23
  panther/serializer.py,sha256=UX-cVS-11KnxijUhPXsBs_Pb-Sm3EVzUQFTf9bFQT0A,9096
24
24
  panther/status.py,sha256=Gc_PnYrHfInTsZpGbqiCfDB-py1C7Rh8KMdb6Lq9Exs,3346
@@ -49,9 +49,9 @@ panther/panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  panther/panel/apis.py,sha256=COsbwKZyTgyHvHYbpDfusifAH9ojMS3z1KhZCt9M-Ms,2428
50
50
  panther/panel/urls.py,sha256=JiV-H4dWE-m_bfaTTVxzOxTvJmOWhyLOvcbM7xU3Bn4,240
51
51
  panther/panel/utils.py,sha256=0Rv79oR5IEqalqwpRKQHMn1p5duVY5mxMqDKiA5mWx4,437
52
- panther-4.2.6.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
- panther-4.2.6.dist-info/METADATA,sha256=UuyDwsfYekAvKNc_9GgkLiPnKs4zgraJUXY2jxRFxq4,6532
54
- panther-4.2.6.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
55
- panther-4.2.6.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
- panther-4.2.6.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
- panther-4.2.6.dist-info/RECORD,,
52
+ panther-4.3.1.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
+ panther-4.3.1.dist-info/METADATA,sha256=32_c-_BRnAQRawYwDccXv1fpYEk571vUqAAEA20ltX8,6537
54
+ panther-4.3.1.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
55
+ panther-4.3.1.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
+ panther-4.3.1.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
+ panther-4.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5