amsdal_cli 0.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 (65) hide show
  1. amsdal_cli/Third-Party Materials - AMSDAL Dependencies - License Notices.md +1099 -0
  2. amsdal_cli/__about__.py +4 -0
  3. amsdal_cli/__init__.py +0 -0
  4. amsdal_cli/app.py +8 -0
  5. amsdal_cli/commands/__init__.py +0 -0
  6. amsdal_cli/commands/build/__init__.py +0 -0
  7. amsdal_cli/commands/build/command.py +27 -0
  8. amsdal_cli/commands/build/constants.py +1 -0
  9. amsdal_cli/commands/build/utils/__init__.py +0 -0
  10. amsdal_cli/commands/build/utils/build_app.py +46 -0
  11. amsdal_cli/commands/build/utils/build_config_file.py +36 -0
  12. amsdal_cli/commands/callbacks.py +47 -0
  13. amsdal_cli/commands/generate/__init__.py +0 -0
  14. amsdal_cli/commands/generate/app.py +5 -0
  15. amsdal_cli/commands/generate/command.py +5 -0
  16. amsdal_cli/commands/generate/enums.py +52 -0
  17. amsdal_cli/commands/generate/sub_commands/__init__.py +13 -0
  18. amsdal_cli/commands/generate/sub_commands/generate_hook.py +35 -0
  19. amsdal_cli/commands/generate/sub_commands/generate_model.py +94 -0
  20. amsdal_cli/commands/generate/sub_commands/generate_modifier.py +32 -0
  21. amsdal_cli/commands/generate/sub_commands/generate_property.py +38 -0
  22. amsdal_cli/commands/generate/sub_commands/generate_transaction.py +38 -0
  23. amsdal_cli/commands/generate/templates/hook.pyt +3 -0
  24. amsdal_cli/commands/generate/templates/modifier/constructor.pyt +10 -0
  25. amsdal_cli/commands/generate/templates/modifier/display_name.pyt +4 -0
  26. amsdal_cli/commands/generate/templates/modifier/version_name.pyt +4 -0
  27. amsdal_cli/commands/generate/templates/property.pyt +4 -0
  28. amsdal_cli/commands/generate/templates/transaction.pyt +7 -0
  29. amsdal_cli/commands/generate/utils/__init__.py +0 -0
  30. amsdal_cli/commands/generate/utils/build_base_path.py +22 -0
  31. amsdal_cli/commands/generate/utils/cast_to_attribute_type.py +19 -0
  32. amsdal_cli/commands/generate/utils/model_attributes.py +131 -0
  33. amsdal_cli/commands/new/__init__.py +0 -0
  34. amsdal_cli/commands/new/command.py +54 -0
  35. amsdal_cli/commands/new/templates/.amsdal-cli +6 -0
  36. amsdal_cli/commands/new/templates/.gitignore +4 -0
  37. amsdal_cli/commands/new/templates/README.md +646 -0
  38. amsdal_cli/commands/new/templates/config.yml +19 -0
  39. amsdal_cli/commands/serve/__init__.py +0 -0
  40. amsdal_cli/commands/serve/command.py +38 -0
  41. amsdal_cli/commands/serve/filters/__init__.py +0 -0
  42. amsdal_cli/commands/serve/filters/models_watch_filter.py +6 -0
  43. amsdal_cli/commands/serve/filters/static_files_watch_filter.py +21 -0
  44. amsdal_cli/commands/serve/services/__init__.py +0 -0
  45. amsdal_cli/commands/serve/services/supervisor.py +373 -0
  46. amsdal_cli/commands/serve/utils.py +16 -0
  47. amsdal_cli/commands/verify/__init__.py +0 -0
  48. amsdal_cli/commands/verify/command.py +74 -0
  49. amsdal_cli/commands/verify/models.py +10 -0
  50. amsdal_cli/commands/verify/utils/__init__.py +0 -0
  51. amsdal_cli/commands/verify/utils/verify_json_model.py +31 -0
  52. amsdal_cli/commands/verify/utils/verify_python_file.py +21 -0
  53. amsdal_cli/config/__init__.py +0 -0
  54. amsdal_cli/config/main.py +38 -0
  55. amsdal_cli/main.py +7 -0
  56. amsdal_cli/py.typed +0 -0
  57. amsdal_cli/utils/__init__.py +0 -0
  58. amsdal_cli/utils/cli_config.py +27 -0
  59. amsdal_cli/utils/copier.py +103 -0
  60. amsdal_cli/utils/render_template.py +12 -0
  61. amsdal_cli-0.0.1.dist-info/METADATA +353 -0
  62. amsdal_cli-0.0.1.dist-info/RECORD +65 -0
  63. amsdal_cli-0.0.1.dist-info/WHEEL +4 -0
  64. amsdal_cli-0.0.1.dist-info/entry_points.txt +2 -0
  65. amsdal_cli-0.0.1.dist-info/licenses/LICENSE.txt +107 -0
