sucuri-framework 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.
- sucuri_framework/__init__.py +3 -0
- sucuri_framework/cli/__init__.py +0 -0
- sucuri_framework/cli/ast_utils.py +41 -0
- sucuri_framework/cli/cli.py +47 -0
- sucuri_framework/cli/commands_db.py +34 -0
- sucuri_framework/cli/commands_generate.py +548 -0
- sucuri_framework/cli/commands_new.py +102 -0
- sucuri_framework/cli/commands_run.py +21 -0
- sucuri_framework/cli/commands_task.py +13 -0
- sucuri_framework/cli/core.py +0 -0
- sucuri_framework/config.py +16 -0
- sucuri_framework/core.py +127 -0
- sucuri_framework/database.py +21 -0
- sucuri_framework/di.py +113 -0
- sucuri_framework/exceptions.py +50 -0
- sucuri_framework/guards.py +48 -0
- sucuri_framework/models.py +7 -0
- sucuri_framework/module.py +25 -0
- sucuri_framework/py.typed +0 -0
- sucuri_framework/routing.py +42 -0
- sucuri_framework/schema.py +3 -0
- sucuri_framework/tasks.py +3 -0
- sucuri_framework/testing.py +72 -0
- sucuri_framework-0.0.1.dist-info/METADATA +297 -0
- sucuri_framework-0.0.1.dist-info/RECORD +27 -0
- sucuri_framework-0.0.1.dist-info/WHEEL +4 -0
- sucuri_framework-0.0.1.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def inject_module_into_app(app_module_path: Path, import_path: str, module_var: str) -> bool:
|
|
5
|
+
"""
|
|
6
|
+
Injeta cirurgicamente a importação e a declaração de um módulo no arquivo
|
|
7
|
+
app_module.py usando a sintaxe de instância (ex: app_module.include_module(...))
|
|
8
|
+
|
|
9
|
+
Retorna True se alterou, False se já existia.
|
|
10
|
+
"""
|
|
11
|
+
if not app_module_path.exists():
|
|
12
|
+
return False
|
|
13
|
+
|
|
14
|
+
content = app_module_path.read_text(encoding="utf-8")
|
|
15
|
+
|
|
16
|
+
# Previne duplicidade
|
|
17
|
+
if f"import {module_var}" in content or module_var in content:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
# 1. Injetar o Import no topo do arquivo (após o último import)
|
|
21
|
+
import_statement = f"from {import_path} import {module_var}\n"
|
|
22
|
+
|
|
23
|
+
# Achar o último import para colocar logo depois
|
|
24
|
+
import_matches = list(re.finditer(r'^import .*|^from .* import .*', content, flags=re.MULTILINE))
|
|
25
|
+
if import_matches:
|
|
26
|
+
last_import = import_matches[-1]
|
|
27
|
+
insert_pos = last_import.end() + 1
|
|
28
|
+
content = content[:insert_pos] + import_statement + content[insert_pos:]
|
|
29
|
+
else:
|
|
30
|
+
# Se não houver nenhum import, joga no topo
|
|
31
|
+
content = import_statement + "\n" + content
|
|
32
|
+
|
|
33
|
+
# 2. Injetar a chamada .include_module no final do arquivo
|
|
34
|
+
call_statement = f"app_module.include_module({module_var})\n"
|
|
35
|
+
|
|
36
|
+
if not content.endswith('\n'):
|
|
37
|
+
content += '\n'
|
|
38
|
+
content += call_statement
|
|
39
|
+
|
|
40
|
+
app_module_path.write_text(new_content := content, encoding="utf-8")
|
|
41
|
+
return True
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from importlib import metadata
|
|
3
|
+
|
|
4
|
+
from sucuri_framework.cli.commands_run import run_server_dev, run_server_prod
|
|
5
|
+
from sucuri_framework.cli.commands_new import new_project
|
|
6
|
+
from sucuri_framework.cli.commands_generate import generate_app
|
|
7
|
+
from sucuri_framework.cli.commands_db import db_app
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="CLI oficial do Sucuri Framework")
|
|
10
|
+
|
|
11
|
+
# Adiciona grupos de comando
|
|
12
|
+
app.add_typer(generate_app, name="generate")
|
|
13
|
+
app.add_typer(db_app, name="db")
|
|
14
|
+
|
|
15
|
+
@app.command(name="version")
|
|
16
|
+
def _version():
|
|
17
|
+
"""Mostra a versão atual do Sucuri."""
|
|
18
|
+
try:
|
|
19
|
+
versao = metadata.version("sucuri")
|
|
20
|
+
print(f"🐍 Sucuri Framework v{versao}")
|
|
21
|
+
except metadata.PackageNotFoundError:
|
|
22
|
+
print("🐍 Sucuri Framework (Versão não encontrada)")
|
|
23
|
+
|
|
24
|
+
@app.command(name="dev")
|
|
25
|
+
def _dev():
|
|
26
|
+
"""Iniciar o Servidor de Desenvolvimento (Hot Reload)."""
|
|
27
|
+
run_server_dev()
|
|
28
|
+
|
|
29
|
+
@app.command(name="run")
|
|
30
|
+
def _run():
|
|
31
|
+
"""Iniciar o Servidor de Produção."""
|
|
32
|
+
run_server_prod()
|
|
33
|
+
|
|
34
|
+
from sucuri_framework.cli.commands_task import run_task
|
|
35
|
+
|
|
36
|
+
@app.command(name="task")
|
|
37
|
+
def _task(nome_da_tarefa: str):
|
|
38
|
+
"""Rodar scripts de automação customizados definidos no pyproject.toml."""
|
|
39
|
+
run_task(nome_da_tarefa)
|
|
40
|
+
|
|
41
|
+
@app.command(name="new")
|
|
42
|
+
def _new(nome_do_projeto: str):
|
|
43
|
+
"""Criar um projeto novo."""
|
|
44
|
+
new_project(nome_do_projeto)
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
db_app = typer.Typer(help="Operações de Banco de Dados")
|
|
5
|
+
|
|
6
|
+
@db_app.command("init")
|
|
7
|
+
def db_init():
|
|
8
|
+
"""Inicializar o controle de versão do banco (primeira vez)."""
|
|
9
|
+
print("Inicializando o banco de dados...")
|
|
10
|
+
try:
|
|
11
|
+
subprocess.run(["aerich", "init", "-t", "app.db.TORTOISE_ORM"], check=True)
|
|
12
|
+
subprocess.run(["aerich", "init-db"], check=True)
|
|
13
|
+
print("Banco inicializado com sucesso!")
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print(f"Falha ao inicializar o banco: {e}")
|
|
16
|
+
|
|
17
|
+
@db_app.command("migrate")
|
|
18
|
+
def db_migrate(motivo: str):
|
|
19
|
+
"""Gerar e Aplicar Migrações de Banco."""
|
|
20
|
+
print(f"Gerando migração: {motivo}")
|
|
21
|
+
# Usa aerich debaixo dos panos silenciosamente
|
|
22
|
+
try:
|
|
23
|
+
subprocess.run(["aerich", "migrate", "--name", motivo], check=True)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"Falha ao gerar migração: {e}")
|
|
26
|
+
|
|
27
|
+
@db_app.command("upgrade")
|
|
28
|
+
def db_upgrade():
|
|
29
|
+
"""Aplicar Migrações de Banco (Upgrade)."""
|
|
30
|
+
print("Aplicando migrações...")
|
|
31
|
+
try:
|
|
32
|
+
subprocess.run(["aerich", "upgrade"], check=True)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"Falha ao aplicar migração: {e}")
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import typer
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
generate_app = typer.Typer(help="Geradores de código para o framework")
|
|
6
|
+
|
|
7
|
+
def to_snake_case(name: str) -> str:
|
|
8
|
+
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
|
9
|
+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
|
10
|
+
|
|
11
|
+
from sucuri_framework.cli.ast_utils import inject_module_into_app
|
|
12
|
+
|
|
13
|
+
def get_names(nome: str):
|
|
14
|
+
# Returns (snake_case, CamelCase)
|
|
15
|
+
camel = nome[0].upper() + nome[1:] if nome else nome
|
|
16
|
+
snake = to_snake_case(camel)
|
|
17
|
+
return snake, camel
|
|
18
|
+
|
|
19
|
+
@generate_app.command("resource", help="Gera Model, Schema, Controller e Service.")
|
|
20
|
+
def generate_resource(
|
|
21
|
+
nome: str,
|
|
22
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
23
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
24
|
+
):
|
|
25
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
26
|
+
|
|
27
|
+
app_dir = Path("app")
|
|
28
|
+
if not app_dir.exists():
|
|
29
|
+
print("Erro: Deve ser executado na raiz do projeto (onde a pasta 'app' está).")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
print(f"Gerando resource para: {nome_capitalized}...")
|
|
33
|
+
|
|
34
|
+
# Gerando o Model
|
|
35
|
+
model_content = f'''from sucuri_framework.models import Model, fields
|
|
36
|
+
|
|
37
|
+
class {nome_capitalized}(Model):
|
|
38
|
+
# 'id' (int) é gerado automaticamente pelo Tortoise/Sucuri
|
|
39
|
+
nome = fields.CharField(max_length=255)
|
|
40
|
+
|
|
41
|
+
class Meta:
|
|
42
|
+
table = "{nome_lower}s"
|
|
43
|
+
'''
|
|
44
|
+
model_path = app_dir / "models" / f"{nome_lower}.py"
|
|
45
|
+
if no_overwrite and model_path.exists():
|
|
46
|
+
print(f"Ignorado: {model_path} já existe.")
|
|
47
|
+
else:
|
|
48
|
+
model_path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
model_path.write_text(model_content)
|
|
50
|
+
print(f"Criado: {model_path}")
|
|
51
|
+
|
|
52
|
+
# Gerando o Schema
|
|
53
|
+
schema_content = f'''from sucuri_framework.schema import Schema
|
|
54
|
+
|
|
55
|
+
class Criar{nome_capitalized}Schema(Schema):
|
|
56
|
+
nome: str
|
|
57
|
+
|
|
58
|
+
class Resposta{nome_capitalized}Schema(Schema):
|
|
59
|
+
id: int
|
|
60
|
+
nome: str
|
|
61
|
+
'''
|
|
62
|
+
schema_path = app_dir / "schemas" / f"{nome_lower}.py"
|
|
63
|
+
if no_overwrite and schema_path.exists():
|
|
64
|
+
print(f"Ignorado: {schema_path} já existe.")
|
|
65
|
+
else:
|
|
66
|
+
schema_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
schema_path.write_text(schema_content)
|
|
68
|
+
print(f"Criado: {schema_path}")
|
|
69
|
+
|
|
70
|
+
# Gerando o Controller
|
|
71
|
+
controller_content = f'''from sucuri_framework.routing import Controller
|
|
72
|
+
from sucuri_framework.di import Inject
|
|
73
|
+
from app.models.{nome_lower} import {nome_capitalized}
|
|
74
|
+
from app.schemas.{nome_lower} import Criar{nome_capitalized}Schema, Resposta{nome_capitalized}Schema
|
|
75
|
+
from app.services.{nome_lower} import {nome_capitalized}Service
|
|
76
|
+
|
|
77
|
+
{nome_lower}s_controller = Controller(prefix="/api/{nome_lower}s", tags=["{nome_capitalized}s"])
|
|
78
|
+
|
|
79
|
+
@{nome_lower}s_controller.get("/", response_model=list[Resposta{nome_capitalized}Schema])
|
|
80
|
+
async def listar_{nome_lower}s(service: {nome_capitalized}Service = Inject()):
|
|
81
|
+
# Exemplo delegando pro service:
|
|
82
|
+
# return await service.listar_todos()
|
|
83
|
+
return await {nome_capitalized}.all()
|
|
84
|
+
|
|
85
|
+
@{nome_lower}s_controller.post("/", response_model=Resposta{nome_capitalized}Schema)
|
|
86
|
+
async def criar_{nome_lower}(payload: Criar{nome_capitalized}Schema, service: {nome_capitalized}Service = Inject()):
|
|
87
|
+
# Exemplo delegando pro service:
|
|
88
|
+
# novo = await service.criar_novo(payload.model_dump())
|
|
89
|
+
novo = await {nome_capitalized}.create(**payload.model_dump())
|
|
90
|
+
return novo
|
|
91
|
+
'''
|
|
92
|
+
controller_path = app_dir / "controllers" / f"{nome_lower}.py"
|
|
93
|
+
if no_overwrite and controller_path.exists():
|
|
94
|
+
print(f"Ignorado: {controller_path} já existe.")
|
|
95
|
+
else:
|
|
96
|
+
controller_path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
controller_path.write_text(controller_content)
|
|
98
|
+
print(f"Criado: {controller_path}")
|
|
99
|
+
|
|
100
|
+
# Gerando o Service
|
|
101
|
+
service_content = f'''from sucuri_framework.di import Injectable
|
|
102
|
+
from app.models.{nome_lower} import {nome_capitalized}
|
|
103
|
+
from app.schemas.{nome_lower} import Criar{nome_capitalized}Schema
|
|
104
|
+
|
|
105
|
+
@Injectable()
|
|
106
|
+
class {nome_capitalized}Service:
|
|
107
|
+
"""Camada de Regras de Negócios para {nome_capitalized}."""
|
|
108
|
+
pass
|
|
109
|
+
'''
|
|
110
|
+
service_path = app_dir / "services" / f"{nome_lower}.py"
|
|
111
|
+
if no_overwrite and service_path.exists():
|
|
112
|
+
print(f"Ignorado: {service_path} já existe.")
|
|
113
|
+
else:
|
|
114
|
+
service_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
service_path.write_text(service_content)
|
|
116
|
+
print(f"Criado: {service_path}")
|
|
117
|
+
|
|
118
|
+
# Gerando o Módulo (AST Inject)
|
|
119
|
+
generate_module(nome, no_overwrite, no_tests)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Gerando Teste
|
|
123
|
+
if not no_tests:
|
|
124
|
+
test_dir = Path("tests")
|
|
125
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
|
|
127
|
+
test_content = f'''import pytest
|
|
128
|
+
|
|
129
|
+
# TODO: Importar dependencias para teste (app, client, db, etc)
|
|
130
|
+
|
|
131
|
+
def test_listar_{nome_lower}s():
|
|
132
|
+
# Exemplo de teste placeholder
|
|
133
|
+
assert True
|
|
134
|
+
'''
|
|
135
|
+
test_path = test_dir / f"test_{nome_lower}.py"
|
|
136
|
+
if no_overwrite and test_path.exists():
|
|
137
|
+
print(f"Ignorado: {test_path} já existe.")
|
|
138
|
+
else:
|
|
139
|
+
test_path.write_text(test_content)
|
|
140
|
+
print(f"Criado: {test_path}")
|
|
141
|
+
|
|
142
|
+
print("Resource gerado com sucesso!")
|
|
143
|
+
|
|
144
|
+
@generate_app.command("controller", help="Gera apenas a declaração de um Controller com rotas vazias.")
|
|
145
|
+
def generate_controller(
|
|
146
|
+
nome: str,
|
|
147
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
148
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
149
|
+
):
|
|
150
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
151
|
+
app_dir = Path("app")
|
|
152
|
+
if not app_dir.exists():
|
|
153
|
+
print("Erro: Deve ser executado na raiz do projeto (onde a pasta 'app' está).")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
print(f"Gerando controller para: {nome_capitalized}...")
|
|
157
|
+
|
|
158
|
+
content = f'''from sucuri_framework.routing import Controller
|
|
159
|
+
from sucuri_framework.di import Inject
|
|
160
|
+
|
|
161
|
+
{nome_lower}_controller = Controller(prefix="/api/{nome_lower}", tags=["{nome_capitalized}"])
|
|
162
|
+
|
|
163
|
+
@{nome_lower}_controller.get("/")
|
|
164
|
+
async def listar_{nome_lower}():
|
|
165
|
+
return {{"message": "Rota de {nome_capitalized}"}}
|
|
166
|
+
'''
|
|
167
|
+
path = app_dir / "controllers" / f"{nome_lower}.py"
|
|
168
|
+
if no_overwrite and path.exists():
|
|
169
|
+
print(f"Ignorado: {path} já existe.")
|
|
170
|
+
else:
|
|
171
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
path.write_text(content)
|
|
173
|
+
print(f"Criado: {path}")
|
|
174
|
+
|
|
175
|
+
if not no_tests:
|
|
176
|
+
test_dir = Path("tests")
|
|
177
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
test_path = test_dir / f"test_{nome_lower}_controller.py"
|
|
179
|
+
if no_overwrite and test_path.exists():
|
|
180
|
+
pass
|
|
181
|
+
else:
|
|
182
|
+
test_path.write_text(f"def test_{nome_lower}_controller():\n assert True\n")
|
|
183
|
+
print(f"Criado: {test_path}")
|
|
184
|
+
|
|
185
|
+
@generate_app.command("model", help="Gera um modelo do banco de dados (Tortoise/Active Record).")
|
|
186
|
+
def generate_model(
|
|
187
|
+
nome: str,
|
|
188
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
189
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
190
|
+
):
|
|
191
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
192
|
+
app_dir = Path("app")
|
|
193
|
+
if not app_dir.exists():
|
|
194
|
+
print("Erro: Deve ser executado na raiz do projeto (onde a pasta 'app' está).")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
print(f"Gerando model para: {nome_capitalized}...")
|
|
198
|
+
|
|
199
|
+
content = f'''from sucuri_framework.models import Model, fields
|
|
200
|
+
|
|
201
|
+
class {nome_capitalized}(Model):
|
|
202
|
+
descricao = fields.CharField(max_length=255)
|
|
203
|
+
|
|
204
|
+
class Meta:
|
|
205
|
+
table = "{nome_lower}s"
|
|
206
|
+
'''
|
|
207
|
+
path = app_dir / "models" / f"{nome_lower}.py"
|
|
208
|
+
if no_overwrite and path.exists():
|
|
209
|
+
print(f"Ignorado: {path} já existe.")
|
|
210
|
+
else:
|
|
211
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
212
|
+
path.write_text(content)
|
|
213
|
+
print(f"Criado: {path}")
|
|
214
|
+
|
|
215
|
+
if not no_tests:
|
|
216
|
+
test_dir = Path("tests")
|
|
217
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
test_path = test_dir / f"test_{nome_lower}_model.py"
|
|
219
|
+
if not (no_overwrite and test_path.exists()):
|
|
220
|
+
test_path.write_text(f"def test_{nome_lower}_model():\n assert True\n")
|
|
221
|
+
print(f"Criado: {test_path}")
|
|
222
|
+
|
|
223
|
+
@generate_app.command("schema", help="Gera um Schema (Pydantic/Sucuri Schema) para validação de dados.")
|
|
224
|
+
def generate_schema(
|
|
225
|
+
nome: str,
|
|
226
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
227
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
228
|
+
):
|
|
229
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
230
|
+
app_dir = Path("app")
|
|
231
|
+
if not app_dir.exists():
|
|
232
|
+
print("Erro: Deve ser executado na raiz do projeto (onde a pasta 'app' está).")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
print(f"Gerando schema para: {nome_capitalized}...")
|
|
236
|
+
|
|
237
|
+
content = f'''from sucuri_framework.schema import Schema
|
|
238
|
+
|
|
239
|
+
class Criar{nome_capitalized}Schema(Schema):
|
|
240
|
+
pass
|
|
241
|
+
'''
|
|
242
|
+
path = app_dir / "schemas" / f"{nome_lower}.py"
|
|
243
|
+
if no_overwrite and path.exists():
|
|
244
|
+
print(f"Ignorado: {path} já existe.")
|
|
245
|
+
else:
|
|
246
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
247
|
+
path.write_text(content)
|
|
248
|
+
print(f"Criado: {path}")
|
|
249
|
+
|
|
250
|
+
if not no_tests:
|
|
251
|
+
test_dir = Path("tests")
|
|
252
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
253
|
+
test_path = test_dir / f"test_{nome_lower}_schema.py"
|
|
254
|
+
if not (no_overwrite and test_path.exists()):
|
|
255
|
+
test_path.write_text(f"def test_{nome_lower}_schema():\n assert True\n")
|
|
256
|
+
print(f"Criado: {test_path}")
|
|
257
|
+
|
|
258
|
+
@generate_app.command("service", help="Gera uma classe ou módulo de serviço para lógica de negócios.")
|
|
259
|
+
def generate_service(
|
|
260
|
+
nome: str,
|
|
261
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
262
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
263
|
+
):
|
|
264
|
+
nome_lower = nome.lower()
|
|
265
|
+
nome_capitalized = nome.capitalize()
|
|
266
|
+
app_dir = Path("app")
|
|
267
|
+
if not app_dir.exists():
|
|
268
|
+
print("Erro: Deve ser executado na raiz do projeto (onde a pasta 'app' está).")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
print(f"Gerando service para: {nome_capitalized}...")
|
|
272
|
+
|
|
273
|
+
content = f'''from sucuri_framework.di import Injectable
|
|
274
|
+
|
|
275
|
+
@Injectable()
|
|
276
|
+
class {nome_capitalized}Service:
|
|
277
|
+
"""Camada de Regras de Negócios para {nome_capitalized}."""
|
|
278
|
+
async def processar_{nome_lower}(self, dados):
|
|
279
|
+
pass
|
|
280
|
+
'''
|
|
281
|
+
path = app_dir / "services" / f"{nome_lower}.py"
|
|
282
|
+
if no_overwrite and path.exists():
|
|
283
|
+
print(f"Ignorado: {path} já existe.")
|
|
284
|
+
else:
|
|
285
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
path.write_text(content)
|
|
287
|
+
print(f"Criado: {path}")
|
|
288
|
+
|
|
289
|
+
if not no_tests:
|
|
290
|
+
test_dir = Path("tests")
|
|
291
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
test_path = test_dir / f"test_{nome_lower}_service.py"
|
|
293
|
+
if not (no_overwrite and test_path.exists()):
|
|
294
|
+
test_path.write_text(f"def test_{nome_lower}_service():\n assert True\n")
|
|
295
|
+
print(f"Criado: {test_path}")
|
|
296
|
+
|
|
297
|
+
@generate_app.command("middleware", help="Gera um middleware (ASGI/FastAPI) para interceptar requisições.")
|
|
298
|
+
def generate_middleware(
|
|
299
|
+
nome: str,
|
|
300
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
301
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
302
|
+
):
|
|
303
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
304
|
+
app_dir = Path("app")
|
|
305
|
+
if not app_dir.exists():
|
|
306
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
print(f"Gerando middleware para: {nome_capitalized}...")
|
|
310
|
+
|
|
311
|
+
content = f'''from sucuri_framework.routing import BaseMiddleware
|
|
312
|
+
|
|
313
|
+
class {nome_capitalized}Middleware(BaseMiddleware):
|
|
314
|
+
async def dispatch(self, request, call_next):
|
|
315
|
+
print(f"Recebida req: {{request.url}}")
|
|
316
|
+
response = await call_next(request)
|
|
317
|
+
return response
|
|
318
|
+
'''
|
|
319
|
+
path = app_dir / "middlewares" / f"{nome_lower}.py"
|
|
320
|
+
if no_overwrite and path.exists():
|
|
321
|
+
print(f"Ignorado: {path} já existe.")
|
|
322
|
+
else:
|
|
323
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
path.write_text(content)
|
|
325
|
+
print(f"Criado: {path}")
|
|
326
|
+
|
|
327
|
+
if not no_tests:
|
|
328
|
+
test_dir = Path("tests")
|
|
329
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
330
|
+
test_path = test_dir / f"test_{nome_lower}_middleware.py"
|
|
331
|
+
if not (no_overwrite and test_path.exists()):
|
|
332
|
+
test_path.write_text(f"def test_{nome_lower}_middleware():\n assert True\n")
|
|
333
|
+
print(f"Criado: {test_path}")
|
|
334
|
+
|
|
335
|
+
@generate_app.command("task", help="Gera um worker/task para ser processado em background.")
|
|
336
|
+
def generate_task(
|
|
337
|
+
nome: str,
|
|
338
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
339
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
340
|
+
):
|
|
341
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
342
|
+
app_dir = Path("app")
|
|
343
|
+
if not app_dir.exists():
|
|
344
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
print(f"Gerando task para: {nome_capitalized}...")
|
|
348
|
+
|
|
349
|
+
content = f'''async def executar_{nome_lower}(payload):
|
|
350
|
+
# Lógica que será executada assincronamente pelo BackgroundWorker
|
|
351
|
+
pass
|
|
352
|
+
'''
|
|
353
|
+
path = app_dir / "tasks" / f"{nome_lower}.py"
|
|
354
|
+
if no_overwrite and path.exists():
|
|
355
|
+
print(f"Ignorado: {path} já existe.")
|
|
356
|
+
else:
|
|
357
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
358
|
+
path.write_text(content)
|
|
359
|
+
print(f"Criado: {path}")
|
|
360
|
+
|
|
361
|
+
if not no_tests:
|
|
362
|
+
test_dir = Path("tests")
|
|
363
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
364
|
+
test_path = test_dir / f"test_{nome_lower}_task.py"
|
|
365
|
+
if not (no_overwrite and test_path.exists()):
|
|
366
|
+
test_path.write_text(f"def test_{nome_lower}_task():\n assert True\n")
|
|
367
|
+
print(f"Criado: {test_path}")
|
|
368
|
+
|
|
369
|
+
@generate_app.command("decorator", help="Gera um decorator customizado.")
|
|
370
|
+
def generate_decorator(
|
|
371
|
+
nome: str,
|
|
372
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
373
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
374
|
+
):
|
|
375
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
376
|
+
app_dir = Path("app")
|
|
377
|
+
if not app_dir.exists():
|
|
378
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
print(f"Gerando decorator para: {nome_capitalized}...")
|
|
382
|
+
|
|
383
|
+
content = f'''def {nome_lower}(*args_decorator):
|
|
384
|
+
def decorator(func):
|
|
385
|
+
async def wrapper(*args, **kwargs):
|
|
386
|
+
# validação de exemplo
|
|
387
|
+
return await func(*args, **kwargs)
|
|
388
|
+
return wrapper
|
|
389
|
+
return decorator
|
|
390
|
+
'''
|
|
391
|
+
path = app_dir / "decorators" / f"{nome_lower}.py"
|
|
392
|
+
if no_overwrite and path.exists():
|
|
393
|
+
print(f"Ignorado: {path} já existe.")
|
|
394
|
+
else:
|
|
395
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
396
|
+
path.write_text(content)
|
|
397
|
+
print(f"Criado: {path}")
|
|
398
|
+
|
|
399
|
+
if not no_tests:
|
|
400
|
+
test_dir = Path("tests")
|
|
401
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
402
|
+
test_path = test_dir / f"test_{nome_lower}_decorator.py"
|
|
403
|
+
if not (no_overwrite and test_path.exists()):
|
|
404
|
+
test_path.write_text(f"def test_{nome_lower}_decorator():\n assert True\n")
|
|
405
|
+
print(f"Criado: {test_path}")
|
|
406
|
+
|
|
407
|
+
@generate_app.command("guard", help="Gera um guard para proteção de rotas (Autorização/Autenticação).")
|
|
408
|
+
def generate_guard(
|
|
409
|
+
nome: str,
|
|
410
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
411
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
412
|
+
):
|
|
413
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
414
|
+
app_dir = Path("app")
|
|
415
|
+
if not app_dir.exists():
|
|
416
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
print(f"Gerando guard para: {nome_capitalized}...")
|
|
420
|
+
|
|
421
|
+
content = f'''from sucuri_framework.guards import BaseGuard
|
|
422
|
+
from fastapi import Request, HTTPException
|
|
423
|
+
|
|
424
|
+
class {nome_capitalized}Guard(BaseGuard):
|
|
425
|
+
async def can_activate(self, request: Request) -> bool:
|
|
426
|
+
# Exemplo: validar headers
|
|
427
|
+
# token = request.headers.get("Authorization")
|
|
428
|
+
# if not token:
|
|
429
|
+
# raise HTTPException(status_code=401, detail="Unauthorized")
|
|
430
|
+
return True
|
|
431
|
+
'''
|
|
432
|
+
path = app_dir / "guards" / f"{nome_lower}.py"
|
|
433
|
+
if no_overwrite and path.exists():
|
|
434
|
+
print(f"Ignorado: {path} já existe.")
|
|
435
|
+
else:
|
|
436
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
437
|
+
path.write_text(content)
|
|
438
|
+
print(f"Criado: {path}")
|
|
439
|
+
|
|
440
|
+
if not no_tests:
|
|
441
|
+
test_dir = Path("tests")
|
|
442
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
443
|
+
test_path = test_dir / f"test_{nome_lower}_guard.py"
|
|
444
|
+
if not (no_overwrite and test_path.exists()):
|
|
445
|
+
test_path.write_text(f"def test_{nome_lower}_guard():\n assert True\n")
|
|
446
|
+
print(f"Criado: {test_path}")
|
|
447
|
+
|
|
448
|
+
@generate_app.command("filter", help="Gera um Exception Filter customizado para interceptar erros globais.")
|
|
449
|
+
def generate_filter(
|
|
450
|
+
nome: str,
|
|
451
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
452
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
453
|
+
):
|
|
454
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
455
|
+
app_dir = Path("app")
|
|
456
|
+
if not app_dir.exists():
|
|
457
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
print(f"Gerando exception filter para: {nome_capitalized}...")
|
|
461
|
+
|
|
462
|
+
content = f'''from sucuri_framework.exceptions import ExceptionFilter, Catch
|
|
463
|
+
from fastapi import Request
|
|
464
|
+
from fastapi.responses import JSONResponse
|
|
465
|
+
|
|
466
|
+
# Altere a classe "Exception" pela exceção real que deseja interceptar
|
|
467
|
+
@Catch(Exception)
|
|
468
|
+
class {nome_capitalized}Filter(ExceptionFilter):
|
|
469
|
+
async def catch(self, exception: Exception, request: Request):
|
|
470
|
+
return JSONResponse(
|
|
471
|
+
status_code=500,
|
|
472
|
+
content={{
|
|
473
|
+
"error": "Erro Interceptado",
|
|
474
|
+
"message": str(exception),
|
|
475
|
+
"path": request.url.path
|
|
476
|
+
}}
|
|
477
|
+
)
|
|
478
|
+
'''
|
|
479
|
+
path = app_dir / "filters" / f"{nome_lower}.py"
|
|
480
|
+
if no_overwrite and path.exists():
|
|
481
|
+
print(f"Ignorado: {path} já existe.")
|
|
482
|
+
else:
|
|
483
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
484
|
+
path.write_text(content)
|
|
485
|
+
print(f"Criado: {path}")
|
|
486
|
+
|
|
487
|
+
if not no_tests:
|
|
488
|
+
test_dir = Path("tests")
|
|
489
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
490
|
+
test_path = test_dir / f"test_{nome_lower}_filter.py"
|
|
491
|
+
if not (no_overwrite and test_path.exists()):
|
|
492
|
+
test_path.write_text(f"def test_{nome_lower}_filter():\n assert True\n")
|
|
493
|
+
print(f"Criado: {test_path}")
|
|
494
|
+
|
|
495
|
+
@generate_app.command("module", help="Gera um Módulo (@Module) isolado e injeta no app_module.py.")
|
|
496
|
+
def generate_module(
|
|
497
|
+
nome: str,
|
|
498
|
+
no_overwrite: bool = typer.Option(False, "--no-overwrite", help="Não sobrescreve arquivos que já existem"),
|
|
499
|
+
no_tests: bool = typer.Option(False, "--no-tests", help="Não gerar arquivos de teste")
|
|
500
|
+
):
|
|
501
|
+
nome_lower, nome_capitalized = get_names(nome)
|
|
502
|
+
app_dir = Path("app")
|
|
503
|
+
if not app_dir.exists():
|
|
504
|
+
print("Erro: Deve ser executado na raiz do projeto.")
|
|
505
|
+
return
|
|
506
|
+
|
|
507
|
+
print(f"Gerando module para: {nome_capitalized}...")
|
|
508
|
+
|
|
509
|
+
path = app_dir / "modules" / f"{nome_lower}.py"
|
|
510
|
+
if no_overwrite and path.exists():
|
|
511
|
+
print(f"Ignorado: {path} já existe.")
|
|
512
|
+
else:
|
|
513
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
514
|
+
# Note: A importação do controller e service pode quebrar se não existirem,
|
|
515
|
+
# mas como é scaffold a intenção é tê-los ou que o dev ajuste.
|
|
516
|
+
content = f'''from sucuri_framework.module import Module
|
|
517
|
+
# Remova/Altere os imports abaixo caso o resource não exista
|
|
518
|
+
try:
|
|
519
|
+
from app.controllers.{nome_lower} import {nome_lower}s_controller
|
|
520
|
+
except ImportError:
|
|
521
|
+
{nome_lower}s_controller = None
|
|
522
|
+
|
|
523
|
+
{nome_lower}_module = Module("{nome_lower}")
|
|
524
|
+
if {nome_lower}s_controller:
|
|
525
|
+
{nome_lower}_module.include_controller({nome_lower}s_controller)
|
|
526
|
+
'''
|
|
527
|
+
path.write_text(content)
|
|
528
|
+
print(f"Criado: {path}")
|
|
529
|
+
|
|
530
|
+
# Injeção via AST
|
|
531
|
+
app_module_path = app_dir / "app_module.py"
|
|
532
|
+
foi_injetado = inject_module_into_app(
|
|
533
|
+
app_module_path,
|
|
534
|
+
import_path=f"app.modules.{nome_lower}",
|
|
535
|
+
module_var=f"{nome_lower}_module"
|
|
536
|
+
)
|
|
537
|
+
if foi_injetado:
|
|
538
|
+
print(f"Injetado: {nome_lower}_module adicionado com sucesso em app_module.py")
|
|
539
|
+
else:
|
|
540
|
+
print(f"Ignorado: {nome_lower}_module já existe em app_module.py ou arquivo não encontrado.")
|
|
541
|
+
|
|
542
|
+
if not no_tests:
|
|
543
|
+
test_dir = Path("tests")
|
|
544
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
545
|
+
test_path = test_dir / f"test_{nome_lower}_module.py"
|
|
546
|
+
if not (no_overwrite and test_path.exists()):
|
|
547
|
+
test_path.write_text(f"def test_{nome_lower}_module():\n assert True\n")
|
|
548
|
+
print(f"Criado: {test_path}")
|