panther 3.9.0__py3-none-any.whl → 4.0.1__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 +171 -94
- panther/caching.py +60 -25
- panther/cli/create_command.py +20 -10
- panther/cli/monitor_command.py +63 -37
- panther/cli/template.py +38 -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 +112 -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 +131 -25
- panther/test.py +31 -21
- panther/utils.py +28 -16
- panther/websocket.py +7 -4
- {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/METADATA +93 -71
- panther-4.0.1.dist-info/RECORD +57 -0
- {panther-3.9.0.dist-info → panther-4.0.1.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.9.0.dist-info/RECORD +0 -54
- {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/LICENSE +0 -0
- {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/entry_points.txt +0 -0
- {panther-3.9.0.dist-info → panther-4.0.1.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'
|
@@ -128,15 +129,16 @@ from panther.app import API
|
|
128
129
|
from panther.request import Request
|
129
130
|
from panther.response import Response
|
130
131
|
from panther.throttling import Throttling
|
131
|
-
from panther.utils import load_env
|
132
|
+
from panther.utils import load_env, timezone_now
|
132
133
|
|
133
134
|
BASE_DIR = Path(__name__).resolve().parent
|
134
135
|
env = load_env(BASE_DIR / '.env')
|
135
136
|
|
136
|
-
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}
|
137
138
|
|
138
139
|
InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
|
139
140
|
|
141
|
+
TIMEZONE = 'UTC'
|
140
142
|
|
141
143
|
@API()
|
142
144
|
async def hello_world_api():
|
@@ -149,7 +151,7 @@ async def info_api(request: Request):
|
|
149
151
|
'panther_version': version(),
|
150
152
|
'method': request.method,
|
151
153
|
'query_params': request.query_params,
|
152
|
-
'datetime_now':
|
154
|
+
'datetime_now': timezone_now().isoformat(),
|
153
155
|
'user_agent': request.headers.user_agent,
|
154
156
|
}
|
155
157
|
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
@@ -157,10 +159,11 @@ async def info_api(request: Request):
|
|
157
159
|
|
158
160
|
url_routing = {
|
159
161
|
'/': hello_world_api,
|
162
|
+
'info/': info_api,
|
160
163
|
}
|
161
164
|
|
162
165
|
app = Panther(__name__, configs=__name__, urls=url_routing)
|
163
|
-
"""
|
166
|
+
""" % datetime.now().date().isoformat()
|
164
167
|
|
165
168
|
SINGLE_FILE_TEMPLATE = {
|
166
169
|
'main.py': single_main_py,
|
@@ -170,18 +173,37 @@ SINGLE_FILE_TEMPLATE = {
|
|
170
173
|
}
|
171
174
|
|
172
175
|
DATABASE_PANTHERDB_PART = """
|
173
|
-
# More Info: Https://PantherPy.GitHub.io/middlewares/
|
174
176
|
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
+
}"""
|
178
185
|
|
179
186
|
DATABASE_MONGODB_PART = """
|
180
|
-
# More Info: Https://PantherPy.GitHub.io/middlewares/
|
181
187
|
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
+
}"""
|
185
207
|
|
186
208
|
USER_MODEL_PART = """
|
187
209
|
|
@@ -207,7 +229,3 @@ AUTO_REFORMAT_PART = """
|
|
207
229
|
|
208
230
|
# More Info: https://pantherpy.github.io/configs/#auto_reformat/
|
209
231
|
AUTO_REFORMAT = True"""
|
210
|
-
|
211
|
-
PANTHERDB_ENCRYPTION = """
|
212
|
-
|
213
|
-
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))
|