Iniciando com o ORM Pony no Python II - Banco de Dados com Docker
Continuando a jornada com o ORM Pony, agora com outros Banco de Dados, em Docker
No primeiro texto desta série, demos os primeiros passos com o ORM Pony, usando o SQLite como banco de dados.
Nesse texto vamos declarar uma tabela e usá-la em três bancos distintos: MySQL, Postgres e o bom e velho SQLite, usasndo docker.
Preparando o terreno
No Debian, distribuição que uso, instale os pacotes oficiais:
sudo apt install docker.io docker-compose
Caso sua distribuição não tenha algo similar, acesse https://www.docker.com/get-started para instruções sobre como fazer uma instalação com pacotes não específicos a uma distribuição.
Outro passo é criar um diretório para trabalharmos:
$ sudo mkdir -p /opt/pony
Darmos permissões para nós mesmos, já que está fora de nosso home:
$ sudo chown ${USER}: /opt/pony
E criar subdiretórios que armazenarão os dados de nossos bancos e código:
$ mkdir -p /opt/pony/data/{mysql,postgresql}
$ mkdir /opt/pony/src
Verificar é sempre legal:
$ tree /opt/pony
/opt/pony
├── data
│ ├── mysql
│ └── postgresql
└── src
4 directories, 0 files
Salve esse texto como /opt/pony/requirements.txt
:
ipython
pony
PyMySQL
Salve esse conteúdo como /opt/pony/Dockerfile
FROM alpine
WORKDIR /code
RUN apk add --no-cache python3 py3-pip py3-psycopg2 bash sqlite postgresql-client mysql-client
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN mkdir src
Esse arquivo define um container com uma imagem Python, para que trabalhemos sem instalar alterar os pacotes de nossa máquina. Optei por usar pacotes pré-compilados de Python para os drivers de banco de dados, para facilitar a vida e não ter que instalar compilador e afins.
Compartilhando os fontes:
E salve o seguinte conteúdo como /opt/pony/docker-compose.yml
:
version: "3"
services:
python:
build: .
volumes:
- /opt/pony/src/:/code/src
mysql:
image: mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: top123
volumes:
- /opt/data/mysql:/var/lib/mysql/
postgres:
image: postgres
environment:
POSTGRES_PASSWORD: top123
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- /opt/data/postgres:/var/lib/postgresql/data/pgdata/
Salve esse arquivo como /opt/pony/src/models.py
:
from pony.orm import Database, db_session, PrimaryKey, Required
db = Database()
class Github(db.Entity):
_table_ = "github"
key = Required(str)
value = Required(str)
E esse como /opt/pony/src/bombril.py
:
import csv
import json
import urllib.request
def get_data():
with urllib.request.urlopen('https://api.github.com/') as f:
response = f.read()
return json.loads(response)
def save_csv(data, filenename='github.csv'):
with open(filename, 'w', newline='') as csvfile:
fieldnames = ['id', 'key', 'value']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
id_value = 0
for key, value in data.items():
id_value += 1
writer.writerow({'id': id_value, 'key': key, 'value': value})
def read_csv(filename='githb.csv'):
with open('github.csv', newline='') as csvfile:
reader = csv.DictReader(csvfile)
return list(reader)
Feito isso, estando no diterório /opt/pony
, podemos subir nosso laboratório de testes, executando o comando a seguir, que em sua primeira executação nos exigirá paciência e banda de Internet, para baixar tudo que é necessário:
$ docker-composer up
Seu terminal será inundado com informações, aproveite para hidratar-se e caminhar um pouco. Ao final de tudo, ele estará "truncado", com algo parecido a isso:
postgres_1| 2021-05-08 20:30:50.406 UTC [1] LOG: database system is ready to accept connections
... muitas linhas entre essas mensagens
mysql_1 | 2021-05-08T20:32:03.868866Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.24' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
Aconselho deixar quieto esse terminal para ver os logs, e abrir outro para trabalhar (pode-se iniciar com docker-compose up -d
para manter o terminal livre e o processo em background).
Pode-se ver o que temos rodando:
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------
pony_mysql_1 docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp
pony_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp
pony_python_1 /bin/sh Exit 0
Para criar os databases, nos conectaremos aos bancos de dados através do container Python, no qual instalamos os clientes para acesso a eles:
$ docker-compose run python bash
bash-5.1# psql -Upostgres -hpostgres
Password for user postgres:
psql (13.2)
Type "help" for help.
postgres=# CREATE DATABASE github;
CREATE DATABASE
postgres=# \q
bash-5.1# mysql -uroot -p -hmysql
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.24 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> CREATE DATASE github;
Query OK, 1 row affected (0.301 sec)
MySQL [(none)]> quit
Bye
bash-5.1#
Por fim, vamos pegar alguns dados para inserir nos bancos. Vamos pegar algumas informações do Github, salvar em um arquivo CSV, e sempre que precisarmos, carregamos em memória num dicionário para usar. Ainda no container Python (se estiber fora, basta um docker-compose run python bash
):
$ docker-compose run python bash
bash-5.1# cd /code/src
bash-5.1# ipython
Python 3.8.10 (default, May 6 2021, 00:05:59)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import bombril
In [2]: data = bombril.get_data()
In [3]: bombril.save_csv(data)
In [4]:
Do you really want to exit ([y]/n)? y
bash-5.1# head github.csv
id,key,value
1,current_user_url,https://api.github.com/user
2,current_user_authorizations_html_url,https://github.com/settings/connections/applications{/client_id}
3,authorizations_url,https://api.github.com/authorizations
4,code_search_url,"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}"
5,commit_search_url,"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}"
6,emails_url,https://api.github.com/user/emails
7,emojis_url,https://api.github.com/emojis
8,events_url,https://api.github.com/events
9,feeds_url,https://api.github.com/feeds
Como queríamos. Temos nosso laboratório preparado, enfim!
Conectando aos bancos com Pony
Agora sim, vamos usar nossos dados, criar uma descrição deles com Pony, e inserí-los em cada banco.
Começando com o sqlite
(importnte notar o so do db_session
para garantir o commit dos dados):
bash-5.1# ipython
Python 3.8.10 (default, May 6 2021, 00:05:59)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import bombril
In [2]: import models
In [3]: from pony.orm import db_session
In [4]: data = bombril.read_csv()
In [5]: db = models.db
In [6]: db.bind(provider='sqlite', filename='/code/src/github.db', create_db=True)
In [7]: db.generate_mapping(create_tables=True)
In [8]: with db_session:
...: for reg in data:
...: models.Github(**reg)
...:
In [9]:
Do you really want to exit ([y]/n)?
bash-5.1# sqlite3 github.db
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> SELECT * FROM github LIMIT 2;
1|current_user_url|https://api.github.com/user
2|current_user_authorizations_html_url|https://github.com/settings/connections/applications{/client_id}
Partamos para os bancos de dados menos eficientes :)
bash-5.1# cd /code/src
bash-5.1# ipython
Python 3.8.10 (default, May 6 2021, 00:05:59)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import bombril
In [2]: import models
In [3]: from pony.orm import db_session
In [4]: data = bombril.read_csv()
In [5]: db = models.db
In [6]: db.bind(provider='mysql', host='mysql', user='root', passwd='top123', db='github')
In [7]: db.generate_mapping(create_tables=True)
In [8]: with db_session:
...: for reg in data:
...: models.Github(**reg)
...:
In [9]: models.Github[10].to_dict()
Out[9]:
{'id': 10,
'key': 'followers_url',
'value': 'https://api.github.com/user/followers'}
In [10]: import iportlib
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-10-4e5d0833eb20> in <module>
----> 1 import iportlib
ModuleNotFoundError: No module named 'iportlib'
In [11]: import importlib
In [12]: importlib.reload(models)
Out[12]: <module 'models' from '/code/src/models.py'>
In [13]: db = models.db
In [14]: db.bind(provider='postgres', user='postgres', password='top123', host='postgres', database='github')
In [15]: db.generate_mapping(create_tables=True)
In [16]: with db_session:
...: for reg in data:
...: models.Github(**reg)
...:
In [17]: models.Github[10].to_dict()
Out[17]:
{'id': 10,
'key': 'followers_url',
'value': 'https://api.github.com/user/followers'}
In [18]: quit
Vamos abrir outro terminal e verificar as coisas:
bash-5.1# psql -Upostgres -hpostgres
Password for user postgres:
psql (13.2)
Type "help" for help.
postgres=# \c github
You are now connected to database "github" as user "postgres".
github=# SELECT COUNT(*) FROM github;
count
-------
32
(1 row)
github=# SELECT * FROM github LIMIT 2;
id | key | value
----+--------------------------------------+------------------------------------------------------------------
1 | current_user_url | https://api.github.com/user
2 | current_user_authorizations_html_url | https://github.com/settings/connections/applications{/client_id}
(2 rows)
github=# ^D\q
bash-5.1# mysql -uroot -p -hmysql
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 8.0.25 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> USE github;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [github]> SELECT COUNT(*) FROM github;
+----------+
| COUNT(*) |
+----------+
| 32 |
+----------+
1 row in set (0.001 sec)
MySQL [github]> SELECT * FROM github LIMIT 2;
+----+--------------------------------------+------------------------------------------------------------------+
| id | key | value |
+----+--------------------------------------+------------------------------------------------------------------+
| 1 | current_user_url | https://api.github.com/user |
| 2 | current_user_authorizations_html_url | https://github.com/settings/connections/applications{/client_id} |
+----+--------------------------------------+------------------------------------------------------------------+
2 rows in set (0.001 sec)
MySQL [github]>
Como se pode perceber, com um mesmo código, fizemos a mesma coisa: inserir dados no banco, para bancos diferentes. Nems sempre isso é possível, nem sempre é desejável e confiável delegar ao ORM a decisão de como realizar essa tarefa, mas isso pode ser interessante em alguns casos, como, por exemplo, sistemas não tão complexos em que ter consultas SQLs espalhadas pelo código seja uma opção.
Materiais consultados
Consultei muito material para resolver umasérie de problemas que fui enfrentando no decorrer do desenvolvimento dessas ideias aqui apresentadas. E pra quem, como eu, estiver aprendendo, aconselhor fortemente consultar com calma as seguintes páginas:
Para as imagens docker
Para os scripts Python
Off-topic
E num movimento que eu mesmo não esperava, comecei a testar o editor micro
: leve, fácil e bonitão, no lugar do vim
. Recomendo: