spa-cli 1.0.0__tar.gz
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-1.0.0/PKG-INFO +24 -0
- spa_cli-1.0.0/README.md +0 -0
- spa_cli-1.0.0/pyproject.toml +29 -0
- spa_cli-1.0.0/spa_cli/__init__.py +0 -0
- spa_cli-1.0.0/spa_cli/__main__.py +3 -0
- spa_cli-1.0.0/spa_cli/cli.py +26 -0
- spa_cli-1.0.0/spa_cli/globals.py +215 -0
- spa_cli-1.0.0/spa_cli/projects_config/.gitkeep +0 -0
- spa_cli-1.0.0/spa_cli/src/endpoint/__init__.py +0 -0
- spa_cli-1.0.0/spa_cli/src/endpoint/endpoint.py +75 -0
- spa_cli-1.0.0/spa_cli/src/lambda_function/__init__.py +0 -0
- spa_cli-1.0.0/spa_cli/src/lambda_function/lambda_function.py +62 -0
- spa_cli-1.0.0/spa_cli/src/model/__init__.py +0 -0
- spa_cli-1.0.0/spa_cli/src/model/model.py +140 -0
- spa_cli-1.0.0/spa_cli/src/project/__init__.py +0 -0
- spa_cli-1.0.0/spa_cli/src/project/project.py +149 -0
- spa_cli-1.0.0/spa_cli/src/utils/build.py +104 -0
- spa_cli-1.0.0/spa_cli/src/utils/build_api_json.py +17 -0
- spa_cli-1.0.0/spa_cli/src/utils/build_local_api.py +148 -0
- spa_cli-1.0.0/spa_cli/src/utils/folders.py +66 -0
- spa_cli-1.0.0/spa_cli/src/utils/install_local_layers.py +164 -0
- spa_cli-1.0.0/spa_cli/src/utils/main_server.py +39 -0
- spa_cli-1.0.0/spa_cli/src/utils/openapi.json +178 -0
- spa_cli-1.0.0/spa_cli/src/utils/strings.py +42 -0
- spa_cli-1.0.0/spa_cli/src/utils/template_gen.py +82 -0
- spa_cli-1.0.0/spa_cli/src/utils/up_local_server.py +63 -0
spa_cli-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spa-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Un cli para manejar proyectos serverless en aws con python
|
|
5
|
+
Author: David Cuy
|
|
6
|
+
Author-email: david.cuy.sanchez@gmail.com
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: cookiecutter (>=2.6.0,<3.0.0)
|
|
14
|
+
Requires-Dist: pydantic (>=2.11.9,<3.0.0)
|
|
15
|
+
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
16
|
+
Requires-Dist: setuptools (>=80.9.0,<81.0.0)
|
|
17
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
18
|
+
Requires-Dist: typer (>=0.19.1,<0.20.0)
|
|
19
|
+
Project-URL: Documentation, https://github.com/DavidCuy/spa-cli
|
|
20
|
+
Project-URL: Homepage, https://github.com/DavidCuy/spa-cli
|
|
21
|
+
Project-URL: Repository, https://github.com/DavidCuy/spa-cli
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
|
spa_cli-1.0.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "spa-cli"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Un cli para manejar proyectos serverless en aws con python"
|
|
5
|
+
authors = ["David Cuy <david.cuy.sanchez@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "spa_cli"}]
|
|
8
|
+
homepage = "https://github.com/DavidCuy/spa-cli"
|
|
9
|
+
repository = "https://github.com/DavidCuy/spa-cli"
|
|
10
|
+
documentation = "https://github.com/DavidCuy/spa-cli"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[tool.poetry.scripts]
|
|
14
|
+
spa-cli = "spa_cli.cli:app"
|
|
15
|
+
spa = "spa_cli.cli:app"
|
|
16
|
+
|
|
17
|
+
[tool.poetry.dependencies]
|
|
18
|
+
python = "^3.11"
|
|
19
|
+
python-dotenv = "^1.1.1"
|
|
20
|
+
toml = "^0.10.2"
|
|
21
|
+
cookiecutter = "^2.6.0"
|
|
22
|
+
setuptools = "^80.9.0"
|
|
23
|
+
pydantic = "^2.11.9"
|
|
24
|
+
typer = "^0.19.1"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["poetry-core"]
|
|
29
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -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")}')
|
|
@@ -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
|