Metadata-Version: 2.3
Name: sucuri-framework
Version: 0.0.5
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-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.

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

*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.bootstrap(AppModule)

# Conecta o banco de dados magicamente (lê as configurações internas do projeto)
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 produtos_controller

produtos_module = Module("produtos")
produtos_module.include_controller(produtos_controller)
```

### 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
from app.models.produtos import Produto
from app.schemas.produtos import ProdutoPublicoSchema

produtos_controller = Controller(prefix="/api/produtos")

@produtos_controller.get("/", response_model=list[ProdutoPublicoSchema])
async def listar():
    # 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()
```

### 3. Controller Dinâmico

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

produtos_controller = Controller(prefix="/api/produtos")

@produtos_controller.post("/")
async def criar(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

@produtos_controller.get("/vip")
@UseGuards(AuthGuard)
async def rota_protegida():
    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

@produtos_controller.post("/notificar")
async def notificar(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
from app.main import app
from app.services.produtos import ProdutoService

@pytest.mark.asyncio
async def test_envio(monkeypatch):
    # Sobrescreve o método da classe nativamente (Mock)
    async def mock_validar(*args):
        return True
        
    monkeypatch.setattr(ProdutoService, "validar_estoque", mock_validar)
    
    # SucuriTestClient inicia o BD e serve a aplicação de forma assíncrona
    async with SucuriTestClient(app, db_url="sqlite://:memory:") as client:
        response = await client.post("/api/produtos/", json={"id": 10})
        assert response.status_code == 200
```
