panther 3.8.2__py3-none-any.whl → 4.0.0__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 +168 -171
- panther/_utils.py +26 -49
- panther/app.py +85 -105
- panther/authentications.py +86 -55
- panther/background_tasks.py +25 -14
- panther/base_request.py +38 -14
- panther/base_websocket.py +172 -94
- panther/caching.py +60 -25
- panther/cli/create_command.py +20 -10
- panther/cli/monitor_command.py +63 -37
- panther/cli/template.py +40 -20
- panther/cli/utils.py +32 -18
- panther/configs.py +65 -58
- panther/db/connections.py +139 -0
- panther/db/cursor.py +43 -0
- panther/db/models.py +64 -29
- panther/db/queries/__init__.py +1 -1
- panther/db/queries/base_queries.py +127 -0
- panther/db/queries/mongodb_queries.py +77 -38
- panther/db/queries/pantherdb_queries.py +59 -30
- panther/db/queries/queries.py +232 -117
- panther/db/utils.py +17 -18
- panther/events.py +44 -0
- panther/exceptions.py +26 -12
- panther/file_handler.py +2 -2
- panther/generics.py +163 -0
- panther/logging.py +7 -2
- panther/main.py +111 -188
- panther/middlewares/base.py +3 -0
- panther/monitoring.py +8 -5
- panther/pagination.py +48 -0
- panther/panel/apis.py +32 -5
- panther/panel/urls.py +2 -1
- panther/permissions.py +3 -3
- panther/request.py +6 -13
- panther/response.py +114 -34
- panther/routings.py +83 -66
- panther/serializer.py +214 -33
- panther/test.py +31 -21
- panther/utils.py +28 -16
- panther/websocket.py +7 -4
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/METADATA +93 -71
- panther-4.0.0.dist-info/RECORD +57 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/WHEEL +1 -1
- panther/db/connection.py +0 -92
- panther/middlewares/db.py +0 -18
- panther/middlewares/redis.py +0 -47
- panther-3.8.2.dist-info/RECORD +0 -54
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/LICENSE +0 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/entry_points.txt +0 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/top_level.txt +0 -0
panther/cli/create_command.py
CHANGED
@@ -16,8 +16,7 @@ from panther.cli.template import (
|
|
16
16
|
AUTO_REFORMAT_PART,
|
17
17
|
DATABASE_PANTHERDB_PART,
|
18
18
|
DATABASE_MONGODB_PART,
|
19
|
-
USER_MODEL_PART,
|
20
|
-
PANTHERDB_ENCRYPTION,
|
19
|
+
USER_MODEL_PART, REDIS_PART,
|
21
20
|
)
|
22
21
|
from panther.cli.utils import cli_error
|
23
22
|
|
@@ -35,6 +34,7 @@ class CreateProject:
|
|
35
34
|
self.base_directory = '.'
|
36
35
|
self.database = '0'
|
37
36
|
self.database_encryption = False
|
37
|
+
self.redis = False
|
38
38
|
self.authentication = False
|
39
39
|
self.monitoring = True
|
40
40
|
self.log_queries = True
|
@@ -61,7 +61,7 @@ class CreateProject:
|
|
61
61
|
},
|
62
62
|
{
|
63
63
|
'field': 'database',
|
64
|
-
'message': ' 0: PantherDB\n 1: MongoDB (Required `pymongo`)\n 2: No Database\nChoose Your Database (default is 0)',
|
64
|
+
'message': ' 0: PantherDB (File-Base, No Requirements)\n 1: MongoDB (Required `pymongo`)\n 2: No Database\nChoose Your Database (default is 0)',
|
65
65
|
'validation_func': lambda x: x in ['0', '1', '2'],
|
66
66
|
'error_message': "Invalid Choice, '{}' not in ['0', '1', '2']",
|
67
67
|
},
|
@@ -71,6 +71,11 @@ class CreateProject:
|
|
71
71
|
'is_boolean': True,
|
72
72
|
'condition': "self.database == '0'"
|
73
73
|
},
|
74
|
+
{
|
75
|
+
'field': 'redis',
|
76
|
+
'message': 'Do You Want To Use Redis (Required `redis`)',
|
77
|
+
'is_boolean': True,
|
78
|
+
},
|
74
79
|
{
|
75
80
|
'field': 'authentication',
|
76
81
|
'message': 'Do You Want To Use JWT Authentication (Required `python-jose`)',
|
@@ -78,7 +83,7 @@ class CreateProject:
|
|
78
83
|
},
|
79
84
|
{
|
80
85
|
'field': 'monitoring',
|
81
|
-
'message': 'Do You Want To Use Built-in Monitoring',
|
86
|
+
'message': 'Do You Want To Use Built-in Monitoring (Required `watchfiles`)',
|
82
87
|
'is_boolean': True,
|
83
88
|
},
|
84
89
|
{
|
@@ -88,7 +93,7 @@ class CreateProject:
|
|
88
93
|
},
|
89
94
|
{
|
90
95
|
'field': 'auto_reformat',
|
91
|
-
'message': 'Do You Want To Use Auto Reformat (Required `ruff`)',
|
96
|
+
'message': 'Do You Want To Use Auto Code Reformat (Required `ruff`)',
|
92
97
|
'is_boolean': True,
|
93
98
|
},
|
94
99
|
]
|
@@ -139,7 +144,9 @@ class CreateProject:
|
|
139
144
|
monitoring_part = MONITORING_PART if self.monitoring else ''
|
140
145
|
log_queries_part = LOG_QUERIES_PART if self.log_queries else ''
|
141
146
|
auto_reformat_part = AUTO_REFORMAT_PART if self.auto_reformat else ''
|
142
|
-
database_encryption =
|
147
|
+
database_encryption = 'True' if self.database_encryption else 'False'
|
148
|
+
database_extension = 'pdb' if self.database_encryption else 'json'
|
149
|
+
redis_part = REDIS_PART if self.redis else ''
|
143
150
|
if self.database == '0':
|
144
151
|
database_part = DATABASE_PANTHERDB_PART
|
145
152
|
elif self.database == '1':
|
@@ -153,7 +160,9 @@ class CreateProject:
|
|
153
160
|
data = data.replace('{LOG_QUERIES}', log_queries_part)
|
154
161
|
data = data.replace('{AUTO_REFORMAT}', auto_reformat_part)
|
155
162
|
data = data.replace('{DATABASE}', database_part)
|
156
|
-
data = data.replace('{PANTHERDB_ENCRYPTION}', database_encryption)
|
163
|
+
data = data.replace('{PANTHERDB_ENCRYPTION}', database_encryption) # Should be after `DATABASE`
|
164
|
+
data = data.replace('{PANTHERDB_EXTENSION}', database_extension) # Should be after `DATABASE`
|
165
|
+
data = data.replace('{REDIS}', redis_part)
|
157
166
|
|
158
167
|
data = data.replace('{PROJECT_NAME}', self.project_name.lower())
|
159
168
|
data = data.replace('{PANTHER_VERSION}', version())
|
@@ -168,19 +177,19 @@ class CreateProject:
|
|
168
177
|
field_name = question.pop('field')
|
169
178
|
question['default'] = getattr(self, field_name)
|
170
179
|
is_boolean = question.pop('is_boolean', False)
|
171
|
-
|
180
|
+
convert_output = str # Do Nothing
|
172
181
|
if is_boolean:
|
173
182
|
question['message'] += f' (default is {self._to_str(question["default"])})'
|
174
183
|
question['validation_func'] = self._is_boolean
|
175
184
|
question['error_message'] = "Invalid Choice, '{}' not in ['y', 'n']"
|
176
|
-
|
185
|
+
convert_output = self._to_boolean
|
177
186
|
|
178
187
|
# Check Question Condition
|
179
188
|
if 'condition' in question and eval(question.pop('condition')) is False:
|
180
189
|
print(flush=True)
|
181
190
|
# Ask Question
|
182
191
|
else:
|
183
|
-
setattr(self, field_name,
|
192
|
+
setattr(self, field_name, convert_output(self.ask(**question)))
|
184
193
|
self.progress(i + 1)
|
185
194
|
|
186
195
|
def ask(
|
@@ -193,6 +202,7 @@ class CreateProject:
|
|
193
202
|
) -> str:
|
194
203
|
value = Prompt.ask(message, console=self.input_console).lower() or default
|
195
204
|
while not validation_func(value):
|
205
|
+
# Remove the last line, show error message and ask again
|
196
206
|
[print(end=self.REMOVE_LAST_LINE, flush=True) for _ in range(message.count('\n') + 1)]
|
197
207
|
error = validation_func(value, return_error=True) if show_validation_error else value
|
198
208
|
self.console.print(error_message.format(error), style='bold red')
|
panther/cli/monitor_command.py
CHANGED
@@ -1,71 +1,97 @@
|
|
1
1
|
import contextlib
|
2
|
+
import logging
|
2
3
|
import os
|
4
|
+
import signal
|
3
5
|
from collections import deque
|
4
6
|
from pathlib import Path
|
5
7
|
|
6
8
|
from rich import box
|
7
9
|
from rich.align import Align
|
8
10
|
from rich.console import Group
|
9
|
-
from rich.layout import Layout
|
10
11
|
from rich.live import Live
|
11
12
|
from rich.panel import Panel
|
12
13
|
from rich.table import Table
|
13
|
-
from watchfiles import watch
|
14
14
|
|
15
|
-
from panther.cli.utils import
|
15
|
+
from panther.cli.utils import import_error
|
16
16
|
from panther.configs import config
|
17
17
|
|
18
|
+
with contextlib.suppress(ImportError):
|
19
|
+
from watchfiles import watch
|
18
20
|
|
19
|
-
|
20
|
-
monitoring_log_file = Path(config['base_dir'] / 'logs' / 'monitoring.log')
|
21
|
+
loggerr = logging.getLogger('panther')
|
21
22
|
|
22
|
-
def _generate_table(rows: deque) -> Panel:
|
23
|
-
layout = Layout()
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
class Monitoring:
|
25
|
+
def __init__(self):
|
26
|
+
self.rows = deque()
|
27
|
+
self.monitoring_log_file = Path(config.BASE_DIR / 'logs' / 'monitoring.log')
|
28
|
+
|
29
|
+
def monitor(self) -> None:
|
30
|
+
if error := self.initialize():
|
31
|
+
# Don't continue if initialize() has error
|
32
|
+
loggerr.error(error)
|
33
|
+
return
|
34
|
+
|
35
|
+
with (
|
36
|
+
self.monitoring_log_file.open() as f,
|
37
|
+
Live(
|
38
|
+
self.generate_table(),
|
39
|
+
vertical_overflow='visible',
|
40
|
+
screen=True,
|
41
|
+
) as live,
|
42
|
+
contextlib.suppress(KeyboardInterrupt)
|
43
|
+
):
|
44
|
+
f.readlines() # Set cursor at the end of the file
|
45
|
+
|
46
|
+
for _ in watch(self.monitoring_log_file):
|
47
|
+
for line in f.readlines():
|
48
|
+
self.rows.append(line.split('|'))
|
49
|
+
live.update(self.generate_table())
|
50
|
+
|
51
|
+
def initialize(self) -> str:
|
52
|
+
# Check requirements
|
53
|
+
try:
|
54
|
+
from watchfiles import watch
|
55
|
+
except ImportError as e:
|
56
|
+
return import_error(e, package='watchfiles').args[0]
|
57
|
+
|
58
|
+
# Check log file
|
59
|
+
if not self.monitoring_log_file.exists():
|
60
|
+
return f'`{self.monitoring_log_file}` file not found. (Make sure `MONITORING` is `True` in `configs`'
|
61
|
+
|
62
|
+
# Initialize Deque
|
63
|
+
self.update_rows()
|
64
|
+
|
65
|
+
# Register the signal handler
|
66
|
+
signal.signal(signal.SIGWINCH, self.update_rows)
|
67
|
+
|
68
|
+
def generate_table(self) -> Panel:
|
69
|
+
# 2023-03-24 01:42:52 | GET | /user/317/ | 127.0.0.1:48856 | 0.0366 ms | 200
|
27
70
|
|
28
71
|
table = Table(box=box.MINIMAL_DOUBLE_HEAD)
|
29
72
|
table.add_column('Datetime', justify='center', style='magenta', no_wrap=True)
|
30
|
-
table.add_column('Method', justify='center', style='cyan')
|
31
|
-
table.add_column('Path', justify='center', style='cyan')
|
73
|
+
table.add_column('Method', justify='center', style='cyan', no_wrap=True)
|
74
|
+
table.add_column('Path', justify='center', style='cyan', no_wrap=True)
|
32
75
|
table.add_column('Client', justify='center', style='cyan')
|
33
76
|
table.add_column('Response Time', justify='center', style='blue')
|
34
|
-
table.add_column('Status
|
77
|
+
table.add_column('Status', justify='center', style='blue', no_wrap=True)
|
35
78
|
|
36
|
-
for row in rows
|
79
|
+
for row in self.rows:
|
37
80
|
table.add_row(*row)
|
38
|
-
layout.update(table)
|
39
81
|
|
40
82
|
return Panel(
|
41
83
|
Align.center(Group(table)),
|
42
84
|
box=box.ROUNDED,
|
43
|
-
padding=(
|
85
|
+
padding=(0, 2),
|
44
86
|
title='Monitoring',
|
45
87
|
border_style='bright_blue',
|
46
88
|
)
|
47
89
|
|
48
|
-
|
49
|
-
|
90
|
+
def update_rows(self, *args, **kwargs):
|
91
|
+
# Top = -4, Bottom = -2 --> -6
|
92
|
+
# Print of each line needs two line, so --> x // 2
|
93
|
+
lines = (os.get_terminal_size()[1] - 6) // 2
|
94
|
+
self.rows = deque(self.rows, maxlen=lines)
|
50
95
|
|
51
|
-
with monitoring_log_file.open() as f:
|
52
|
-
f.readlines() # Set cursor at the end of file
|
53
96
|
|
54
|
-
|
55
|
-
messages = deque(maxlen=init_lines_count - 10) # Save space for header and footer
|
56
|
-
|
57
|
-
with (
|
58
|
-
Live(
|
59
|
-
_generate_table(messages),
|
60
|
-
auto_refresh=False,
|
61
|
-
vertical_overflow='visible',
|
62
|
-
screen=True,
|
63
|
-
) as live,
|
64
|
-
contextlib.suppress(KeyboardInterrupt),
|
65
|
-
):
|
66
|
-
for _ in watch(monitoring_log_file):
|
67
|
-
data = f.readline().split('|')
|
68
|
-
# 2023-03-24 01:42:52 | GET | /user/317/ | 127.0.0.1:48856 | 0.0366 ms | 200
|
69
|
-
messages.append(data)
|
70
|
-
live.update(_generate_table(messages))
|
71
|
-
live.refresh()
|
97
|
+
monitor = Monitoring().monitor
|
panther/cli/template.py
CHANGED
@@ -11,6 +11,7 @@ from panther import status, version
|
|
11
11
|
from panther.app import API
|
12
12
|
from panther.request import Request
|
13
13
|
from panther.response import Response
|
14
|
+
from panther.utils import timezone_now
|
14
15
|
|
15
16
|
|
16
17
|
@API()
|
@@ -24,7 +25,7 @@ async def info_api(request: Request):
|
|
24
25
|
'panther_version': version(),
|
25
26
|
'method': request.method,
|
26
27
|
'query_params': request.query_params,
|
27
|
-
'datetime_now':
|
28
|
+
'datetime_now': timezone_now().isoformat(),
|
28
29
|
'user_agent': request.headers.user_agent,
|
29
30
|
}
|
30
31
|
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
@@ -55,19 +56,19 @@ configs_py = """\"""
|
|
55
56
|
{PROJECT_NAME} Project (Generated by Panther on %s)
|
56
57
|
\"""
|
57
58
|
|
58
|
-
from datetime import timedelta
|
59
59
|
from pathlib import Path
|
60
60
|
|
61
|
-
from panther.throttling import Throttling
|
62
61
|
from panther.utils import load_env
|
63
62
|
|
64
63
|
BASE_DIR = Path(__name__).resolve().parent
|
65
64
|
env = load_env(BASE_DIR / '.env')
|
66
65
|
|
67
|
-
SECRET_KEY = env['SECRET_KEY']{DATABASE}{USER_MODEL}{AUTHENTICATION}{MONITORING}{LOG_QUERIES}{AUTO_REFORMAT}
|
66
|
+
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{MONITORING}{LOG_QUERIES}{AUTO_REFORMAT}
|
68
67
|
|
69
68
|
# More Info: https://PantherPy.GitHub.io/urls/
|
70
69
|
URLs = 'core.urls.url_routing'
|
70
|
+
|
71
|
+
TIMEZONE = 'UTC'
|
71
72
|
""" % datetime.now().date().isoformat()
|
72
73
|
|
73
74
|
env = """SECRET_KEY='%s'
|
@@ -99,6 +100,7 @@ requirements = """panther==%s
|
|
99
100
|
|
100
101
|
TEMPLATE = {
|
101
102
|
'app': {
|
103
|
+
'__init__.py': '',
|
102
104
|
'apis.py': apis_py,
|
103
105
|
'models.py': models_py,
|
104
106
|
'serializers.py': serializers_py,
|
@@ -106,6 +108,7 @@ TEMPLATE = {
|
|
106
108
|
'urls.py': app_urls_py,
|
107
109
|
},
|
108
110
|
'core': {
|
111
|
+
'__init__.py': '',
|
109
112
|
'configs.py': configs_py,
|
110
113
|
'urls.py': urls_py,
|
111
114
|
},
|
@@ -126,15 +129,16 @@ from panther.app import API
|
|
126
129
|
from panther.request import Request
|
127
130
|
from panther.response import Response
|
128
131
|
from panther.throttling import Throttling
|
129
|
-
from panther.utils import load_env
|
132
|
+
from panther.utils import load_env, timezone_now
|
130
133
|
|
131
134
|
BASE_DIR = Path(__name__).resolve().parent
|
132
135
|
env = load_env(BASE_DIR / '.env')
|
133
136
|
|
134
|
-
SECRET_KEY = env['SECRET_KEY']{DATABASE}{USER_MODEL}{AUTHENTICATION}{MONITORING}{LOG_QUERIES}{AUTO_REFORMAT}
|
137
|
+
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{MONITORING}{LOG_QUERIES}{AUTO_REFORMAT}
|
135
138
|
|
136
139
|
InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
|
137
140
|
|
141
|
+
TIMEZONE = 'UTC'
|
138
142
|
|
139
143
|
@API()
|
140
144
|
async def hello_world_api():
|
@@ -147,7 +151,7 @@ async def info_api(request: Request):
|
|
147
151
|
'panther_version': version(),
|
148
152
|
'method': request.method,
|
149
153
|
'query_params': request.query_params,
|
150
|
-
'datetime_now':
|
154
|
+
'datetime_now': timezone_now().isoformat(),
|
151
155
|
'user_agent': request.headers.user_agent,
|
152
156
|
}
|
153
157
|
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
@@ -155,10 +159,11 @@ async def info_api(request: Request):
|
|
155
159
|
|
156
160
|
url_routing = {
|
157
161
|
'/': hello_world_api,
|
162
|
+
'info/': info_api,
|
158
163
|
}
|
159
164
|
|
160
165
|
app = Panther(__name__, configs=__name__, urls=url_routing)
|
161
|
-
"""
|
166
|
+
""" % datetime.now().date().isoformat()
|
162
167
|
|
163
168
|
SINGLE_FILE_TEMPLATE = {
|
164
169
|
'main.py': single_main_py,
|
@@ -168,18 +173,37 @@ SINGLE_FILE_TEMPLATE = {
|
|
168
173
|
}
|
169
174
|
|
170
175
|
DATABASE_PANTHERDB_PART = """
|
171
|
-
# More Info: Https://PantherPy.GitHub.io/middlewares/
|
172
176
|
|
173
|
-
|
174
|
-
|
175
|
-
|
177
|
+
# More Info: https://PantherPy.GitHub.io/database/
|
178
|
+
DATABASE = {
|
179
|
+
'engine': {
|
180
|
+
'class': 'panther.db.connections.PantherDBConnection',
|
181
|
+
'path': BASE_DIR / 'database.{PANTHERDB_EXTENSION}',
|
182
|
+
'encryption': {PANTHERDB_ENCRYPTION}
|
183
|
+
}
|
184
|
+
}"""
|
176
185
|
|
177
186
|
DATABASE_MONGODB_PART = """
|
178
|
-
# More Info: Https://PantherPy.GitHub.io/middlewares/
|
179
187
|
|
180
|
-
|
181
|
-
|
182
|
-
|
188
|
+
# More Info: https://PantherPy.GitHub.io/database/
|
189
|
+
DATABASE = {
|
190
|
+
'engine': {
|
191
|
+
'class': 'panther.db.connections.MongoDBConnection',
|
192
|
+
'host': '127.0.0.1',
|
193
|
+
'port': 27017,
|
194
|
+
'database': '{PROJECT_NAME}'
|
195
|
+
}
|
196
|
+
}"""
|
197
|
+
|
198
|
+
REDIS_PART = """
|
199
|
+
|
200
|
+
# More Info: https://PantherPy.GitHub.io/redis/
|
201
|
+
REDIS = {
|
202
|
+
'class': 'panther.db.connections.RedisConnection',
|
203
|
+
'host': '127.0.0.1',
|
204
|
+
'port': 6379,
|
205
|
+
'db': 0,
|
206
|
+
}"""
|
183
207
|
|
184
208
|
USER_MODEL_PART = """
|
185
209
|
|
@@ -205,7 +229,3 @@ AUTO_REFORMAT_PART = """
|
|
205
229
|
|
206
230
|
# More Info: https://pantherpy.github.io/configs/#auto_reformat/
|
207
231
|
AUTO_REFORMAT = True"""
|
208
|
-
|
209
|
-
PANTHERDB_ENCRYPTION = """
|
210
|
-
|
211
|
-
PANTHERDB_ENCRYPTION = True"""
|
panther/cli/utils.py
CHANGED
@@ -3,11 +3,11 @@ import platform
|
|
3
3
|
|
4
4
|
from rich import print as rprint
|
5
5
|
|
6
|
-
from panther.
|
6
|
+
from panther.configs import Config
|
7
|
+
from panther.exceptions import PantherError
|
7
8
|
|
8
9
|
logger = logging.getLogger('panther')
|
9
10
|
|
10
|
-
|
11
11
|
if platform.system() == 'Windows':
|
12
12
|
h = '|'
|
13
13
|
v = '_'
|
@@ -63,11 +63,11 @@ help_message = f"""{logo}
|
|
63
63
|
"""
|
64
64
|
|
65
65
|
|
66
|
-
def import_error(message: str | Exception, package: str | None = None) ->
|
66
|
+
def import_error(message: str | Exception, package: str | None = None) -> PantherError:
|
67
67
|
msg = str(message)
|
68
68
|
if package:
|
69
69
|
msg += f' -> Hint: `pip install {package}`'
|
70
|
-
|
70
|
+
return PantherError(msg)
|
71
71
|
|
72
72
|
|
73
73
|
def cli_error(message: str | Exception) -> None:
|
@@ -109,44 +109,58 @@ def print_uvicorn_help_message():
|
|
109
109
|
rprint('Run `uvicorn --help` for more help')
|
110
110
|
|
111
111
|
|
112
|
-
def print_info(config:
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
112
|
+
def print_info(config: Config):
|
113
|
+
from panther.db.connections import redis
|
114
|
+
|
115
|
+
mo = config.MONITORING
|
116
|
+
lq = config.LOG_QUERIES
|
117
|
+
bt = config.BACKGROUND_TASKS
|
118
|
+
ws = config.HAS_WS
|
119
|
+
rd = redis.is_connected
|
120
|
+
bd = '{0:<39}'.format(str(config.BASE_DIR))
|
118
121
|
if len(bd) > 39:
|
119
122
|
bd = f'{bd[:36]}...'
|
120
123
|
|
121
124
|
# Monitoring
|
122
|
-
if config
|
125
|
+
if config.MONITORING:
|
123
126
|
monitor = f'{h} * Run "panther monitor" in another session for Monitoring{h}\n'
|
124
127
|
else:
|
125
128
|
monitor = None
|
126
129
|
|
127
130
|
# Uvloop
|
131
|
+
uvloop_msg = None
|
128
132
|
if platform.system() != 'Windows':
|
129
133
|
try:
|
130
134
|
import uvloop
|
131
|
-
uvloop = None
|
132
135
|
except ImportError:
|
133
|
-
|
136
|
+
uvloop_msg = (
|
134
137
|
f'{h} * You may want to install `uvloop` for better performance{h}\n'
|
135
138
|
f'{h} `pip install uvloop` {h}\n')
|
136
|
-
|
137
|
-
|
139
|
+
|
140
|
+
# Gunicorn if Websocket
|
141
|
+
gunicorn_msg = None
|
142
|
+
if config.HAS_WS:
|
143
|
+
try:
|
144
|
+
import gunicorn
|
145
|
+
gunicorn_msg = f'{h} * You have WS so make sure to run gunicorn with --preload{h}\n'
|
146
|
+
except ImportError:
|
147
|
+
pass
|
138
148
|
|
139
149
|
# Message
|
140
150
|
info_message = f"""{logo}
|
151
|
+
{h} Redis: {rd} \t {h}
|
152
|
+
{h} Websocket: {ws} \t {h}
|
141
153
|
{h} Monitoring: {mo} \t {h}
|
142
154
|
{h} Log Queries: {lq} \t {h}
|
143
155
|
{h} Background Tasks: {bt} \t {h}
|
144
|
-
{h} Websocket: {ws} \t {h}
|
145
156
|
{h} Base directory: {bd}{h}
|
146
157
|
"""
|
147
158
|
if monitor:
|
148
159
|
info_message += monitor
|
149
|
-
if
|
150
|
-
info_message +=
|
160
|
+
if uvloop_msg:
|
161
|
+
info_message += uvloop_msg
|
162
|
+
if gunicorn_msg:
|
163
|
+
info_message += gunicorn_msg
|
164
|
+
|
151
165
|
info_message += bottom
|
152
166
|
rprint(info_message)
|
panther/configs.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import copy
|
1
2
|
import typing
|
2
3
|
from dataclasses import dataclass
|
3
4
|
from datetime import timedelta
|
@@ -7,7 +8,6 @@ from typing import Callable
|
|
7
8
|
from pydantic._internal._model_construction import ModelMetaclass
|
8
9
|
|
9
10
|
from panther.throttling import Throttling
|
10
|
-
from panther.utils import Singleton
|
11
11
|
|
12
12
|
|
13
13
|
class JWTConfig:
|
@@ -41,71 +41,78 @@ class QueryObservable:
|
|
41
41
|
@classmethod
|
42
42
|
def update(cls):
|
43
43
|
for observer in cls.observers:
|
44
|
-
observer._reload_bases(parent=config.
|
44
|
+
observer._reload_bases(parent=config.QUERY_ENGINE)
|
45
45
|
|
46
46
|
|
47
47
|
@dataclass
|
48
|
-
class Config
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
48
|
+
class Config:
|
49
|
+
BASE_DIR: Path
|
50
|
+
MONITORING: bool
|
51
|
+
LOG_QUERIES: bool
|
52
|
+
DEFAULT_CACHE_EXP: timedelta | None
|
53
|
+
THROTTLING: Throttling | None
|
54
|
+
SECRET_KEY: bytes | None
|
55
|
+
HTTP_MIDDLEWARES: list[tuple]
|
56
|
+
WS_MIDDLEWARES: list[tuple]
|
57
|
+
USER_MODEL: ModelMetaclass | None
|
58
|
+
AUTHENTICATION: ModelMetaclass | None
|
59
|
+
WS_AUTHENTICATION: ModelMetaclass | None
|
60
|
+
JWT_CONFIG: JWTConfig | None
|
61
|
+
MODELS: list[dict]
|
62
|
+
FLAT_URLS: dict
|
63
|
+
URLS: dict
|
64
|
+
WEBSOCKET_CONNECTIONS: typing.Callable | None
|
65
|
+
BACKGROUND_TASKS: bool
|
66
|
+
HAS_WS: bool
|
67
|
+
STARTUPS: list[Callable]
|
68
|
+
SHUTDOWNS: list[Callable]
|
69
|
+
TIMEZONE: str
|
70
|
+
AUTO_REFORMAT: bool
|
71
|
+
QUERY_ENGINE: typing.Callable | None
|
72
|
+
DATABASE: typing.Callable | None
|
73
73
|
|
74
74
|
def __setattr__(self, key, value):
|
75
75
|
super().__setattr__(key, value)
|
76
|
-
if key == '
|
76
|
+
if key == 'QUERY_ENGINE' and value:
|
77
77
|
QueryObservable.update()
|
78
78
|
|
79
79
|
def __setitem__(self, key, value):
|
80
|
-
setattr(self, key, value)
|
80
|
+
setattr(self, key.upper(), value)
|
81
81
|
|
82
82
|
def __getitem__(self, item):
|
83
|
-
return getattr(self, item)
|
84
|
-
|
85
|
-
|
86
|
-
config
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
83
|
+
return getattr(self, item.upper())
|
84
|
+
|
85
|
+
def refresh(self):
|
86
|
+
# In some tests we need to `refresh` the `config` values
|
87
|
+
for key, value in copy.deepcopy(default_configs).items():
|
88
|
+
setattr(self, key, value)
|
89
|
+
|
90
|
+
|
91
|
+
default_configs = {
|
92
|
+
'BASE_DIR': Path(),
|
93
|
+
'MONITORING': False,
|
94
|
+
'LOG_QUERIES': False,
|
95
|
+
'DEFAULT_CACHE_EXP': None,
|
96
|
+
'THROTTLING': None,
|
97
|
+
'SECRET_KEY': None,
|
98
|
+
'HTTP_MIDDLEWARES': [],
|
99
|
+
'WS_MIDDLEWARES': [],
|
100
|
+
'USER_MODEL': None,
|
101
|
+
'AUTHENTICATION': None,
|
102
|
+
'WS_AUTHENTICATION': None,
|
103
|
+
'JWT_CONFIG': None,
|
104
|
+
'MODELS': [],
|
105
|
+
'FLAT_URLS': {},
|
106
|
+
'URLS': {},
|
107
|
+
'WEBSOCKET_CONNECTIONS': None,
|
108
|
+
'BACKGROUND_TASKS': False,
|
109
|
+
'HAS_WS': False,
|
110
|
+
'STARTUPS': [],
|
111
|
+
'SHUTDOWNS': [],
|
112
|
+
'TIMEZONE': 'UTC',
|
113
|
+
'AUTO_REFORMAT': False,
|
114
|
+
'QUERY_ENGINE': None,
|
115
|
+
'DATABASE': None,
|
116
|
+
}
|
117
|
+
|
118
|
+
config = Config(**copy.deepcopy(default_configs))
|