Brava ORM para MySQL/MariaDB
SDK Python para aumentar produtividade no desenvolvimento de aplicações com integração a banco de dados relacional MySQL/MariaDB.
Instalação
Via Pip:
pip install bravaorm
Via Git/Clone:
git clone https://github.com/robertons/bravaorm
cd bravaorm
pip install -r requirements.txt
python setup.py install
Dependência principal: mysql-connector-python
Conexão com Banco de Dados
import bravaorm
conn = bravaorm.Connection(
db_user="root",
db_password="pass",
db_host="host",
db_port=3306,
db_database="dbname",
db_charset="utf8mb4"
)
Parâmetros da Conexão
| parâmetro | default | tipo | obrigatório | |
|---|---|---|---|---|
db_user |
None | string | sim | Nome do usuário |
db_password |
None | string | sim | Senha |
db_host |
None | string | sim | Host |
db_port |
None | int | sim | Porta |
db_database |
None | string | sim | Nome do banco |
db_ssl |
False | boolean | não | Habilitar SSL |
db_ssl_ca |
None | string | não | Certificado CA |
db_ssl_cert |
None | string | não | Certificado |
db_ssl_key |
None | string | não | Chave do certificado |
db_charset |
utf8 |
string | não | Charset do banco |
log_level |
error |
string | não | Nível de log |
pool |
None | MySQLConnectionPool | não | Pool de conexões |
uncountable_words |
None | list | não | Palavras não contáveis |
irregular_words |
None | dict | não | Palavras irregulares |
Conexão via Context Manager
A conexão suporta o protocolo de gerenciador de contexto (with). Em caso de exceção, o rollback é executado automaticamente e a conexão é fechada.
with bravaorm.Connection(db_user="root", db_password="pass", db_host="host", db_port=3306, db_database="dbname") as conn:
produto = Produto(prod_nome="Exemplo", prod_preco=99.90)
conn.add(produto)
conn.save()
# conn.close() é chamado automaticamente ao sair do bloco
Pool de Conexões
O BravaORM suporta pool de conexões MySQL via MySQLConnectionPool. Configure o pool uma única vez no startup da aplicação e passe o pool para cada instância de Connection.
import bravaorm
# No startup da aplicação (executado uma vez)
bravaorm.configure_pool(
pool_name="main",
pool_size=10,
user="root",
password="pass",
host="host",
port=3306,
database="dbname"
)
# Em cada requisição/thread
pool = bravaorm.get_pool("main")
with bravaorm.Connection(pool=pool) as conn:
produtos = conn.produtos.all
# Para liberar o pool (ex: shutdown da aplicação)
bravaorm.release_pool("main")
Funções do Pool
| função | descrição |
|---|---|
configure_pool(pool_name, pool_size, **config) |
Configura o pool globalmente. Idempotente. |
get_pool(pool_name) |
Retorna o pool configurado. Lança RuntimeError se não encontrado. |
release_pool(pool_name) |
Remove a referência ao pool. |
Nota para RDS Proxy (AWS): O RDS Proxy pina a conexão de backend enquanto houver uma transação aberta. Sempre termine operações com
save()(commit) ourollback()seguido declose()o mais rápido possível.
Gerando Modelo de Entidade
O Make() conecta ao banco de dados, lê o information_schema e gera automaticamente as classes Python a partir das tabelas, colunas e chaves estrangeiras.
import os
import bravaorm
bravaorm.Make(
dir=os.path.dirname(os.path.abspath(__file__)),
db_user="user",
db_password="pass",
db_host="host",
db_port=3306,
db_database="dbname"
)
Parâmetros do Make()
| parâmetro | default | tipo | obrigatório | |
|---|---|---|---|---|
dir |
— | string | sim | Diretório raiz do projeto |
db_user |
— | string | sim | Nome do usuário |
db_password |
— | string | sim | Senha |
db_host |
— | string | sim | Host |
db_port |
— | int | sim | Porta |
db_database |
— | string | sim | Nome do banco |
db_ssl |
False | bool | não | Habilitar SSL |
date_format |
"%d/%m/%Y %H:%M:%S" |
string | não | Formato de datas geradas nos modelos |
field_types |
None | dict | não | Mapeamento manual {campo: "Tipo(args)"} por nome |
uncountable_words |
None | list | não | Palavras não contáveis para o inflector |
irregular_words |
None | dict | não | Palavras irregulares para o inflector |
Estrutura Gerada
O script gera dois diretórios: model/lib/ (gerado automaticamente, não editar) e model/ (camada de customização, editável):
.
├── model/
│ ├── __init__.py
│ ├── produto.py # Stub editável — herda de model/lib/produto.py
│ ├── categoria.py
│ └── lib/
│ ├── __init__.py
│ ├── produto.py # Gerado pelo Make() — regenerado a cada execução
│ └── categoria.py
└── ...
Convenção de nomes: As tabelas devem usar o nome no plural (ex:
produtos) e o ORM deriva o nome da classe no singular (Produto) via inflector.
Exemplo de Classe Gerada
# model/lib/produto.py (gerado automaticamente pelo Make)
# -*- coding: utf-8 -*-
from bravaorm.entity import *
class Produto(Entity):
def __init__(cls, **kw):
cls.__metadata__ = {'pk': ['id']}
# FIELDS
cls.id = Int(pk=True, auto_increment=True, not_null=True, precision=10, scale=0)
cls.id_categoria = Int(fk=True, not_null=True, precision=10, scale=0)
cls.prod_nome = String(max=155)
cls.prod_preco = Decimal(not_null=True, precision=19, scale=2)
cls.prod_ativo = Boolean()
cls.prod_fabricado = DateTime(format='%d/%m/%Y')
cls.prod_alterado = DateTime(format='%d/%m/%Y %H:%M:%S')
# One-to-One
cls.categorias = Obj(
context=cls, keyname='categorias',
name='Categoria', key='id',
reference='id_categoria', table='categorias'
)
# One-to-many
cls.compras = ObjList(
context=cls, keyname='compras',
name='Compra', key='id_produto',
reference='id', table='compras'
)
# Many-to-many
cls.tags = ObjListOfMany(
context=cls, keyname='tags',
name='Tag', reference='id',
intermediate='produto_tags',
ref_key='id_produto', rel_key='id_tag',
table='tags', key='id'
)
super().__init__(**kw)
# model/produto.py (stub editável — customizações aqui)
# -*- coding: utf-8 -*-
from bravaorm.entity.datatype import *
from .lib import Produto
class Produto(Produto):
def __init__(cls, **kw):
return super(Produto, cls).__init__(**kw)
Tipos de Dados
Os campos de uma entidade são declarados com tipos que espelham os tipos do banco de dados e realizam validação automática na atribuição.
| Tipo | Python equivalente | Descrição |
|---|---|---|
String |
str |
Texto. Aceita max para limite de caracteres |
Int |
int |
Inteiro. Converte automaticamente se possível |
Decimal |
decimal.Decimal |
Decimal de precisão fixa |
Float |
float |
Ponto flutuante |
Boolean |
bool |
Verdadeiro/Falso |
DateTime |
datetime.datetime |
Data e hora. Aceita string no formato definido |
Dict |
dict / list |
Desserializa JSON (str → dict/list) na leitura, inclusive na hidratação de query |
Json |
str |
Armazena e retorna como string JSON crua (LONGTEXT no banco); não desserializa |
Obj |
Entity | Relacionamento 1:1 (FK nesta tabela) |
ObjList |
ListType | Relacionamento 1:N (FK na tabela filha) |
ObjListOfMany |
ListType | Relacionamento N:M (tabela intermediária) |
Hidratação de
Dict×Json(desde 0.0.38): camposDict()são desserializados na leitura — inclusive na hidratação de query (.first/.all) —, retornandodict/list. O fast-path de hidratação de alta performance faz essa conversão apenas paraDict(os demais tipos passam direto, sem custo). CamposJson()permanecem como string JSON crua por design (use-os quando quiser o JSON sem parse).
Referência da API
Saída / Resultados
| método / propriedade | aplicável | resultado |
|---|---|---|
.first |
Connection | Primeiro objeto do SELECT (ou None) |
.all |
Connection | Lista de objetos do SELECT |
.fetch |
Connection | Lista de dict sem conversão para objetos |
.count |
Connection | Inteiro com o total de registros |
.toJSON() |
Entity, ListType | Objeto ou lista convertidos para dict |
Operações de Escrita
| método | aplicável | descrição |
|---|---|---|
.add(obj) |
Connection | Adiciona objeto à fila de inserção/atualização |
.save() |
Connection | Persiste toda a fila no banco (INSERT/UPDATE + commit) |
.delete(obj) |
Connection | Remove objeto pelo PK (requer .save()) |
.delete() |
Connection | DELETE com condição WHERE (sem .save()) |
.set(campo, val) |
Connection | UPDATE de um único campo com WHERE |
.update(**kw) |
Connection | UPDATE de múltiplos campos com WHERE |
.rollback() |
Connection | Desfaz transação e limpa a fila |
Construtores do Query Builder
| método | descrição |
|---|---|
.where(*cláusula) |
Condição AND principal |
.orwhere(*cláusula) |
Bloco OR adicional (requer .where() prévio) |
.select(*campos) |
Campos específicos a selecionar (aceita tabela.*) |
.distinct(*campos) |
Adiciona DISTINCT(campos) ao SELECT |
.alias(expr, nome) |
Cria alias para campo ou expressão |
.orderby(*campos) |
Ordenação (campo ASC/DESC) |
.groupby(*campos) |
Agrupamento |
.having(*cláusula) |
Condição HAVING após GROUP BY |
.orhaving(*cláusula) |
Bloco OR adicional para HAVING |
.limit(inicio, fim) |
Limite de registros |
.join(*tabelas) |
LEFT JOIN por relacionamento definido na entidade |
.inner(*tabelas) |
INNER JOIN por relacionamento definido na entidade |
.include(*tabelas) |
Eager loading 1:N e N:M (bulk IN, sem N+1) |
.on(sel,tipo,tab,cond) |
JOIN personalizado sem relacionamento pré-definido |
.execute(sql, args, class_name) |
Query SQL direta |
Seleção de Objetos
produto = conn.produtos.where("id = 10").first
print(produto.toJSON())
# {'id': 10, 'id_categoria': 3, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), ...}
Condição OR
orwhere depende de um where anterior e cria um bloco OR a cada chamada.
produtos = conn.produtos.where("id = 10").orwhere("id = 12").orwhere("id = 14").all
SQL gerado: WHERE (id = 10) OR (id = 12) OR (id = 14)
Seleção de Campos Específicos
produto = conn.produtos.where("id = 10").select("id, prod_nome").first
# {'id': 10, 'prod_nome': 'Exemplo'}
Wildcard por tabela:
# Seleciona todos os campos de produtos + apenas id e nome de categorias
produto = conn.produtos.join("categorias").select("produtos.*, categorias.id, categorias.cat_nome").first
Alias
produto = conn.produtos.alias("prod_nome", "nome").where("id = 10").first
print(produto["nome"]) # Exemplo
print(produto.prod_nome) # Exemplo
Aliases são campos somente leitura.
Distinct
categorias = conn.produtos.distinct("id_categoria").all
Ordenamento
produtos = conn.produtos.orderby("prod_nome ASC").all
produtos = conn.produtos.orderby("prod_preco DESC").all
Agrupamento e Having
# Agrupamento simples
resultado = conn.produtos.groupby("id_categoria").all
# Com HAVING
resultado = conn.produtos.groupby("id_categoria").having("COUNT(*) > 5").all
Limite
produtos = conn.produtos.orderby("prod_nome").limit(0, 10).all
Relacionamentos
join — LEFT JOIN (1:1 / N:1)
Equivalente ao LEFT JOIN. Recomendado quando o relacionamento retorna um único resultado por linha principal.
produtos = conn.produtos.join("categorias").all
# {'id': 10, ..., 'categorias': {'id': 3, 'cat_nome': 'Categoria Teste'}}
inner — INNER JOIN (1:1 / N:1)
Retorna apenas os registros que possuem correspondência na tabela relacionada.
produtos = conn.produtos.inner("categorias").where("categorias.id = 1").all
include — Eager Loading (1:N e N:M)
Busca os objetos relacionados com uma query IN única por relacionamento, sem problema N+1. Recomendado para coleções.
produto = conn.produtos.include("compras").where("id = 10, compras.compra_paga = 1").first
# {'id': 10, ..., 'compras': [{'id': 1, ...}, {'id': 23, ...}]}
Multiple includes:
produtos = conn.produtos.include("compras, tags").all
on — JOIN Personalizado
Permite JOINs com tabelas sem relacionamento definido na entidade. Aceita qualquer tipo (LEFT, RIGHT, INNER).
Parâmetros: select (campos a selecionar), jointype (left/right/inner), table (nome da tabela), condition (condição ON).
produto = conn.produtos.on(
"cupons.cod_cupom, cupons.cup_preco_max",
"left",
"cupons",
"cupons.cup_preco_max >= produtos.prod_preco"
).where("NOT cupons.id IS NULL").all
Fetch (Resultados sem Objetos)
Retorna diretamente a lista de dict do banco, sem instanciar objetos. Indicado para leituras de alta performance onde o dado será serializado imediatamente.
produtos = conn.produtos.where("prod_ativo = 1").fetch
# [{'id': 10, 'prod_nome': 'Exemplo', ...}, ...]
Count
total = conn.produtos.where("prod_ativo = 1").count
# 42
Criação de Objetos
Simples
from model import Produto
produto = Produto()
produto.prod_nome = "Exemplo"
produto.prod_preco = 99.90
conn.add(produto)
conn.save()
Via construtor:
produto = Produto(prod_nome="Exemplo", prod_preco=99.90)
conn.add(produto)
conn.save()
Com Relacionamentos (1:N)
from model import Produto, ProdutoFoto
produto = Produto(prod_nome="Exemplo", prod_preco=99.90)
produto.produto_fotos.add(ProdutoFoto(foto_descricao="Vista Frontal", foto_arquivo="frontal.jpg"))
produto.produto_fotos.add(ProdutoFoto(foto_descricao="Vista Lateral", foto_arquivo="lateral.jpg"))
conn.add(produto)
conn.save()
O ORM persiste primeiro o objeto pai, captura o
lastrowide propaga automaticamente a FK para os filhos antes de salvá-los.
Atualização de Objetos
Auto-enfileiramento ao modificar: alterar um campo de uma entidade persistível (com PK) já a adiciona à fila do
save()— oconn.add()explícito é opcional nesses casos. Entidades sem PK (ex.: linhas de VIEW, usadas só para leitura/moldar a resposta) não são enfileiradas nem podem ser persistidas viaadd()/save().
Por Objeto
produto = conn.produtos.where("id = 10").first
produto.prod_preco = 89.90
conn.add(produto)
conn.save()
Em Lote
produtos = conn.produtos.where("prod_preco >= 100").all
for produto in produtos:
produto.prod_preco = produto.prod_preco * 0.9
conn.add(produto)
conn.save()
Via Relacionamento
categoria = conn.categorias.include("produtos").where("id = 1, produtos.prod_preco >= 100").first
for produto in categoria.produtos:
produto.prod_preco = produto.prod_preco * 0.9
conn.add(produto)
conn.save()
Update Query (SQL Direto)
Para atualizações em massa sem instanciar objetos:
# Atualizar um campo
conn.produtos.where("prod_ativo = 0").set("prod_ativo", 1)
# Atualizar múltiplos campos
conn.produtos.where("prod_ativo = 0").update(prod_ativo=1, prod_promo=0)
Exclusão de Objetos
# Por objeto
conn.delete(produto)
conn.save()
# Por condição (executa imediatamente, sem save())
conn.produtos.where("prod_preco = 0").delete()
Execute Query (SQL Personalizado)
Para queries complexas que não se enquadram no query builder:
# Retorna lista de objetos Produto
produtos = conn.execute(
"SELECT * FROM produtos WHERE prod_preco > %(min_preco)s",
args={"min_preco": 100},
class_name="Produto"
)
# Retorna lista de dict
resultado = conn.execute("SELECT COUNT(*) as total FROM produtos")
O parâmetro args aceita um dicionário com placeholders %(chave)s para evitar SQL injection.
Rollback
try:
produto = Produto(prod_nome="Novo")
conn.add(produto)
conn.save()
except Exception:
conn.rollback()
raise
finally:
conn.close()
Com context manager o rollback é automático em caso de exceção.
toJSON()
Converte objeto ou lista de objetos para dict, incluindo relacionamentos carregados:
produto = conn.produtos.include("compras").where("id = 10").first
print(produto.toJSON())
# {
# 'id': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'),
# 'compras': [{'id': 1, 'id_produto': 10, ...}, ...]
# }
lista = conn.produtos.all
print(lista.toJSON()) # lista de dict
Gerando Modelo TypeScript (Angular)
O Angular() conecta ao banco e gera classes TypeScript espelhando a estrutura do banco de dados, para uso em projetos Angular ou qualquer frontend TypeScript.
import os
import bravaorm
bravaorm.Angular(
dir=os.path.join(os.path.dirname(os.path.abspath(__file__)), "src/model"),
db_user="user",
db_password="pass",
db_host="host",
db_port=3306,
db_database="dbname"
)
Gera arquivos .ts no diretório especificado e um index.ts de barrel export.
Arquitetura
bravaorm/
├── __init__.py # API pública: Connection, Make, Angular,
│ # configure_pool, get_pool, release_pool
│
├── context/
│ ├── connection.py # Connection — query builder + unit-of-work
│ ├── database.py # DataBase — wrapper mysql.connector
│ └── pool.py # configure_pool / get_pool / release_pool
│
├── entity/
│ ├── entity.py # Entity — classe base de todos os modelos
│ └── datatype.py # Tipos: String, Int, Decimal, Float, Boolean,
│ # DateTime, Dict, Json, Obj, ObjList,
│ # ObjListOfMany, ListType
│
└── utils/
├── make.py # Make() — geração de código Python a partir do DB
├── angular.py # Angular() — geração de código TypeScript
├── inflector/ # Inflector — pluralização/singularização
│ └── languages/ # Suporte: Português, Inglês, Espanhol
└── log/ # Logger colorido com níveis debug/error
Fluxo de uma Query de Leitura
conn.produtos → Connection.__getattr__ carrega model.produto.Produto
.where("ativo = 1") → armazena cláusula formatada
.include("compras") → declara eager loading
.all → monta SQL, chama DataBase.fetchall()
→ hidrata lista de Produto
→ busca Compra via IN (bulk, sem N+1)
→ chama entity.add() para anexar filhos
Fluxo de uma Operação de Escrita
conn.add(produto) → enfileira em __queue__['add']
conn.save() → __save__object__():
objetos COM pk → executemany() (bulk)
objetos SEM pk → save() individual (captura lastrowid)
→ DataBase.commit()
Status do Objeto
Cada entidade possui um __metadata__['status'] que reflete seu ciclo de vida:
| status | quando |
|---|---|
created |
Após instanciação |
modified |
Após qualquer alteração de campo |
inserted |
Após commit de nova inserção |
updated |
Após commit de atualização |
deleted |
Após commit de exclusão |
loaded |
Após hidratação via .all / .first |
Inflector
O Inflector realiza a conversão automática entre nome de tabela e nome de classe:
classify("produtos")→"Produto"tableize("Produto")→"produtos"
Aceita vocabulário customizado:
conn = bravaorm.Connection(
...,
uncountable_words=["status", "lms"],
irregular_words={"perfil": "perfis", "raiz": "raizes"}
)
License
MIT
Copyright (c) 2019-2026 Roberto Neves. All rights reserved. robertonsilva@gmail.com
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file bravaorm-0.0.40.tar.gz.
File metadata
- Download URL: bravaorm-0.0.40.tar.gz
- Upload date:
- Size: 49.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51d4c40ccadecf70f5b1b0bedf177c3f6c527979860189bc50b25c42f38ff45e
|
|
| MD5 |
0b912003951d596de0b9c6025e9e9883
|
|
| BLAKE2b-256 |
afb088c83f7f7dfe91a3de6ba35a44bd8f80e4fc937dc3fe6f1f77b2afb34ebb
|