panther 4.3.3__py3-none-any.whl → 4.3.5__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.3.3'
3
+ __version__ = '4.3.5'
4
4
 
5
5
 
6
6
  def version():
panther/_load_configs.py CHANGED
@@ -4,6 +4,8 @@ import types
4
4
  from importlib import import_module
5
5
  from multiprocessing import Manager
6
6
 
7
+ import jinja2
8
+
7
9
  from panther._utils import import_class, check_function_type_endpoint, check_class_type_endpoint
8
10
  from panther.background_tasks import background_tasks
9
11
  from panther.base_websocket import WebsocketConnections
@@ -89,6 +91,11 @@ def load_templates_dir(_configs: dict, /) -> None:
89
91
  if templates_dir := _configs.get('TEMPLATES_DIR'):
90
92
  config.TEMPLATES_DIR = templates_dir
91
93
 
94
+ if config.TEMPLATES_DIR == '.':
95
+ config.TEMPLATES_DIR = config.BASE_DIR
96
+
97
+ config.JINJA_ENVIRONMENT = jinja2.Environment(loader=jinja2.FileSystemLoader(config.TEMPLATES_DIR))
98
+
92
99
 
93
100
  def load_database(_configs: dict, /) -> None:
94
101
  database_config = _configs.get('DATABASE', {})
panther/_utils.py CHANGED
@@ -78,15 +78,9 @@ def is_function_async(func: Callable) -> bool:
78
78
  return bool(func.__code__.co_flags & (1 << 7))
79
79
 
80
80
 
81
- def clean_traceback_message(exception: Exception) -> str:
82
- """We are ignoring packages traceback message"""
81
+ def traceback_message(exception: Exception) -> str:
83
82
  tb = TracebackException(type(exception), exception, exception.__traceback__)
84
- stack = tb.stack.copy()
85
- for t in stack:
86
- if t.filename.find('site-packages/panther') != -1 or t.filename.find('site-packages\\panther') != -1:
87
- tb.stack.remove(t)
88
- _traceback = list(tb.format(chain=False))
89
- return exception if len(_traceback) == 1 else f'{exception}\n' + ''.join(_traceback)
83
+ return ''.join(tb.format(chain=False))
90
84
 
91
85
 
92
86
  def reformat_code(base_dir):
panther/app.py CHANGED
@@ -34,16 +34,16 @@ logger = logging.getLogger('panther')
34
34
 
35
35
  class API:
