Python ORM for MySQL
Project description
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
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
File details
Details for the file bravaorm-0.0.39.tar.gz.
File metadata
- Download URL: bravaorm-0.0.39.tar.gz
- Upload date:
- Size: 48.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e87fcea60493adb47b08fa7db267b4099e5e0bce2578978e97937a66d7af9e10
|
|
| MD5 |
ce8486717fc72f39876738fa57790e69
|
|
| BLAKE2b-256 |
5efe5ddd06c3cfdad9cc7c7c1b08e4819f65b145cc91344c4da488b81471ace1
|