Skip to main content

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.

PyPI version License: MIT


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) ou rollback() seguido de close() 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 (strdict/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): campos Dict() são desserializados na leitura — inclusive na hidratação de query (.first/.all) —, retornando dict/list. O fast-path de hidratação de alta performance faz essa conversão apenas para Dict (os demais tipos passam direto, sem custo). Campos Json() 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 lastrowid e propaga automaticamente a FK para os filhos antes de salvá-los.


Atualização de Objetos

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bravaorm-0.0.38.tar.gz (47.5 kB view details)

Uploaded Source

File details

Details for the file bravaorm-0.0.38.tar.gz.

File metadata

  • Download URL: bravaorm-0.0.38.tar.gz
  • Upload date:
  • Size: 47.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for bravaorm-0.0.38.tar.gz
Algorithm Hash digest
SHA256 7fe35b60284da6202072c3090fc2b813e49cd6c2821395d4d39637c2fb1e7861
MD5 bbbc7820eda3c9eeaf865858dced90d3
BLAKE2b-256 204eae2e5b6a8ce19f4d93d85253873179ea1e48c01446474cb6867a1de355f7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page