Skip to main content

Python ORM for MySQL

Project description

Brava ORM para MySQL/MariaDB

SDK fornece uma série de recursos para aumentar produtividade no desenvolvimento de aplicações com integração a banco de dados relacional MySql/MariaDB.

Instalação

Instalação utilizando Pip

pip install bravaorm

Git/Clone

git clone https://github.com/robertons/bravaorm
cd bravaorm
pip install -r requirements.txt
python setup.py install

Dados de Entrada

entrada default tipo obrigatório
db_user None string sim Nome Usuário
db_password None string sim Senha Usuario
db_host None string sim Host
db_port None string sim Porta
db_database None string sim Nome DB
db_ssl False boolean não Conexão Segura
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 Certificado
db_charset utf8 string não Charset DB
log_level error string não Nível log

Saída

método aplicável resultado
first Conexão objeto de um select
all Conexão lista de objetos de um select
fetch Conexão lista de um select sem conversão em objeto
delete(obj) Conexão exclui um objeto
save() Conexão salva operações no db
add(obj) Objeto, Conexão adiciona objeto a uma lista/tabela
ToJSON() Objeto, Conexão resultado em formato dict

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="dbmae", db_charset="utf8mb4")

Gerando Modelo de Entidade

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")

O script acima irá gerar na raiz do projeto as classes objetos baseados no Banco de Dados. As tabelas do banco dedos devem seguir os requisitos de pluralização, conforme exemplo abaixo:

.
├── ...
├── model                           # Raiz Entidade   ├── __init__.py             ├── categoria.py               # Classe Categoria | Tabela categorias   ├── cliente.py                 # Classe Cliente  	| Tabela clientes   └── compra.py                  # Classe Compra  	| Tabela compras   └── produto.py                 # Classe Produto  	| Tabela produtos
└── ...

Tomando como exemplo a tabela produtos, a classe gerada estará assim:

# -*- coding: utf-8 -*-
from bravaorm.entity import *

class Produto(Entity):

	def __init__(cls, **kw):
		cls.__metadata__ = {'pk': ['id']}

		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_data_fabricacao = DateTime(format='%d/%m/%Y')
		cls.prod_data_modificacao = DateTime(format='%d/%m/%Y HH:MM:SS')

		# 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_compra', reference='id', table='compras')

		super().__init__(**kw)

Seleção de Objetos e Condições

produto = conn.produtos.where("id=10").first
print(produto)
print(produto.toJSON())

> <model.produto.Produto object at 0x101237c10>

