panther 4.3.4__py3-none-any.whl → 4.3.6__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.4'
3
+ __version__ = '4.3.6'
4
4
 
5
5
 
6
6
  def version():
panther/_load_configs.py CHANGED
@@ -154,22 +154,26 @@ def load_middlewares(_configs: dict, /) -> None:
154
154
  # Collect Middlewares
155
155
  for middleware in _configs.get('MIDDLEWARES') or []:
156
156
  if not isinstance(middleware, list | tuple):
157
- raise _exception_handler(field='MIDDLEWARES', error=f'{middleware} should have 2 part: (path, kwargs)')
157
+ path_or_type = middleware
158
+ data = {}
158
159
 
159
- if len(middleware) == 1:
160
- path = middleware[0]
160
+ elif len(middleware) == 1:
161
+ path_or_type = middleware[0]
161
162
  data = {}
162
163
 
163
164
  elif len(middleware) > 2:
164
165
  raise _exception_handler(field='MIDDLEWARES', error=f'{middleware} too many arguments')
165
166
 
166
167
  else:
167
- path, data = middleware
168
+ path_or_type, data = middleware
168
169
 
169
- try:
170
- middleware_class = import_class(path)
171
- except (AttributeError, ModuleNotFoundError):
172
- raise _exception_handler(field='MIDDLEWARES', error=f'{path} is not a valid middleware path')
170
+ if callable(path_or_type):
171
+ middleware_class = path_or_type
172
+ else:
173
+ try:
174
+ middleware_class = import_class(path_or_type)
175
+ except (AttributeError, ModuleNotFoundError):
176
+ raise _exception_handler(field='MIDDLEWARES', error=f'{path_or_type} is not a valid middleware path')
173
177
 
174
178
  if issubclass(middleware_class, BaseMiddleware) is False:
175
179
  raise _exception_handler(field='MIDDLEWARES', error='is not a sub class of BaseMiddleware')
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:
@@ -19,7 +19,7 @@ from panther.configs import config
19
19
  with contextlib.suppress(ImportError):
20
20
  from watchfiles import watch
21
21
 
22
- loggerr = logging.getLogger('panther')
22
+ logger = logging.getLogger('panther')
23
23
 
24
24
 
25
25
  class Monitoring:
@@ -30,7 +30,7 @@ class Monitoring:
30
30
  def monitor(self) -> None:
31
31
  if error := self.initialize():
32
32
  # Don't continue if initialize() has error
33
- loggerr.error(error)
33
+ logger.error(error)
34
34
  return
35
35
 
