Metadata-Version: 2.3
Name: sucuri-framework
Version: 0.0.8
Summary: Framework Web API - Sucuri Project
Author: Antonio Henrique Machado
Author-email: Antonio Henrique Machado <machadoah@proton.me>
Requires-Dist: aerich>=0.8.1
Requires-Dist: fastapi[standard]>=0.137.1
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pydantic-settings>=2.14.1
Requires-Dist: tortoise-orm>=1.1.7
Requires-Dist: typer>=0.26.7
Requires-Dist: taskipy>=1.14.1
Requires-Dist: loguru>=0.7.3
Requires-Dist: tomlkit>=0.13.0
Requires-Python: >=3.14
Description-Content-Type: text/markdown

# 🐍 Sucuri Framework

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".

## Filosofia

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. Trazemos a robustez arquitetural do ecossistema corporativo (inspirado fortemente no **NestJS**) com a velocidade e tipagem do Python (Pydantic).

---

## 🏛️ A Base da Arquitetura

O Sucuri adota as três principais fundações arquiteturais:

1. **Modules (Módulos)**: São os blocos de construção da aplicação. Decorados com `@Module()`, eles organizam o código em domínios específicos (ex: `UsersModule`, `AuthModule`). Toda aplicação tem pelo menos um módulo raiz (`AppModule`).
2. **Controllers (Controladores)**: Responsáveis por receber as requisições HTTP do cliente e retornar as respostas. Usando decorators como `@Get()`, `@Post()`, você define as rotas e como os dados da requisição são tratados.
3. **Providers / Services (Serviços)**: Onde reside a lógica de negócios. Decorados com `@Injectable()`, eles são injetados nos controladores ou em outros serviços através do sistema de Injeção de Dependência (DI) nativo do Sucuri. Isso mantém os controladores limpos e focados apenas no roteamento.

---

## 🌀 O Ciclo de Vida da Requisição (O "Funil" do Sucuri)

Quando uma requisição chega, ela passa por uma série de camadas antes de atingir a lógica de negócios e antes da resposta voltar ao cliente. A ordem de execução reflete o modelo do NestJS, mas abraçando as melhores ferramentas do Python:

1. **Middlewares**: Funções ASGI padrão executadas antes que a requisição chegue ao roteador. Usados para logs, tracing ou modificação bruta do escopo.
2. **Guards (Guardas)**: Decorados nas rotas com `@UseGuards()`, têm o único propósito de determinar se uma requisição será processada ou não. Perfeitos para Autenticação e Autorização (ex: verificar se o usuário é *admin*).
3. **Pipes & Validação (Pydantic)**: Aqui o Sucuri brilha. Em vez de usar bibliotecas pesadas de transformação, dependemos inteiramente da tipagem e do poder do **Pydantic**. Os "Pipes" operam validando e transformando magicamente os parâmetros de entrada (`payload: Schema`) antes de chegarem ao controlador. Se forem inválidos, um erro detalhado (422) é retornado sem tocar na sua regra de negócio.
4. **Execução do Controlador e Serviço**: A requisição limpa e validada chega ao `@Controller()`, que chama o Service injetado para executar a regra de negócio.
5. **Exception Filters (Filtros de Exceção)**: Se em qualquer momento ocorrer uma falha não tratada, ela cai no `ExceptionFilter`. Usado para capturar erros e formatar uma mensagem padronizada em JSON (ex: interceptar erros do banco de dados e devolver um 404 limpo).

## Instalação

```bash
# Instale a partir do repositório/diretório local usando o uv ou pip
uv pip install -e .
```

---

## 🛠️ O Guia Prático (Como usar)

A única forma oficial de gerir seu projeto Sucuri é através da CLI `sucuri`.

### 1. Criar um Novo Projeto
O gerador oficial montará o esqueleto do projeto com as pastas necessárias (controllers, models, schemas).

```bash
sucuri new meu_projeto
cd meu_projeto
```
Isso criará uma pasta `app/` contendo `main.py` e os submódulos, já pré-configurado com a base de dados.

### 2. Gerar Recursos
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.