@@ -0,0 +1,6 @@
1
+ from watchfiles.filters import DefaultFilter
2
+
3
+
4
+ class ModelsWatchFilter(DefaultFilter):
5
+ def __init__(self) -> None:
6
+ super().__init__(ignore_dirs=['fixtures'])
@@ -0,0 +1,21 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from watchfiles.filters import DefaultFilter
5
+
6
+ if TYPE_CHECKING:
7
+ from watchfiles.main import Change
8
+
9
+
10
+ class StaticFilesWatchFilter(DefaultFilter):
11
+ def __init__(self) -> None:
12
+ self.static_directory_name = 'static'
13
+ super().__init__()
14
+
15
+ def __call__(self, change: 'Change', path: str) -> bool:
16
+ parts = path.lstrip(os.sep).split(os.sep)
17
+
18
+ if not any(p == self.static_directory_name for p in parts):
19
+ return False
20
+
21
+ return super().__call__(change, path)
File without changes
@@ -0,0 +1,373 @@
1
+ import multiprocessing
2
+ from dataclasses import dataclass
3
+ from multiprocessing.context import SpawnProcess
4
+ from pathlib import Path
5
+ from time import sleep
6
+ from typing import TYPE_CHECKING
7
+ from typing import Any
8
+ from typing import TypeAlias
9
+
10
+ from amsdal_data.table_schemas.manager import TableSchemasManager
11
+ from amsdal_framework.configs.main import settings
12
+ from amsdal_framework.errors import AmsdalRuntimeError
13
+ from amsdal_framework.manager import AmsdalManager
14
+ from amsdal_framework.schemas.manager import SchemaManager
15
+ from amsdal_models.classes.manager import ClassManager
16
+ from amsdal_server.server import start
17
+ from amsdal_utils.classes.version_manager import ClassVersionManager
18
+ from amsdal_utils.config.manager import AmsdalConfigManager
19
+ from rich import print
20
+ from watchfiles import run_process
21
+ from watchfiles.filters import PythonFilter
22
+
23
+ from amsdal_cli.commands.serve.filters.models_watch_filter import ModelsWatchFilter
24
+ from amsdal_cli.commands.serve.filters.static_files_watch_filter import StaticFilesWatchFilter
25
+
26
+ spawn_context = multiprocessing.get_context('spawn')
27
+
28
+ START_SERVER_EVENT = 'start_server'
29
+ STOP_SERVER_EVENT = 'stop_server'
30
+
31
+ if TYPE_CHECKING:
32
+ QueueType: TypeAlias = multiprocessing.Queue[str]
33
+ else:
34
+ QueueType: TypeAlias = multiprocessing.Queue
35
+
36
+
37
+ @dataclass
38
+ class ProcessState:
39
+ server: SpawnProcess | None = None
40
+ watch_models: SpawnProcess | None = None
41
+ watch_static_files: SpawnProcess | None = None
42
+ watch_fixtures: SpawnProcess | None = None
43
+ watch_transactions: SpawnProcess | None = None
44
+
45
+
46
+ class Supervisor:
47
+ def __init__(
48
+ self,
49
+ app_source_path: Path,
50
+ output_path: Path,
51
+ config_path: Path,
52
+ ) -> None:
53
+ self._app_source_path: Path = app_source_path
54
+ self._output_path: Path = output_path
55
+ self._config_path: Path = config_path
56
+ self._process_state: ProcessState = ProcessState()
57
+ self._queue: QueueType = spawn_context.Queue()
58
+
59
+ def run(self) -> None:
60
+ try:
61
+ self.start()
62
+
63
+ while True:
64
+ sleep(0.1)
65
+ except (KeyboardInterrupt, Exception):
66
+ self.stop()
67
+
68
+ def start(self) -> None:
69
+ self._process_state.server = self._run_server(
70
+ self._queue,
71
+ self._output_path,
72
+ self._config_path,
73
+ )
74
+ self._process_state.watch_models = self._run_watch_models(
75
+ self._queue,
76
+ self._app_source_path,
77
+ self._output_path,
78
+ self._config_path,
79
+ )
80
+ self._process_state.watch_static_files = self._run_watch_static_files(
81
+ self._app_source_path,
82
+ self._output_path,
83
+ self._config_path,
84
+ )
85
+ self._process_state.watch_transactions = self._run_watch_transactions(
86
+ self._app_source_path,
87
+ self._output_path,
88
+ self._config_path,
89
+ )
90
+
91
+ def stop(self) -> None:
92
+ if self._process_state.server is not None and self._process_state.server.is_alive():
93
+ self._process_state.server.terminate()
94
+
95
+ if self._process_state.watch_models is not None and self._process_state.watch_models.is_alive():
96
+ self._process_state.watch_models.terminate()
97
+
98
+ if self._process_state.watch_static_files is not None and self._process_state.watch_static_files.is_alive():
99
+ self._process_state.watch_static_files.terminate()
100
+
101
+ if self._process_state.watch_transactions is not None and self._process_state.watch_transactions.is_alive():
102
+ self._process_state.watch_transactions.terminate()
103
+
104
+ def wait(self) -> None:
105
+ if self._process_state.server is not None and self._process_state.server.is_alive():
106
+ self._process_state.server.join()
107
+
108
+ if self._process_state.watch_models is not None and self._process_state.watch_models.is_alive():
109
+ self._process_state.watch_models.join()
110
+
111
+ if self._process_state.watch_static_files is not None and self._process_state.watch_static_files.is_alive():
112
+ self._process_state.watch_static_files.join()
113
+
114
+ if self._process_state.watch_transactions is not None and self._process_state.watch_transactions.is_alive():
115
+ self._process_state.watch_transactions.join()
116
+
117
+ @classmethod
118
+ def _run_server(
119
+ cls,
120
+ queue: QueueType,
121
+ output_path: Path,
122
+ config_path: Path,
123
+ ) -> SpawnProcess:
124
+ process = spawn_context.Process(
125
+ target=cls._check_and_serve,
126
+ kwargs={
127
+ 'queue': queue,
128
+ 'is_development_mode': False,
129
+ 'output_path': output_path,
130
+ 'config_path': config_path,
131
+ },
132
+ )
133
+ process.start()
134
+
135
+ return process
136
+
137
+ @classmethod
138
+ def _check_and_serve(
139
+ cls,
140
+ queue: QueueType,
141
+ *,
142
+ is_development_mode: bool,
143
+ output_path: Path,
144
+ config_path: Path,
145
+ ) -> None:
146
+ _server_process: SpawnProcess | None = None
147
+
148
+ try:
149
+ while True:
150
+ event = queue.get()
151
+ if event == STOP_SERVER_EVENT:
152
+ if _server_process is not None and _server_process.is_alive():
153
+ _server_process.terminate()
154
+ elif event == START_SERVER_EVENT:
155
+ _server_process = spawn_context.Process(
156
+ target=cls._serve,
157
+ kwargs={
158
+ 'is_development_mode': is_development_mode,
159
+ 'output_path': output_path,
160
+ 'config_path': config_path,
161
+ },
162
+ )
163
+ _server_process.start()
164
+ except KeyboardInterrupt:
165
+ if _server_process is not None and _server_process.is_alive():
166
+ _server_process.terminate()
167
+
168
+ @classmethod
169
+ def _run_watch_models(
170
+ cls,
171
+ queue: QueueType,
172
+ app_source_path: Path,
173
+ output_path: Path,
174
+ config_path: Path,
175
+ ) -> SpawnProcess:
176
+ process = spawn_context.Process(
177
+ target=cls._watch,
178
+ args=(
179
+ 'ModelsWatch',
180
+ app_source_path / 'models',
181
+ ),
182
+ kwargs={
183
+ 'target': cls._build_models_and_migrate,
184
+ 'kwargs': {
185
+ 'queue': queue,
186
+ 'app_source_path': app_source_path,
187
+ 'output_path': output_path,
188
+ 'config_path': config_path,
189
+ },
190
+ 'watch_filter': ModelsWatchFilter(),
191
+ },
192
+ )
193
+ process.start()
194
+
195
+ return process
196
+
197
+ @classmethod
198
+ def _run_watch_static_files(
199
+ cls,
200
+ app_source_path: Path,
201
+ output_path: Path,
202
+ config_path: Path,
203
+ ) -> SpawnProcess:
204
+ process = spawn_context.Process(
205
+ target=cls._watch,
206
+ args=(
207
+ 'StaticFilesWatch',
208
+ app_source_path,
209
+ ),
210
+ kwargs={
211
+ 'target': cls._build_static_files,
212
+ 'kwargs': {
213
+ 'app_source_path': app_source_path,
214
+ 'output_path': output_path,
215
+ 'config_path': config_path,
216
+ },
217
+ 'watch_filter': StaticFilesWatchFilter(),
218
+ },
219
+ )
220
+ process.start()
221
+
222
+ return process
223
+
224
+ @classmethod
225
+ def _run_watch_transactions(
226
+ cls,
227
+ app_source_path: Path,
228
+ output_path: Path,
229
+ config_path: Path,
230
+ ) -> SpawnProcess:
231
+ process = spawn_context.Process(
232
+ target=cls._watch,
233
+ args=(
234
+ 'TransactionsWatch',
235
+ app_source_path / 'transactions',
236
+ ),
237
+ kwargs={
238
+ 'target': cls._build_transactions_and_apply,
239
+ 'kwargs': {
240
+ 'app_source_path': app_source_path,
241
+ 'output_path': output_path,
242
+ 'config_path': config_path,
243
+ },
244
+ 'watch_filter': PythonFilter(),
245
+ },
246
+ )
247
+ process.start()
248
+
249
+ return process
250
+
251
+ @classmethod
252
+ def _serve(
253
+ cls,
254
+ output_path: Path,
255
+ config_path: Path,
256
+ **kwargs: Any,
257
+ ) -> None:
258
+ from amsdal_utils.lifecycle.consumer import LifecycleConsumer
259
+ from amsdal_utils.lifecycle.enum import LifecycleEvent
260
+ from amsdal_utils.lifecycle.producer import LifecycleProducer
261
+
262
+ cls._invalidate_amsdal_state()
263
+
264
+ class AmsdalInitConsumer(LifecycleConsumer):
265
+ def on_event(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
266
+ settings.override(APP_PATH=output_path)
267
+
268
+ config_manager = AmsdalConfigManager()
269
+ config_manager.load_config(config_path)
270
+
271
+ amsdal_manager = AmsdalManager()
272
+ amsdal_manager.setup()
273
+ amsdal_manager.authenticate()
274
+ amsdal_manager.init_class_versions()
275
+
276
+ LifecycleProducer.add_listener(LifecycleEvent.ON_SERVER_STARTUP, AmsdalInitConsumer)
277
+ start(**kwargs)
278
+
279
+ @classmethod
280
+ def _build_models_and_migrate(
281
+ cls,
282
+ queue: QueueType,
283
+ app_source_path: Path,
284
+ output_path: Path,
285
+ config_path: Path,
286
+ ) -> None:
287
+ queue.put(STOP_SERVER_EVENT)
288
+ cls._invalidate_amsdal_state()
289
+ settings.override(APP_PATH=output_path)
290
+
291
+ config_manager = AmsdalConfigManager()
292
+ config_manager.load_config(config_path)
293
+
294
+ amsdal_manager = AmsdalManager()
295
+ amsdal_manager.pre_setup()
296
+
297
+ print('[blue]Building models...[/blue]', end=' ')
298
+ amsdal_manager.build_models(app_source_path / 'models')
299
+ print('[green]OK![/green]')
300
+
301
+ print('[blue]Apply migrations...[/blue]')
302
+ amsdal_manager = AmsdalManager()
303
+ amsdal_manager.setup()
304
+ amsdal_manager.authenticate()
305
+ amsdal_manager.migrate()
306
+ print('[green]OK![/green]')
307
+
308
+ print('[blue]Building fixtures...[/blue]', end=' ')
309
+ amsdal_manager.build_fixtures(app_source_path / 'models')
310
+ print('[green]OK![/green]')
311
+
312
+ print('[blue]Applying fixtures...[/blue]', end=' ')
313
+ amsdal_manager.apply_fixtures()
314
+ print('[green]OK![/green]')
315
+
316
+ queue.put(START_SERVER_EVENT)
317
+
318
+ @staticmethod
319
+ def _invalidate_amsdal_state() -> None:
320
+ try:
321
+ amsdal_manager = AmsdalManager()
322
+ amsdal_manager.teardown()
323
+ except AmsdalRuntimeError:
324
+ ...
325
+
326
+ AmsdalManager.invalidate()
327
+ ClassManager.invalidate()
328
+ ClassVersionManager.invalidate()
329
+ SchemaManager.invalidate()
330
+ TableSchemasManager.invalidate()
331
+
332
+ @staticmethod
333
+ def _build_static_files(
334
+ app_source_path: Path,
335
+ output_path: Path,
336
+ config_path: Path,
337
+ ) -> None:
338
+ settings.override(APP_PATH=output_path)
339
+
340
+ config_manager = AmsdalConfigManager()
341
+ config_manager.load_config(config_path)
342
+
343
+ amsdal_manager = AmsdalManager()
344
+ amsdal_manager.pre_setup()
345
+
346
+ print('[blue]Building static files...[/blue]', end=' ')
347
+ amsdal_manager.build_static_files(app_source_path)
348
+ print('[green]OK![/green]')
349
+
350
+ @staticmethod
351
+ def _build_transactions_and_apply(
352
+ app_source_path: Path,
353
+ output_path: Path,
354
+ config_path: Path,
355
+ ) -> None:
356
+ settings.override(APP_PATH=output_path)
357
+
358
+ config_manager = AmsdalConfigManager()
359
+ config_manager.load_config(config_path)
360
+
361
+ amsdal_manager = AmsdalManager()
362
+ amsdal_manager.pre_setup()
363
+
364
+ print('[blue]Building transactions...[/blue]', end=' ')
365
+ amsdal_manager.build_transactions(app_source_path)
366
+ print('[green]OK![/green]')
367
+
368
+ @staticmethod
369
+ def _watch(name: str, *args: Any, **kwargs: Any) -> None:
370
+ try:
371
+ run_process(*args, **kwargs)
372
+ except KeyboardInterrupt:
373
+ print(f'[yellow]Task {name} was gracefully stopped[/yellow]')
@@ -0,0 +1,16 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+
5
+ def cleanup_app(output_path: Path) -> None:
6
+ """
7
+ Cleanup the generated models and files after stopping.
8
+ """
9
+
10
+ for path in (
11
+ (output_path / 'models'),
12
+ (output_path / 'schemas'),
13
+ (output_path / 'fixtures'),
14
+ (output_path / 'warehouse'),
15
+ ):
16
+ shutil.rmtree(str(path.resolve()))
File without changes
@@ -0,0 +1,74 @@
1
+ import tempfile
2
+ import traceback
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from amsdal_framework.configs.constants import CORE_SCHEMAS_PATH
7
+ from amsdal_framework.configs.constants import TYPE_SCHEMAS_PATH
8
+ from amsdal_models.classes.manager import ClassManager
9
+ from amsdal_models.schemas.data_models.schemas_directory import SchemasDirectory
10
+ from amsdal_utils.models.enums import SchemaTypes
11
+ from rich import print
12
+
13
+ from amsdal_cli.app import app
14
+ from amsdal_cli.commands.generate.enums import MODEL_JSON_FILE
15
+ from amsdal_cli.commands.generate.enums import SOURCES_DIR
16
+ from amsdal_cli.commands.verify.utils.verify_json_model import verify_json_model
17
+ from amsdal_cli.commands.verify.utils.verify_python_file import verify_python_file
18
+ from amsdal_cli.utils.cli_config import CliConfig
19
+ from amsdal_cli.utils.copier import walk
20
+
21
+
22
+ @app.command(name='verify')
23
+ def verify_command(
24
+ ctx: typer.Context,
25
+ *,
26
+ building: bool = typer.Option(False, help='Do verify model building?'),
27
+ ) -> None:
28
+ """
29
+ Verifies all application's files such as models, properties, transactions, etc.
30
+ """
31
+ cli_config: CliConfig = ctx.meta['config']
32
+ errors = []
33
+
34
+ print('[blue]Syntax checking...[/blue]', end=' ')
35
+
36
+ for file_item in walk(cli_config.app_directory / SOURCES_DIR):
37
+ if file_item.name == MODEL_JSON_FILE:
38
+ errors.extend(verify_json_model(file_item))
39
+ elif file_item.name.endswith('.py'):
40
+ errors.extend(verify_python_file(file_item))
41
+
42
+ if errors:
43
+ for error in errors:
44
+ print(f'[red]File: {error.file_path.resolve()}: {error.message}[/red]')
45
+ print(f'[red]{error.details}[/red]')
46
+
47
+ raise typer.Exit(1)
48
+
49
+ print('[green]OK![/green]')
50
+
51
+ if not building:
52
+ return
53
+
54
+ print('[blue]Build models checking...[/blue]', end=' ')
55
+ with tempfile.TemporaryDirectory() as _temp_dir:
56
+ output_path: Path = Path(_temp_dir)
57
+ class_manager = ClassManager()
58
+ class_manager.init_models_root(output_path / 'models')
59
+
60
+ try:
61
+ class_manager.generate_models(
62
+ [
63
+ SchemasDirectory(path=TYPE_SCHEMAS_PATH, schema_type=SchemaTypes.TYPE),
64
+ SchemasDirectory(path=CORE_SCHEMAS_PATH, schema_type=SchemaTypes.CORE),
65
+ SchemasDirectory(
66
+ path=cli_config.app_directory / SOURCES_DIR / 'models', schema_type=SchemaTypes.USER
67
+ ),
68
+ ]
69
+ )
70
+ except Exception as ex:
71
+ print(f'[red]Failed: {ex} - {traceback.format_exc()}[/red]')
72
+ raise typer.Exit(1) from ex
73
+
74
+ print('[green]OK![/green]')
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class VerifyError(BaseModel):
8
+ file_path: Path
9
+ message: str
10
+ details: Any
File without changes
@@ -0,0 +1,31 @@
1
+ import json
2
+ import traceback
3
+ from json import JSONDecodeError
4
+ from pathlib import Path
5
+
6
+ from amsdal_cli.commands.verify.models import VerifyError
7
+
8
+
9
+ def verify_json_model(model_file: Path) -> list[VerifyError]:
10
+ try:
11
+ with model_file.open('rt') as _file:
12
+ data = json.loads(_file.read())
13
+ except (JSONDecodeError, TypeError) as err:
14
+ return [
15
+ VerifyError(
16
+ file_path=model_file,
17
+ message='Cannot parse JSON file',
18
+ details=f'{err}: {traceback.format_exc()}',
19
+ ),
20
+ ]
21
+
22
+ if not isinstance(data, dict):
23
+ return [
24
+ VerifyError(
25
+ file_path=model_file,
26
+ message='Incorrect JSON format',
27
+ details=f'The JSON model should be defined as an object instead of {type(data)}',
28
+ ),
29
+ ]
30
+
31
+ return []
@@ -0,0 +1,21 @@
1
+ import ast
2
+ import traceback
3
+ from pathlib import Path
4
+
5
+ from amsdal_cli.commands.verify.models import VerifyError
6
+
7
+
8
+ def verify_python_file(python_file: Path) -> list[VerifyError]:
9
+ try:
10
+ with python_file.open('rt') as _file:
11
+ ast.parse(_file.read())
12
+ except Exception as ex:
13
+ return [
14
+ VerifyError(
15
+ file_path=python_file,
16
+ message='Cannot parse PY file',
17
+ details=f'{ex}: {traceback.format_exc()}',
18
+ ),
19
+ ]
20
+
21
+ return []
File without changes
@@ -0,0 +1,38 @@
1
+ import importlib
2
+ from types import ModuleType
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+ from pydantic import field_validator
7
+ from pydantic_settings import BaseSettings
8
+ from pydantic_settings import SettingsConfigDict
9
+
10
+
11
+ class Settings(BaseSettings):
12
+ model_config = SettingsConfigDict(case_sensitive=True)
13
+
14
+ COMMANDS: list[str] = Field(
15
+ default=[
16
+ 'amsdal_cli.commands.new',
17
+ 'amsdal_cli.commands.generate',
18
+ 'amsdal_cli.commands.verify',
19
+ 'amsdal_cli.commands.build',
20
+ 'amsdal_cli.commands.serve',
21
+ # 'amsdal_cli.commands.migrate',
22
+ # 'amsdal_cli.commands.write_logs',
23
+ ]
24
+ )
25
+
26
+ @field_validator('COMMANDS')
27
+ @classmethod
28
+ def load_commands(cls, value: list[Any]) -> list[ModuleType]:
29
+ commands: list[ModuleType] = []
30
+
31
+ for module in value:
32
+ if isinstance(module, str):
33
+ commands.append(importlib.import_module(f'{module}.command'))
34
+
35
+ return commands
36
+
37
+
38
+ settings = Settings()
amsdal_cli/main.py ADDED
@@ -0,0 +1,7 @@
1
+ from amsdal_cli.app import app
2
+ from amsdal_cli.config.main import settings
3
+
4
+ __all__ = ['app']
5
+
6
+ if __name__ == '__main__':
7
+ app(context_settings={'settings': settings})
amsdal_cli/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,27 @@
1
+ from pathlib import Path
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic import model_validator
5
+
6
+
7
+ class CliConfig(BaseModel):
8
+ templates_path: Path
9
+ config_path: Path = Path('/dev/null')
10
+ http_port: int = 8080
11
+ check_model_exists: bool = True
12
+ json_indent: int = 4
13
+ app_directory: Path = Path('/dev/null')
14
+
15
+ @model_validator(mode='after')
16
+ def validate_config_path(self) -> 'CliConfig':
17
+ if self.app_directory == Path('/dev/null'):
18
+ return self
19
+
20
+ full_config_path: Path = self.app_directory / self.config_path
21
+ self.config_path = full_config_path
22
+
23
+ if not full_config_path.exists():
24
+ msg = f'The "{full_config_path}" does not exists. Check ".amdsal-cli -> config_path".'
25
+ raise ValueError(msg)
26
+
27
+ return self