36
36
  with (
@@ -51,7 +51,10 @@ class Monitoring:
51
51
 
52
52
  for _ in watching:
53
53
  for line in f.readlines():
54
- self.rows.append(line.split('|'))
54
+ # line = date_time | method | path | ip:port | response_time(seconds) | status
55
+ columns = line.split('|')
56
+ columns[4] = self._clean_response_time(float(columns[4]))
57
+ self.rows.append(columns)
55
58
  live.update(self.generate_table())
56
59
 
57
60
  def initialize(self) -> str:
@@ -100,5 +103,19 @@ class Monitoring:
100
103
  lines = (os.get_terminal_size()[1] - 6) // 2
101
104
  self.rows = deque(self.rows, maxlen=lines)
102
105
 
106
+ @classmethod
107
+ def _clean_response_time(cls, response_time: int) -> str:
108
+ time_unit = ' s'
109
+
110
+ if response_time < 0.01:
111
+ response_time = response_time * 1_000
112
+ time_unit = 'ms'
113
+
114
+ elif response_time >= 10:
115
+ response_time = response_time / 60
116
+ time_unit = ' m'
117
+
118
+ return f'{round(response_time, 4)} {time_unit}'
119
+
103
120
 
104
121
  monitor = Monitoring().monitor
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/monitoring.py CHANGED
@@ -11,7 +11,7 @@ logger = logging.getLogger('monitoring')
11
11
  class Monitoring:
12
12
  """
13
13
  Create Log Message Like Below:
14
- date time | method | path | ip:port | response_time [ms, s] | status
14
+ date_time | method | path | ip:port | response_time(seconds) | status
15
15
  """
16
16
  def __init__(self, is_ws: bool = False):
17
17
  self.is_ws = is_ws
@@ -30,15 +30,5 @@ class Monitoring:
30
30
 
31
31
  async def after(self, status: int | Literal['Accepted', 'Rejected', 'Closed'], /):
32
32
  if config.MONITORING:
33
- response_time = perf_counter() - self.start_time
34
- time_unit = ' s'
35
-
36
- if response_time < 0.01:
37
- response_time = response_time * 1_000
38
- time_unit = 'ms'
39
-
40
- elif response_time >= 10:
41
- response_time = response_time / 60
42
- time_unit = ' m'
43
-
44
- logger.info(f'{self.log} | {round(response_time, 4)} {time_unit} | {status}')
33
+ response_time = perf_counter() - self.start_time # Seconds
34
+ logger.info(f'{self.log} | {response_time} | {status}')
panther/response.py CHANGED
@@ -13,7 +13,6 @@ else:
13
13
 
14
14
  import orjson as json
15
15
  from pydantic import BaseModel
16
- from jinja2 import Environment, FileSystemLoader
17
16
 
18
17
  from panther import status
19
18
  from panther.configs import config
@@ -43,7 +42,7 @@ class Response:
43
42
  :param headers: should be dict of headers
44
43
  :param status_code: should be int
45
44
  :param pagination: instance of Pagination or None
46
- Its template() method will be used
45
+ The `pagination.template()` method will be used
47
46
  """
48
47
  self.headers = headers or {}
49
48
  self.pagination: Pagination | None = pagination
@@ -236,7 +235,6 @@ class TemplateResponse(HTMLResponse):
236
235
  context: dict | NoneType = None,
237
236
  headers: dict | NoneType = None,
238
237
  status_code: int = status.HTTP_200_OK,
239
- pagination: Pagination | NoneType = None,
240
238
  ):
241
239
  """
242
240
  :param source: should be a string
@@ -244,8 +242,13 @@ class TemplateResponse(HTMLResponse):
244
242
  :param context: should be dict of items
245
243
  :param headers: should be dict of headers
246
244
  :param status_code: should be int
247
- :param pagination: instance of Pagination or None
248
- Its template() method will be used
249
245
  """
250
- template = config.JINJA_ENVIRONMENT.get_template(path) if path is not None else config.JINJA_ENVIRONMENT.from_string(source)
251
- super().__init__(template.render(context or {}), headers, status_code, pagination=pagination)
246
+ if path:
247
+ template = config.JINJA_ENVIRONMENT.get_template(name=path)
248
+ else:
249
+ template = config.JINJA_ENVIRONMENT.from_string(source=source)
250
+ super().__init__(
251
+ data=template.render(context or {}),
252
+ headers=headers,
253
+ status_code=status_code,
254
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: panther
3
- Version: 4.3.4
3
+ Version: 4.3.6
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
@@ -14,16 +14,13 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: pantherdb~=2.1.0
17
+ Requires-Dist: pantherdb~=2.1.1
18
18
  Requires-Dist: pydantic~=2.8.2
19
19
  Requires-Dist: rich~=13.7.1
20
20
  Requires-Dist: uvicorn~=0.27.1
21
21
  Requires-Dist: pytz~=2024.1
22
22
  Requires-Dist: Jinja2~=3.1
23
23
  Requires-Dist: httptools~=0.6.1
24
- Provides-Extra: dev
25
- Requires-Dist: ruff~=0.1.9; extra == "dev"
26
- Requires-Dist: pytest~=8.3.3; extra == "dev"
27
24
  Provides-Extra: full
28
25
  Requires-Dist: redis==5.0.1; extra == "full"
29
26
  Requires-Dist: motor~=3.5.0; extra == "full"
@@ -33,6 +30,9 @@ Requires-Dist: ruff~=0.1.9; extra == "full"
33
30
  Requires-Dist: websockets~=12.0; extra == "full"
34
31
  Requires-Dist: cryptography~=42.0.8; extra == "full"
35
32
  Requires-Dist: watchfiles~=0.21.0; extra == "full"
33
+ Provides-Extra: dev
34
+ Requires-Dist: ruff~=0.1.9; extra == "dev"
35
+ Requires-Dist: pytest~=8.3.3; extra == "dev"
36
36
 
37
37
 
38
38
  [![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=pi--vX1J1xQHzhNFeCOc2PFlgcN6A952z5Na058Avng,110
2
- panther/_load_configs.py,sha256=tVzUB8WqwkOcsRbs1T4vhaKICPbDlXelr5h7LVdVupU,10079
3
- panther/_utils.py,sha256=R1tsUde1zA5iJtk-ZvWe8hp6JaNhKgNoH2rxpdiQDMc,4449
4
- panther/app.py,sha256=e2eb4sXIaBje5vpcm4pbvvEO_sj83pLfBHCIZJFFX38,8222
1
+ panther/__init__.py,sha256=a3xRGlPVogHnbjt1Se22KYGrRsBLPl7Ko7i9hL_QH1o,110
2
+ panther/_load_configs.py,sha256=NMQxKMMjkV6J3rxH5tfCTaZ6Rie7xzx-knEByXEtCUQ,10166
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
9
+ panther/caching.py,sha256=0UWg2xlTkyTKcf6rMjf-oZIE_kJWpPfpKKaDOCZxazg,4299
10
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
17
- panther/monitoring.py,sha256=y1F3c8FJlnmooM-m1nSyOTa9eWq0v1nHnmw9zz-4Kls,1314
16
+ panther/main.py,sha256=UgzsXKC1zhtBfVIDELa9axDkWPCtVVU3aAWJpYkxTOs,9075
17
+ panther/monitoring.py,sha256=C0tYBKGci6QR33CN-MixMzCP24ka0a6V0AU2H1sS4HU,1026
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=vcKyhpjdKNNw8iksTfGSeDO3VJ6_TgvwzIOfmHAdGIc,8794
21
+ panther/response.py,sha256=iauz2akIq6O3k_XheH6uS38fYdS6X3GTWakGBsYCTp4,8697
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
@@ -29,7 +29,7 @@ panther/websocket.py,sha256=YRWgc_FUrv94-dnM2nm41EDgCsqZHxQ1N53Gma7c9s0,1452
29
29
  panther/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  panther/cli/create_command.py,sha256=mT5GFzsTYZbzqShNHlw_UIkMcWLw9btm2mTwcg7TlfI,10292
31
31
  panther/cli/main.py,sha256=pCqnOTazgMhTvFHTugutIsiFXueU5kx2VmGngwAl54Q,1679
32
- panther/cli/monitor_command.py,sha256=sbnxQOSy0Q6GB-ELrfyoY6S-ZwgbI7eZyKx7YkvrEas,3383
32
+ panther/cli/monitor_command.py,sha256=KQUGu3L_PDmM0b5Ygy_eeKQmGPM3r8-WkLxUdmS9cBE,3982
33
33
  panther/cli/run_command.py,sha256=yWcDoWC-c4ph4M5EDj0jvR9xSjh-apG5r6-NpDdArUo,2195
34
34
  panther/cli/template.py,sha256=hVkY1A3HZDVGEZzRkMtYte6FagKGTAxoFeG0wot7Zn4,5320
35
35
  panther/cli/utils.py,sha256=g_Xkvh_GCFrc3Remgp02lVi3YkmCAvcNKuAY_QvBTLI,5290
@@ -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.4.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
- panther-4.3.4.dist-info/METADATA,sha256=1qZpoxF1s8VPGSwg14Mq24lTKFpg4Txjl0lNHogW93o,6695
54
- panther-4.3.4.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
55
- panther-4.3.4.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
- panther-4.3.4.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
- panther-4.3.4.dist-info/RECORD,,
52
+ panther-4.3.6.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
53
+ panther-4.3.6.dist-info/METADATA,sha256=5Qf1rcXy9pvYF72LlzjLYrNapDyKJwdzQF8hOOGgsRk,6695
54
+ panther-4.3.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
55
+ panther-4.3.6.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
56
+ panther-4.3.6.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
57
+ panther-4.3.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5