```bash
sucuri generate resource Usuario
```
Você verá os seguintes arquivos gerados:
- `app/models/usuario.py` (Modelo Active Record)
- `app/schemas/usuario.py` (Validação de Dados)
- `app/services/usuario.py` (Lógica de negócios via DI)
- `app/controllers/usuario.py` (Rotas RESTful)
- `app/modules/usuario.py` (Módulo autônomo aglomerando Controllers e Services)

✨ **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.

**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`.

### 3. Migrações de Banco de Dados
O Sucuri já vem pré-configurado para rastrear alterações no banco de dados.

*Na primeira vez (Setup Inicial do Banco):*
```bash
sucuri db init
```

*Sempre que alterar um Model:* Crie e aplique uma migração.
```bash
sucuri db migrate "Adicionado campo idade no Usuario"
sucuri db upgrade
```

### 4. Rodar o Servidor
O framework utiliza a CLI moderna do FastAPI por debaixo dos panos. Você tem duas opções:

**Modo de Desenvolvimento (Com Hot Reloading):**
```bash
sucuri dev
```

**Modo de Produção (Otimizado, sem Hot Reloading):**
```bash
sucuri run
```

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!

### 5. Scripts Customizados (Taskipy)
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.

```bash
# Executando um script customizado com o nome "format"
sucuri task format
```

---

## 🧩 Referência de Componentes

### Aplicação (Core) e Tratamento de Erros
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.

```python
from sucuri_framework.core import SucuriApp
from app.app_module import AppModule
from app.filters.database_filter import DatabaseNotFoundFilter

app = SucuriApp(title="Minha API", version="1.0.0")

# Registra um Exception Filter global para erros não tratados
app.use_global_filters(DatabaseNotFoundFilter())

# Inicializa os controllers e dependências varrendo a árvore de módulos
app.include_module(AppModule)

# Conecta o banco de dados magicamente (lê o DatabaseConfig de app/db.py)
app.connect_database()
```

### Organização em Módulos (@Module)
Projetos crescem. Para evitar bagunça global, agrupamos responsabilidades e importações em Módulos, criando uma hierarquia limpa a partir do `AppModule`.

```python
from sucuri_framework.module import Module
from app.controllers.produtos import ProdutosController

@Module(
    imports=[],
    controllers=[ProdutosController],
    services=[],
    exports=[]
)
class ProdutosModule:
    pass
```

### Modelagem (Active Record)
Utilizamos o Tortoise ORM nativamente. Operações como `.create()`, `.filter()`, e `.get()` são providas de forma assíncrona.

```python
from sucuri_framework.models import Model, fields

class Produto(Model):
    nome = fields.CharField(max_length=255)
    
    class Meta:
        table = "produtos"
```

### Validação (Schemas)
O framework abstrai as rotas através de `Controllers` com uma interface fluida.
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`.

### 1. Criando os Schemas
```python
# app/schemas/produtos.py
from sucuri_framework.schema import Schema

class ProdutoSchema(Schema):
    nome: str
    preco: float

class ProdutoPublicoSchema(Schema):
    id: int
    nome: str
    preco: float
    
    class Config:
        from_attributes = True # Permite leitura direta de objetos Tortoise ORM
```

### 2. O Roteador e a Ocultação Mágica

```python
# app/controllers/produtos.py
from sucuri_framework.routing import Controller, Get
from app.models.produtos import Produto
from app.schemas.produtos import ProdutoPublicoSchema

@Controller(prefix="/api/produtos")
class ProdutosController:
    @Get("/", response_model=list[ProdutoPublicoSchema])
    async def listar(self):
        # O Pydantic oculta automaticamente qualquer campo extra do banco de dados 
        # (ex: log_auditoria, senha, flags internas) que não estiver no Schema!
        return await Produto.all()
```

### Injeção de Dependências Explícita (DI)
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.

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**.

```python
# app/services/email.py
from sucuri_framework.di import Injectable

@Injectable()
class EmailService:
    async def enviar(self): pass

# app/services/produtos.py
from sucuri_framework.di import Injectable, Inject
from app.services.email import EmailService

@Injectable()
class ProdutoService:
    def __init__(self, email: EmailService = Inject()):
        self.email = email
        
    async def validar_estoque(self, id: int):
        await self.email.enviar()
```

### Injeção de Dependência Mágica (@Inject)
Serviços de negócio (anotados com `@Injectable`) são automaticamente resolvidos e instanciados quando um Controller (ou outro Service) pede por eles. Suporte a ciclo de vida **Singleton** (padrão) e **Transient**.

