Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Iniciando com o ORM Pony no Python III - Erros e Exceções

Seguindo o trilha com o ORM Pony, neste terceiro texto, agora começaremos a gerar situações de erro e ver o que os objetos retornam ou que exceções são geradas.

Logo do projeto Pony

https://ponyorm.org

No primeiro texto desta série, demos os primeiros passos com o ORM Pony, usando o SQLite como banco de dados.

Já no segundo texto desta coleção, criamos um pequeno conjunto de dados a partir de uma requisição à API do Github, e usamos em três bancos distintos: MySQL, Postgres e o bom e velho SQLite, usando docker.

Agora vamos fazer um monte de coisa errada para entender as situações de erro e estarmos preparados para programarmos essas situações.

O Laboratório

Partamos do laboratório construído no segundo texto acima referenciado. Por gosto pessoal, escolho o Postgres para conectar, mas os exemplos devem funcionar com qualquer um dos bancos, basta trocar a conexão (método bind, conforme visto no segundo texto).

Em um terminal execute o comando docker-compose up --build para subir os bancos e em outro terminal entremos na imagem Alpine, que contém o Python:


In [1]: import bombril

In [2]: import models

In [3]: db = models.db

In [4]: data = bombril.read_csv()

In [5]: db.bind(provider='postgres', user='postgres', password='top123', host='postgres', database='github')

In [6]: db.generate_mapping(create_tables=True)

Como vimos, tudo funcionando, que tal fazer algumas coisas erradas?

Começando pelo driver:


In [10]: db.bind(provider='postgress', user='postgres', password='top123', host='postgres', database='github')

... muitas linhas depois

ModuleNotFoundError: No module named 'pony.orm.dbproviders.postgress'

Errando o usuário:


In [11]: db.bind(provider='postgres', user='postgress', password='top123', host='postgres', database='github')

... muitas linhas depois

OperationalError: FATAL:  password authentication failed for user "postgress"

Que por acaso gera o mesmo erro para uma senha errada:


In [12]: db.bind(provider='postgres', user='postgres', password='top123....', host='postgres', database='github')

... muitas linhas depois

OperationalError: FATAL:  password authentication failed for user "postgres"

Errando o host:


In [13]: db.bind(provider='postgres', user='postgres', password='top123', host='postgress', database='github')

... muitas linhas depois

OperationalError: could not translate host name "postgress" to address: Name does not resolve

Finalmente, errando o nome do banco:


In [14]: db.bind(provider='postgres', user='postgres', password='top123', host='postgres', database='github..')

... muitas linhas depois

OperationalError: FATAL: database "github.." does not exist

O que nos leva a poder programar algo assim:


In [15]: from pony import orm

In [16]: try:
    ...:     db.bind(provider='postgres', user='postgres', password='top123', host='postgres', database='github..')
    ...: except orm.OperationalError as e:
    ...:     print(e)
    ...:
FATAL:  database "github.." does not exist

Vamos arrumas as coisas:


In [17]: db.bind(provider='postgres', user='postgres', password='top123', host='postgres', database='github')

In [18]: db.generate_mapping(create_tables=True)

Pegar um registro pela chave primária:


In [19]: models.Github[1]
Out[19]: Github[1]

In [20]: models.Github[1].to_dict()
Out[20]: {'id': 1, 'key': 'current_user_url', 'value': 'https://api.github.com/user'}

E tentar pegar um registro que não existe:


In [21]: models.Github[99].to_dict()

... muitas linhas depois


ObjectNotFound: Github[99]

Temos mais uma exceção para nossa coleção. Sempre lembrando, elas "moram" em pony.orm.

Outras formas de pesquisa

Uma forma de não gerar exceção e ficar no ifizinho é usar o get:


In [22]: response = models.Github.get(id=99)

In [23]: response

In [24]: type(response)
Out[24]: NoneType

In [25]: response is None
Out[25]: True

Porém, caso haja mais que uma resposta, teremos o belo estouro de uma exceção:


In [26]: response = models.Github.get(lambda p: 'a' in p.key)

... muitas linhas depois

MultipleObjectsFoundError: Multiple objects were found. Use select(...) to retrieve them

Interessante que o erro já vem com um conselho, então sigamo-lo:


In [27]: response = models.Github.select(lambda p: 'a' in p.key)

In [28]: response.count()
Out[28]: 17

Ou uma busca mais real:


In [29]: response = models.Github.select(lambda p: 'user' in p.key)

In [30]: response.show()
id|key                                 |value                                   
--+------------------------------------+----------------------------------------
1 |current_user_url                    |https://api.github.com/user             
2 |current_user_authorizations_html_url|https://github.com/settings/connectio...
26|current_user_repositories_url       |https://api.github.com/user/repos{?ty...
29|user_url                            |https://api.github.com/users/{user}
30|user_organizations_url              |https://api.github.com/user/orgs
31|user_repositories_url               |https://api.github.com/users/{user}/r...
32|user_search_url                     |https://api.github.com/search/users?q...

O show() é muito útil em casos de debug, e poderíamos ter feito essa consulta em uma só linha com:


models.Github.select(lambda p: 'user' in p.key).show()

O select retorna um iterável, então, pode-se transformá-lo em uma lista, ou, antes verificar pela contagem do próprio Pony:


In [31]: response = models.Github.select(lambda p: 'non exist' in p.key)

In [32]: response.count()
Out[32]: 0

E para fechar nosso guia básico de erros, que tal tentar incluir um registro com chave primária existente? Aqui o experimento:


In [33]: with orm.db_session:
...:     models.Github(id=1,key='bla', value='ble')

... muitas linhas depois

CacheIndexError: Cannot create Github: instance with primary key 1 already exists

Acredito que com essa enfadonha e nada exaustiva lista com situações de erro, eu possa ter pego e passado o espírito do tratamento de erros no Pony. O importante é ter a documentação em mãos e um terminal com seu laboratório para testar as diversas situações e poder defender-se desse mundo hostil que é a comunicação em rede.

Nunca nos esqueçamos desses princípios:


The Eight Fallacies of
Distributed Computing

Peter Deutsch

Essentially everyone,
when they first build a distributed application,
makes the following eight assumptions.

All prove to be false in the long run and all
cause big trouble and painful learning experiences.

1.  The network is reliable
2.  Latency is zero
3.  Bandwidth is infinite
4.  The network is secure
5.  Topology doesn't change
6.  There is one administrator
7.  Transport cost is zero
8.  The network is homogeneous

https://www.researchgate.net/publication/322500050FallaciesofDistributedComputing_Explained

Então, sempre na defesa!