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.
Files changed (52) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +168 -171
  3. panther/_utils.py +26 -49
  4. panther/app.py +85 -105
  5. panther/authentications.py +86 -55
  6. panther/background_tasks.py +25 -14
  7. panther/base_request.py +38 -14
  8. panther/base_websocket.py +171 -94
  9. panther/caching.py +60 -25
  10. panther/cli/create_command.py +20 -10
  11. panther/cli/monitor_command.py +63 -37
  12. panther/cli/template.py +38 -20
  13. panther/cli/utils.py +32 -18
  14. panther/configs.py +65 -58
  15. panther/db/connections.py +139 -0
  16. panther/db/cursor.py +43 -0
  17. panther/db/models.py +64 -29
  18. panther/db/queries/__init__.py +1 -1
  19. panther/db/queries/base_queries.py +127 -0
  20. panther/db/queries/mongodb_queries.py +77 -38
  21. panther/db/queries/pantherdb_queries.py +59 -30
  22. panther/db/queries/queries.py +232 -117
  23. panther/db/utils.py +17 -18
  24. panther/events.py +44 -0
  25. panther/exceptions.py +26 -12
  26. panther/file_handler.py +2 -2
  27. panther/generics.py +163 -0
  28. panther/logging.py +7 -2
  29. panther/main.py +112 -188
  30. panther/middlewares/base.py +3 -0
  31. panther/monitoring.py +8 -5
  32. panther/pagination.py +48 -0
  33. panther/panel/apis.py +32 -5
  34. panther/panel/urls.py +2 -1
  35. panther/permissions.py +3 -3
  36. panther/request.py +6 -13
  37. panther/response.py +114 -34
  38. panther/routings.py +83 -66
  39. panther/serializer.py +131 -25
  40. panther/test.py +31 -21
  41. panther/utils.py +28 -16
  42. panther/websocket.py +7 -4
  43. {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/METADATA +93 -71
  44. panther-4.0.1.dist-info/RECORD +57 -0
  45. {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/WHEEL +1 -1
  46. panther/db/connection.py +0 -92
  47. panther/middlewares/db.py +0 -18
  48. panther/middlewares/redis.py +0 -47
  49. panther-3.9.0.dist-info/RECORD +0 -54
  50. {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/LICENSE +0 -0
  51. {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/entry_points.txt +0 -0
  52. {panther-3.9.0.dist-info → panther-4.0.1.dist-info}/top_level.txt +0 -0
@@ -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 = PANTHERDB_ENCRYPTION if self.database_encryption else ''
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
- clean_output = str # Do Nothing
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
- clean_output = self._to_boolean
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, clean_output(self.ask(**question)))
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')
@@ -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 cli_error
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
- def monitor() -> None:
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
- rows = list(rows)
26
- _, lines = os.get_terminal_size()
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 Code', justify='center', style='blue')
77
+ table.add_column('Status', justify='center', style='blue', no_wrap=True)
35
78
 
36
- for row in rows[-lines:]: # It will give us "lines" last lines of "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=(1, 2),
85
+ padding=(0, 2),
44
86
  title='Monitoring',
45
87
  border_style='bright_blue',
46
88
  )
47
89
 
48
- if not monitoring_log_file.exists():
49
- return cli_error('Monitoring file not found. (You need at least one monitoring record for this action)')
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
- _, init_lines_count = os.get_terminal_size()
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': datetime.now().isoformat(),
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}{PANTHERDB_ENCRYPTION}
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}{PANTHERDB_ENCRYPTION}
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': datetime.now().isoformat(),
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
- MIDDLEWARES = [
176
- ('panther.middlewares.db.DatabaseMiddleware', {'url': f'pantherdb://{BASE_DIR}/database.pdb'}),
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
- MIDDLEWARES = [
183
- ('panther.middlewares.db.DatabaseMiddleware', {'url': f'mongodb://127.0.0.1:27017/{PROJECT_NAME}'}),
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.exceptions import PantherException
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) -> 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
- raise PantherException(msg)
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: dict):
113
- mo = config['monitoring']
114
- lq = config['log_queries']
115
- bt = config['background_tasks']
116
- ws = config['has_ws']
117
- bd = '{0:<39}'.format(str(config['base_dir']))
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['monitoring']:
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
- uvloop = (
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
- else:
137
- uvloop = None
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 uvloop:
150
- info_message += uvloop
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.query_engine)
44
+ observer._reload_bases(parent=config.QUERY_ENGINE)
45
45
 
46
46
 
47
47
  @dataclass
48
- class Config(Singleton):
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
56
- ws_middlewares: list
57
- reversed_http_middlewares: list
58
- reversed_ws_middlewares: list
59
- user_model: ModelMetaclass | None
60
- authentication: ModelMetaclass | None
61
- jwt_config: JWTConfig | None
62
- models: list[dict]
63
- flat_urls: dict
64
- urls: dict
65
- query_engine: typing.Callable | None
66
- websocket_connections: typing.Callable | None
67
- background_tasks: bool
68
- has_ws: bool
69
- startup: Callable | None
70
- shutdown: Callable | None
71
- auto_reformat: bool
72
- pantherdb_encryption: bool
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 == 'query_engine':
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 = Config(
87
- base_dir=Path(),
88
- monitoring=False,
89
- log_queries=False,
90
- default_cache_exp=None,
91
- throttling=None,
92
- secret_key=None,
93
- http_middlewares=[],
94
- ws_middlewares=[],
95
- reversed_http_middlewares=[],
96
- reversed_ws_middlewares=[],
97
- user_model=None,
98
- authentication=None,
99
- jwt_config=None,
100
- models=[],
101
- flat_urls={},
102
- urls={},
103
- query_engine=None,
104
- websocket_connections=None,
105
- background_tasks=False,
106
- has_ws=False,
107
- startup=None,
108
- shutdown=None,
109
- auto_reformat=False,
110
- pantherdb_encryption=False,
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))