panther 3.1.4__py3-none-any.whl → 3.1.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 +1 -1
- panther/_load_configs.py +60 -42
- panther/_utils.py +1 -1
- panther/main.py +7 -0
- panther/monitoring.py +22 -8
- panther/panel/apis.py +5 -10
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/METADATA +1 -1
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/RECORD +12 -12
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/LICENSE +0 -0
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/WHEEL +0 -0
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/entry_points.txt +0 -0
- {panther-3.1.4.dist-info → panther-3.1.5.dist-info}/top_level.txt +0 -0
panther/__init__.py
CHANGED
panther/_load_configs.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import ast
|
2
|
-
import os
|
3
2
|
import platform
|
4
3
|
import sys
|
5
4
|
from datetime import timedelta
|
@@ -11,7 +10,6 @@ from pydantic._internal._model_construction import ModelMetaclass
|
|
11
10
|
from panther._utils import import_class
|
12
11
|
from panther.configs import JWTConfig, config
|
13
12
|
from panther.exceptions import PantherException
|
14
|
-
from panther.middlewares import BaseMiddleware
|
15
13
|
from panther.routings import finalize_urls, flatten_urls
|
16
14
|
from panther.throttling import Throttling
|
17
15
|
|
@@ -73,6 +71,8 @@ def load_default_cache_exp(configs: dict, /) -> timedelta | None:
|
|
73
71
|
|
74
72
|
def load_middlewares(configs: dict, /) -> list:
|
75
73
|
"""Collect The Middlewares & Set db_engine If One Of Middlewares Was For DB"""
|
74
|
+
from panther.middlewares import BaseMiddleware
|
75
|
+
|
76
76
|
middlewares = []
|
77
77
|
|
78
78
|
for path, data in configs.get('MIDDLEWARES', []):
|
@@ -113,46 +113,64 @@ def collect_all_models() -> list[dict]:
|
|
113
113
|
"""Collecting all models for panel APIs"""
|
114
114
|
from panther.db.models import Model
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
for f in
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
116
|
+
# Just load all the python files from 'base_dir',
|
117
|
+
# so Model.__subclasses__ can find all the subclasses
|
118
|
+
slash = '\\' if platform.system() == 'Windows' else '/'
|
119
|
+
python_files = [
|
120
|
+
f for f in config['base_dir'].rglob('*.py')
|
121
|
+
if not f.name.startswith('_') and 'site-packages' not in f.parents._parts
|
122
|
+
]
|
123
|
+
for file in python_files:
|
124
|
+
# Analyse the file
|
125
|
+
with Path(file).open() as f:
|
126
|
+
node = ast.parse(f.read())
|
127
|
+
|
128
|
+
model_imported = False
|
129
|
+
panther_imported = False
|
130
|
+
panther_called = False
|
131
|
+
for n in node.body:
|
132
|
+
match n:
|
133
|
+
|
134
|
+
# from panther.db import Model
|
135
|
+
case ast.ImportFrom(module='panther.db', names=[ast.alias(name='Model')]):
|
136
|
+
model_imported = True
|
137
|
+
|
138
|
+
# from panther.db.models import ..., Model, ...
|
139
|
+
case ast.ImportFrom(module='panther.db.models', names=[*names]):
|
140
|
+
try:
|
141
|
+
next(v for v in names if v.name == 'Model')
|
142
|
+
model_imported = True
|
143
|
+
except StopIteration:
|
144
|
+
pass
|
145
|
+
|
146
|
+
# from panther import Panther, ...
|
147
|
+
case ast.ImportFrom(module='panther', names=[ast.alias(name='Panther'), *_]):
|
148
|
+
panther_imported = True
|
149
|
+
|
150
|
+
# from panther import ..., Panther
|
151
|
+
case ast.ImportFrom(module='panther', names=[*_, ast.alias(name='Panther')]):
|
152
|
+
panther_imported = True
|
153
|
+
|
154
|
+
# ... = Panther(...)
|
155
|
+
case ast.Assign(value=ast.Call(func=ast.Name(id='Panther'))):
|
156
|
+
panther_called = True
|
157
|
+
|
158
|
+
# Panther() should not be called in the file and Model() should be imported,
|
159
|
+
# We check the import of the Panther to make sure he is calling the panther.Panther and not any Panther
|
160
|
+
if panther_imported and panther_called or not model_imported:
|
161
|
+
continue
|
162
|
+
|
163
|
+
# Load the module
|
164
|
+
dotted_f = str(file).removeprefix(f'{config["base_dir"]}{slash}').removesuffix('.py').replace(slash, '.')
|
165
|
+
import_module(dotted_f)
|
166
|
+
|
167
|
+
return [
|
168
|
+
{
|
169
|
+
'name': m.__name__,
|
170
|
+
'module': m.__module__,
|
171
|
+
'class': m
|
172
|
+
} for m in Model.__subclasses__() if m.__module__ != 'panther.db.models'
|
173
|
+
]
|
156
174
|
|
157
175
|
|
158
176
|
def load_urls(configs: dict, /, urls: dict | None) -> tuple[dict, dict]:
|
panther/_utils.py
CHANGED
@@ -45,7 +45,7 @@ async def http_response(
|
|
45
45
|
elif status_code == status.HTTP_204_NO_CONTENT or body == b'null':
|
46
46
|
body = None
|
47
47
|
|
48
|
-
await monitoring.after(status_code
|
48
|
+
await monitoring.after(status_code)
|
49
49
|
|
50
50
|
await _http_response_start(send, headers=headers, status_code=status_code)
|
51
51
|
await _http_response_body(send, body=body)
|
panther/main.py
CHANGED
@@ -127,16 +127,20 @@ class Panther:
|
|
127
127
|
|
128
128
|
async def handle_ws(self, scope: dict, receive: Callable, send: Callable) -> None:
|
129
129
|
from panther.websocket import GenericWebsocket, Websocket
|
130
|
+
monitoring = Monitoring(is_active=config['monitoring'], is_ws=True)
|
130
131
|
|
131
132
|
temp_connection = Websocket(scope=scope, receive=receive, send=send)
|
133
|
+
await monitoring.before(request=temp_connection)
|
132
134
|
|
133
135
|
endpoint, found_path = find_endpoint(path=temp_connection.path)
|
134
136
|
if endpoint is None:
|
137
|
+
await monitoring.after('Rejected')
|
135
138
|
return await temp_connection.close(status.WS_1000_NORMAL_CLOSURE)
|
136
139
|
path_variables: dict = collect_path_variables(request_path=temp_connection.path, found_path=found_path)
|
137
140
|
|
138
141
|
if not issubclass(endpoint, GenericWebsocket):
|
139
142
|
logger.critical(f'You may have forgotten to inherit from GenericWebsocket on the {endpoint.__name__}()')
|
143
|
+
await monitoring.after('Rejected')
|
140
144
|
return await temp_connection.close(status.WS_1014_BAD_GATEWAY)
|
141
145
|
|
142
146
|
del temp_connection
|
@@ -152,12 +156,15 @@ class Panther:
|
|
152
156
|
break
|
153
157
|
else:
|
154
158
|
await self.websocket_connections.new_connection(connection=connection)
|
159
|
+
await monitoring.after('Accepted')
|
155
160
|
await connection.listen()
|
156
161
|
|
157
162
|
# Call 'After' Middleware
|
158
163
|
for middleware in config['reversed_middlewares']:
|
159
164
|
with contextlib.suppress(APIException):
|
160
165
|
await middleware.after(response=connection)
|
166
|
+
|
167
|
+
await monitoring.after('Closed')
|
161
168
|
return None
|
162
169
|
|
163
170
|
async def handle_http(self, scope: dict, receive: Callable, send: Callable) -> None:
|
panther/monitoring.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from time import perf_counter
|
3
|
+
from typing import Literal
|
3
4
|
|
4
|
-
from panther.
|
5
|
+
from panther.base_request import BaseRequest
|
5
6
|
|
6
7
|
|
7
8
|
logger = logging.getLogger('monitoring')
|
@@ -10,18 +11,31 @@ logger = logging.getLogger('monitoring')
|
|
10
11
|
class Monitoring:
|
11
12
|
"""
|
12
13
|
Create Log Message Like Below:
|
13
|
-
|
14
|
+
date time | method | path | ip:port | response_time [ms, s] | status
|
14
15
|
"""
|
15
|
-
def __init__(self, is_active: bool):
|
16
|
+
def __init__(self, is_active: bool, is_ws: bool = False):
|
16
17
|
self.is_active = is_active
|
18
|
+
self.is_ws = is_ws
|
17
19
|
|
18
|
-
async def before(self, request:
|
20
|
+
async def before(self, request: BaseRequest):
|
19
21
|
if self.is_active:
|
20
22
|
ip, port = request.client
|
21
|
-
|
23
|
+
|
24
|
+
if self.is_ws:
|
25
|
+
method = 'WS'
|
26
|
+
else:
|
27
|
+
method = request.scope['method']
|
28
|
+
|
29
|
+
self.log = f'{method} | {request.path} | {ip}:{port}'
|
22
30
|
self.start_time = perf_counter()
|
23
31
|
|
24
|
-
async def after(self,
|
32
|
+
async def after(self, status: int | Literal['Accepted', 'Rejected', 'Closed'], /):
|
25
33
|
if self.is_active:
|
26
|
-
response_time =
|
27
|
-
|
34
|
+
response_time = perf_counter() - self.start_time
|
35
|
+
time_unit = ' s'
|
36
|
+
|
37
|
+
if response_time < 0.01:
|
38
|
+
response_time = response_time * 1_000
|
39
|
+
time_unit = 'ms'
|
40
|
+
|
41
|
+
logger.info(f'{self.log} | {round(response_time, 4)} {time_unit} | {status}')
|
panther/panel/apis.py
CHANGED
@@ -8,16 +8,11 @@ from panther.response import Response
|
|
8
8
|
|
9
9
|
@API()
|
10
10
|
async def models_api():
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
'path': m['path'],
|
17
|
-
'index': i
|
18
|
-
}
|
19
|
-
result.append(data)
|
20
|
-
return result
|
11
|
+
return [{
|
12
|
+
'name': m['name'],
|
13
|
+
'module': m['module'],
|
14
|
+
'index': i
|
15
|
+
} for i, m in enumerate(config['models'])]
|
21
16
|
|
22
17
|
|
23
18
|
@API()
|
@@ -1,6 +1,6 @@
|
|
1
|
-
panther/__init__.py,sha256=
|
2
|
-
panther/_load_configs.py,sha256=
|
3
|
-
panther/_utils.py,sha256=
|
1
|
+
panther/__init__.py,sha256=6LwXZVmGyh5YmGDfYREWyRqoMsv9NiCxNibakKFUzJo,110
|
2
|
+
panther/_load_configs.py,sha256=S5zN5ErktjXCIi7cgg8HZwRCez51qiYxa1Ppu2No9Xw,7476
|
3
|
+
panther/_utils.py,sha256=H0VIv9VPt7mIDZo-N7x5ZaMMes1jo60wp1TtxkNvFnQ,4346
|
4
4
|
panther/app.py,sha256=8-W1XsOfVjNcP1RxV8X9TgQzshP9Qk9E0LBcXBf25aM,7961
|
5
5
|
panther/authentications.py,sha256=Fh7Z7rpysSJXN9xEKJqH94oYf2L3jJQxr6fPhRz3UFg,4149
|
6
6
|
panther/background_tasks.py,sha256=J6JrjiEzyGohYsaFZHnZtXeP52piP84RlGFLsKOFa-Y,7225
|
@@ -11,8 +11,8 @@ panther/configs.py,sha256=lvmPDT_W8gnXNv214zLQedYFZhM78E-3XGa7KyWQbo4,1920
|
|
11
11
|
panther/exceptions.py,sha256=DB6nGfU5LxrglEN_I-HInMqdIA3ZmN8rRv0ynEuQyGA,1332
|
12
12
|
panther/file_handler.py,sha256=b-paQykGfjlvslfEkM23V50v8bosjRLq1S1HmsmlXEc,855
|
13
13
|
panther/logging.py,sha256=DZVf3nxzLodT-hD4820J1jEAffU8zIxXRPKs2lbP8ho,2074
|
14
|
-
panther/main.py,sha256=
|
15
|
-
panther/monitoring.py,sha256=
|
14
|
+
panther/main.py,sha256=F8yGaVN97JMD0FKWvTPuGZ43g1BGdEEtDzjWSelG4mg,10301
|
15
|
+
panther/monitoring.py,sha256=krmcoTUcV12pHwCFVAHywgsp1-US9cyiMlgsrMJdUQ0,1203
|
16
16
|
panther/permissions.py,sha256=Q-l25369yQaP-tY11tFQm-v9PPh8iVImbfUpY3pnQUk,355
|
17
17
|
panther/request.py,sha256=W3L_udvIxgjqVxxc8aaKDIQBVjdc7TTrFyoOJQ8M8FA,1773
|
18
18
|
panther/response.py,sha256=HcL8nuXpZX_vhT9QUw0dnbtP75T_LbvKAWGT48_LPuU,3382
|
@@ -42,12 +42,12 @@ panther/middlewares/base.py,sha256=kBJp0U6982pfwDRSzDfMRfdUra9oVoL9Ms52EavW0r4,2
|
|
42
42
|
panther/middlewares/db.py,sha256=Dotc7LPKEZ26w3yK0SY_PhO47JEKJtYpVZPO9SLA2Lg,638
|
43
43
|
panther/middlewares/redis.py,sha256=Wa_uvTe2yGw9LYO1CehrUYYXc6hdyNMOJc7X5lDJ5LU,1374
|
44
44
|
panther/panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
-
panther/panel/apis.py,sha256=
|
45
|
+
panther/panel/apis.py,sha256=f4eWTi212ZnnPYcxuU4SdJ-7yYQxZFImaXgTTnXw_fc,1703
|
46
46
|
panther/panel/urls.py,sha256=BQkWqSJBPP3VEQYeorKSHIRx-PUl21Y7Z6NFylmhs1I,192
|
47
47
|
panther/panel/utils.py,sha256=0Rv79oR5IEqalqwpRKQHMn1p5duVY5mxMqDKiA5mWx4,437
|
48
|
-
panther-3.1.
|
49
|
-
panther-3.1.
|
50
|
-
panther-3.1.
|
51
|
-
panther-3.1.
|
52
|
-
panther-3.1.
|
53
|
-
panther-3.1.
|
48
|
+
panther-3.1.5.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
|
49
|
+
panther-3.1.5.dist-info/METADATA,sha256=YmW-daTXDu085RPgfX1YbQYKkq6SewmDkw4QR50E-eY,6076
|
50
|
+
panther-3.1.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
51
|
+
panther-3.1.5.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
|
52
|
+
panther-3.1.5.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
|
53
|
+
panther-3.1.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|