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.
Files changed (57) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +46 -37
  3. panther/_utils.py +49 -34
  4. panther/app.py +96 -97
  5. panther/authentications.py +97 -50
  6. panther/background_tasks.py +98 -124
  7. panther/base_request.py +16 -10
  8. panther/base_websocket.py +8 -8
  9. panther/caching.py +16 -80
  10. panther/cli/create_command.py +17 -16
  11. panther/cli/main.py +1 -1
  12. panther/cli/monitor_command.py +11 -6
  13. panther/cli/run_command.py +5 -71
  14. panther/cli/template.py +7 -7
  15. panther/cli/utils.py +58 -69
  16. panther/configs.py +70 -72
  17. panther/db/connections.py +30 -24
  18. panther/db/cursor.py +3 -1
  19. panther/db/models.py +26 -10
  20. panther/db/queries/base_queries.py +4 -5
  21. panther/db/queries/mongodb_queries.py +21 -21
  22. panther/db/queries/pantherdb_queries.py +1 -1
  23. panther/db/queries/queries.py +26 -8
  24. panther/db/utils.py +1 -1
  25. panther/events.py +25 -14
  26. panther/exceptions.py +2 -7
  27. panther/file_handler.py +1 -1
  28. panther/generics.py +74 -100
  29. panther/logging.py +2 -1
  30. panther/main.py +12 -13
  31. panther/middlewares/cors.py +67 -0
  32. panther/middlewares/monitoring.py +5 -3
  33. panther/openapi/urls.py +2 -2
  34. panther/openapi/utils.py +3 -3
  35. panther/openapi/views.py +20 -37
  36. panther/pagination.py +4 -2
  37. panther/panel/apis.py +2 -7
  38. panther/panel/urls.py +2 -6
  39. panther/panel/utils.py +9 -5
  40. panther/panel/views.py +13 -22
  41. panther/permissions.py +2 -1
  42. panther/request.py +2 -1
  43. panther/response.py +101 -94
  44. panther/routings.py +12 -12
  45. panther/serializer.py +20 -43
  46. panther/test.py +73 -58
  47. panther/throttling.py +68 -3
  48. panther/utils.py +5 -11
  49. panther-5.0.0b5.dist-info/METADATA +188 -0
  50. panther-5.0.0b5.dist-info/RECORD +75 -0
  51. panther/monitoring.py +0 -34
  52. panther-5.0.0b3.dist-info/METADATA +0 -223
  53. panther-5.0.0b3.dist-info/RECORD +0 -75
  54. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/WHEEL +0 -0
  55. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/entry_points.txt +0 -0
  56. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/licenses/LICENSE +0 -0
  57. {panther-5.0.0b3.dist-info → panther-5.0.0b5.dist-info}/top_level.txt +0 -0
@@ -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 = date_time | method | path | ip:port | response_time(seconds) | status
55
+ # line = datetime | method | path | ip:port | response_time(seconds) | status
56
56
  columns = line.split('|')
57
- columns[4] = self._clean_response_time(columns[4])
58
- self.rows.append(columns)
59
- live.update(self.generate_table())
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 f'`{self.monitoring_log_file}` file not found. (Make sure `panther.middlewares.monitoring.MonitoringMiddleware` is in your `MIDDLEWARES`)'
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()
@@ -1,82 +1,16 @@
1
1
  import contextlib
2
- import os
2
+ import sys
3
3
 
4
4
  import uvicorn
5
5
 
6
- from panther.cli.utils import print_uvicorn_help_message, clean_args, cli_error
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
- uvicorn.run('main:app', **command)
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 InfoThrottling
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=InfoThrottling)
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 Throttling
42
+ from panther.throttling import Throttle
43
43
 
44
- InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
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 Throttling
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
- InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
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=InfoThrottling)
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}{58 * v}{tr}'
27
- bottom = f'{bl}{58 * v}{br}'
26
+ top = f'{tl}{60 * v}{tr}'
27
+ bottom = f'{bl}{60 * v}{br}'
28
28
 
29
29
  logo = rf"""{top}
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 = f"""{logo}
40
- {h} usage: {h}
41
- {h} - panther create {h}
42
- {h} Create project interactive {h}
43
- {h} {h}
44
- {h} - panther create <project_name> <directory> {h}
45
- {h} Default<directory> is `.` {h}
46
- {h} * It will create the project non-interactive {h}
47
- {h} {h}
48
- {h} - panther run [--reload | --help] {h}
49
- {h} Run your project with uvicorn {h}
50
- {h} {h}
51
- {h} - panther shell <application file path> {h}
52
- {h} Run interactive python shell {h}
53
- {h} * Example: `panther shell main.py` {h}
54
- {h} {h}
55
- {h} - panther monitor {h}
56
- {h} Show the monitor :) {h}
57
- {h} {h}
58
- {h} - panther version | --version {h}
59
- {h} Print the current version of Panther {h}
60
- {h} {h}
61
- {h} - panther h | help | --help | -h {h}
62
- {h} Show this message and exit {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 = '{0:<39}'.format(str(config.BASE_DIR))
122
- if len(bd) > 39:
123
- bd = f'{bd[:36]}...'
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` {h}\n')
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
- gunicorn_msg = f'{h} * You have WS, so make sure to run gunicorn with --preload{h}\n'
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 {h}
153
- {h} Websocket: {ws} \t {h}
154
- {h} Monitoring: {mo} \t {h}
155
- {h} Log Queries: {lq} \t {h}
156
- {h} Background Tasks: {bt} \t {h}
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 dataclasses import dataclass
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
- from panther.throttling import Throttling
9
+
11
10
 
12
11
  class JWTConfig:
13
12
  def __init__(
14
- self,
15
- key: str,
16
- algorithm: str = 'HS256',
17
- life_time: timedelta | int = timedelta(days=1),
18
- refresh_life_time: timedelta | int | None = None,
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
- DEFAULT_CACHE_EXP: timedelta | None
52
- THROTTLING: Throttling | None
53
- SECRET_KEY: bytes | None
54
- HTTP_MIDDLEWARES: list
55
- WS_MIDDLEWARES: list
56
- USER_MODEL: type[PydanticBaseModel] | None # type: type[panther.db.Model]
57
- AUTHENTICATION: type[PydanticBaseModel] | None
58
- WS_AUTHENTICATION: type[PydanticBaseModel] | None
59
- JWT_CONFIG: JWTConfig | None
60
- MODELS: list[type[PydanticBaseModel]] # type: type[panther.db.Model]
61
- FLAT_URLS: dict
62
- URLS: dict
63
- WEBSOCKET_CONNECTIONS: typing.Callable | None
64
- BACKGROUND_TASKS: bool
65
- HAS_WS: bool
66
- STARTUPS: list[Callable]
67
- SHUTDOWNS: list[Callable]
68
- TIMEZONE: str
69
- TEMPLATES_DIR: str | list[str]
70
- JINJA_ENVIRONMENT: jinja2.Environment | None
71
- AUTO_REFORMAT: bool
72
- QUERY_ENGINE: typing.Callable | None
73
- DATABASE: typing.Callable | None
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
- def refresh(self):
87
- # In some tests we need to `refresh` the `config` values
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
- 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,
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
- self,
104
- init: bool = False,
105
- host: str = 'localhost',
106
- port: int = 6379,
107
- db: int = 0,
108
- websocket_db: int = 0,
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):