panther 1.7.1__tar.gz → 1.7.3__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.
- {panther-1.7.1 → panther-1.7.3}/PKG-INFO +11 -6
- {panther-1.7.1 → panther-1.7.3}/README.md +10 -5
- {panther-1.7.1 → panther-1.7.3}/panther/__init__.py +1 -1
- {panther-1.7.1 → panther-1.7.3}/panther/app.py +11 -11
- panther-1.7.3/panther/cli/monitor_command.py +58 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/template.py +27 -12
- {panther-1.7.1 → panther-1.7.3}/panther/db/utils.py +1 -1
- {panther-1.7.1 → panther-1.7.3}/panther/main.py +3 -4
- {panther-1.7.1 → panther-1.7.3}/panther/middlewares/redis.py +3 -11
- {panther-1.7.1 → panther-1.7.3}/panther.egg-info/PKG-INFO +11 -6
- panther-1.7.3/panther.egg-info/requires.txt +14 -0
- {panther-1.7.1 → panther-1.7.3}/setup.py +8 -6
- panther-1.7.1/panther/cli/monitor_command.py +0 -69
- panther-1.7.1/panther.egg-info/requires.txt +0 -12
- {panther-1.7.1 → panther-1.7.3}/LICENSE +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/_utils.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/authentications.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/caching.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/__init__.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/create_command.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/main.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/run_command.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/cli/utils.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/configs.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/__init__.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/connection.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/models.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/queries/__init__.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/queries/mongodb_queries.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/queries/pantherdb_queries.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/db/queries/queries.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/exceptions.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/logger.py +1 -1
- {panther-1.7.1 → panther-1.7.3}/panther/middlewares/__init__.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/middlewares/base.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/middlewares/db.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/middlewares/monitoring.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/permissions.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/request.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/response.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/routings.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/status.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/throttling.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther/utils.py +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther.egg-info/SOURCES.txt +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther.egg-info/dependency_links.txt +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther.egg-info/entry_points.txt +0 -0
- {panther-1.7.1 → panther-1.7.3}/panther.egg-info/top_level.txt +0 -0
- {panther-1.7.1 → panther-1.7.3}/pyproject.toml +0 -0
- {panther-1.7.1 → panther-1.7.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.3
|
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,23 +14,27 @@ Description-Content-Type: text/markdown
|
|
14
14
|
Provides-Extra: full
|
15
15
|
License-File: LICENSE
|
16
16
|
|
17
|
-
|
18
17
|
## Panther
|
19
18
|
<b>Is A Fast & Friendly, Web Framework For Building Async APIs With Python 3.11+</b>
|
20
19
|
|
20
|
+
<p align="center">
|
21
|
+
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo.png" alt="logo" style="width: 200px">
|
22
|
+
</p>
|
23
|
+
|
21
24
|
>_Full Documentation_ -> [https://pantherpy.github.io](https://pantherpy.github.io)
|
22
25
|
>
|
23
26
|
>_PyPI_ -> [https://pypi.org/project/panther/](https://pypi.org/project/panther/)
|
24
27
|
|
25
28
|
---
|
26
29
|
|
27
|
-
###
|
30
|
+
### Why Use Panther ?
|
28
31
|
- Document-oriented Databases ODM ([PantherDB](https://pypi.org/project/pantherdb/), MongoDB)
|
29
32
|
- Visual API Monitoring (In Terminal)
|
30
|
-
-
|
33
|
+
- Caching for APIs (In Memory, In Redis)
|
31
34
|
- Built-in Authentication Classes (Customizable)
|
32
35
|
- Built-in Permission Classes (Customizable)
|
33
36
|
- Handle Custom Middlewares
|
37
|
+
- Handle Custom Throttling
|
34
38
|
---
|
35
39
|
|
36
40
|
### Benchmark
|
@@ -150,13 +154,14 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
150
154
|
**app/apis.py**:
|
151
155
|
|
152
156
|
```python
|
153
|
-
from datetime import datetime
|
157
|
+
from datetime import datetime, timedelta
|
154
158
|
|
155
159
|
from panther.app import API
|
156
160
|
from panther.configs import config
|
157
161
|
from panther import version, status
|
158
162
|
from panther.request import Request
|
159
163
|
from panther.response import Response
|
164
|
+
from panther.throttling import Throttling
|
160
165
|
|
161
166
|
|
162
167
|
@API()
|
@@ -164,7 +169,7 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
164
169
|
return {'detail': 'Hello World'}
|
165
170
|
|
166
171
|
|
167
|
-
@API(cache=True)
|
172
|
+
@API(cache=True, throttling=Throttling(rate=5, duration=timedelta(minutes=1)))
|
168
173
|
async def info(request: Request):
|
169
174
|
data = {
|
170
175
|
'version': version(),
|
@@ -1,20 +1,24 @@
|
|
1
|
-
|
2
1
|
## Panther
|
3
2
|
<b>Is A Fast & Friendly, Web Framework For Building Async APIs With Python 3.11+</b>
|
4
3
|
|
4
|
+
<p align="center">
|
5
|
+
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo.png" alt="logo" style="width: 200px">
|
6
|
+
</p>
|
7
|
+
|
5
8
|
>_Full Documentation_ -> [https://pantherpy.github.io](https://pantherpy.github.io)
|
6
9
|
>
|
7
10
|
>_PyPI_ -> [https://pypi.org/project/panther/](https://pypi.org/project/panther/)
|
8
11
|
|
9
12
|
---
|
10
13
|
|
11
|
-
###
|
14
|
+
### Why Use Panther ?
|
12
15
|
- Document-oriented Databases ODM ([PantherDB](https://pypi.org/project/pantherdb/), MongoDB)
|
13
16
|
- Visual API Monitoring (In Terminal)
|
14
|
-
-
|
17
|
+
- Caching for APIs (In Memory, In Redis)
|
15
18
|
- Built-in Authentication Classes (Customizable)
|
16
19
|
- Built-in Permission Classes (Customizable)
|
17
20
|
- Handle Custom Middlewares
|
21
|
+
- Handle Custom Throttling
|
18
22
|
---
|
19
23
|
|
20
24
|
### Benchmark
|
@@ -134,13 +138,14 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
134
138
|
**app/apis.py**:
|
135
139
|
|
136
140
|
```python
|
137
|
-
from datetime import datetime
|
141
|
+
from datetime import datetime, timedelta
|
138
142
|
|
139
143
|
from panther.app import API
|
140
144
|
from panther.configs import config
|
141
145
|
from panther import version, status
|
142
146
|
from panther.request import Request
|
143
147
|
from panther.response import Response
|
148
|
+
from panther.throttling import Throttling
|
144
149
|
|
145
150
|
|
146
151
|
@API()
|
@@ -148,7 +153,7 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
148
153
|
return {'detail': 'Hello World'}
|
149
154
|
|
150
155
|
|
151
|
-
@API(cache=True)
|
156
|
+
@API(cache=True, throttling=Throttling(rate=5, duration=timedelta(minutes=1)))
|
152
157
|
async def info(request: Request):
|
153
158
|
data = {
|
154
159
|
'version': version(),
|
@@ -41,44 +41,44 @@ class API:
|
|
41
41
|
async def wrapper(*args, **kwargs):
|
42
42
|
self.request: Request = kwargs.pop('request') # NOQA: Non-self attribute could not be type hinted
|
43
43
|
|
44
|
-
#
|
44
|
+
# 1. Authentication
|
45
45
|
self.handle_authentications()
|
46
46
|
|
47
|
-
#
|
47
|
+
# 2. Throttling
|
48
48
|
self.handle_throttling()
|
49
49
|
|
50
|
-
#
|
50
|
+
# 3. Permissions
|
51
51
|
self.handle_permissions()
|
52
52
|
|
53
|
-
# Validate Input
|
53
|
+
# 4. Validate Input
|
54
54
|
self.validate_input()
|
55
55
|
|
56
|
-
# Validate Path Variables
|
56
|
+
# 5. Validate Path Variables
|
57
57
|
self.validate_path_variables(func, kwargs)
|
58
58
|
|
59
|
-
# Get Cached Response
|
59
|
+
# 6. Get Cached Response
|
60
60
|
if self.cache and self.request.method == 'GET':
|
61
61
|
if cached := get_cached_response_data(request=self.request):
|
62
62
|
return Response(data=cached.data, status_code=cached.status_code)
|
63
63
|
|
64
|
-
# Put Request In kwargs
|
64
|
+
# 7. Put Request In kwargs
|
65
65
|
if req_arg := [k for k, v in func.__annotations__.items() if v == Request]:
|
66
66
|
kwargs[req_arg[0]] = self.request
|
67
67
|
|
68
|
-
# Call Endpoint
|
68
|
+
# 8. Call Endpoint
|
69
69
|
response = await func(**kwargs)
|
70
70
|
|
71
|
-
# Clean Output
|
71
|
+
# 9. Clean Output
|
72
72
|
if not isinstance(response, Response):
|
73
73
|
response = Response(data=response)
|
74
74
|
data = self.serialize_response_data(data=response._data) # NOQA: Access to a protected member
|
75
75
|
response.set_data(data)
|
76
76
|
|
77
|
-
# Set New Response To Cache
|
77
|
+
# 10. Set New Response To Cache
|
78
78
|
if self.cache and self.request.method == 'GET':
|
79
79
|
set_cache_response(request=self.request, response=response, cache_exp_time=self.cache_exp_time)
|
80
80
|
|
81
|
-
# Warning CacheExpTime
|
81
|
+
# 11. Warning CacheExpTime
|
82
82
|
if self.cache_exp_time and self.cache is False:
|
83
83
|
logger.warning('"cache_exp_time" won\'t work while "cache" is False')
|
84
84
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import os
|
2
|
+
from collections import deque
|
3
|
+
|
4
|
+
from rich.console import Group
|
5
|
+
from rich.layout import Layout
|
6
|
+
from rich.align import Align
|
7
|
+
from rich.panel import Panel
|
8
|
+
from rich.table import Table
|
9
|
+
from watchfiles import watch
|
10
|
+
from rich.live import Live
|
11
|
+
from rich import box
|
12
|
+
|
13
|
+
|
14
|
+
def monitor() -> None:
|
15
|
+
monitoring_log_file = 'logs/monitoring.log'
|
16
|
+
|
17
|
+
def _generate_table(rows: deque):
|
18
|
+
layout = Layout()
|
19
|
+
|
20
|
+
rows = list(rows)
|
21
|
+
_, lines = os.get_terminal_size()
|
22
|
+
|
23
|
+
table = Table(box=box.MINIMAL_DOUBLE_HEAD)
|
24
|
+
table.add_column('Datetime', justify='center', style='magenta', no_wrap=True)
|
25
|
+
table.add_column('Method', justify='center', style='cyan')
|
26
|
+
table.add_column('Path', justify='center', style='cyan')
|
27
|
+
table.add_column('Client', justify='center', style='cyan')
|
28
|
+
table.add_column('Response Time', justify='center', style='blue')
|
29
|
+
table.add_column('Status Code', justify='center', style='blue')
|
30
|
+
|
31
|
+
for row in rows[-lines:]: # It will give us "lines" last lines of "rows"
|
32
|
+
table.add_row(*row)
|
33
|
+
layout.update(table)
|
34
|
+
|
35
|
+
return Panel(
|
36
|
+
Align.center(Group(table)),
|
37
|
+
box=box.ROUNDED,
|
38
|
+
padding=(1, 2),
|
39
|
+
title='Monitoring',
|
40
|
+
border_style='bright_blue',
|
41
|
+
)
|
42
|
+
|
43
|
+
with open(monitoring_log_file) as f:
|
44
|
+
f.readlines() # Set cursor at the end of file
|
45
|
+
|
46
|
+
_, init_lines_count = os.get_terminal_size()
|
47
|
+
messages = deque(maxlen=init_lines_count - 10) # Save space for header and footer
|
48
|
+
|
49
|
+
with Live(_generate_table(messages), auto_refresh=False, vertical_overflow='visible', screen=True) as live:
|
50
|
+
try:
|
51
|
+
for _ in watch(monitoring_log_file):
|
52
|
+
data = f.readline().split('|')
|
53
|
+
# 2023-03-24 01:42:52 | GET | /user/317/ | 127.0.0.1:48856 | 0.0366 ms | 200
|
54
|
+
messages.append(data)
|
55
|
+
live.update(_generate_table(messages))
|
56
|
+
live.refresh()
|
57
|
+
except KeyboardInterrupt:
|
58
|
+
pass
|
@@ -10,15 +10,16 @@ from panther.configs import config
|
|
10
10
|
from panther import version, status
|
11
11
|
from panther.request import Request
|
12
12
|
from panther.response import Response
|
13
|
+
from app.throttling import InfoThrottling
|
13
14
|
|
14
15
|
|
15
16
|
@API()
|
16
|
-
async def
|
17
|
+
async def hello_world_api():
|
17
18
|
return {'detail': 'Hello World'}
|
18
19
|
|
19
20
|
|
20
|
-
@API(cache=True)
|
21
|
-
async def
|
21
|
+
@API(cache=True, throttling=InfoThrottling)
|
22
|
+
async def info_api(request: Request):
|
22
23
|
data = {
|
23
24
|
'version': version(),
|
24
25
|
'datetime_now': datetime.now().isoformat(),
|
@@ -34,11 +35,18 @@ models_py = """from panther.db import Model
|
|
34
35
|
serializers_py = """from pydantic import BaseModel as Serializer
|
35
36
|
"""
|
36
37
|
|
37
|
-
|
38
|
+
throttling_py = """from datetime import timedelta
|
39
|
+
from panther.throttling import Throttling
|
40
|
+
|
41
|
+
|
42
|
+
InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
|
43
|
+
"""
|
44
|
+
|
45
|
+
app_urls_py = """from app.apis import hello_world_api, info_api
|
38
46
|
|
39
47
|
urls = {
|
40
|
-
'':
|
41
|
-
'info/':
|
48
|
+
'': hello_world_api,
|
49
|
+
'info/': info_api,
|
42
50
|
}
|
43
51
|
"""
|
44
52
|
|
@@ -56,23 +64,29 @@ env = load_env(BASE_DIR / '.env')
|
|
56
64
|
DB_NAME = env['DB_NAME']
|
57
65
|
SECRET_KEY = env['SECRET_KEY']
|
58
66
|
|
59
|
-
|
67
|
+
# # # More Info: Https://PantherPy.GitHub.io/middlewares/
|
60
68
|
MIDDLEWARES = [
|
61
69
|
('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pantherdb'}),
|
62
70
|
]
|
63
71
|
|
64
72
|
USER_MODEL = 'panther.db.models.User'
|
65
73
|
|
74
|
+
# # # More Info: Https://PantherPy.GitHub.io/authentications/
|
75
|
+
AUTHENTICATION = 'panther.authentications.JWTAuthentication'
|
76
|
+
|
77
|
+
# # # More Info: Https://PantherPy.GitHub.io/monitoring/
|
66
78
|
MONITORING = True
|
67
79
|
|
80
|
+
# # # More Info: Https://PantherPy.GitHub.io/log_queries/
|
68
81
|
LOG_QUERIES = True
|
69
82
|
|
83
|
+
# # # More Info: Https://PantherPy.GitHub.io/throttling/
|
84
|
+
THROTTLING = Throttling(rate=60, duration=timedelta(minutes=1))
|
85
|
+
|
86
|
+
# # # More Info: Https://PantherPy.GitHub.io/urls/
|
70
87
|
URLs = 'core/urls.py'
|
71
88
|
""" % datetime.now().date().isoformat()
|
72
89
|
|
73
|
-
middlewares = """from panther.middlewares import BaseMiddleware
|
74
|
-
"""
|
75
|
-
|
76
90
|
env = """
|
77
91
|
SECRET_KEY = '%s'
|
78
92
|
|
@@ -94,8 +108,9 @@ urls = {
|
|
94
108
|
git_ignore = """__pycache__/
|
95
109
|
.venv/
|
96
110
|
.idea/
|
97
|
-
.env
|
98
111
|
logs/
|
112
|
+
|
113
|
+
.env
|
99
114
|
*.pantherdb
|
100
115
|
"""
|
101
116
|
|
@@ -107,11 +122,11 @@ Template = {
|
|
107
122
|
'apis.py': apis_py,
|
108
123
|
'models.py': models_py,
|
109
124
|
'serializers.py': serializers_py,
|
125
|
+
'throttling.py': throttling_py,
|
110
126
|
'urls.py': app_urls_py,
|
111
127
|
},
|
112
128
|
'core': {
|
113
129
|
'configs.py': configs_py,
|
114
|
-
'middlewares.py': middlewares,
|
115
130
|
'urls.py': urls_py,
|
116
131
|
},
|
117
132
|
'main.py': main_py,
|
@@ -26,7 +26,7 @@ def clean_object_id(_id: bson.ObjectId | str) -> bson.ObjectId:
|
|
26
26
|
try:
|
27
27
|
return bson.ObjectId(_id)
|
28
28
|
except Exception:
|
29
|
-
raise bson.errors.InvalidId
|
29
|
+
raise bson.errors.InvalidId # NOQA: Py Unresolved References
|
30
30
|
|
31
31
|
|
32
32
|
def clean_object_id_in_dicts(*args):
|
@@ -180,11 +180,10 @@ class Panther:
|
|
180
180
|
if path.find('panther.middlewares.db.Middleware') != -1:
|
181
181
|
config['db_engine'] = data['url'].split(':')[0]
|
182
182
|
|
183
|
-
#
|
184
|
-
Middleware = import_class(path)
|
183
|
+
Middleware = import_class(path) # NOQA: Py Pep8 Naming
|
185
184
|
if not issubclass(Middleware, BaseMiddleware):
|
186
185
|
logger.critical(f'{Middleware} is not a sub class of BaseMiddleware.')
|
187
186
|
continue
|
188
|
-
|
189
|
-
middlewares.append(Middleware(**data))
|
187
|
+
|
188
|
+
middlewares.append(Middleware(**data)) # NOQA: Py Argument List
|
190
189
|
return middlewares
|
@@ -20,17 +20,9 @@ class Middleware(BaseMiddleware):
|
|
20
20
|
self.kwargs['host'] = '127.0.0.1'
|
21
21
|
|
22
22
|
def validate_port(self):
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
self.kwargs['port'] = int(port)
|
27
|
-
except ValueError:
|
28
|
-
logger.critical('Redis "port" should be number')
|
29
|
-
|
30
|
-
elif not isinstance(port, int):
|
31
|
-
logger.critical('Redis "port" is not valid.')
|
32
|
-
else:
|
33
|
-
self.kwargs['port'] = 6379
|
23
|
+
port = self.kwargs.setdefault('port', 6379)
|
24
|
+
if not isinstance(port, int):
|
25
|
+
logger.critical('Redis "port" is not valid.')
|
34
26
|
|
35
27
|
async def before(self, request: Request) -> Request:
|
36
28
|
self.redis = RedisConnection(**self.kwargs)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.3
|
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,23 +14,27 @@ Description-Content-Type: text/markdown
|
|
14
14
|
Provides-Extra: full
|
15
15
|
License-File: LICENSE
|
16
16
|
|
17
|
-
|
18
17
|
## Panther
|
19
18
|
<b>Is A Fast & Friendly, Web Framework For Building Async APIs With Python 3.11+</b>
|
20
19
|
|
20
|
+
<p align="center">
|
21
|
+
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo.png" alt="logo" style="width: 200px">
|
22
|
+
</p>
|
23
|
+
|
21
24
|
>_Full Documentation_ -> [https://pantherpy.github.io](https://pantherpy.github.io)
|
22
25
|
>
|
23
26
|
>_PyPI_ -> [https://pypi.org/project/panther/](https://pypi.org/project/panther/)
|
24
27
|
|
25
28
|
---
|
26
29
|
|
27
|
-
###
|
30
|
+
### Why Use Panther ?
|
28
31
|
- Document-oriented Databases ODM ([PantherDB](https://pypi.org/project/pantherdb/), MongoDB)
|
29
32
|
- Visual API Monitoring (In Terminal)
|
30
|
-
-
|
33
|
+
- Caching for APIs (In Memory, In Redis)
|
31
34
|
- Built-in Authentication Classes (Customizable)
|
32
35
|
- Built-in Permission Classes (Customizable)
|
33
36
|
- Handle Custom Middlewares
|
37
|
+
- Handle Custom Throttling
|
34
38
|
---
|
35
39
|
|
36
40
|
### Benchmark
|
@@ -150,13 +154,14 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
150
154
|
**app/apis.py**:
|
151
155
|
|
152
156
|
```python
|
153
|
-
from datetime import datetime
|
157
|
+
from datetime import datetime, timedelta
|
154
158
|
|
155
159
|
from panther.app import API
|
156
160
|
from panther.configs import config
|
157
161
|
from panther import version, status
|
158
162
|
from panther.request import Request
|
159
163
|
from panther.response import Response
|
164
|
+
from panther.throttling import Throttling
|
160
165
|
|
161
166
|
|
162
167
|
@API()
|
@@ -164,7 +169,7 @@ with [https://github.com/nakabonne/ali](https://github.com/nakabonne/ali) and he
|
|
164
169
|
return {'detail': 'Hello World'}
|
165
170
|
|
166
171
|
|
167
|
-
@API(cache=True)
|
172
|
+
@API(cache=True, throttling=Throttling(rate=5, duration=timedelta(minutes=1)))
|
168
173
|
async def info(request: Request):
|
169
174
|
data = {
|
170
175
|
'version': version(),
|
@@ -43,14 +43,16 @@ setup(
|
|
43
43
|
'panther': ['cli/*'],
|
44
44
|
},
|
45
45
|
install_requires=[
|
46
|
-
'pantherdb>=1.2.0',
|
47
|
-
'pydantic>=1.10.5',
|
48
|
-
'watchfiles>=0.18.1',
|
49
|
-
'uvicorn[standard]',
|
50
46
|
'bpython>=0.24',
|
51
47
|
'bson>=0.5.10',
|
52
|
-
'
|
53
|
-
'
|
48
|
+
'httptools>=0.5.0',
|
49
|
+
'pantherdb>=1.2.2',
|
50
|
+
'pydantic>=1.10.7',
|
51
|
+
'redis>=4.5.3',
|
52
|
+
'rich>=13.3.2',
|
53
|
+
'uvicorn>=0.21.1',
|
54
|
+
'uvloop>=0.17.0',
|
55
|
+
'watchfiles>=0.18.1',
|
54
56
|
],
|
55
57
|
extras_require=EXTRAS_REQUIRE,
|
56
58
|
)
|
@@ -1,69 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from collections import deque
|
3
|
-
|
4
|
-
from rich import box
|
5
|
-
from rich.align import Align
|
6
|
-
from rich.console import Console, Group
|
7
|
-
from rich.layout import Layout
|
8
|
-
from rich.live import Live
|
9
|
-
from rich.panel import Panel
|
10
|
-
from rich.table import Table
|
11
|
-
from watchfiles import watch
|
12
|
-
|
13
|
-
from panther.cli.utils import cli_error
|
14
|
-
|
15
|
-
|
16
|
-
def monitor() -> None:
|
17
|
-
def _generate_table(rows):
|
18
|
-
layout = Layout()
|
19
|
-
console = Console()
|
20
|
-
|
21
|
-
rows = list(rows)
|
22
|
-
n_rows = os.get_terminal_size()[1]
|
23
|
-
|
24
|
-
while n_rows >= 0:
|
25
|
-
table = Table(box=box.MINIMAL_DOUBLE_HEAD)
|
26
|
-
table.add_column('Datetime', justify='center', style='magenta', no_wrap=True)
|
27
|
-
table.add_column('Method', justify='center', style='cyan')
|
28
|
-
table.add_column('Path', justify='center', style='cyan')
|
29
|
-
table.add_column('Client', justify='center', style='cyan')
|
30
|
-
table.add_column('Response Time', justify='center', style='blue')
|
31
|
-
table.add_column('Status Code', justify='center', style='blue')
|
32
|
-
|
33
|
-
for row in rows[-n_rows:]:
|
34
|
-
table.add_row(*row)
|
35
|
-
layout.update(table)
|
36
|
-
render_map = layout.render(console, console.options)
|
37
|
-
|
38
|
-
if len(render_map[layout].render[-1]) > 2:
|
39
|
-
n_rows -= 1 # The table is overflowing
|
40
|
-
else:
|
41
|
-
break
|
42
|
-
|
43
|
-
return Panel(
|
44
|
-
Align.center(Group(table)),
|
45
|
-
box=box.ROUNDED,
|
46
|
-
padding=(1, 2),
|
47
|
-
title='Monitoring',
|
48
|
-
border_style='bright_blue',
|
49
|
-
)
|
50
|
-
|
51
|
-
try:
|
52
|
-
with open('logs/monitoring.log') as f:
|
53
|
-
f.readlines()
|
54
|
-
width, height = os.get_terminal_size()
|
55
|
-
messages = deque(maxlen=height - 8) # Save space for header and footer
|
56
|
-
|
57
|
-
with Live(_generate_table(messages), auto_refresh=False, vertical_overflow='visible', screen=True) as live:
|
58
|
-
# TODO: Is it only watch logs/monitoring.log or the whole directory ?
|
59
|
-
for _ in watch('logs/monitoring.log'):
|
60
|
-
data = f.readline().split('|')
|
61
|
-
messages.append(data)
|
62
|
-
live.update(_generate_table(messages))
|
63
|
-
live.refresh()
|
64
|
-
|
65
|
-
except FileNotFoundError:
|
66
|
-
cli_error("Monitor Log File Does Not Exists.\n\nHint: Make sure 'Monitor' is True in 'core/configs' "
|
67
|
-
"or you are in a correct directory.")
|
68
|
-
except KeyboardInterrupt:
|
69
|
-
pass
|
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
|