panther 5.0.0b3__py3-none-any.whl → 5.0.0b5__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 +46 -37
- panther/_utils.py +49 -34
- panther/app.py +96 -97
- panther/authentications.py +97 -50
- panther/background_tasks.py +98 -124
- panther/base_request.py +16 -10
- panther/base_websocket.py +8 -8
- panther/caching.py +16 -80
- panther/cli/create_command.py +17 -16
- panther/cli/main.py +1 -1
- panther/cli/monitor_command.py +11 -6
- panther/cli/run_command.py +5 -71
- panther/cli/template.py +7 -7
- panther/cli/utils.py +58 -69
- panther/configs.py +70 -72
- panther/db/connections.py +30 -24
- panther/db/cursor.py +3 -1
- panther/db/models.py +26 -10
- panther/db/queries/base_queries.py +4 -5
- panther/db/queries/mongodb_queries.py +21 -21
- panther/db/queries/pantherdb_queries.py +1 -1
- panther/db/queries/queries.py +26 -8
- panther/db/utils.py +1 -1
- panther/events.py +25 -14
- panther/exceptions.py +2 -7
- panther/file_handler.py +1 -1
- panther/generics.py +74 -100
- panther/logging.py +2 -1
- panther/main.py +12 -13
- panther/middlewares/cors.py +67 -0
- panther/middlewares/monitoring.py +5 -3
- panther/openapi/urls.py +2 -2
- panther/openapi/utils.py +3 -3
- panther/openapi/views.py +20 -37
- panther/pagination.py +4 -2
- panther/panel/apis.py +2 -7
- panther/panel/urls.py +2 -6
- panther/panel/utils.py +9 -5
- panther/panel/views.py +13 -22
- panther/permissions.py +2 -1
- panther/request.py +2 -1
- panther/response.py +101 -94
- panther/routings.py +12 -12
- panther/serializer.py +20 -43
- panther/test.py +73 -58
- panther/throttling.py +68 -3
- panther/utils.py +5 -11
- panther-5.0.0b5.dist-info/METADATA +188 -0
- panther-5.0.0b5.dist-info/RECORD +75 -0
- panther/monitoring.py +0 -34
- panther-5.0.0b3.dist-info/METADATA +0 -223
- panther-5.0.0b3.dist-info/RECORD +0 -75
- {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/WHEEL +0 -0
- {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/entry_points.txt +0 -0
- {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/licenses/LICENSE +0 -0
- {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/top_level.txt +0 -0
panther/cli/monitor_command.py
CHANGED
@@ -41,7 +41,7 @@ class Monitoring:
|
|
41
41
|
vertical_overflow='visible',
|
42
42
|
screen=True,
|
43
43
|
) as live,
|
44
|
-
contextlib.suppress(KeyboardInterrupt)
|
44
|
+
contextlib.suppress(KeyboardInterrupt),
|
45
45
|
):
|
46
46
|
f.readlines() # Set cursor at the end of the file
|
47
47
|
|
@@ -52,11 +52,12 @@ class Monitoring:
|
|
52
52
|
|
53
53
|
for _ in watching:
|
54
54
|
for line in f.readlines():
|
55
|
-
# line =
|
55
|
+
# line = datetime | method | path | ip:port | response_time(seconds) | status
|
56
56
|
columns = line.split('|')
|
57
|
-
columns
|
58
|
-
|
59
|
-
|
57
|
+
if len(columns) != 2: # Can be `datetime | ` on initiation.
|
58
|
+
columns[4] = self._clean_response_time(columns[4])
|
59
|
+
self.rows.append(columns)
|
60
|
+
live.update(self.generate_table())
|
60
61
|
|
61
62
|
def initialize(self) -> str:
|
62
63
|
# Check requirements
|
@@ -67,7 +68,11 @@ class Monitoring:
|
|
67
68
|
|
68
69
|
# Check log file
|
69
70
|
if not self.monitoring_log_file.exists():
|
70
|
-
return
|
71
|
+
return (
|
72
|
+
f'`{self.monitoring_log_file}` file not found. '
|
73
|
+
f'Make sure `panther.middlewares.monitoring.MonitoringMiddleware` is in your `MIDDLEWARES`.\n'
|
74
|
+
f'documentation: https://PantherPy.github.io/middlewares/#monitoring-middleware'
|
75
|
+
)
|
71
76
|
|
72
77
|
# Initialize Deque
|
73
78
|
self.update_rows()
|
panther/cli/run_command.py
CHANGED
@@ -1,82 +1,16 @@
|
|
1
1
|
import contextlib
|
2
|
-
import
|
2
|
+
import sys
|
3
3
|
|
4
4
|
import uvicorn
|
5
5
|
|
6
|
-
from panther.cli.utils import
|
7
|
-
|
8
|
-
|
9
|
-
def _handle_commands(args: dict[str, str | None]) -> dict:
|
10
|
-
"""
|
11
|
-
Boolean Commands:
|
12
|
-
- reload
|
13
|
-
- access-log
|
14
|
-
- no-access-log
|
15
|
-
- use-colors
|
16
|
-
- no-use-colors
|
17
|
-
- server-header
|
18
|
-
- no-server-header
|
19
|
-
|
20
|
-
Int Commands:
|
21
|
-
- port
|
22
|
-
- ws_max_size
|
23
|
-
- ws_max_queue
|
24
|
-
- ws_ping_interval
|
25
|
-
- ws_ping_timeout
|
26
|
-
"""
|
27
|
-
_command = {}
|
28
|
-
|
29
|
-
if 'reload' in args:
|
30
|
-
_command['reload'] = bool(args.pop('reload', None))
|
31
|
-
|
32
|
-
_command['access_log'] = False # Default
|
33
|
-
if 'access-log' in args:
|
34
|
-
_command['access_log'] = bool(args.pop('access-log', None))
|
35
|
-
|
36
|
-
if 'no-access-log' in args:
|
37
|
-
_command['access_log'] = not bool(args.pop('no-access-log', None))
|
38
|
-
|
39
|
-
if 'use-colors' in args:
|
40
|
-
_command['use_colors'] = bool(args.pop('use-colors', None))
|
41
|
-
|
42
|
-
if 'no-use-colors' in args:
|
43
|
-
_command['use_colors'] = not bool(args.pop('no-use-colors', None))
|
44
|
-
|
45
|
-
if 'server-header' in args:
|
46
|
-
_command['server_header'] = bool(args.pop('server-header', None))
|
47
|
-
|
48
|
-
if 'no-server-header' in args:
|
49
|
-
_command['server_header'] = not bool(args.pop('no-server-header', None))
|
50
|
-
|
51
|
-
if 'port' in args:
|
52
|
-
_command['port'] = int(args.pop('port'))
|
53
|
-
|
54
|
-
if 'ws_max_size' in args:
|
55
|
-
_command['ws_max_size'] = int(args.pop('ws_max_size'))
|
56
|
-
|
57
|
-
if 'ws_max_queue' in args:
|
58
|
-
_command['ws_max_queue'] = int(args.pop('ws_max_queue'))
|
59
|
-
|
60
|
-
if 'ws_ping_interval' in args:
|
61
|
-
_command['ws_ping_interval'] = int(args.pop('ws_ping_interval'))
|
62
|
-
|
63
|
-
if 'ws_ping_timeout' in args:
|
64
|
-
_command['ws_ping_timeout'] = int(args.pop('ws_ping_timeout'))
|
65
|
-
|
66
|
-
return _command
|
6
|
+
from panther.cli.utils import cli_error
|
67
7
|
|
68
8
|
|
69
9
|
def run(args: list[str]) -> None:
|
70
|
-
args = clean_args(args)
|
71
|
-
|
72
|
-
if any(a in args for a in ['h', 'help', '-h', '--help']):
|
73
|
-
print_uvicorn_help_message()
|
74
|
-
return
|
75
|
-
command = {'app_dir': os.getcwd()}
|
76
|
-
command.update(_handle_commands(args))
|
77
|
-
command.update(args)
|
78
10
|
try:
|
79
11
|
with contextlib.suppress(KeyboardInterrupt):
|
80
|
-
|
12
|
+
# First arg will be ignored by @Click, so ...
|
13
|
+
sys.argv = ['main'] + args
|
14
|
+
uvicorn.main()
|
81
15
|
except TypeError as e:
|
82
16
|
cli_error(e)
|
panther/cli/template.py
CHANGED
@@ -5,7 +5,7 @@ from panther.utils import generate_secret_key
|
|
5
5
|
|
6
6
|
apis_py = """from datetime import datetime
|
7
7
|
|
8
|
-
from app.throttling import
|
8
|
+
from app.throttling import InfoThrottle
|
9
9
|
|
10
10
|
from panther import status, version
|
11
11
|
from panther.app import API
|
@@ -19,7 +19,7 @@ async def hello_world_api():
|
|
19
19
|
return {'detail': 'Hello World'}
|
20
20
|
|
21
21
|
|
22
|
-
@API(cache=True, throttling=
|
22
|
+
@API(cache=True, throttling=InfoThrottle)
|
23
23
|
async def info_api(request: Request):
|
24
24
|
data = {
|
25
25
|
'panther_version': version(),
|
@@ -39,9 +39,9 @@ serializers_py = """from panther.serializer import ModelSerializer
|
|
39
39
|
|
40
40
|
throttling_py = """from datetime import timedelta
|
41
41
|
|
42
|
-
from panther.throttling import
|
42
|
+
from panther.throttling import Throttle
|
43
43
|
|
44
|
-
|
44
|
+
InfoThrottle = Throttle(rate=5, duration=timedelta(minutes=1))
|
45
45
|
"""
|
46
46
|
|
47
47
|
app_urls_py = """from app.apis import hello_world_api, info_api
|
@@ -132,7 +132,7 @@ from panther import Panther, status, version
|
|
132
132
|
from panther.app import API
|
133
133
|
from panther.request import Request
|
134
134
|
from panther.response import Response
|
135
|
-
from panther.throttling import
|
135
|
+
from panther.throttling import Throttle
|
136
136
|
from panther.utils import load_env, timezone_now
|
137
137
|
|
138
138
|
BASE_DIR = Path(__name__).resolve().parent
|
@@ -144,7 +144,7 @@ MIDDLEWARES = [
|
|
144
144
|
{MONITORING}
|
145
145
|
]
|
146
146
|
|
147
|
-
|
147
|
+
InfoThrottle = Throttle(rate=5, duration=timedelta(minutes=1))
|
148
148
|
|
149
149
|
TIMEZONE = 'UTC'
|
150
150
|
|
@@ -154,7 +154,7 @@ async def hello_world_api():
|
|
154
154
|
return {'detail': 'Hello World'}
|
155
155
|
|
156
156
|
|
157
|
-
@API(cache=True, throttling=
|
157
|
+
@API(cache=True, throttling=InfoThrottle)
|
158
158
|
async def info_api(request: Request):
|
159
159
|
data = {
|
160
160
|
'panther_version': version(),
|
panther/cli/utils.py
CHANGED
@@ -23,43 +23,52 @@ else:
|
|
23
23
|
br = '╯'
|
24
24
|
bl = '╰'
|
25
25
|
|
26
|
-
top = f'{tl}{
|
27
|
-
bottom = f'{bl}{
|
26
|
+
top = f'{tl}{60 * v}{tr}'
|
27
|
+
bottom = f'{bl}{60 * v}{br}'
|
28
28
|
|
29
29
|
logo = rf"""{top}
|
30
|
-
{h}
|
31
|
-
{h}
|
32
|
-
{h}
|
33
|
-
{h}
|
34
|
-
{h}
|
35
|
-
{h}
|
36
|
-
{h}
|
37
|
-
{h}
|
38
|
-
|
39
|
-
help_message =
|
40
|
-
{h}
|
41
|
-
{h}
|
42
|
-
{h}
|
43
|
-
{h}
|
44
|
-
{h}
|
45
|
-
{h}
|
46
|
-
{h} *
|
47
|
-
{h}
|
48
|
-
{h}
|
49
|
-
{h}
|
50
|
-
{h}
|
51
|
-
{h}
|
52
|
-
{h}
|
53
|
-
{h}
|
54
|
-
{h}
|
55
|
-
{h}
|
56
|
-
{h}
|
57
|
-
{h}
|
58
|
-
{h}
|
59
|
-
{h}
|
60
|
-
{h}
|
61
|
-
{h}
|
62
|
-
{h}
|
30
|
+
{h} ____ __ __ {h}
|
31
|
+
{h} /\ _`\ /\ \__/\ \ {h}
|
32
|
+
{h} \ \ \L\ \ __ ___\ \ ,_\ \ \___ __ _ __ {h}
|
33
|
+
{h} \ \ ,__/'__`\ /' _ `\ \ \/\ \ _ `\ /'__`\/\`'__\ {h}
|
34
|
+
{h} \ \ \/\ \L\.\_/\ \/\ \ \ \_\ \ \ \ \/\ __/\ \ \/ {h}
|
35
|
+
{h} \ \_\ \__/.\_\ \_\ \_\ \__\\ \_\ \_\ \____\\ \_\ {h}
|
36
|
+
{h} \/_/\/__/\/_/\/_/\/_/\/__/ \/_/\/_/\/____/ \/_/ {h}
|
37
|
+
{h} {h}"""
|
38
|
+
|
39
|
+
help_message = rf"""{logo}
|
40
|
+
{h} Usage: panther <command> \[options] {h}
|
41
|
+
{h} {h}
|
42
|
+
{h} Commands: {h}
|
43
|
+
{h} - create \[project_name] \[directory] {h}
|
44
|
+
{h} Create a new Panther project. {h}
|
45
|
+
{h} * Interactive mode if no arguments provided. {h}
|
46
|
+
{h} * Non-interactive if project_name and directory {h}
|
47
|
+
{h} are specified (default directory: .). {h}
|
48
|
+
{h} Example: {h}
|
49
|
+
{h} - `panther create` {h}
|
50
|
+
{h} - `panther create myapp myapp` {h}
|
51
|
+
{h} {h}
|
52
|
+
{h} - run <app> \[options] {h}
|
53
|
+
{h} Run your Panther project using Uvicorn. {h}
|
54
|
+
{h} * app: address of your application. {h}
|
55
|
+
{h} * options: Check `uvicorn --help` for options. {h}
|
56
|
+
{h} * `panther run` is alias of `uvicorn`. {h}
|
57
|
+
{h} Example: `panther run main:app --reload` {h}
|
58
|
+
{h} {h}
|
59
|
+
{h} - shell <application_file> {h}
|
60
|
+
{h} Start an interactive Python shell with your app. {h}
|
61
|
+
{h} * application_file: path to your main app file. {h}
|
62
|
+
{h} Example: `panther shell main.py` {h}
|
63
|
+
{h} {h}
|
64
|
+
{h} - monitor {h}
|
65
|
+
{h} Display real-time request monitoring. {h}
|
66
|
+
{h} {h}
|
67
|
+
{h} - version | --version {h}
|
68
|
+
{h} Display the current version of Panther. {h}
|
69
|
+
{h} {h}
|
70
|
+
{h} - help | h | --help | -h {h}
|
71
|
+
{h} Show this help message and exit. {h}
|
63
72
|
{bottom}
|
64
73
|
"""
|
65
74
|
|
@@ -87,29 +96,10 @@ def cli_info(message: str) -> None:
|
|
87
96
|
logger.info('Use "panther -h" for more help\n')
|
88
97
|
|
89
98
|
|
90
|
-
def clean_args(args: list[str]) -> dict:
|
91
|
-
"""
|
92
|
-
Input: ['--reload', '--host', '127.0.0.1', ...]
|
93
|
-
Output: {'--reload: None, 'host': '127.0.0.1', ...}
|
94
|
-
"""
|
95
|
-
_args = {}
|
96
|
-
for i, arg in enumerate(args):
|
97
|
-
if arg.startswith('--'):
|
98
|
-
if (i + 1) < len(args):
|
99
|
-
_args[arg[2:]] = args[i + 1]
|
100
|
-
else:
|
101
|
-
_args[arg[2:]] = True
|
102
|
-
return _args
|
103
|
-
|
104
|
-
|
105
99
|
def print_help_message():
|
106
100
|
rprint(help_message)
|
107
101
|
|
108
102
|
|
109
|
-
def print_uvicorn_help_message():
|
110
|
-
rprint('Run `uvicorn --help` for more help')
|
111
|
-
|
112
|
-
|
113
103
|
def print_info(config: Config):
|
114
104
|
from panther.db.connections import redis
|
115
105
|
|
@@ -118,15 +108,12 @@ def print_info(config: Config):
|
|
118
108
|
bt = config.BACKGROUND_TASKS
|
119
109
|
ws = config.HAS_WS
|
120
110
|
rd = redis.is_connected
|
121
|
-
bd = '{
|
122
|
-
if len(bd) >
|
123
|
-
bd = f'{bd[:
|
111
|
+
bd = f'{config.BASE_DIR!s:<41}'
|
112
|
+
if len(bd) > 41:
|
113
|
+
bd = f'{bd[:38]}...'
|
124
114
|
|
125
115
|
# Monitoring
|
126
|
-
if config.MONITORING
|
127
|
-
monitor = f'{h} * Run "panther monitor" in another session for Monitoring{h}\n'
|
128
|
-
else:
|
129
|
-
monitor = None
|
116
|
+
monitor = f'{h} * Run "panther monitor" in another session for Monitoring {h}\n' if config.MONITORING else None
|
130
117
|
|
131
118
|
# Uvloop
|
132
119
|
uvloop_msg = None
|
@@ -135,25 +122,27 @@ def print_info(config: Config):
|
|
135
122
|
import uvloop
|
136
123
|
except ImportError:
|
137
124
|
uvloop_msg = (
|
138
|
-
f'{h} * You may want to install `uvloop` for better performance{h}\n'
|
139
|
-
f'{h} `pip install uvloop`
|
125
|
+
f'{h} * You may want to install `uvloop` for better performance {h}\n'
|
126
|
+
f'{h} `pip install uvloop` {h}\n'
|
127
|
+
)
|
140
128
|
|
141
129
|
# Gunicorn if Websocket
|
142
130
|
gunicorn_msg = None
|
143
131
|
if config.HAS_WS:
|
144
132
|
try:
|
145
133
|
import gunicorn
|
146
|
-
|
134
|
+
|
135
|
+
gunicorn_msg = f'{h} * You have WS, so make sure to run gunicorn with --preload {h}\n'
|
147
136
|
except ImportError:
|
148
137
|
pass
|
149
138
|
|
150
139
|
# Message
|
151
140
|
info_message = f"""{logo}
|
152
|
-
{h} Redis: {rd} \t
|
153
|
-
{h} Websocket: {ws} \t
|
154
|
-
{h} Monitoring: {mo} \t
|
155
|
-
{h} Log Queries: {lq} \t
|
156
|
-
{h} Background Tasks: {bt} \t
|
141
|
+
{h} Redis: {rd} \t {h}
|
142
|
+
{h} Websocket: {ws} \t {h}
|
143
|
+
{h} Monitoring: {mo} \t {h}
|
144
|
+
{h} Log Queries: {lq} \t {h}
|
145
|
+
{h} Background Tasks: {bt} \t {h}
|
157
146
|
{h} Base directory: {bd}{h}
|
158
147
|
"""
|
159
148
|
if monitor:
|
panther/configs.py
CHANGED
@@ -1,25 +1,24 @@
|
|
1
|
-
import copy
|
2
1
|
import typing
|
3
|
-
from
|
2
|
+
from collections.abc import Callable
|
3
|
+
from dataclasses import dataclass, field
|
4
4
|
from datetime import timedelta
|
5
5
|
from pathlib import Path
|
6
|
-
from typing import Callable
|
7
6
|
|
8
7
|
import jinja2
|
9
8
|
from pydantic import BaseModel as PydanticBaseModel
|
10
|
-
|
9
|
+
|
11
10
|
|
12
11
|
class JWTConfig:
|
13
12
|
def __init__(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
self,
|
14
|
+
key: str,
|
15
|
+
algorithm: str = 'HS256',
|
16
|
+
life_time: timedelta | int = timedelta(days=1),
|
17
|
+
refresh_life_time: timedelta | int | None = None,
|
19
18
|
):
|
20
19
|
self.key = key
|
21
20
|
self.algorithm = algorithm
|
22
|
-
self.life_time = life_time.total_seconds() if isinstance(life_time, timedelta) else life_time
|
21
|
+
self.life_time = int(life_time.total_seconds()) if isinstance(life_time, timedelta) else life_time
|
23
22
|
|
24
23
|
if refresh_life_time:
|
25
24
|
if isinstance(refresh_life_time, timedelta):
|
@@ -29,6 +28,14 @@ class JWTConfig:
|
|
29
28
|
else:
|
30
29
|
self.refresh_life_time = self.life_time * 2
|
31
30
|
|
31
|
+
def __eq__(self, other):
|
32
|
+
return bool(
|
33
|
+
self.key == other.key
|
34
|
+
and self.algorithm == other.algorithm
|
35
|
+
and self.life_time == other.life_time
|
36
|
+
and self.refresh_life_time == other.refresh_life_time,
|
37
|
+
)
|
38
|
+
|
32
39
|
|
33
40
|
class QueryObservable:
|
34
41
|
observers = []
|
@@ -45,77 +52,68 @@ class QueryObservable:
|
|
45
52
|
|
46
53
|
@dataclass
|
47
54
|
class Config:
|
48
|
-
BASE_DIR: Path
|
49
|
-
MONITORING: bool
|
50
|
-
LOG_QUERIES: bool
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
55
|
+
BASE_DIR: Path = Path()
|
56
|
+
MONITORING: bool = False
|
57
|
+
LOG_QUERIES: bool = False
|
58
|
+
THROTTLING = None # type: panther.throttling.Throttle
|
59
|
+
SECRET_KEY: str | None = None
|
60
|
+
HTTP_MIDDLEWARES: list = field(default_factory=list)
|
61
|
+
WS_MIDDLEWARES: list = field(default_factory=list)
|
62
|
+
USER_MODEL: type[PydanticBaseModel] | None = None
|
63
|
+
AUTHENTICATION: type[PydanticBaseModel] | None = None
|
64
|
+
WS_AUTHENTICATION: type[PydanticBaseModel] | None = None
|
65
|
+
JWT_CONFIG: JWTConfig | None = None
|
66
|
+
MODELS: list = field(default_factory=list)
|
67
|
+
FLAT_URLS: dict = field(default_factory=dict)
|
68
|
+
URLS: dict = field(default_factory=dict)
|
69
|
+
WEBSOCKET_CONNECTIONS: Callable | None = None
|
70
|
+
BACKGROUND_TASKS: bool = False
|
71
|
+
HAS_WS: bool = False
|
72
|
+
TIMEZONE: str = 'UTC'
|
73
|
+
TEMPLATES_DIR: str | list[str] = '.'
|
74
|
+
JINJA_ENVIRONMENT: jinja2.Environment | None = None
|
75
|
+
AUTO_REFORMAT: bool = False
|
76
|
+
QUERY_ENGINE: Callable | None = None
|
77
|
+
DATABASE: Callable | None = None
|
78
|
+
|
79
|
+
def refresh(self):
|
80
|
+
"""
|
81
|
+
Reset built-in fields and remove any custom (non-built-in) attributes.
|
82
|
+
* In some tests we need to `refresh` the `config` values
|
83
|
+
"""
|
84
|
+
builtin_fields = set(self.__dataclass_fields__)
|
85
|
+
current_fields = set(self.__dict__)
|
86
|
+
|
87
|
+
# Reset built-in fields
|
88
|
+
for field_name in builtin_fields:
|
89
|
+
field_def = self.__dataclass_fields__[field_name]
|
90
|
+
default = field_def.default_factory() if callable(field_def.default_factory) else field_def.default
|
91
|
+
setattr(self, field_name, default)
|
92
|
+
|
93
|
+
# Delete custom attributes
|
94
|
+
for field_name in current_fields - builtin_fields:
|
95
|
+
delattr(self, field_name)
|
96
|
+
|
97
|
+
def vars(self) -> dict[str, typing.Any]:
|
98
|
+
"""Return all config variables (built-in + custom)."""
|
99
|
+
return dict(self.__dict__)
|
74
100
|
|
75
101
|
def __setattr__(self, key, value):
|
76
102
|
super().__setattr__(key, value)
|
77
103
|
if key == 'QUERY_ENGINE' and value:
|
78
104
|
QueryObservable.update()
|
79
105
|
|
106
|
+
def __getattr__(self, item: str):
|
107
|
+
try:
|
108
|
+
return object.__getattribute__(self, item)
|
109
|
+
except AttributeError:
|
110
|
+
return None
|
111
|
+
|
80
112
|
def __setitem__(self, key, value):
|
81
113
|
setattr(self, key.upper(), value)
|
82
114
|
|
83
115
|
def __getitem__(self, item):
|
84
116
|
return getattr(self, item.upper())
|
85
117
|
|
86
|
-
|
87
|
-
|
88
|
-
for key, value in copy.deepcopy(default_configs).items():
|
89
|
-
setattr(self, key, value)
|
90
|
-
|
91
|
-
|
92
|
-
default_configs = {
|
93
|
-
'BASE_DIR': Path(),
|
94
|
-
'MONITORING': False,
|
95
|
-
'LOG_QUERIES': False,
|
96
|
-
'DEFAULT_CACHE_EXP': None,
|
97
|
-
'THROTTLING': None,
|
98
|
-
'SECRET_KEY': None,
|
99
|
-
'HTTP_MIDDLEWARES': [],
|
100
|
-
'WS_MIDDLEWARES': [],
|
101
|
-
'USER_MODEL': None,
|
102
|
-
'AUTHENTICATION': None,
|
103
|
-
'WS_AUTHENTICATION': None,
|
104
|
-
'JWT_CONFIG': None,
|
105
|
-
'MODELS': [],
|
106
|
-
'FLAT_URLS': {},
|
107
|
-
'URLS': {},
|
108
|
-
'WEBSOCKET_CONNECTIONS': None,
|
109
|
-
'BACKGROUND_TASKS': False,
|
110
|
-
'HAS_WS': False,
|
111
|
-
'STARTUPS': [],
|
112
|
-
'SHUTDOWNS': [],
|
113
|
-
'TIMEZONE': 'UTC',
|
114
|
-
'TEMPLATES_DIR': '.',
|
115
|
-
'JINJA_ENVIRONMENT': None,
|
116
|
-
'AUTO_REFORMAT': False,
|
117
|
-
'QUERY_ENGINE': None,
|
118
|
-
'DATABASE': None,
|
119
|
-
}
|
120
|
-
|
121
|
-
config = Config(**copy.deepcopy(default_configs))
|
118
|
+
|
119
|
+
config = Config()
|
panther/db/connections.py
CHANGED
@@ -38,15 +38,15 @@ class BaseDatabaseConnection:
|
|
38
38
|
|
39
39
|
class MongoDBConnection(BaseDatabaseConnection):
|
40
40
|
def init(
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
self,
|
42
|
+
host: str = 'localhost',
|
43
|
+
port: int = 27017,
|
44
|
+
document_class: dict[str, Any] | None = None,
|
45
|
+
tz_aware: bool | None = None,
|
46
|
+
connect: bool | None = None,
|
47
|
+
type_registry=None, # type: bson.codec_options.TypeRegistry
|
48
|
+
database: str | None = None,
|
49
|
+
**kwargs: Any,
|
50
50
|
) -> None:
|
51
51
|
try:
|
52
52
|
from motor.motor_asyncio import AsyncIOMotorClient
|
@@ -55,6 +55,7 @@ class MongoDBConnection(BaseDatabaseConnection):
|
|
55
55
|
|
56
56
|
with contextlib.suppress(ImportError):
|
57
57
|
import uvloop
|
58
|
+
|
58
59
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
59
60
|
|
60
61
|
self._client: AsyncIOMotorClient = AsyncIOMotorClient(
|
@@ -72,6 +73,10 @@ class MongoDBConnection(BaseDatabaseConnection):
|
|
72
73
|
def session(self):
|
73
74
|
return self._database
|
74
75
|
|
76
|
+
@property
|
77
|
+
def client(self):
|
78
|
+
return self._client
|
79
|
+
|
75
80
|
|
76
81
|
class PantherDBConnection(BaseDatabaseConnection):
|
77
82
|
def init(self, path: str | None = None, encryption: bool = False):
|
@@ -81,7 +86,7 @@ class PantherDBConnection(BaseDatabaseConnection):
|
|
81
86
|
import cryptography
|
82
87
|
except ImportError as e:
|
83
88
|
raise import_error(e, package='cryptography')
|
84
|
-
params['secret_key'] = config.SECRET_KEY
|
89
|
+
params['secret_key'] = config.SECRET_KEY.encode()
|
85
90
|
|
86
91
|
self._connection: PantherDB = PantherDB(**params)
|
87
92
|
|
@@ -89,30 +94,36 @@ class PantherDBConnection(BaseDatabaseConnection):
|
|
89
94
|
def session(self):
|
90
95
|
return self._connection
|
91
96
|
|
97
|
+
@property
|
98
|
+
def client(self):
|
99
|
+
return self._connection
|
100
|
+
|
92
101
|
|
93
102
|
class DatabaseConnection(Singleton):
|
94
103
|
@property
|
95
104
|
def session(self):
|
96
105
|
return config.DATABASE.session
|
97
106
|
|
107
|
+
@property
|
108
|
+
def client(self):
|
109
|
+
return config.DATABASE.client
|
110
|
+
|
98
111
|
|
99
112
|
class RedisConnection(Singleton, _Redis):
|
100
113
|
is_connected: bool = False
|
101
114
|
|
102
115
|
def __init__(
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
**kwargs
|
116
|
+
self,
|
117
|
+
init: bool = False,
|
118
|
+
host: str = 'localhost',
|
119
|
+
port: int = 6379,
|
120
|
+
db: int = 0,
|
121
|
+
**kwargs,
|
110
122
|
):
|
111
123
|
if init:
|
112
124
|
self.host = host
|
113
125
|
self.port = port
|
114
126
|
self.db = db
|
115
|
-
self.websocket_db = websocket_db
|
116
127
|
self.kwargs = kwargs
|
117
128
|
|
118
129
|
super().__init__(host=host, port=port, db=db, **kwargs)
|
@@ -132,12 +143,7 @@ class RedisConnection(Singleton, _Redis):
|
|
132
143
|
|
133
144
|
def create_connection_for_websocket(self) -> _Redis:
|
134
145
|
if not hasattr(self, 'websocket_connection'):
|
135
|
-
self.websocket_connection = _Redis(
|
136
|
-
host=self.host,
|
137
|
-
port=self.port,
|
138
|
-
db=self.websocket_db,
|
139
|
-
**self.kwargs
|
140
|
-
)
|
146
|
+
self.websocket_connection = _Redis(host=self.host, port=self.port, db=0, **self.kwargs)
|
141
147
|
return self.websocket_connection
|
142
148
|
|
143
149
|
|
panther/db/cursor.py
CHANGED
@@ -36,6 +36,9 @@ class Cursor(_Cursor):
|
|
36
36
|
def __aiter__(self) -> Self:
|
37
37
|
return self
|
38
38
|
|
39
|
+
def __iter__(self) -> Self:
|
40
|
+
return self
|
41
|
+
|
39
42
|
async def next(self) -> Self:
|
40
43
|
return await self.cls._create_model_instance(document=super().next())
|
41
44
|
|
@@ -51,7 +54,6 @@ class Cursor(_Cursor):
|
|
51
54
|
except StopIteration:
|
52
55
|
raise
|
53
56
|
|
54
|
-
|
55
57
|
def __getitem__(self, index: int | slice) -> Cursor[Self] | Self:
|
56
58
|
document = super().__getitem__(index)
|
57
59
|
if isinstance(document, dict):
|