36
36
  def __init__(
37
- self,
38
- *,
39
- input_model: type[ModelSerializer] | type[BaseModel] | None = None,
40
- output_model: type[ModelSerializer] | type[BaseModel] | None = None,
41
- auth: bool = False,
42
- permissions: list | None = None,
43
- throttling: Throttling | None = None,
44
- cache: bool = False,
45
- cache_exp_time: timedelta | int | None = None,
46
- methods: list[Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE']] | None = None,
37
+ self,
38
+ *,
39
+ input_model: type[ModelSerializer] | type[BaseModel] | None = None,
40
+ output_model: type[ModelSerializer] | type[BaseModel] | None = None,
41
+ auth: bool = False,
42
+ permissions: list | None = None,
43
+ throttling: Throttling | None = None,
44
+ cache: bool = False,
45
+ cache_exp_time: timedelta | int | None = None,
46
+ methods: list[Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE']] | None = None,
47
47
  ):
48
48
  self.input_model = input_model
49
49
  self.output_model = output_model
@@ -51,7 +51,7 @@ class API:
51
51
  self.permissions = permissions or []
52
52
  self.throttling = throttling
53
53
  self.cache = cache
54
- self.cache_exp_time = cache_exp_time
54
+ self.cache_exp_time = cache_exp_time # or config.DEFAULT_CACHE_EXP
55
55
  self.methods = methods
56
56
  self.request: Request | None = None
57
57
 
@@ -84,7 +84,7 @@ class API:
84
84
  # 6. Get Cached Response
85
85
  if self.cache and self.request.method == 'GET':
86
86
  if cached := await get_response_from_cache(request=self.request, cache_exp_time=self.cache_exp_time):
87
- return Response(data=cached.data, status_code=cached.status_code)
87
+ return Response(data=cached.data, headers=cached.headers, status_code=cached.status_code)
88
88
 
89
89
  # 7. Put PathVariables and Request(If User Wants It) In kwargs
90
90
  kwargs = self.request.clean_parameters(func)
@@ -105,11 +105,7 @@ class API:
105
105
 
106
106
  # 10. Set New Response To Cache
107
107
  if self.cache and self.request.method == 'GET':
108
- await set_response_in_cache(
109
- request=self.request,
110
- response=response,
111
- cache_exp_time=self.cache_exp_time
112
- )
108
+ await set_response_in_cache(request=self.request, response=response, cache_exp_time=self.cache_exp_time)
113
109
 
114
110
  # 11. Warning CacheExpTime
115
111
  if self.cache_exp_time and self.cache is False:
panther/caching.py CHANGED
@@ -8,14 +8,14 @@ import orjson as json
8
8
  from panther.configs import config
9
9
  from panther.db.connections import redis
10
10
  from panther.request import Request
11
- from panther.response import Response, ResponseDataTypes
11
+ from panther.response import Response
12
12
  from panther.throttling import throttling_storage
13
13
  from panther.utils import generate_hash_value_from_string, round_datetime
14
14
 
15
15
  logger = logging.getLogger('panther')
16
16
 
17
17
  caches = {}
18
- CachedResponse = namedtuple('CachedResponse', ['data', 'status_code'])
18
+ CachedResponse = namedtuple('CachedResponse', ['data', 'headers', 'status_code'])
19
19
 
20
20
 
21
21
  def api_cache_key(request: Request, cache_exp_time: timedelta | None = None) -> str:
@@ -46,13 +46,16 @@ async def get_response_from_cache(*, request: Request, cache_exp_time: timedelta
46
46
  if redis.is_connected:
47
47
  key = api_cache_key(request=request)
48
48
  data = (await redis.get(key) or b'{}').decode()
49
- if cached_value := json.loads(data):
50
- return CachedResponse(*cached_value)
51
-
49
+ if value := json.loads(data):
50
+ return CachedResponse(
51
+ data=value[0].encode(),
52
+ headers=value[1],
53
+ status_code=value[2]
54
+ )
52
55
  else:
53
56
  key = api_cache_key(request=request, cache_exp_time=cache_exp_time)
54
- if cached_value := caches.get(key):
55
- return CachedResponse(*cached_value)
57
+ if value := caches.get(key):
58
+ return CachedResponse(*value)
56
59
 
57
60
 
58
61
  async def set_response_in_cache(*, request: Request, response: Response, cache_exp_time: timedelta | int) -> None:
@@ -63,16 +66,14 @@ async def set_response_in_cache(*, request: Request, response: Response, cache_e
63
66
  Cache The Data In Memory
64
67
  """
65
68
 
66
- cache_data: tuple[ResponseDataTypes, int] = (response.data, response.status_code)
67
-
68
69
  if redis.is_connected:
69
70
  key = api_cache_key(request=request)
70
-
71
+ cache_data: tuple[str, str, int] = (response.body.decode(), response.headers, response.status_code)
71
72
  cache_exp_time = cache_exp_time or config.DEFAULT_CACHE_EXP
72
73
  cache_data: bytes = json.dumps(cache_data)
73
74
 
74
75
  if not isinstance(cache_exp_time, timedelta | int | NoneType):
75
- msg = '"cache_exp_time" should be instance of "datetime.timedelta" or "int" or "None"'
76
+ msg = '`cache_exp_time` should be instance of `datetime.timedelta`, `int` or `None`'
76
77
  raise TypeError(msg)
77
78
 
78
79
  if cache_exp_time is None:
@@ -86,6 +87,8 @@ async def set_response_in_cache(*, request: Request, response: Response, cache_e
86
87
 
87
88
  else:
88
89
  key = api_cache_key(request=request, cache_exp_time=cache_exp_time)
90
+ cache_data: tuple[bytes, str, int] = (response.body, response.headers, response.status_code)
91
+
89
92
  caches[key] = cache_data
90
93
 
91
94
  if cache_exp_time:
panther/configs.py CHANGED
@@ -5,6 +5,7 @@ from datetime import timedelta
5
5
  from pathlib import Path
6
6
  from typing import Callable
7
7
 
8
+ import jinja2
8
9
  from pydantic._internal._model_construction import ModelMetaclass
9
10
 
10
11
  from panther.throttling import Throttling
@@ -68,6 +69,7 @@ class Config:
68
69
  SHUTDOWNS: list[Callable]
69
70
  TIMEZONE: str
70
71
  TEMPLATES_DIR: str | list[str]
72
+ JINJA_ENVIRONMENT: jinja2.Environment | None
71
73
  AUTO_REFORMAT: bool
72
74
  QUERY_ENGINE: typing.Callable | None
73
75
  DATABASE: typing.Callable | None
@@ -111,7 +113,8 @@ default_configs = {
111
113
  'STARTUPS': [],
112
114
  'SHUTDOWNS': [],
113
115
  'TIMEZONE': 'UTC',
114
- 'TEMPLATES_DIR': 'templates',
116
+ 'TEMPLATES_DIR': '.',
117
+ 'JINJA_ENVIRONMENT': None,
115
118
  'AUTO_REFORMAT': False,
116
119
  'QUERY_ENGINE': None,
117
120
  'DATABASE': None,
panther/main.py CHANGED
@@ -1,4 +1,3 @@
1
- import asyncio
2
1
  import contextlib
3
2
  import logging
4
3
  import sys
@@ -12,7 +11,7 @@ import orjson as json
12
11
  import panther.logging
13
12
  from panther import status
14
13
  from panther._load_configs import *
15
- from panther._utils import clean_traceback_message, reformat_code, check_class_type_endpoint, check_function_type_endpoint
14
+ from panther._utils import traceback_message, reformat_code
16
15
  from panther.cli.utils import print_info
17
16
  from panther.configs import config
18
17
  from panther.events import Event
@@ -38,7 +37,7 @@ class Panther:
38
37
  if config.AUTO_REFORMAT:
39
38
  reformat_code(base_dir=config.BASE_DIR)
40
39
  except Exception as e: # noqa: BLE001
41
- logger.error(e.args[0] if isinstance(e, PantherError) else clean_traceback_message(e))
40
+ logger.error(e.args[0] if isinstance(e, PantherError) else traceback_message(exception=e))
42
41
  sys.exit()
43
42
 
44
43
  # Print Info
@@ -191,8 +190,8 @@ class Panther:
191
190
 
192
191
  except Exception as e: # noqa: BLE001
193
192
  # All unhandled exceptions are caught here
194
- exception = clean_traceback_message(exception=e)
195
- logger.critical(exception)
193
+ exception = traceback_message(exception=e)
194
+ logger.error(exception)
196
195
  return await self._raise(send, monitoring=monitoring)
197
196
 
198
197
  # Call Middlewares .after()
@@ -226,4 +225,3 @@ class Panther:
226
225
  await monitoring.after(status_code)
227
226
  await send({'type': 'http.response.start', 'status': status_code, 'headers': headers})
228
227
  await send({'type': 'http.response.body', 'body': body, 'more_body': False})
229
-
panther/response.py CHANGED
@@ -43,7 +43,7 @@ class Response:
43
43
  :param headers: should be dict of headers
44
44
  :param status_code: should be int
45
45
  :param pagination: instance of Pagination or None
46
- Its template() method will be used
46
+ The `pagination.template()` method will be used
47
47
  """
48
48
  self.headers = headers or {}
49
49
  self.pagination: Pagination | None = pagination
@@ -229,8 +229,6 @@ class PlainTextResponse(Response):
229
229
 
230
230
 
231
231
  class TemplateResponse(HTMLResponse):
232
- environment = Environment(loader=FileSystemLoader(config.TEMPLATES_DIR))
233
-
234
232
  def __init__(
235
233
  self,
236
234
  source: str | LiteralString | NoneType = None,
@@ -238,7 +236,6 @@ class TemplateResponse(HTMLResponse):
238
236
  context: dict | NoneType = None,
239
237
  headers: dict | NoneType = None,
240
238
  status_code: int = status.HTTP_200_OK,
241
- pagination: Pagination | NoneType = None,
242
239
  ):
243
240
  """
244
241
  :param source: should be a string
@@ -246,10 +243,13 @@ class TemplateResponse(HTMLResponse):
246
243
  :param context: should be dict of items
247
244
  :param headers: should be dict of headers
248
245
  :param status_code: should be int
249
- :param pagination: instance of Pagination or None
250
- Its template() method will be used
251
246
  """
252
- if not context:
253
- context = {}
254
- template = self.environment.get_template(path) if path is not None else self.environment.from_string(source)
255
- super().__init__(template.render(context), headers, status_code, pagination=pagination)
247
+ if path:
248
+ template = config.JINJA_ENVIRONMENT.get_template(name=path)
249
+ else:
250
+ template = config.JINJA_ENVIRONMENT.from_string(source=source)
251
+ super().__init__(
252
+ data=template.render(context or {}),
253
+ headers=headers,
254
+ status_code=status_code,
255
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: panther
3
- Version: 4.3.3
3
+ Version: 4.3.5
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
@@ -1,24 +1,24 @@
1
- panther/__init__.py,sha256=dHSHKCuH-PIVagzBpiGQuX4E_rciYT3fi8UlfzKEUNM,110
2
- panther/_load_configs.py,sha256=NZEwp3s7Mq_gU6w5FFN-3JfJImjZtf4_q-qengDQ6Y0,9875
3
- panther/_utils.py,sha256=R1tsUde1zA5iJtk-ZvWe8hp6JaNhKgNoH2rxpdiQDMc,4449
4
- panther/app.py,sha256=e2eb4sXIaBje5vpcm4pbvvEO_sj83pLfBHCIZJFFX38,8222
1
+ panther/__init__.py,sha256=UJP3FufxNFIxwJDwS3VMJjiDNeagB0sRjhRqiRnTnWw,110
2
+ panther/_load_configs.py,sha256=tVzUB8WqwkOcsRbs1T4vhaKICPbDlXelr5h7LVdVupU,10079
3
+ panther/_utils.py,sha256=uufhLXT_TdiQacrcCV-Jr12ET7uqaiX2E3Y9AzbVfDQ,4109
4
+ panther/app.py,sha256=aVVoUihLMo7rofKetw95HPq2B92a-AI9KooxFpbuTec,8158
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
- panther/caching.py,sha256=ltuJYdjNiAaKIs3jpO5EBpL8Y6CF1vAIQqh8J_Np10g,4098
10
- panther/configs.py,sha256=Z8bo--VqspXJKk1_LR2zovDi3oG2lITMJ9brgo5lNog,3243
9
+ panther/caching.py,sha256=0UWg2xlTkyTKcf6rMjf-oZIE_kJWpPfpKKaDOCZxazg,4299
10
+ panther/configs.py,sha256=0VmXWFnktMGUI8X8o2xoC92872OS1G9ohAzRcvQSD2U,3329
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=SnDN_aqpg5qG7jqwrb9JEiBs9bSmUUDursGuXs3FJbQ,9159
16
+ panther/main.py,sha256=UgzsXKC1zhtBfVIDELa9axDkWPCtVVU3aAWJpYkxTOs,9075
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=ZjmYfbaZyF8DnLtBCdnxZCIBvnEG_BTkGy_9Uwwnuvw,8899
21
+ panther/response.py,sha256=mzJjCKWhfDRaW4JNlIeulGymRYJbLKcs9tA7ChCu3YA,8746
22
22
  panther/routings.py,sha256=1eqbjubLnUUEQRlz8mIF464ImvCMjyasiekHBtxEQoQ,6218
23
23
  panther/serializer.py,sha256=5O5dypP9ys0qTKrjwaXONmOqCfDHoXY1q5ajsirFjM8,9083
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.3.3.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
- panther-4.3.3.dist-info/METADATA,sha256=h6jZdw3vY-nVfpeAkl53zjn7V4WMeuyapMxc7gqgiPU,6695
54
- panther-4.3.3.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
55
- panther-4.3.3.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
- panther-4.3.3.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
- panther-4.3.3.dist-info/RECORD,,
52
+ panther-4.3.5.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
+ panther-4.3.5.dist-info/METADATA,sha256=XT3S_mACyba0AlZbjR5I4GcF0AR1ngURQXl5dCwy2Go,6695
54
+ panther-4.3.5.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
55
+ panther-4.3.5.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
+ panther-4.3.5.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
+ panther-4.3.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.4.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5