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.
- amsdal_cli/Third-Party Materials - AMSDAL Dependencies - License Notices.md +1099 -0
- amsdal_cli/__about__.py +4 -0
- amsdal_cli/__init__.py +0 -0
- amsdal_cli/app.py +8 -0
- amsdal_cli/commands/__init__.py +0 -0
- amsdal_cli/commands/build/__init__.py +0 -0
- amsdal_cli/commands/build/command.py +27 -0
- amsdal_cli/commands/build/constants.py +1 -0
- amsdal_cli/commands/build/utils/__init__.py +0 -0
- amsdal_cli/commands/build/utils/build_app.py +46 -0
- amsdal_cli/commands/build/utils/build_config_file.py +36 -0
- amsdal_cli/commands/callbacks.py +47 -0
- amsdal_cli/commands/generate/__init__.py +0 -0
- amsdal_cli/commands/generate/app.py +5 -0
- amsdal_cli/commands/generate/command.py +5 -0
- amsdal_cli/commands/generate/enums.py +52 -0
- amsdal_cli/commands/generate/sub_commands/__init__.py +13 -0
- amsdal_cli/commands/generate/sub_commands/generate_hook.py +35 -0
- amsdal_cli/commands/generate/sub_commands/generate_model.py +94 -0
- amsdal_cli/commands/generate/sub_commands/generate_modifier.py +32 -0
- amsdal_cli/commands/generate/sub_commands/generate_property.py +38 -0
- amsdal_cli/commands/generate/sub_commands/generate_transaction.py +38 -0
- amsdal_cli/commands/generate/templates/hook.pyt +3 -0
- amsdal_cli/commands/generate/templates/modifier/constructor.pyt +10 -0
- amsdal_cli/commands/generate/templates/modifier/display_name.pyt +4 -0
- amsdal_cli/commands/generate/templates/modifier/version_name.pyt +4 -0
- amsdal_cli/commands/generate/templates/property.pyt +4 -0
- amsdal_cli/commands/generate/templates/transaction.pyt +7 -0
- amsdal_cli/commands/generate/utils/__init__.py +0 -0
- amsdal_cli/commands/generate/utils/build_base_path.py +22 -0
- amsdal_cli/commands/generate/utils/cast_to_attribute_type.py +19 -0
- amsdal_cli/commands/generate/utils/model_attributes.py +131 -0
- amsdal_cli/commands/new/__init__.py +0 -0
- amsdal_cli/commands/new/command.py +54 -0
- amsdal_cli/commands/new/templates/.amsdal-cli +6 -0
- amsdal_cli/commands/new/templates/.gitignore +4 -0
- amsdal_cli/commands/new/templates/README.md +646 -0
- amsdal_cli/commands/new/templates/config.yml +19 -0
- amsdal_cli/commands/serve/__init__.py +0 -0
- amsdal_cli/commands/serve/command.py +38 -0
- amsdal_cli/commands/serve/filters/__init__.py +0 -0
- amsdal_cli/commands/serve/filters/models_watch_filter.py +6 -0
- amsdal_cli/commands/serve/filters/static_files_watch_filter.py +21 -0
- amsdal_cli/commands/serve/services/__init__.py +0 -0
- amsdal_cli/commands/serve/services/supervisor.py +373 -0
- amsdal_cli/commands/serve/utils.py +16 -0
- amsdal_cli/commands/verify/__init__.py +0 -0
- amsdal_cli/commands/verify/command.py +74 -0
- amsdal_cli/commands/verify/models.py +10 -0
- amsdal_cli/commands/verify/utils/__init__.py +0 -0
- amsdal_cli/commands/verify/utils/verify_json_model.py +31 -0
- amsdal_cli/commands/verify/utils/verify_python_file.py +21 -0
- amsdal_cli/config/__init__.py +0 -0
- amsdal_cli/config/main.py +38 -0
- amsdal_cli/main.py +7 -0
- amsdal_cli/py.typed +0 -0
- amsdal_cli/utils/__init__.py +0 -0
- amsdal_cli/utils/cli_config.py +27 -0
- amsdal_cli/utils/copier.py +103 -0
- amsdal_cli/utils/render_template.py +12 -0
- amsdal_cli-0.0.1.dist-info/METADATA +353 -0
- amsdal_cli-0.0.1.dist-info/RECORD +65 -0
- amsdal_cli-0.0.1.dist-info/WHEEL +4 -0
- amsdal_cli-0.0.1.dist-info/entry_points.txt +2 -0
- amsdal_cli-0.0.1.dist-info/licenses/LICENSE.txt +107 -0
|
@@ -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]')
|
|
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
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
|