Framework Web API - Sucuri Project
Project description
🐍 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:
- 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). - 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. - 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:
- 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.
- 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). - 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. - 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. - 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
# 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).
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.
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):
sucuri db init
Sempre que alterar um Model: Crie e aplique uma migração.
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):
sucuri dev
Modo de Produção (Otimizado, sem Hot Reloading):
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.
# 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.
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.
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.
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
# 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
# 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.
# 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.
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.
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
# 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.
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.
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.
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!
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sucuri_framework-0.0.8.tar.gz.
File metadata
- Download URL: sucuri_framework-0.0.8.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
352ae38fe8911e9824c067aaeca972af62767f813b934ff9ddfa2919758edf9f
|
|
| MD5 |
c9f2d27f66cc864a068bce291c0b9c9b
|
|
| BLAKE2b-256 |
4e9673c79d7c1990fce4c7f24c857825f7b922b3badc75371b45c4d14cc3a2e2
|
File details
Details for the file sucuri_framework-0.0.8-py3-none-any.whl.
File metadata
- Download URL: sucuri_framework-0.0.8-py3-none-any.whl
- Upload date:
- Size: 29.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a88a9d78f0cfaddccfcd01a8a4aa7de525772129c3a8891db3e94d231ceae1be
|
|
| MD5 |
b1e97a39b29bd82725a61000434d7b16
|
|
| BLAKE2b-256 |
a65bc2c2c94faa7f1726c29dc8cab584f6d35b3b2ea0563ee3a1c23cc513992e
|