Forge — banco de dados
Cinco motores, uma interface, zero dependências.
Forge conecta a PostgreSQL, MySQL, MariaDB, MongoDB, Redis e SQLite. Trocar o motor troca a URL, e mais nada:
adopt Forge
db := Forge.conectar("postgres://usuario:senha@localhost:5432/app")
// ou "mysql://root@localhost/app"
// ou "mongodb://localhost/app"
// ou "redis://localhost"
// ou "dados.db" — SQLite
out Forge.versao(db)Sem dependência nenhuma#
Cada driver fala o protocolo do seu banco, por socket. Não há psycopg2, PyMySQL, redis-py nem pymongo aqui — há um arquivo por protocolo, implementando a especificação publicada de cada servidor.
O custo foi escrevê-los. O que se ganha é que pip install dataforge funciona numa máquina sem compilador, e a versão do driver nunca briga com a de outro pacote.
| Motor | Autenticação | Testado contra |
|---|---|---|
| PostgreSQL | SCRAM-SHA-256, MD5, trust | 16 |
| MySQL | caching_sha2_password (com RSA), mysql_native_password | 8.4 |
| MariaDB | mysql_native_password | 11 |
| Redis | AUTH com usuário e senha | 7.4 |
| MongoDB | SCRAM-SHA-256 e SHA-1 | 7.0 |
| SQLite | — | o do Python |
Primeiro programa#
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, """
create table produtos (
id integer primary key autoincrement,
nome text not null,
preco real
)
""")
Forge.de(db, "produtos").inserir([
{"nome": "Teclado", "preco": 250.0},
{"nome": "Mouse", "preco": 90.0}
])
caros := Forge.de(db, "produtos").onde("preco", ">=", 100).buscar()
assert len(caros) is 1
assert caros[0]["nome"] is "Teclado"Valores nunca entram no texto da consulta#
Esta é a garantia central, e ela vale para o construtor e para o SQL escrito à mão:
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, "create table u (id integer primary key, nome text)")
malicioso := "'; drop table u; --"
Forge.de(db, "u").inserir({"nome": malicioso})
// a tabela continua lá: o valor foi tratado como dado, não como SQL
assert "u" in Forge.tabelas(db)
assert Forge.de(db, "u").contar() is 1Erros que dizem o que houve#
Cada erro do servidor vira uma classe da linguagem, capturável por família:
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, "create table u (id integer primary key, email text unique)")
Forge.de(db, "u").inserir({"email": "a@b.co"})
monitor:
Forge.de(db, "u").inserir({"email": "a@b.co"})
handle ConstraintError as e:
assert "UNIQUE" in e.message| Classe | Quando |
|---|---|
ConnectionError | o servidor não respondeu ou recusou |
AuthenticationError | usuário ou senha inválidos |
QueryError | SQL inválido, tabela ou coluna inexistente |
ConstraintError | chave única, estrangeira, ou not null |
TransactionError | deadlock, ou confirmar sem transação aberta |
PoolExhaustedError | todas as conexões ocupadas |
RecordNotFoundError | buscar_ou_erro não achou |
ValidationError | o modelo recusou os dados |
Todas descendem de DatabaseError, então handle DatabaseError pega qualquer uma. Ver catálogo de erros.