> {'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22)}

Condição 'Ou'

A condição orwhere é dependente de condição where, e cria novos blocos a cada utilização.

produtos = conn.produtos.where("id=10").orwhere("id=12").orwhere("id=14").all
print(produtos.toJSON())

> [{'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22)}, {'id': 12, 'prod_nome': 'Outro Exemplo ', 'prod_preco': Decimal('88,20'), 'prod_data_fabricacao': datetime(2011, 10, 8, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 02, 12, 10, 24, 20)}, {'id': 14, 'prod_nome': 'Novo Exemplo', 'prod_preco': Decimal('129,90'), 'prod_data_fabricacao': datetime(2009, 11, 7, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 01, 10, 14, 34, 15)}]

Seleção de Campos

produto = conn.produtos.where("id=10").select("id, prod_nome").first
print(produto.toJSON())

> {'id': 10, 'prod_nome': 'Exemplo'}

Alias de Funções ou Campos

produto = conn.produtos.alias('prod_nome','nome').where("id=10").first
print(produto['nome'])
print(produto.prod_nome)

> Exemplo > Exemplo

Alias são campos não editáveis

Ordenamento

# ORDERBY
produto = conn.produtos.orderby("prod_nome").all
produto = conn.produtos.orderby("prod_nome DESC").all

Agrupamento

# GROUPBY
produto = conn.produtos.groupby("id_categoria").all

Limite

# LIMIT
produto = conn.produtos.orderby("prod_nome").limit(0,10).all

União de Objetos

O métodos "join" e "inner" são recomendados para uso de objetos com relacionamento "um para um", ou em casos onde o resultado da tabela secundária irá retornar apenas um resultado. A utilização desses métodos em casos de seleção "um para vários" o resultado será baseado na tabela secundária, podendo ocorrer a repetição do objeto principal. Neste neste caso é recomendável a utilização do método "include" onde o resultado da lista será com objetos únicos incluindo os vários resultados da tabela secundária.

Exemplos:

Join

O método join, é equivalente ao "LEFT JOIN" e retorna o objeto principal incluindo a seleção secundária, de duas tabelas.

produto = conn.produtos.join("categegorias").all

> {'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22), 'categorias': {'id':1, 'cat_nome':'Categoria Teste'}}

Inner

O método inner, é equivalente ao "INNER JOIN" e retorna o objeto principal baseado na interceção da tabela secundária.

produto = conn.produtos.inner("categorias").where("categorias.id=1").all

> [{'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22), 'categorias': {'id':1, 'cat_nome':'Categoria Teste'}}, {'id': 12, 'prod_nome': 'Outro Exemplo ', 'prod_preco': Decimal('88,20'), 'prod_data_fabricacao': datetime(2011, 10, 8, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 02, 12, 10, 24, 20), 'categorias': {'id':1, 'cat_nome':'Categoria Teste'}}, {'id': 14, 'prod_nome': 'Novo Exemplo', 'prod_preco': Decimal('129,90'), 'prod_data_fabricacao': datetime(2009, 11, 7, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 01, 10, 14, 34, 15), 'categorias': {'id':1, 'cat_nome':'Categoria Teste'}}]

Include

O método include, faz seleção de um ou vários objetos relacionados ao objeto principal, recomendado para relacionamento um para vários.

este método requer atenção em relação a performance para grandes seleções

produto = conn.produtos.include("compras").where("id=10, compras.compra_paga=1").all

> [{'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22), 'compras': [{'id':1, 'compra_data':datetime(2010, 12, 10, 0, 0, 0), 'id_cliente':12}, {'id':23, 'compra_data':datetime(2017, 11, 13, 0, 0, 0), 'id_cliente':54}, {'id':34, 'compra_data':datetime(2018, 1 11, 0, 0, 0), 'id_cliente':20}, {'id':110, 'compra_data':datetime(2019, 7, 22, 0, 0, 0), 'id_cliente':16}]}]

ON

O método on, é possibilidade de escritas de join personalizados com tabelas sem relacionamentos. É possível escrever qualquer condição, "RIGHT, INNER, LEFT JOIN" e retorna o objeto principal com os campos em alias de acordo com a tabela incluída.

O método possui 4 parametros de entrada, select ( campos da tabela selecionada), metodo join (left, right, inner), tabela e condição

No exemplo abaixo, selecionamos produtos com cupons de desconto, onde o preço do produto atinge o limite do preço do cumpom

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

> {'id': 10, 'id_categoria': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22), 'cod_cupom': 'ACAFS' ,'cup_preco_max': Decimal(19,00)}

Fetch

O método fetch permite o obter a resposta direta do DB em formato dict a partir de um select, sem a conversão dos dados em classe/objeto. Este método oferece ganho significativo de performance.

# GROUPBY
produtos = conn.produtos.fetch

> [{'id': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22)}, {'id': 12, 'prod_nome': 'Outro Exemplo ', 'prod_preco': Decimal('88,20'), 'prod_data_fabricacao': datetime(2011, 10, 8, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 02, 12, 10, 24, 20)}, {'id': 14, 'prod_nome': 'Novo Exemplo', 'prod_preco': Decimal('129,90'), 'prod_data_fabricacao': datetime(2009, 11, 7, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 01, 10, 14, 34, 15)}]

Método toJSON()

O metodo toJSON() retorna o objeto ou lista de resultado em formato dict.

	print(produto)
	print(produto.toJSON())

> <model.produto.Produto object at 0x107737c10>

> {'id': 10, 'prod_nome': 'Exemplo', 'prod_preco': Decimal('99.90'), 'prod_data_fabricacao': datetime(2010, 12, 10, 0, 0, 0), 'prod_data_modificacao': 'datetime(2021, 03, 31, 16, 54, 22)}

Criação de Objetos

Simples

from model import *

produto = Produto()
produto.prod_nome = 'Exemplo'
produto.prod_preco = Decimal('99.90')
produto.prod_data_fabricacao = datetime(2010, 12, 10, 0, 0, 0)
produto.prod_data_modificacao = '31/03/2021 16:54:22' # Conversão para datetime seguindo formato '%d/%m/%Y HH:MM:SS'

conn.add(produto)
conn.save()

OU

from model import *

produto = Produto(prod_nome='Exemplo', prod_preco=Decimal('99.90'), prod_data_fabricacao = datetime(2010, 12, 10, 0, 0, 0), prod_data_modificacao = '31/03/2021 16:54:22')

conn.add(produto)
conn.save()

OU

from model import *

produto = Produto(**{prod_nome:'Exemplo', prod_preco:Decimal('99.90'), prod_data_fabricacao:datetime(2010, 12, 10, 0, 0, 0), prod_data_modificacao :'31/03/2021 16:54:22'})

conn.add(produto)
conn.save()

Com Relacionamentos

No exemplo abaixo, criamos um produto na tabela produtos com 3 fotos na tabela produtos_fotos

from model import *

produto = Produto()
produto.prod_nome = 'Exemplo'
produto.prod_preco = Decimal('99.90')
produto.prod_data_fabricacao = datetime(2010, 12, 10, 0, 0, 0)
produto.prod_data_modificacao = '31/03/2021 16:54:22' # Conversão para datetime seguindo formato '%d/%m/%Y HH:MM:SS'

foto = ProdutoFoto()
foto.foto_descricao = 'Vista Frontal'
foto.foto_arquivo =  'fronta.jpg'
produto.produtos_fotos.add(foto)

foto = ProdutoFoto(**{foto_descricao:'Vista Lateral', foto_arquivo:'lateral.jpg'})
produto.produtos_fotos.add(foto)

produto.produtos_fotos.add(ProdutoFoto(foto_descricao='Vista Lateral', foto_arquivo='lateral.jpg'))

conn.add(produto)
conn.save()

Atualizando Objeto

produto = conn.produtos.where("id=10").first
produto.prod_preco = Decimal(89.90)
conn.add(produto).save()

ou vários

produtos = db.produtos.where("prod_preco>=100").all
for produto in produtos:
	produto.prod_preco = produto.prod_preco * 0.9
	conn.add(produto)
db.save()

ou relativos

categoria = db.categorias.include("produtos").where("id=1, produtos.pro_preco>=100").all
for produto in categoria.produtos:
	produto.prod_preco = produto.prod_preco * 0.9
	conn.add(produto)
db.save()

Exclusão de Objetos

O método requer um objeto de entrada, ou uma condição where definida:

conn.delete(produto).save()

ou condicional

conn.produtos.where("prod_preco=100").delete()

Contador

quantidade_produtos = conn.produtos.where("prod_preco=100").count

print(quantidade_produtos)

**> 113 **

Update Query

Realizar Atualização de registros com condição.

Atualizando um campo

conn.produtos.where("prod_active=0").set("prod_active", 1)

Atualizando um ou mais campos

conn.produtos.where("prod_active=0").update(**{"prod_active": 1, "prod_promo": 1})

OU

conn.produtos.where("prod_active=0").update(prod_active=1, prod_promo=0)

Execute Query

É possível executar queries mais complexas e com condicionais específicas, nesse caso é possível escrever a query diretamente na conexão e informar qual o tipo de objeto será retornado a partir dela.

A definição do objeto é opcional.

produtos = conn.execute("SELECT * FROM produtos WHERE preco > 100", "Produto")

A consulta acima irá retornar uma lista de objetos Produto

produtos = conn.execute("SELECT * FROM produtos WHERE preco > 100")

A consulta acima irá retornar uma lista com dictionary com dados de produtos

Descrição completa do Projeto e arquitetura

🧬 Visão Geral do Projeto

O BravaORM é um SDK (Object-Relational Mapper) desenvolvido em Python projetado especificamente para MySQL e MariaDB, focado em aumentar a produtividade. Ele possui uma abordagem intuitiva e amigável para Query Building (similar aos ORMs mais modernos), com suporte embutido a relacionamentos complexos e foco em performance e praticidade.

A filosofia do ORM, centrada principalmente em "Gerar o Código do Modelo a partir do Banco de Dados", lembra abordagens consolidadas e altamente produtivas na web hoje.

📂 Dependência Princial

mysql-connector-python

📂 Estrutura da Arquitetura (Core)

A biblioteca está debaixo do diretório principal bravaorm/. Esse diretório é inteligentemente dividido em três domínios principais:

1. Módulo context (O Motor do Banco de Dados)

Responsável por estabelecer e gerenciar a comunicação com o MySql/MariaDB.

connection.py: A espinha dorsal das conexões (bravaorm.Connection), capaz de traduzir a sintaxe fluida do Python para as queries em SQL, aplicando where, orderby, groupby, limites e gerindo as transações com save() ou execuções diretas com execute().

database.py: Abstrações gerais e handlers de queries sobre as instâncias do DB.

2. Módulo entity (Abstração dos Dados)

Gerencia as classes que abstraem as tabelas.

entity.py: Contém a classe base Entity da qual todos os modelos gerados herdam. É o que permite gerir o ciclo de vida do objeto e sua serialização rápida (toJSON()).

datatype.py : Define a tipagem rigorosa que espelha os tipos do banco de dados relacional para o Python. Estão lá as definições de Int, String, Decimal, DateTime, além dos objetos relacionais Obj (One-to-One) e ObjList (One-to-Many).

3. Módulo utils (Ferramentas de Produtividade)

Aqui moram as "mágicas" do seu orquestrador.

make.py (bravaorm.Make): Um recurso incrível de introspecção do DB. Ele varre as tabelas, analisa as FKs/PKs e escreve sozinho as classes de modelo em código Python na raiz do projeto.

inflector/: Essencial para o ORM. Lida com a pluralização natural das tabelas (ex: entende que a tabela produtos se refere ao objeto Produto). log/: Estrutura padronizada para manter registros de debug e de erros baseada no DB.

angular.py: Um recurso de introspecção do DB. Ele varre as tabelas, analisa as FKs/PKs e escreve sozinho as classes de modelo em código TypeScript no diretório desejado.

💡 Destaques da API (O que torna o BravaORM forte)

Sintaxe Fluente (Query Builder): A interface para buscar dados é muito polida. Ex: conn.produtos.where("id=10").alias('prod_nome','nome').first

Relacionamentos Bem Resolvidos:

join(...) e inner(...) resolvem bem e de forma previsível buscas One-to-One / Many-to-One. include(...) lida com o complexo problema N+1 ao fazer Eager Loading para One-to-Many (ex: busque as compras do produto X). O on(..) para junções customizadas para tabelas sem relacionamento fixo (ideal para relatórios).

Conversão Imediata (Data to Dictionary)

O método .toJSON() direto no objeto, ou o uso imediato de .fetch ignorando classes, é excelente para APIs REST onde o desenvolvedor só precisa transferir o dado em JSON pela reposta HTTP sem overhead de parser.

License

MIT

Copyright (c) 2019-2021 Roberto Neves. All rights reserved. info (at) 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.31.tar.gz (39.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bravaorm-0.0.31-py3-none-any.whl (39.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: bravaorm-0.0.31.tar.gz
  • Upload date:
  • Size: 39.9 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.31.tar.gz
Algorithm Hash digest
SHA256 4f42505f706876d35318f51a9b08e903c0672b42748947b0f2695805dc473ac4
MD5 eecc82172d914d2e3405c5810c9534a0
BLAKE2b-256 cc173f370cb47b9970435d9ce2ed1242e56ce26ca40b361db3e5f8e0d58de0d2

See more details on using hashes here.

File details

Details for the file bravaorm-0.0.31-py3-none-any.whl.

File metadata

  • Download URL: bravaorm-0.0.31-py3-none-any.whl
  • Upload date:
  • Size: 39.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for bravaorm-0.0.31-py3-none-any.whl
Algorithm Hash digest
SHA256 6559cfda52e076d30252d201e9d379ae13a9d24ba8c42bd1c7a286f55ed0f40a
MD5 d747f887d80fe4f2fb9ab5da67990345
BLAKE2b-256 28079a48289d3f4ea03855e4d7d24ff3fcf4c266799941d226f61f2970f1bd1e

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