sucuri-framework 0.0.1__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.
Files changed (26) hide show
  1. sucuri_framework-0.0.1/PKG-INFO +297 -0
  2. sucuri_framework-0.0.1/README.md +281 -0
  3. sucuri_framework-0.0.1/pyproject.toml +26 -0
  4. sucuri_framework-0.0.1/src/sucuri_framework/__init__.py +3 -0
  5. sucuri_framework-0.0.1/src/sucuri_framework/cli/__init__.py +0 -0
  6. sucuri_framework-0.0.1/src/sucuri_framework/cli/ast_utils.py +41 -0
  7. sucuri_framework-0.0.1/src/sucuri_framework/cli/cli.py +47 -0
  8. sucuri_framework-0.0.1/src/sucuri_framework/cli/commands_db.py +34 -0
  9. sucuri_framework-0.0.1/src/sucuri_framework/cli/commands_generate.py +548 -0
  10. sucuri_framework-0.0.1/src/sucuri_framework/cli/commands_new.py +102 -0
  11. sucuri_framework-0.0.1/src/sucuri_framework/cli/commands_run.py +21 -0
  12. sucuri_framework-0.0.1/src/sucuri_framework/cli/commands_task.py +13 -0
  13. sucuri_framework-0.0.1/src/sucuri_framework/cli/core.py +0 -0
  14. sucuri_framework-0.0.1/src/sucuri_framework/config.py +16 -0
  15. sucuri_framework-0.0.1/src/sucuri_framework/core.py +127 -0
  16. sucuri_framework-0.0.1/src/sucuri_framework/database.py +21 -0
  17. sucuri_framework-0.0.1/src/sucuri_framework/di.py +113 -0
  18. sucuri_framework-0.0.1/src/sucuri_framework/exceptions.py +50 -0
  19. sucuri_framework-0.0.1/src/sucuri_framework/guards.py +48 -0
  20. sucuri_framework-0.0.1/src/sucuri_framework/models.py +7 -0
  21. sucuri_framework-0.0.1/src/sucuri_framework/module.py +25 -0
  22. sucuri_framework-0.0.1/src/sucuri_framework/py.typed +0 -0
  23. sucuri_framework-0.0.1/src/sucuri_framework/routing.py +42 -0
  24. sucuri_framework-0.0.1/src/sucuri_framework/schema.py +3 -0
  25. sucuri_framework-0.0.1/src/sucuri_framework/tasks.py +3 -0
  26. sucuri_framework-0.0.1/src/sucuri_framework/testing.py +72 -0
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.3
2
+ Name: sucuri-framework
3
+ Version: 0.0.1
4
+ Summary: Framework Web API - Sucuri Project
5
+ Author: Antonio Henrique Machado
6
+ Author-email: Antonio Henrique Machado <machadoah@proton.me>
7
+ Requires-Dist: aerich>=0.8.1
8
+ Requires-Dist: fastapi>=0.137.1
9
+ Requires-Dist: pydantic>=2.13.4
10
+ Requires-Dist: pydantic-settings>=2.14.1
11
+ Requires-Dist: tortoise-orm>=1.1.7
12
+ Requires-Dist: typer>=0.26.7
13
+ Requires-Dist: taskipy>=1.14.1
14
+ Requires-Python: >=3.14
15
+ Description-Content-Type: text/markdown
16
+
17
+ # 🐍 Sucuri Framework
18
+
19
+ O **Sucuri** é um framework Web Python assíncrono e opinativo, criado para simplificar o desenvolvimento de APIs. Ele atua como uma fachada elegante sobre **FastAPI**, **Tortoise ORM** e **Pydantic**, abstraindo a complexidade dessas bibliotecas e focando em uma arquitetura limpa, rápida e direta, com "baterias inclusas".
20
+
21
+ ## Filosofia
22
+
23
+ O desenvolvedor final do Sucuri **não** lida diretamente com os pacotes ASGI originais. O isolamento garante uma experiência padronizada, onde você interage unicamente através dos pacotes `sucuri.*`. O framework dita como as pastas são estruturadas e como a injeção de dependências e roteamento são gerenciados.
24
+
25
+ ## Instalação
26
+
27
+ ```bash
28
+ # Instale a partir do repositório/diretório local usando o uv ou pip
29
+ uv pip install -e .
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 🛠️ O Guia Prático (Como usar)
35
+
36
+ A única forma oficial de gerir seu projeto Sucuri é através da CLI `sucuri`.
37
+
38
+ ### 1. Criar um Novo Projeto
39
+ O gerador oficial montará o esqueleto do projeto com as pastas necessárias (controllers, models, schemas).
40
+
41
+ ```bash
42
+ sucuri new meu_projeto
43
+ cd meu_projeto
44
+ ```
45
+ Isso criará uma pasta `app/` contendo `main.py` e os submódulos, já pré-configurado com a base de dados.
46
+
47
+ ### 2. Gerar Recursos
48
+ Para evitar código repetitivo, use o gerador de recursos (Resource). Ele criará o Model, o Schema, o Service, o Controller e o Módulo correspondente, amarrando tudo automaticamente.
49
+
50
+ ```bash
51
+ sucuri generate resource Usuario
52
+ ```
53
+ Você verá os seguintes arquivos gerados:
54
+ - `app/models/usuario.py` (Modelo Active Record)
55
+ - `app/schemas/usuario.py` (Validação de Dados)
56
+ - `app/services/usuario.py` (Lógica de negócios via DI)
57
+ - `app/controllers/usuario.py` (Rotas RESTful)
58
+ - `app/modules/usuario.py` (Módulo autônomo aglomerando Controllers e Services)
59
+
60
+ ✨ **Magia do Framework**: O arquivo `app/app_module.py` será lido via AST e modificado silenciosamente, injetando e conectando o novo `UsuarioModule` direto na árvore principal de dependências! Sem a necessidade de importações manuais de rotas.
61
+
62
+ **Nota:** Você também pode gerar componentes individuais com: `sucuri generate module`, `sucuri generate controller`, `sucuri generate service`, `sucuri generate guard`, `sucuri generate filter`, `sucuri generate task`, `sucuri generate model` ou `sucuri generate schema`.
63
+
64
+ ### 3. Migrações de Banco de Dados
65
+ O Sucuri já vem pré-configurado para rastrear alterações no banco de dados.
66
+
67
+ *Sempre que alterar um Model:* Crie e aplique uma migração.
68
+ ```bash
69
+ sucuri db migrate "Adicionado campo idade no Usuario"
70
+ sucuri db upgrade
71
+ ```
72
+
73
+ ### 4. Rodar o Servidor
74
+ O framework utiliza a CLI moderna do FastAPI por debaixo dos panos. Você tem duas opções:
75
+
76
+ **Modo de Desenvolvimento (Com Hot Reloading):**
77
+ ```bash
78
+ sucuri dev
79
+ ```
80
+
81
+ **Modo de Produção (Otimizado, sem Hot Reloading):**
82
+ ```bash
83
+ sucuri run
84
+ ```
85
+
86
+ Acesse `http://127.0.0.1:8000/docs` para ver a sua documentação automática interativa Swagger gerada pela nossa abstração de rotas!
87
+
88
+ ### 5. Scripts Customizados (Taskipy)
89
+ Você pode rodar comandos curtos de automação com o executor integrado de scripts! Os scripts devem ser definidos na seção `[tool.taskipy.tasks]` do arquivo `pyproject.toml` gerado pelo framework.
90
+
91
+ ```bash
92
+ # Executando um script customizado com o nome "format"
93
+ sucuri task format
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 🧩 Referência de Componentes
99
+
100
+ ### Aplicação (Core) e Tratamento de Erros
101
+ O `app/main.py` é minimalista. Ele inicializa a Fachada, varre a árvore de módulos e conecta o banco. Opcionalmente, permite interceptadores globais de erros.
102
+
103
+ ```python
104
+ from sucuri_framework.core import SucuriApp
105
+ from app.app_module import AppModule
106
+ from app.filters.database_filter import DatabaseNotFoundFilter
107
+
108
+ app = SucuriApp(title="Minha API", version="1.0.0")
109
+
110
+ # Registra um Exception Filter global para erros não tratados
111
+ app.use_global_filters(DatabaseNotFoundFilter())
112
+
113
+ # Inicializa os controllers e dependências varrendo a árvore de módulos
114
+ app.bootstrap(AppModule)
115
+
116
+ # Conecta o banco de dados magicamente (lê as configurações internas do projeto)
117
+ app.connect_database()
118
+ ```
119
+
120
+ ### Organização em Módulos (@Module)
121
+ Projetos crescem. Para evitar bagunça global, agrupamos responsabilidades e importações em Módulos, criando uma hierarquia limpa a partir do `AppModule`.
122
+
123
+ ```python
124
+ from sucuri_framework.module import Module
125
+ from app.controllers.produtos import produtos_controller
126
+
127
+ produtos_module = Module("produtos")
128
+ produtos_module.include_controller(produtos_controller)
129
+ ```
130
+
131
+ ### Modelagem (Active Record)
132
+ Utilizamos o Tortoise ORM nativamente. Operações como `.create()`, `.filter()`, e `.get()` são providas de forma assíncrona.
133
+
134
+ ```python
135
+ from sucuri_framework.models import Model, fields
136
+
137
+ class Produto(Model):
138
+ nome = fields.CharField(max_length=255)
139
+
140
+ class Meta:
141
+ table = "produtos"
142
+ ```
143
+
144
+ ### Validação (Schemas)
145
+ O framework abstrai as rotas através de `Controllers` com uma interface fluida.
146
+ Os Schemas do Pydantic interagem perfeitamente com o ORM. Para retornar dados do Banco diretamente e esconder campos sensíveis, basta usar o `response_model` no Controller e garantir que o Schema tenha a flag `from_attributes=True`.
147
+
148
+ ### 1. Criando os Schemas
149
+ ```python
150
+ # app/schemas/produtos.py
151
+ from sucuri_framework.schema import Schema
152
+
153
+ class ProdutoSchema(Schema):
154
+ nome: str
155
+ preco: float
156
+
157
+ class ProdutoPublicoSchema(Schema):
158
+ id: int
159
+ nome: str
160
+ preco: float
161
+
162
+ class Config:
163
+ from_attributes = True # Permite leitura direta de objetos Tortoise ORM
164
+ ```
165
+
166
+ ### 2. O Roteador e a Ocultação Mágica
167
+
168
+ ```python
169
+ # app/controllers/produtos.py
170
+ from sucuri_framework.routing import Controller
171
+ from app.models.produtos import Produto
172
+ from app.schemas.produtos import ProdutoPublicoSchema
173
+
174
+ produtos_controller = Controller(prefix="/api/produtos")
175
+
176
+ @produtos_controller.get("/", response_model=list[ProdutoPublicoSchema])
177
+ async def listar():
178
+ # O Pydantic oculta automaticamente qualquer campo extra do banco de dados
179
+ # (ex: log_auditoria, senha, flags internas) que não estiver no Schema!
180
+ return await Produto.all()
181
+ ```
182
+
183
+ ### Injeção de Dependências Explícita (DI)
184
+ Regras de negócios devem residir em classes separadas. Seguindo a premissa de que "explícito é melhor que implícito", o Sucuri utiliza um contêiner nativo super-rápido para injetar dependências nas suas rotas.
185
+
186
+ Basta marcar o seu Service com `@Injectable()`. Por padrão, eles são tratados como **Singletons**. O contêiner do Sucuri suporta **Injeção em Cascata** (serviços injetando outros serviços no construtor) e previne falhas com **Detecção de Dependência Circular**.
187
+
188
+ ```python
189
+ # app/services/email.py
190
+ from sucuri_framework.di import Injectable
191
+
192
+ @Injectable()
193
+ class EmailService:
194
+ async def enviar(self): pass
195
+
196
+ # app/services/produtos.py
197
+ from sucuri_framework.di import Injectable, Inject
198
+ from app.services.email import EmailService
199
+
200
+ @Injectable()
201
+ class ProdutoService:
202
+ def __init__(self, email: EmailService = Inject()):
203
+ self.email = email
204
+
205
+ async def validar_estoque(self, id: int):
206
+ await self.email.enviar()
207
+ ```
208
+
209
+ ### 3. Controller Dinâmico
210
+
211
+ ```python
212
+ # app/controllers/produtos.py
213
+ from sucuri_framework.routing import Controller
214
+ from sucuri_framework.di import Inject
215
+ from app.services.produtos import ProdutoService
216
+
217
+ produtos_controller = Controller(prefix="/api/produtos")
218
+
219
+ @produtos_controller.post("/")
220
+ async def criar(payload: dict, service: ProdutoService = Inject()):
221
+ # O framework usa a dica de tipo (ProdutoService) e injeta a
222
+ # instância Singleton correspondente dinamicamente pelo FastAPI.
223
+ await service.validar_estoque(payload["id"])
224
+ return {"status": "sucesso"}
225
+ ```
226
+
227
+ ### Autorização e Proteção de Rotas (Guards)
228
+ Guards verificam se a requisição pode acessar a rota antes que o Controller seja executado.
229
+
230
+ ```python
231
+ from sucuri_framework.core import Request
232
+ from sucuri_framework.guards import BaseGuard, UseGuards
233
+ from sucuri_framework.exceptions import HTTPException
234
+
235
+ class AuthGuard(BaseGuard):
236
+ async def can_activate(self, request: Request) -> bool:
237
+ if not request.headers.get("Authorization"):
238
+ raise HTTPException(status_code=401, detail="Token ausente")
239
+ return True
240
+
241
+ @produtos_controller.get("/vip")
242
+ @UseGuards(AuthGuard)
243
+ async def rota_protegida():
244
+ return {"mensagem": "Protegido"}
245
+ ```
246
+
247
+ ### Tarefas em Segundo Plano (Background Worker)
248
+ O framework injeta gerenciadores de tarefas para você realizar operações lentas sem bloquear o retorno HTTP.
249
+
250
+ ```python
251
+ from sucuri_framework.tasks import BackgroundWorker
252
+
253
+ async def enviar_email(email: str):
254
+ ... # lógica demorada
255
+
256
+ @produtos_controller.post("/notificar")
257
+ async def notificar(email: str, worker: BackgroundWorker):
258
+ worker.add_task(enviar_email, email)
259
+ return {"status": "enfileirado"}
260
+ ```
261
+
262
+ ### Filtros de Exceção (Exception Filters)
263
+ Traduza exceções difíceis ou de terceiros em payloads HTTP controlados.
264
+
265
+ ```python
266
+ from sucuri_framework.exceptions import ExceptionFilter, Catch
267
+ from tortoise.exceptions import DoesNotExist
268
+ from fastapi.responses import JSONResponse
269
+
270
+ @Catch(DoesNotExist)
271
+ class DatabaseNotFoundFilter(ExceptionFilter):
272
+ async def catch(self, exception: Exception, request):
273
+ return JSONResponse(status_code=404, content={"error": "Não encontrado"})
274
+ ```
275
+
276
+ ### Utilitários de Teste E2E (Test Client)
277
+ O framework oferece o `SucuriTestClient` para que você não precise gerenciar o Event Loop ou as conexões do banco de dados na mão. Ele faz o setup do Tortoise (em memória) e já lida com chamadas HTTP 100% assíncronas internamente!
278
+
279
+ ```python
280
+ import pytest
281
+ from sucuri_framework.testing import SucuriTestClient
282
+ from app.main import app
283
+ from app.services.produtos import ProdutoService
284
+
285
+ @pytest.mark.asyncio
286
+ async def test_envio(monkeypatch):
287
+ # Sobrescreve o método da classe nativamente (Mock)
288
+ async def mock_validar(*args):
289
+ return True
290
+
291
+ monkeypatch.setattr(ProdutoService, "validar_estoque", mock_validar)
292
+
293
+ # SucuriTestClient inicia o BD e serve a aplicação de forma assíncrona
294
+ async with SucuriTestClient(app, db_url="sqlite://:memory:") as client:
295
+ response = await client.post("/api/produtos/", json={"id": 10})
296
+ assert response.status_code == 200
297
+ ```
@@ -0,0 +1,281 @@
1
+ # 🐍 Sucuri Framework
2
+
3
+ O **Sucuri** é um framework Web Python assíncrono e opinativo, criado para simplificar o desenvolvimento de APIs. Ele atua como uma fachada elegante sobre **FastAPI**, **Tortoise ORM** e **Pydantic**, abstraindo a complexidade dessas bibliotecas e focando em uma arquitetura limpa, rápida e direta, com "baterias inclusas".
4
+
5
+ ## Filosofia
6
+
7
+ O desenvolvedor final do Sucuri **não** lida diretamente com os pacotes ASGI originais. O isolamento garante uma experiência padronizada, onde você interage unicamente através dos pacotes `sucuri.*`. O framework dita como as pastas são estruturadas e como a injeção de dependências e roteamento são gerenciados.
8
+
9
+ ## Instalação
10
+
11
+ ```bash
12
+ # Instale a partir do repositório/diretório local usando o uv ou pip
13
+ uv pip install -e .
14
+ ```
15
+
16
+ ---
17
+
18
+ ## 🛠️ O Guia Prático (Como usar)
19
+
20
+ A única forma oficial de gerir seu projeto Sucuri é através da CLI `sucuri`.
21
+
22
+ ### 1. Criar um Novo Projeto
23
+ O gerador oficial montará o esqueleto do projeto com as pastas necessárias (controllers, models, schemas).
24
+
25
+ ```bash
26
+ sucuri new meu_projeto
27
+ cd meu_projeto
28
+ ```
29
+ Isso criará uma pasta `app/` contendo `main.py` e os submódulos, já pré-configurado com a base de dados.
30
+
31
+ ### 2. Gerar Recursos
32
+ Para evitar código repetitivo, use o gerador de recursos (Resource). Ele criará o Model, o Schema, o Service, o Controller e o Módulo correspondente, amarrando tudo automaticamente.
33
+
34
+ ```bash
35
+ sucuri generate resource Usuario
36
+ ```
37
+ Você verá os seguintes arquivos gerados:
38
+ - `app/models/usuario.py` (Modelo Active Record)
39
+ - `app/schemas/usuario.py` (Validação de Dados)
40
+ - `app/services/usuario.py` (Lógica de negócios via DI)
41
+ - `app/controllers/usuario.py` (Rotas RESTful)
42
+ - `app/modules/usuario.py` (Módulo autônomo aglomerando Controllers e Services)
43
+
44
+ ✨ **Magia do Framework**: O arquivo `app/app_module.py` será lido via AST e modificado silenciosamente, injetando e conectando o novo `UsuarioModule` direto na árvore principal de dependências! Sem a necessidade de importações manuais de rotas.
45
+
46
+ **Nota:** Você também pode gerar componentes individuais com: `sucuri generate module`, `sucuri generate controller`, `sucuri generate service`, `sucuri generate guard`, `sucuri generate filter`, `sucuri generate task`, `sucuri generate model` ou `sucuri generate schema`.
47
+
48
+ ### 3. Migrações de Banco de Dados
49
+ O Sucuri já vem pré-configurado para rastrear alterações no banco de dados.
50
+
51
+ *Sempre que alterar um Model:* Crie e aplique uma migração.
52
+ ```bash
53
+ sucuri db migrate "Adicionado campo idade no Usuario"
54
+ sucuri db upgrade
55
+ ```
56
+
57
+ ### 4. Rodar o Servidor
58
+ O framework utiliza a CLI moderna do FastAPI por debaixo dos panos. Você tem duas opções:
59
+
60
+ **Modo de Desenvolvimento (Com Hot Reloading):**
61
+ ```bash
62
+ sucuri dev
63
+ ```
64
+
65
+ **Modo de Produção (Otimizado, sem Hot Reloading):**
66
+ ```bash
67
+ sucuri run
68
+ ```
69
+
70
+ Acesse `http://127.0.0.1:8000/docs` para ver a sua documentação automática interativa Swagger gerada pela nossa abstração de rotas!
71
+
72
+ ### 5. Scripts Customizados (Taskipy)
73
+ Você pode rodar comandos curtos de automação com o executor integrado de scripts! Os scripts devem ser definidos na seção `[tool.taskipy.tasks]` do arquivo `pyproject.toml` gerado pelo framework.
74
+
75
+ ```bash
76
+ # Executando um script customizado com o nome "format"
77
+ sucuri task format
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 🧩 Referência de Componentes
83
+
84
+ ### Aplicação (Core) e Tratamento de Erros
85
+ O `app/main.py` é minimalista. Ele inicializa a Fachada, varre a árvore de módulos e conecta o banco. Opcionalmente, permite interceptadores globais de erros.
86
+
87
+ ```python
88
+ from sucuri_framework.core import SucuriApp
89
+ from app.app_module import AppModule
90
+ from app.filters.database_filter import DatabaseNotFoundFilter
91
+
92
+ app = SucuriApp(title="Minha API", version="1.0.0")
93
+
94
+ # Registra um Exception Filter global para erros não tratados
95
+ app.use_global_filters(DatabaseNotFoundFilter())
96
+
97
+ # Inicializa os controllers e dependências varrendo a árvore de módulos
98
+ app.bootstrap(AppModule)
99
+
100
+ # Conecta o banco de dados magicamente (lê as configurações internas do projeto)
101
+ app.connect_database()
102
+ ```
103
+
104
+ ### Organização em Módulos (@Module)
105
+ Projetos crescem. Para evitar bagunça global, agrupamos responsabilidades e importações em Módulos, criando uma hierarquia limpa a partir do `AppModule`.
106
+
107
+ ```python
108
+ from sucuri_framework.module import Module
109
+ from app.controllers.produtos import produtos_controller
110
+
111
+ produtos_module = Module("produtos")
112
+ produtos_module.include_controller(produtos_controller)
113
+ ```
114
+
115
+ ### Modelagem (Active Record)
116
+ Utilizamos o Tortoise ORM nativamente. Operações como `.create()`, `.filter()`, e `.get()` são providas de forma assíncrona.
117
+
118
+ ```python
119
+ from sucuri_framework.models import Model, fields
120
+
121
+ class Produto(Model):
122
+ nome = fields.CharField(max_length=255)
123
+
124
+ class Meta:
125
+ table = "produtos"
126
+ ```
127
+
128
+ ### Validação (Schemas)
129
+ O framework abstrai as rotas através de `Controllers` com uma interface fluida.
130
+ Os Schemas do Pydantic interagem perfeitamente com o ORM. Para retornar dados do Banco diretamente e esconder campos sensíveis, basta usar o `response_model` no Controller e garantir que o Schema tenha a flag `from_attributes=True`.
131
+
132
+ ### 1. Criando os Schemas
133
+ ```python
134
+ # app/schemas/produtos.py
135
+ from sucuri_framework.schema import Schema
136
+
137
+ class ProdutoSchema(Schema):
138
+ nome: str
139
+ preco: float
140
+
141
+ class ProdutoPublicoSchema(Schema):
142
+ id: int
143
+ nome: str
144
+ preco: float
145
+
146
+ class Config:
147
+ from_attributes = True # Permite leitura direta de objetos Tortoise ORM
148
+ ```
149
+
150
+ ### 2. O Roteador e a Ocultação Mágica
151
+
152
+ ```python
153
+ # app/controllers/produtos.py
154
+ from sucuri_framework.routing import Controller
155
+ from app.models.produtos import Produto
156
+ from app.schemas.produtos import ProdutoPublicoSchema
157
+
158
+ produtos_controller = Controller(prefix="/api/produtos")
159
+
160
+ @produtos_controller.get("/", response_model=list[ProdutoPublicoSchema])
161
+ async def listar():
162
+ # O Pydantic oculta automaticamente qualquer campo extra do banco de dados
163
+ # (ex: log_auditoria, senha, flags internas) que não estiver no Schema!
164
+ return await Produto.all()
165
+ ```
166
+
167
+ ### Injeção de Dependências Explícita (DI)
168
+ Regras de negócios devem residir em classes separadas. Seguindo a premissa de que "explícito é melhor que implícito", o Sucuri utiliza um contêiner nativo super-rápido para injetar dependências nas suas rotas.
169
+
170
+ Basta marcar o seu Service com `@Injectable()`. Por padrão, eles são tratados como **Singletons**. O contêiner do Sucuri suporta **Injeção em Cascata** (serviços injetando outros serviços no construtor) e previne falhas com **Detecção de Dependência Circular**.
171
+
172
+ ```python
173
+ # app/services/email.py
174
+ from sucuri_framework.di import Injectable
175
+
176
+ @Injectable()
177
+ class EmailService:
178
+ async def enviar(self): pass
179
+
180
+ # app/services/produtos.py
181
+ from sucuri_framework.di import Injectable, Inject
182
+ from app.services.email import EmailService
183
+
184
+ @Injectable()
185
+ class ProdutoService:
186
+ def __init__(self, email: EmailService = Inject()):
187
+ self.email = email
188
+
189
+ async def validar_estoque(self, id: int):
190
+ await self.email.enviar()
191
+ ```
192
+
193
+ ### 3. Controller Dinâmico
194
+
195
+ ```python
196
+ # app/controllers/produtos.py
197
+ from sucuri_framework.routing import Controller
198
+ from sucuri_framework.di import Inject
199
+ from app.services.produtos import ProdutoService
200
+
201
+ produtos_controller = Controller(prefix="/api/produtos")
202
+
203
+ @produtos_controller.post("/")
204
+ async def criar(payload: dict, service: ProdutoService = Inject()):
205
+ # O framework usa a dica de tipo (ProdutoService) e injeta a
206
+ # instância Singleton correspondente dinamicamente pelo FastAPI.
207
+ await service.validar_estoque(payload["id"])
208
+ return {"status": "sucesso"}
209
+ ```
210
+
211
+ ### Autorização e Proteção de Rotas (Guards)
212
+ Guards verificam se a requisição pode acessar a rota antes que o Controller seja executado.
213
+
214
+ ```python
215
+ from sucuri_framework.core import Request
216
+ from sucuri_framework.guards import BaseGuard, UseGuards
217
+ from sucuri_framework.exceptions import HTTPException
218
+
219
+ class AuthGuard(BaseGuard):
220
+ async def can_activate(self, request: Request) -> bool:
221
+ if not request.headers.get("Authorization"):
222
+ raise HTTPException(status_code=401, detail="Token ausente")
223
+ return True
224
+
225
+ @produtos_controller.get("/vip")
226
+ @UseGuards(AuthGuard)
227
+ async def rota_protegida():
228
+ return {"mensagem": "Protegido"}
229
+ ```
230
+
231
+ ### Tarefas em Segundo Plano (Background Worker)
232
+ O framework injeta gerenciadores de tarefas para você realizar operações lentas sem bloquear o retorno HTTP.
233
+
234
+ ```python
235
+ from sucuri_framework.tasks import BackgroundWorker
236
+
237
+ async def enviar_email(email: str):
238
+ ... # lógica demorada
239
+
240
+ @produtos_controller.post("/notificar")
241
+ async def notificar(email: str, worker: BackgroundWorker):
242
+ worker.add_task(enviar_email, email)
243
+ return {"status": "enfileirado"}
244
+ ```
245
+
246
+ ### Filtros de Exceção (Exception Filters)
247
+ Traduza exceções difíceis ou de terceiros em payloads HTTP controlados.
248
+
249
+ ```python
250
+ from sucuri_framework.exceptions import ExceptionFilter, Catch
251
+ from tortoise.exceptions import DoesNotExist
252
+ from fastapi.responses import JSONResponse
253
+
254
+ @Catch(DoesNotExist)
255
+ class DatabaseNotFoundFilter(ExceptionFilter):
256
+ async def catch(self, exception: Exception, request):
257
+ return JSONResponse(status_code=404, content={"error": "Não encontrado"})
258
+ ```
259
+
260
+ ### Utilitários de Teste E2E (Test Client)
261
+ O framework oferece o `SucuriTestClient` para que você não precise gerenciar o Event Loop ou as conexões do banco de dados na mão. Ele faz o setup do Tortoise (em memória) e já lida com chamadas HTTP 100% assíncronas internamente!
262
+
263
+ ```python
264
+ import pytest
265
+ from sucuri_framework.testing import SucuriTestClient
266
+ from app.main import app
267
+ from app.services.produtos import ProdutoService
268
+
269
+ @pytest.mark.asyncio
270
+ async def test_envio(monkeypatch):
271
+ # Sobrescreve o método da classe nativamente (Mock)
272
+ async def mock_validar(*args):
273
+ return True
274
+
275
+ monkeypatch.setattr(ProdutoService, "validar_estoque", mock_validar)
276
+
277
+ # SucuriTestClient inicia o BD e serve a aplicação de forma assíncrona
278
+ async with SucuriTestClient(app, db_url="sqlite://:memory:") as client:
279
+ response = await client.post("/api/produtos/", json={"id": 10})
280
+ assert response.status_code == 200
281
+ ```
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "sucuri-framework"
3
+ version = "0.0.1"
4
+ description = "Framework Web API - Sucuri Project"
5
+ readme = "README.md"
6
+ authors = [{ name = "Antonio Henrique Machado", email = "machadoah@proton.me" }]
7
+ requires-python = ">=3.14"
8
+ dependencies = [
9
+ "aerich>=0.8.1",
10
+ "fastapi>=0.137.1",
11
+ "pydantic>=2.13.4",
12
+ "pydantic-settings>=2.14.1",
13
+ "tortoise-orm>=1.1.7",
14
+ "typer>=0.26.7",
15
+ "taskipy>=1.14.1",
16
+ ]
17
+
18
+ [project.scripts]
19
+ sucuri = "sucuri.cli.cli:app"
20
+
21
+ [build-system]
22
+ requires = ["uv_build>=0.11.13,<0.12.0"]
23
+ build-backend = "uv_build"
24
+
25
+ [dependency-groups]
26
+ dev = ["httpx>=0.28.1", "pytest>=9.1.0", "pytest-asyncio>=1.4.0"]
@@ -0,0 +1,3 @@
1
+ from sucuri_framework.core import SucuriApp
2
+
3
+ __all__ = ["SucuriApp"]
@@ -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()