```python
from sucuri_framework.di import Injectable, Inject
from sucuri_framework.routing import Controller, Get

@Injectable()
class UsuariosService:
    def get_nome(self):
        return "Sucuri"

@Controller(prefix="/usuarios")
class UsuariosController:
    @Get("/")
    def ler_usuario(self, service: UsuariosService = Inject()):
        return {"usuario": service.get_nome()}
```

### Logging Ultra Rápido
Esqueça o `print()` solto pelo código ou as configurações verbosas do módulo padrão de logs. O Sucuri integra nativamente o **Loguru**. Importe-o globalmente e use-o em qualquer lugar. Ele descobre dinamicamente de qual arquivo a chamada partiu.

```python
from sucuri_framework import Controller, Get, Logger

@Controller(prefix="/exemplo")
class LogController:
    @Get("/")
    def testar_log(self):
        Logger.info("Apenas uma informação!")
        Logger.success("Impresso com cores, emojis e o contexto correto do arquivo!")
        return {"msg": "ok"}
```

### 3. Controller Dinâmico

```python
# app/controllers/produtos.py
from sucuri_framework.routing import Controller, Post
from sucuri_framework.di import Inject
from app.services.produtos import ProdutoService

@Controller(prefix="/api/produtos")
class ProdutosController:
    @Post("/")
    async def criar(self, payload: dict, service: ProdutoService = Inject()):
        # O framework usa a dica de tipo (ProdutoService) e injeta a 
        # instância Singleton correspondente dinamicamente pelo FastAPI.
        await service.validar_estoque(payload["id"])
        return {"status": "sucesso"}
```

### Autorização e Proteção de Rotas (Guards)
Guards verificam se a requisição pode acessar a rota antes que o Controller seja executado.

```python
from sucuri_framework.core import Request
from sucuri_framework.guards import BaseGuard, UseGuards
from sucuri_framework.exceptions import HTTPException

class AuthGuard(BaseGuard):
    async def can_activate(self, request: Request) -> bool:
        if not request.headers.get("Authorization"):
            raise HTTPException(status_code=401, detail="Token ausente")
        return True

@Controller(prefix="/api/produtos")
class ProdutosController:
    @Get("/vip")
    @UseGuards(AuthGuard)
    async def rota_protegida(self):
        return {"mensagem": "Protegido"}
```

### Tarefas em Segundo Plano (Background Worker)
O framework injeta gerenciadores de tarefas para você realizar operações lentas sem bloquear o retorno HTTP.

```python
from sucuri_framework.tasks import BackgroundWorker

async def enviar_email(email: str):
    ... # lógica demorada

@Controller(prefix="/api/produtos")
class ProdutosController:
    @Post("/notificar")
    async def notificar(self, email: str, worker: BackgroundWorker):
        worker.add_task(enviar_email, email)
        return {"status": "enfileirado"}
```

### Filtros de Exceção (Exception Filters)
Traduza exceções difíceis ou de terceiros em payloads HTTP controlados.

```python
from sucuri_framework.exceptions import ExceptionFilter, Catch
from tortoise.exceptions import DoesNotExist
from fastapi.responses import JSONResponse

@Catch(DoesNotExist)
class DatabaseNotFoundFilter(ExceptionFilter):
    async def catch(self, exception: Exception, request):
        return JSONResponse(status_code=404, content={"error": "Não encontrado"})
```

### Utilitários de Teste E2E (Test Client)
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!

```python
import pytest
from sucuri_framework.testing import SucuriTestClient, TestModule
from app.main import app
from app.services.produtos import ProdutoService

# Mock
class MockProdutoService:
    async def validar_estoque(self, id: int):
        return True

@pytest.mark.asyncio
async def test_envio():
    # Substitui a dependência no contêiner com TestModule
    app_test = TestModule(app).override_provider(ProdutoService, MockProdutoService()).compile()
    
    # SucuriTestClient inicia o BD e serve a aplicação de forma assíncrona
    async with SucuriTestClient(app_test, db_url="sqlite://:memory:") as client:
        response = await client.post("/api/produtos/", json={"id": 10})
        assert response.status_code == 200
```
