18. Escrever código independente do SGBD
Vimos anteriormente que, em alguns casos, era possível migrar facilmente código Python escrito para o SGBD MySQL para código escrito para o SGBD PostgreSQL. Neste capítulo, mostramos como sistematizar esta abordagem. A arquitetura proposta é a seguinte:

Queremos que a escolha do conector — e, portanto, do SGBD — seja feita através da configuração e não exija a reescrita do script. Note-se que isto só é possível nos casos em que o script não utiliza extensões proprietárias do SGBD.
A estrutura do diretório do script será a seguinte:

Os scripts [any_xx] baseiam-se nos scripts já abordados para os SGBDs MySQL e PostgreSQL. Não iremos abordar todos eles. Iremos concentrar-nos no script [any_04], que é o mais complexo. Note-se que este script executa os comandos SQL do seguinte ficheiro [data/commandes.sql]:
# suppression de la table [personnes]
drop table if exists personnes
# création de la table personnes
create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))
# insertion de deux personnes
insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)
insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)
# affichage de la table
select prenom, nom, age from personnes
# erreur volontaire
xx
# insertion de trois personnes
insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)
insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)
insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)
# affichage de la table
select prenom, nom, age from personnes
# liste des personnes par ordre alphabétique des noms et à nom égal par ordre alphabétique des prénoms
select nom,prenom from personnes order by nom asc, prenom desc
# liste des personnes ayant un âge dans l'intervalle [20,40] par ordre décroissant de l'âge
# puis à âge égal par ordre alphabétique des noms et à nom égal par ordre alphabétique des prénoms
select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
# insertion de mme Bruneau
insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)
# mise à jour de son âge
update personnes set age=47 where nom='Bruneau'
# liste des personnes ayant Bruneau pour nom
select nom,prenom,age from personnes where nom='Bruneau'
# suppression de Mme Bruneau
delete from personnes where nom='Bruneau'
# liste des personnes ayant Bruneau pour nom
select nom,prenom,age from personnes where nom='Bruneau'
Modificámos a linha 2 para que o comando se comporte da mesma forma tanto para o SGBD MySQL como para o PostgreSQL, caso a tabela [pessoas] não exista.
O script [any_04] é configurado pelo seguinte script [config.py]:
As novas alterações encontram-se nas linhas 18–43:
- linha 20: [sgbds] é um dicionário com duas chaves: [mysql] na linha 21 e [postgresql] na linha 32;
- o valor associado a estas chaves é um dicionário que contém os elementos necessários para se ligar a um SGBD:
- linhas 21–32: os elementos para se ligar ao SGBD MySQL;
- linha 23: o conector Python a utilizar;
- linha 25: o módulo que contém funções partilhadas;
- linhas 26–30: as credenciais de ligação;
- linhas 32–41: os mesmos elementos para uma ligação ao SGBD PostgreSQL;
O script [any_04] que executa o ficheiro de comandos SQL [data/commandes.sql] é o seguinte:
Comentários
- linhas 1-4: recuperar a configuração da aplicação [config];
- linhas 10-21: o script é chamado com dois parâmetros [db_name with_transaction]:
- [db_name]: o nome do sistema de gestão de bases de dados a utilizar;
- [with_transaction]: True se pretender executar o script SQL dentro de uma transação, False caso contrário;
- linhas 10–25: os parâmetros são recuperados e verificados;
- linha 28: configuração do SGBD selecionado;
- linha 30: o conector para o SGBD selecionado é importado. Para tal, utiliza-se a biblioteca [importlib] (linha 7), que permite importar um módulo cujo nome está armazenado numa variável. O resultado da operação [importlib.import_module] é um módulo. Assim, após a linha 30, tudo prossegue como se a instrução executada tivesse sido:
Isto permite-nos escrever [sgbd_connector.connect] na linha 52, onde utilizamos a função [connect] do módulo [sgbd_connector]. É importante lembrar aqui que [sgbd_connector] é ou [mysql.connector] ou [psycopg2]. Ambos os módulos possuem a função [connect]. Da mesma forma, na linha 60, podemos escrever [sgbd_connector.InterfaceError, sgbd_connector.DatabaseError].
- Linha 32: Importamos o módulo que contém as funções utilizadas pelo script;
- Linha 58: É chamada a função [execute_file_of_commands] do módulo que contém as funções utilizadas pelo script. Em comparação com versões anteriores, a assinatura desta função tem um parâmetro adicional — o primeiro. Passamos o conector Python [sgbd_connector] para a função para que esta o utilize;
- Para além destes pontos, o script [any_04] permanece inalterado em relação às versões anteriores;
A biblioteca de funções [any_module] é a seguinte:
O parâmetro [sgbd_connector] foi utilizado na linha 31 para especificar o tipo de exceções interceptadas.
A execução do script [any_04] com os parâmetros [mysql false] produz os seguintes resultados:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/anysgbd/any_04.py mysql false
--------------------------------------------------------------------
Exécution du fichier SQL C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\anysgbd/data/commandes.sql sans transaction
--------------------------------------------------------------------
[drop table if exists personnes] : Exécution réussie
nombre de lignes modifiées : 0
[create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))] : Exécution réussie
nombre de lignes modifiées : 0
[insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)
[insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
('Pierre', 'Nicazou', 35)
('Geraldine', 'Colou', 26)
('Paulette', 'Girond', 56)
*****************
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom, prenom,
************
('Colou', 'Geraldine')
('Girond', 'Paulette')
('Langevin', 'Paul')
('Lefur', 'Sylvie')
('Nicazou', 'Pierre')
************
[select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
nom, prenom, age,
*****************
('Nicazou', 'Pierre', 35)
('Colou', 'Geraldine', 26)
*****************
[insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)] : Exécution réussie
nombre de lignes modifiées : 1
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
('Bruneau', 'Josette', 47)
*****************
[delete from personnes where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
*****************
--------------------------------------------------------------------
Exécution terminée
--------------------------------------------------------------------
Il y a eu 1 erreur(s)
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)
Process finished with exit code 0