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.
@@ -0,0 +1,3 @@
1
+ from sucuri_framework.core import SucuriApp
2
+
3
+ __all__ = ["SucuriApp"]
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}")