spa-cli 1.0.0__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.
- spa_cli/__init__.py +0 -0
- spa_cli/__main__.py +3 -0
- spa_cli/cli.py +26 -0
- spa_cli/globals.py +215 -0
- spa_cli/projects_config/.gitkeep +0 -0
- spa_cli/src/endpoint/__init__.py +0 -0
- spa_cli/src/endpoint/endpoint.py +75 -0
- spa_cli/src/lambda_function/__init__.py +0 -0
- spa_cli/src/lambda_function/lambda_function.py +62 -0
- spa_cli/src/model/__init__.py +0 -0
- spa_cli/src/model/model.py +140 -0
- spa_cli/src/project/__init__.py +0 -0
- spa_cli/src/project/project.py +149 -0
- spa_cli/src/utils/build.py +104 -0
- spa_cli/src/utils/build_api_json.py +17 -0
- spa_cli/src/utils/build_local_api.py +148 -0
- spa_cli/src/utils/folders.py +66 -0
- spa_cli/src/utils/install_local_layers.py +164 -0
- spa_cli/src/utils/main_server.py +39 -0
- spa_cli/src/utils/openapi.json +178 -0
- spa_cli/src/utils/strings.py +42 -0
- spa_cli/src/utils/template_gen.py +82 -0
- spa_cli/src/utils/up_local_server.py +63 -0
- spa_cli-1.0.0.dist-info/METADATA +24 -0
- spa_cli-1.0.0.dist-info/RECORD +27 -0
- spa_cli-1.0.0.dist-info/WHEEL +4 -0
- spa_cli-1.0.0.dist-info/entry_points.txt +4 -0
spa_cli/__init__.py
ADDED
|
File without changes
|
spa_cli/__main__.py
ADDED
spa_cli/cli.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from importlib.metadata import version as v
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
|
|
6
|
+
from .src.project import project
|
|
7
|
+
from .src.model import model
|
|
8
|
+
from .src.endpoint import endpoint
|
|
9
|
+
from .src.lambda_function import lambda_function
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
app.add_typer(project.app, name='project')
|
|
15
|
+
# app.add_typer(model.app, name='model')
|
|
16
|
+
app.add_typer(endpoint.app, name='endpoint')
|
|
17
|
+
app.add_typer(lambda_function.app, name='lambda')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.callback(invoke_without_command=True)
|
|
21
|
+
def callback_version(version: bool = False):
|
|
22
|
+
"""
|
|
23
|
+
Imprime la versión del CLI.
|
|
24
|
+
"""
|
|
25
|
+
if version:
|
|
26
|
+
typer.echo(f'version: {v("spa-cli")}')
|
spa_cli/globals.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, TypeVar, Type, cast, Callable, List
|
|
6
|
+
import typer
|
|
7
|
+
import toml
|
|
8
|
+
import datetime
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
class Constants(Enum):
|
|
12
|
+
PROJECT_TEMPLATE = "https://github.com/DavidCuy/serverless-python-application-pattern.git"
|
|
13
|
+
MYSQL_ENGINE = "mysql"
|
|
14
|
+
POSTGRESQL_ENGINE = "postgresql"
|
|
15
|
+
|
|
16
|
+
AWS_REPOSITORY = "aws"
|
|
17
|
+
OTHER_REPOSITORY = "other"
|
|
18
|
+
|
|
19
|
+
LOCALHOST_DB_DOCKER = "host.docker.internal"
|
|
20
|
+
MSSQL_SA_USER = "sa"
|
|
21
|
+
|
|
22
|
+
VALID_MODEL_TYPES = (int, str, float, datetime.datetime)
|
|
23
|
+
JSON_INT_DTYPE = 'int'
|
|
24
|
+
JSON_FLOAT_DTYPE = 'float'
|
|
25
|
+
JSON_STRING_DTYPE = 'str'
|
|
26
|
+
JSON_DATETIME_DTYPE = 'datetime'
|
|
27
|
+
|
|
28
|
+
DRIVERS = {
|
|
29
|
+
Constants.MYSQL_ENGINE.value: "pymysql",
|
|
30
|
+
Constants.POSTGRESQL_ENGINE.value: "psycopg2"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def get_driver_from_engine(engine: Constants):
|
|
34
|
+
return DRIVERS.get(engine.value, None)
|
|
35
|
+
|
|
36
|
+
SQL_PORTS_DEFAULT = {
|
|
37
|
+
Constants.MYSQL_ENGINE.value: "3306",
|
|
38
|
+
Constants.POSTGRESQL_ENGINE.value: "5432"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
JSON_MAPPING_VALUE = {
|
|
42
|
+
Constants.JSON_INT_DTYPE.value: 'Column("{ColumnName}", Integer)',
|
|
43
|
+
Constants.JSON_STRING_DTYPE.value: 'Column("{ColumnName}", String)',
|
|
44
|
+
Constants.JSON_FLOAT_DTYPE.value: 'Column("{ColumnName}", Float)',
|
|
45
|
+
Constants.JSON_DATETIME_DTYPE.value: 'Column("{ColumnName}", DateTime)'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
T = TypeVar("T")
|
|
49
|
+
|
|
50
|
+
def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
|
|
51
|
+
assert isinstance(x, list)
|
|
52
|
+
return [f(y) for y in x]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def from_str(x: Any) -> str:
|
|
56
|
+
assert isinstance(x, str)
|
|
57
|
+
return x
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def from_str_to_list(x: Any) -> List[str]:
|
|
61
|
+
assert isinstance(x, str)
|
|
62
|
+
return [x]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def from_union(fs, x):
|
|
66
|
+
for f in fs:
|
|
67
|
+
try:
|
|
68
|
+
return f(x)
|
|
69
|
+
except:
|
|
70
|
+
pass
|
|
71
|
+
assert False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def to_class(c: Type[T], x: Any) -> dict:
|
|
75
|
+
assert isinstance(x, c)
|
|
76
|
+
return cast(Any, x).to_dict()
|
|
77
|
+
|
|
78
|
+
class BaseConf:
|
|
79
|
+
@property
|
|
80
|
+
def attrs(self) -> List[str]:
|
|
81
|
+
""" Returns a list of the attributes of an object
|
|
82
|
+
Returns:
|
|
83
|
+
List[str]: Attributes
|
|
84
|
+
"""
|
|
85
|
+
return list(filter(lambda prop: not str(prop).startswith('_'), self.__dict__.keys()))
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_dict(cls_, obj: Any) -> 'Folders':
|
|
89
|
+
assert isinstance(obj, dict)
|
|
90
|
+
|
|
91
|
+
obj_dict = {}
|
|
92
|
+
attributes = list(filter(lambda prop: not str(prop).startswith('_'), (cls_).__dataclass_fields__.keys()))
|
|
93
|
+
for attr in attributes:
|
|
94
|
+
obj_dict.update({attr: from_str(obj.get(attr))})
|
|
95
|
+
return cls_(**obj_dict)
|
|
96
|
+
|
|
97
|
+
def to_dict(self):
|
|
98
|
+
output_dict = {}
|
|
99
|
+
for attr in self.attrs:
|
|
100
|
+
output_dict.update({attr: self.__getattribute__(attr)})
|
|
101
|
+
return output_dict
|
|
102
|
+
|
|
103
|
+
def __repr__(self) -> str:
|
|
104
|
+
""" Model representation
|
|
105
|
+
Returns:
|
|
106
|
+
str: Model output string formatted
|
|
107
|
+
"""
|
|
108
|
+
attr_array = [f"{attr}={self.__getattribute__(attr)}" for attr in self.attrs]
|
|
109
|
+
args_format = ",".join(attr_array)
|
|
110
|
+
return f"<{type(self).__name__}({args_format})>"
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class Files(BaseConf):
|
|
114
|
+
model: str
|
|
115
|
+
service: str
|
|
116
|
+
controller: str
|
|
117
|
+
endpoint: str
|
|
118
|
+
lambda_function: str
|
|
119
|
+
test_lambda: str
|
|
120
|
+
lambda_conf: str
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass
|
|
124
|
+
class Folders(BaseConf):
|
|
125
|
+
models: str
|
|
126
|
+
services: str
|
|
127
|
+
controllers: str
|
|
128
|
+
lambdas: str
|
|
129
|
+
root: str
|
|
130
|
+
jsons: str
|
|
131
|
+
layers: str
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class Definition(BaseConf):
|
|
135
|
+
name: str
|
|
136
|
+
description: str
|
|
137
|
+
author: str
|
|
138
|
+
author_email: str
|
|
139
|
+
base_api: str
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class Project(BaseConf):
|
|
143
|
+
definition: Definition
|
|
144
|
+
folders: Folders
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def from_dict(obj: Any) -> 'Project':
|
|
148
|
+
assert isinstance(obj, dict)
|
|
149
|
+
definition = Definition.from_dict(obj.get("definition"))
|
|
150
|
+
folders = Folders.from_dict(obj.get("folders"))
|
|
151
|
+
return Project(definition, folders)
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class Template(BaseConf):
|
|
155
|
+
files: Files
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def from_dict(obj: Any) -> 'Template':
|
|
159
|
+
assert isinstance(obj, dict)
|
|
160
|
+
files = Files.from_dict(obj.get("files"))
|
|
161
|
+
return Template(files)
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class Config(BaseConf):
|
|
165
|
+
project: Project
|
|
166
|
+
template: Template
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def from_dict(obj: Any) -> 'Config':
|
|
170
|
+
assert isinstance(obj, dict)
|
|
171
|
+
project = Project.from_dict(obj.get("project"))
|
|
172
|
+
template = Template.from_dict(obj.get("template"))
|
|
173
|
+
return Config(project, template)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def load_config(path="spa_project.toml") -> Config:
|
|
177
|
+
config_path = Path(Path.cwd() / path)
|
|
178
|
+
if not config_path.exists():
|
|
179
|
+
typer.echo("config file not found in the project.", color=typer.colors.YELLOW)
|
|
180
|
+
load_dotenv(".env")
|
|
181
|
+
app_name = os.getenv("app_name") or ""
|
|
182
|
+
config_path.write_text(f"""[spa.project.definition]
|
|
183
|
+
name = "{app_name}"
|
|
184
|
+
description = ""
|
|
185
|
+
author = "David Cuy"
|
|
186
|
+
author_email = ""
|
|
187
|
+
base_api = "api.yaml"
|
|
188
|
+
|
|
189
|
+
[spa.template.files]
|
|
190
|
+
model = ".spa/templates/models/model.txt"
|
|
191
|
+
service = ".spa/templates/models/service.txt"
|
|
192
|
+
controller = ".spa/templates/models/controller.txt"
|
|
193
|
+
endpoint = ".spa/templates/lambda_endpoint.txt"
|
|
194
|
+
lambda_function = ".spa/templates/lambda.txt"
|
|
195
|
+
test_lambda = ".spa/templates/test_lambda_function.txt"
|
|
196
|
+
lambda_conf = ".spa/templates/lambda_conf.txt"
|
|
197
|
+
|
|
198
|
+
[spa.project.folders]
|
|
199
|
+
root = "src"
|
|
200
|
+
models = "src/layers/databases/python/core_db/models"
|
|
201
|
+
services = "src/layers/databases/python/core_db/services"
|
|
202
|
+
controllers = "src/layers/core/python/core_http/controllers"
|
|
203
|
+
jsons = ".spa/templates/json"
|
|
204
|
+
lambdas = "src/lambdas"
|
|
205
|
+
layers = "src/layers"
|
|
206
|
+
""")
|
|
207
|
+
typer.echo(
|
|
208
|
+
f"Created config file at {config_path} in this path you can find all configuration for the project here.")
|
|
209
|
+
typer.echo(f"Please add the file {config_path} to git tracking and commit it")
|
|
210
|
+
try:
|
|
211
|
+
toml_config = toml.loads(config_path.read_text())
|
|
212
|
+
except Exception as e:
|
|
213
|
+
typer.echo("Error reading the config file, please check the file format.", color=typer.colors.RED)
|
|
214
|
+
raise
|
|
215
|
+
return Config.from_dict(toml_config["spa"])
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from ...globals import load_config
|
|
2
|
+
from ..utils.folders import validate_path_not_exist
|
|
3
|
+
from ..utils.strings import camel_case
|
|
4
|
+
from ..utils.template_gen import copy_template_file
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import typer
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
|
|
12
|
+
@app.command('add')
|
|
13
|
+
def new_endpoint(
|
|
14
|
+
method: str = typer.Option(..., help='Metodo de la peticion. Valores permitidos [GET, POST, PUT, PATCH, DELETE]'),
|
|
15
|
+
path: str = typer.Option(..., help='Path del endpoint.'),
|
|
16
|
+
endpoint_name: str = typer.Option(help='Nombre de la funcion lambda.')
|
|
17
|
+
):
|
|
18
|
+
|
|
19
|
+
if "-" in endpoint_name or " " in endpoint_name:
|
|
20
|
+
typer.echo('El nombre de la lambda no debe contener espacios o guiones. Se modificará por guiones bajos.', color=typer.colors.YELLOW)
|
|
21
|
+
endpoint_name = endpoint_name.replace("-", "_").replace(" ", "_")
|
|
22
|
+
|
|
23
|
+
config = load_config()
|
|
24
|
+
camel_name = camel_case(endpoint_name)
|
|
25
|
+
|
|
26
|
+
lambda_template_path = Path(config.template.files.lambda_function)
|
|
27
|
+
lambda_test_template_path = Path(config.template.files.test_lambda)
|
|
28
|
+
lambda_conf_template_path = Path(config.template.files.lambda_conf)
|
|
29
|
+
lambda_endpoint_template_path = Path(config.template.files.endpoint)
|
|
30
|
+
|
|
31
|
+
lambda_output_folder_path = Path(config.project.folders.lambdas)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
model_exists = validate_path_not_exist(path=lambda_output_folder_path.joinpath(endpoint_name), custom_error_message=f'Ya existe una ruta con nombre: {endpoint_name}', abort=False)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
typer.echo(str(e))
|
|
37
|
+
|
|
38
|
+
if not model_exists:
|
|
39
|
+
os.mkdir(lambda_output_folder_path.joinpath(endpoint_name))
|
|
40
|
+
|
|
41
|
+
copy_template_file(
|
|
42
|
+
template_path=lambda_template_path,
|
|
43
|
+
destination_path=lambda_output_folder_path.joinpath(endpoint_name).joinpath('lambda_function.py'),
|
|
44
|
+
code_format_override={}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
copy_template_file(
|
|
48
|
+
template_path=lambda_test_template_path,
|
|
49
|
+
destination_path=lambda_output_folder_path.joinpath(endpoint_name).joinpath('test_lambda_function.py'),
|
|
50
|
+
code_format_override={
|
|
51
|
+
"camel_name": camel_name,
|
|
52
|
+
"lambda_name": endpoint_name
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
copy_template_file(
|
|
57
|
+
template_path=lambda_conf_template_path,
|
|
58
|
+
destination_path=lambda_output_folder_path.joinpath(endpoint_name).joinpath('infra_config.py'),
|
|
59
|
+
code_format_override={
|
|
60
|
+
"lambda_name": endpoint_name,
|
|
61
|
+
"camel_name": camel_name
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
copy_template_file(
|
|
66
|
+
template_path=lambda_endpoint_template_path,
|
|
67
|
+
destination_path=lambda_output_folder_path.joinpath(endpoint_name).joinpath('endpoint.yaml'),
|
|
68
|
+
code_format_override={
|
|
69
|
+
"endpoint_url": path,
|
|
70
|
+
"endpoint_method": method.lower(),
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
typer.echo(f'La ruta {path} [{method}] se agrego correctamente!', color=typer.colors.GREEN)
|
|
75
|
+
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from ...globals import load_config
|
|
2
|
+
from ..utils.folders import validate_path_not_exist
|
|
3
|
+
from ..utils.strings import camel_case
|
|
4
|
+
from ..utils.template_gen import copy_template_file
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import typer
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
|
|
12
|
+
@app.command('add')
|
|
13
|
+
def new_lambda(
|
|
14
|
+
lambda_name: str = typer.Option(help='Nombre de la funcion lambda.')
|
|
15
|
+
):
|
|
16
|
+
config = load_config()
|
|
17
|
+
|
|
18
|
+
if "-" in lambda_name or " " in lambda_name:
|
|
19
|
+
typer.echo('El nombre de la lambda no debe contener espacios o guiones. Se modificará por guiones bajos.', color=typer.colors.YELLOW)
|
|
20
|
+
lambda_name = lambda_name.replace("-", "_").replace(" ", "_")
|
|
21
|
+
camel_name = camel_case(lambda_name)
|
|
22
|
+
|
|
23
|
+
lambda_template_path = Path(config.template.files.lambda_function)
|
|
24
|
+
lambda_test_template_path = Path(config.template.files.test_lambda)
|
|
25
|
+
lambda_conf_template_path = Path(config.template.files.lambda_conf)
|
|
26
|
+
|
|
27
|
+
lambda_output_folder_path = Path(config.project.folders.lambdas)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
model_exists = validate_path_not_exist(path=lambda_output_folder_path.joinpath(lambda_name), custom_error_message=f'Ya existe una ruta con nombre: {lambda_name}', abort=False)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
typer.echo(str(e))
|
|
33
|
+
|
|
34
|
+
if not model_exists:
|
|
35
|
+
os.mkdir(lambda_output_folder_path.joinpath(lambda_name))
|
|
36
|
+
|
|
37
|
+
copy_template_file(
|
|
38
|
+
template_path=lambda_template_path,
|
|
39
|
+
destination_path=lambda_output_folder_path.joinpath(lambda_name).joinpath('lambda_function.py'),
|
|
40
|
+
code_format_override={}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
copy_template_file(
|
|
44
|
+
template_path=lambda_test_template_path,
|
|
45
|
+
destination_path=lambda_output_folder_path.joinpath(lambda_name).joinpath('test_lambda_function.py'),
|
|
46
|
+
code_format_override={
|
|
47
|
+
"camel_name": camel_name,
|
|
48
|
+
"lambda_name": lambda_name
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
copy_template_file(
|
|
53
|
+
template_path=lambda_conf_template_path,
|
|
54
|
+
destination_path=lambda_output_folder_path.joinpath(lambda_name).joinpath('infra_config.py'),
|
|
55
|
+
code_format_override={
|
|
56
|
+
"lambda_name": lambda_name,
|
|
57
|
+
"camel_name": camel_name
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
typer.echo(f'La lambda {lambda_name} se agrego correctamente!', color=typer.colors.GREEN)
|
|
62
|
+
|
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from ...globals import load_config, Config, Constants, JSON_MAPPING_VALUE
|
|
2
|
+
from ..utils.folders import validate_path_not_exist, validate_path_exist
|
|
3
|
+
from ..utils.strings import camel_case, snake_case
|
|
4
|
+
from ..utils.template_gen import add_code_to_module, add_file_to_module, read_project_config
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import typer
|
|
8
|
+
from typing import cast
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from dateutil import parser
|
|
11
|
+
from typing_extensions import Annotated
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
|
|
15
|
+
@app.command('new')
|
|
16
|
+
def new_model(name: str = typer.Option(..., help='Nombre del nuevo modelo.'), tablename: str = typer.Option(help='Nombre de la tabla', default='same-model-name')):
|
|
17
|
+
config = load_config()
|
|
18
|
+
name = camel_case(name)
|
|
19
|
+
tablename = camel_case(name if tablename == 'same-model-name' else tablename)
|
|
20
|
+
models_folder_path = Path(config.project.folders.models)
|
|
21
|
+
|
|
22
|
+
validate_path_not_exist(path=models_folder_path.joinpath(f'{name}.py'), custom_error_message=f'Ya existe un modelo con nombre: {name}')
|
|
23
|
+
__generate_full_model_config(config, name, tablename)
|
|
24
|
+
|
|
25
|
+
typer.echo(f'{name} se agrego correctamente!', color=typer.colors.GREEN)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command('fromJson')
|
|
30
|
+
def new_from_json(
|
|
31
|
+
name: str = typer.Option(..., help='Nombre del archivo JSON a leer. Sin extension'),
|
|
32
|
+
tablename: str = typer.Option(help='Nombre de la tabla', default='same-model-name')
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Crea un nuevo modelo de acuerdo con el nombre especificado y el archivo en la ruta "./.spa/templates/json"
|
|
36
|
+
"""
|
|
37
|
+
config = load_config()
|
|
38
|
+
try:
|
|
39
|
+
project_config = read_project_config()
|
|
40
|
+
except:
|
|
41
|
+
typer.echo('No se puedo leer la configuracion del proyecto', color=typer.colors.RED)
|
|
42
|
+
raise typer.Abort()
|
|
43
|
+
name = camel_case(name)
|
|
44
|
+
tablename = camel_case(name if tablename == 'same-model-name' else tablename)
|
|
45
|
+
json_folder_path = Path(config.project.folders.jsons)
|
|
46
|
+
validate_path_exist(path=json_folder_path.joinpath(f'{name.lower()}.json'), custom_error_message=f'No se pudo encontrar el archivo {name.lower()}.json')
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
model_map = cast(dict, json.loads(json_folder_path.joinpath(f'{name.lower()}.json').read_text()))
|
|
50
|
+
except Exception as e:
|
|
51
|
+
typer.echo('No se pudo leer el json correctamente', typer.colors.RED)
|
|
52
|
+
typer.Abort()
|
|
53
|
+
|
|
54
|
+
__generate_full_model_config(config, name, tablename)
|
|
55
|
+
|
|
56
|
+
full_column_text = ''
|
|
57
|
+
full_propmap_text = ''
|
|
58
|
+
full_display_member = ''
|
|
59
|
+
for key in model_map.keys():
|
|
60
|
+
if isinstance(model_map[key], Constants.VALID_MODEL_TYPES.value):
|
|
61
|
+
key_type = type(model_map[key]).__name__
|
|
62
|
+
|
|
63
|
+
if key_type == Constants.JSON_STRING_DTYPE.value:
|
|
64
|
+
try:
|
|
65
|
+
is_date = bool(parser.parse(model_map[key]))
|
|
66
|
+
if is_date:
|
|
67
|
+
key_type = Constants.JSON_DATETIME_DTYPE.value
|
|
68
|
+
except ValueError:
|
|
69
|
+
# 'String is not datetime format'
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
data_column_line_text = f"""
|
|
73
|
+
{key} = {JSON_MAPPING_VALUE[key_type].format(**{'ColumnName': camel_case(key)})}"""
|
|
74
|
+
#here replace
|
|
75
|
+
if key_type == Constants.JSON_STRING_DTYPE.value:
|
|
76
|
+
if project_config['dbDialect'] == Constants.MYSQL_ENGINE.value:
|
|
77
|
+
data_column_line_text = data_column_line_text.replace('String', 'String(512)')
|
|
78
|
+
full_column_text += data_column_line_text
|
|
79
|
+
full_propmap_text += f""",
|
|
80
|
+
"{key}": "{camel_case(key)}\""""
|
|
81
|
+
full_display_member += f""",
|
|
82
|
+
"{key}\""""
|
|
83
|
+
|
|
84
|
+
models_folder_path = Path(config.project.folders.models)
|
|
85
|
+
model_text = models_folder_path.joinpath(f'{name}.py').read_text()
|
|
86
|
+
search_text_id = f'id = Column("Id{name}", Integer, primary_key=True)'
|
|
87
|
+
search_text_propmap = f"""def property_map(self) -> Dict:
|
|
88
|
+
return """ + '{' + f"""
|
|
89
|
+
"id": "Id{name}\""""
|
|
90
|
+
search_text_display = """def display_members(cls_) -> List[str]:
|
|
91
|
+
return [
|
|
92
|
+
"id\""""
|
|
93
|
+
|
|
94
|
+
search_id = model_text.index(search_text_id) + len(search_text_id)
|
|
95
|
+
search_propmap = model_text.index(search_text_propmap) + len(search_text_propmap)
|
|
96
|
+
search_display = model_text.index(search_text_display) + len(search_text_display)
|
|
97
|
+
|
|
98
|
+
model_text = model_text[:search_id] \
|
|
99
|
+
+ full_column_text \
|
|
100
|
+
+ model_text[search_id:search_propmap] \
|
|
101
|
+
+ full_propmap_text \
|
|
102
|
+
+ model_text[search_propmap:search_display] \
|
|
103
|
+
+ full_display_member \
|
|
104
|
+
+ model_text[search_display:]
|
|
105
|
+
models_folder_path.joinpath(f'{name}.py').write_text(model_text)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
typer.echo(f'{name} se agrego correctamente!', color=typer.colors.GREEN)
|
|
109
|
+
|
|
110
|
+
def __generate_full_model_config(config: Config, name: str, tablename: str):
|
|
111
|
+
models_folder_path = Path(config.project.folders.models)
|
|
112
|
+
template_model_path = Path(config.template.files.model)
|
|
113
|
+
add_code_to_module(template_model_path, models_folder_path, name, {'model_name': name, 'model_name_lower': name.lower(), 'table_name': tablename})
|
|
114
|
+
add_file_to_module(models_folder_path, name)
|
|
115
|
+
|
|
116
|
+
service_template_path = Path(config.template.files.service)
|
|
117
|
+
service_folder_path = Path(config.project.folders.services)
|
|
118
|
+
add_code_to_module(service_template_path, service_folder_path, f"{name}Service", {'model_name': name})
|
|
119
|
+
add_file_to_module(service_folder_path, f"{name}Service")
|
|
120
|
+
|
|
121
|
+
controller_template_path = Path(config.template.files.controller)
|
|
122
|
+
controller_folder_path = Path(config.project.folders.controllers)
|
|
123
|
+
add_code_to_module(controller_template_path, controller_folder_path, f"{name}Controller", {})
|
|
124
|
+
|
|
125
|
+
routes_template_path = Path(config.template.files.endpoint)
|
|
126
|
+
routes_folder_path = Path(config.project.folders.endpoints)
|
|
127
|
+
add_code_to_module(routes_template_path, routes_folder_path, f"{name}Router", {'model_name': name, 'model_name_lower': name.lower()})
|
|
128
|
+
add_file_to_module(routes_folder_path, f"{name}Router", f"{name.lower()}_router")
|
|
129
|
+
|
|
130
|
+
index_code = Path(config.project.folders.root).joinpath('__init__.py').read_text()
|
|
131
|
+
|
|
132
|
+
search_return = index_code.index('return app')
|
|
133
|
+
|
|
134
|
+
block_insert_code = f"""from .routes import {name.lower()}_router
|
|
135
|
+
app.register_blueprint({name.lower()}_router, url_prefix='/{name.lower()}')
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
index_code = index_code[:search_return] + block_insert_code + index_code[search_return:]
|
|
140
|
+
Path(config.project.folders.root).joinpath('__init__.py').write_text(index_code)
|
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from ...globals import Constants, DRIVERS, load_config
|
|
2
|
+
from ..utils.template_gen import generate_project_template
|
|
3
|
+
from ..utils.install_local_layers import install_layers, build_layers
|
|
4
|
+
from ..utils.up_local_server import main as up_local_server
|
|
5
|
+
from ..utils.build import build_lambdas, build_lambda_stack, build_api
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import typer
|
|
10
|
+
from typing import cast
|
|
11
|
+
from click.types import Choice
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from shutil import copytree, rmtree
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
@app.command('init')
|
|
18
|
+
def init_project(
|
|
19
|
+
pattern_version: str = typer.Option(help='Version del patron.', default='latest')
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Genera un nuevo proyecto con template
|
|
23
|
+
"""
|
|
24
|
+
db_config = {
|
|
25
|
+
"db_engine": None,
|
|
26
|
+
"db_driver": None,
|
|
27
|
+
"secret_name": '',
|
|
28
|
+
}
|
|
29
|
+
project_name = typer.prompt("Nombre del proyecto")
|
|
30
|
+
project_description = typer.prompt("Descripción del proyecto")
|
|
31
|
+
|
|
32
|
+
author_name = typer.prompt("Nombre del autor", default=os.getlogin())
|
|
33
|
+
author_email = typer.prompt("Email del autor", default="")
|
|
34
|
+
|
|
35
|
+
dbChoices = Choice([
|
|
36
|
+
Constants.MYSQL_ENGINE.value,
|
|
37
|
+
Constants.POSTGRESQL_ENGINE.value
|
|
38
|
+
])
|
|
39
|
+
db_config['db_engine'] = typer.prompt(
|
|
40
|
+
"Elija su motor de base de datos",
|
|
41
|
+
Constants.MYSQL_ENGINE.value,
|
|
42
|
+
show_choices=True,
|
|
43
|
+
type=dbChoices
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
aws_region = typer.prompt("Región de AWS", default="us-east-1")
|
|
47
|
+
|
|
48
|
+
db_config['db_driver'] = DRIVERS[Constants.MYSQL_ENGINE.value]
|
|
49
|
+
db_config['secret_name'] = typer.prompt("Escriba el nombre del secreto para las credenciales de la base de datos - Revise la documentación para el formato correcto")
|
|
50
|
+
|
|
51
|
+
generate_project_template(
|
|
52
|
+
project_name,
|
|
53
|
+
author_name=author_name,
|
|
54
|
+
author_email=author_email,
|
|
55
|
+
**db_config,
|
|
56
|
+
aws_region=aws_region,
|
|
57
|
+
pattern_version=pattern_version,
|
|
58
|
+
project_description=project_description
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
local_project_dir = Path(os.getcwd()).joinpath(project_name).joinpath('.spa')
|
|
63
|
+
if not local_project_dir.exists():
|
|
64
|
+
os.mkdir(local_project_dir)
|
|
65
|
+
|
|
66
|
+
with open(local_project_dir.joinpath('project.json'), 'w') as f:
|
|
67
|
+
json.dump({
|
|
68
|
+
"project_name": project_name,
|
|
69
|
+
"dbDialect": db_config['db_engine'],
|
|
70
|
+
"pattern_version": pattern_version
|
|
71
|
+
}, f)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command('install')
|
|
75
|
+
def install_project():
|
|
76
|
+
try:
|
|
77
|
+
project_config = load_config()
|
|
78
|
+
except:
|
|
79
|
+
typer.echo('No se puedo leer la configuracion del proyecto', color=typer.colors.RED)
|
|
80
|
+
raise typer.Abort()
|
|
81
|
+
install_layers(project_config)
|
|
82
|
+
|
|
83
|
+
@app.command('run-api')
|
|
84
|
+
def run_app():
|
|
85
|
+
try:
|
|
86
|
+
project_config = load_config()
|
|
87
|
+
except:
|
|
88
|
+
typer.echo('No se puedo leer la configuracion del proyecto', color=typer.colors.RED)
|
|
89
|
+
raise typer.Abort()
|
|
90
|
+
|
|
91
|
+
typer.echo('Iniciando servidor local')
|
|
92
|
+
up_local_server(project_config)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command('build')
|
|
96
|
+
def build_project():
|
|
97
|
+
try:
|
|
98
|
+
project_config = load_config()
|
|
99
|
+
except:
|
|
100
|
+
typer.echo('No se puedo leer la configuracion del proyecto', color=typer.colors.RED)
|
|
101
|
+
raise typer.Abort()
|
|
102
|
+
|
|
103
|
+
typer.echo('Construyendo proyecto')
|
|
104
|
+
build_path = Path(os.getcwd()).joinpath('build')
|
|
105
|
+
if build_path.exists():
|
|
106
|
+
for item in os.listdir(build_path):
|
|
107
|
+
item_path = os.path.join(build_path, item)
|
|
108
|
+
if os.path.isdir(item_path):
|
|
109
|
+
try:
|
|
110
|
+
rmtree(item_path)
|
|
111
|
+
typer.echo(f"Deleted directory: {item_path}")
|
|
112
|
+
except OSError as e:
|
|
113
|
+
typer.echo(f"Error deleting directory '{item_path}': {e}")
|
|
114
|
+
|
|
115
|
+
rmtree(build_path)
|
|
116
|
+
typer.echo(f"Deleted directory: {build_path}")
|
|
117
|
+
os.mkdir(build_path)
|
|
118
|
+
|
|
119
|
+
copytree(
|
|
120
|
+
Path(os.getcwd()).joinpath('infra'),
|
|
121
|
+
build_path,
|
|
122
|
+
dirs_exist_ok=True
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
layers_path = Path(os.getcwd()) / project_config.project.folders.layers
|
|
126
|
+
lambdas_path = Path(os.getcwd()) / project_config.project.folders.lambdas
|
|
127
|
+
output_layers_path = build_path / 'tmp_build_layer'
|
|
128
|
+
|
|
129
|
+
typer.echo(f'Building layers from {layers_path} into {output_layers_path}...')
|
|
130
|
+
build_layers(layers_path, output_layers_path)
|
|
131
|
+
|
|
132
|
+
typer.echo(f'Building lambdas from {lambdas_path}...')
|
|
133
|
+
build_lambdas(lambdas_path, build_path / 'components' / 'lambdas')
|
|
134
|
+
|
|
135
|
+
typer.echo('Building lambda stack...')
|
|
136
|
+
build_lambda_stack(
|
|
137
|
+
build_lambdas_path=build_path / "components" / "lambdas",
|
|
138
|
+
environment=os.getenv("ENVIRONMENT") or "dev",
|
|
139
|
+
app_name=os.getenv("APP_NAME") or cast(str, project_config.project.definition.name)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
typer.echo('Building API definition...')
|
|
143
|
+
build_api(
|
|
144
|
+
api_path=Path(project_config.project.definition.base_api),
|
|
145
|
+
lambdas_path=build_path / "components" / "lambdas",
|
|
146
|
+
output_file=build_path / "components" / "openapi.json"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
typer.echo('Build completed.')
|