Interacção Python/MySQL

Introdução

Bases de dados são armazéns estruturados de informação, nas quais é guardada uma variedade imensa de dados. Na verdade, são muitas vezes o núcleo duro de aplicações informáticas, sem que, também na maioria das vezes, o utilizador final se aperceba. A importância desta tecnologia hoje em dia é imensa, sendo que foi necessário o desenvolvimento de ferramentas para que programadores possam dela tirar partido. Entre essas ferramentas, incluem-se diversos sistemas de gestão de bases de dados, como o MySQL, o SQL Server, a Oracle, entre outros, que permitem a manutenção das bases de dados de uma maneira relativamente simples. Porém, também para estas ferramentas de gestão foi desenvolvido software para facilitar ainda mais a vida a quem desenvolve. Neste artigo pretende-se explicar e exemplificar o uso de uma API que permite a manutenção de bases de dados usando MySQL a partir de scripts de Python. Porém, não é limitado apenas a MySQL. Outros adaptadores para outros sistemas de gestão de bases de dados funcionam de maneira muito semelhante, por vezes até bastando apenas mudar uma linha de código.

Instalação

Antes de mais nada há que fazer o download do software necessário. Este artigo e o código foram criados e testados numa distribuição de GNU/Linux, neste caso Ubuntu, mas deve funcionar da mesma forma noutros sistemas operativos. Primeiro, é necessário ter o Python instalado (www.python.org). Depois, é preciso ter o MySQL instalado (www.mysql.com). Por último, a nossa peça de ligação entre os dois: sourceforge.net/projects/mysql-python. Na minha máquina, o processo não demorou mais de 2 minutos graças ao gestor de pacotes do Ubuntu. Antes de se dirigirem ao site, caso usem Linux, verifiquem se no vosso gestor de pacotes já não está uma versão disponível. Para verificarem se está tudo a funcionar, cheguem a uma qualquer consola Python e tentem importar o módulo (cuidado com as maiúsculas!):

import MySQLdb

Ligação ao servidor

O primeiro passo na ligação à base de dados é a ligação ao servidor MySQL. Por muito complicado que isto possa parecer, faz-se numa linha de código:

ligação = MySQLdb.connect('localhost', 'utilizador', 'password')

E já está. Caso isto não funcione, convém rever quer a localização do servidor, quer o nome e password do utilizador da base de dados. Claro que podemos substituir cada elemento por uma variável previamente definida pelo utilizador (ou pelo próprio script). Também podemos optar por ligar (ou tentar ligar) directamente a uma base de dados (BD). O código que se segue tira partido desta última abordagem:

nome_utilizador = 'utilizador'
palavra_chave = 'password'
servidor = 'localhost'
base_de_dados = 'teste'
MySQLdb.connect(servidor, nome_utilizador, palavra_chave, base_de_dados)

Se tudo correr bem a consola age silenciosamente, ligando-se ao servidor e à BD em questão. Mas caso nos tenhamos enganado no nome da BD, ou, um caso provável, se esta não existir, muito provavelmente aparece algo como:

Traceback (most recent call last): 
[ ...... ]
_mysql_exceptions.OperationalError: (1049, "Unknown database 'teste'") 

É-nos devolvida uma excepção, que pode ser aproveitada. De facto, uma vez perguntei numa mailing-list se não havia maneira de ligar a um servidor e verificar se dada base de dados ou tabela existia. A resposta foi consensual: mesmo que exista, lidar com estas excepções torna-se o processo mais simples. A biblioteca MySQLdb traz consigo um método que permite então lidar com as excepções: MySQLdb.Error. Chamando este método através de um except, pode-se aceder a 2 argumentos: o número do erro e a mensagem do erro. No nosso caso, vemos que o erro tem o número 1049. Vamos então melhorar o nosso código para incluir o tratamento deste erro:

try:
  connection = MySQLdb.connect(servidor , nome_utilizador, palavra_chave, base_de_dados)
except MySQLdb.Error, e:
  if e.args[0] == '1049':
    if (raw_input('Base de dados inexistente. Deseja criar?')) == 's':
      connection = MySQLdb.connect(servidor , nome_utilizador, palavra_chave)
      cursor = connection.cursor()
      cursor.execute('CREATE DATABASE %s' %base_de_dados)
      cursor.close()

Este excerto de código inclui exemplos de métodos ainda não explicados, mas cada coisa a seu tempo. De facto, como se pode observar, a excepção gerada tem 2 argumentos, sendo o primeiro, tal como já havia dito, o número do erro (e.args[0]). Podemos depois decidir como lidar com o problema. Neste caso, optei por perguntar ao utilizador se queria criar a base de dados teste, uma vez que esta ainda não existia. Caso a resposta fosse afirmativa, o programa encarregava-se de ligar ao servidor, criar um cursor, construir a query de SQL que cria a base de dados, e executar o comando.

Antes de avançarmos para a definição de cursor, e dos seus métodos, deixo antes um link para uma listagem dos erros, e respectivos códigos numéricos: dev.mysql.com/doc/refman/5.0/en/error-messages-server.html

Cursores

Um cursor não passa de um apontador para a base de dados. De facto é o que nos permite interagir com ela. Irei abordar 2 dos seus propósitos: execução de comandos e recuperação de resultados.

Como vimos no exemplo acima, caso não tivéssemos uma base de dados à qual ligar, éramos confrontados com um erro. Porém, conseguimos contornar esse erro, tirando partido, entretanto, da criação de uma base de dados a partir do nosso script. Como o conseguimos?

cursor.execute('CREATE DATABASE %s' %base_de_dados)

Este pequeno pedaço de código ilustra um método associado ao cursor que nos é indispensável: execute(). É este método que permite a interacção “directa”, por assim dizer, com a base de dados. É através dele que passamos os comandos de SQL e que fazemos perguntas, que criamos tabelas e campos, que preenchemos bases de dados, ou que as actualizamos, ou mesmo destruímos. Como vimos, a sintaxe é relativamente simples: o código SQL é passado como argumento à função execute(), sob forma de uma string. Para além do método execute(), supondo que queremos efectuar vários comandos similares de uma só vez, por exemplo, uma múltipla inserção, podemos recorrer a um método “irmão”, mais “guloso”: executemany(). A sua sintaxe é semelhante à do execute().

Construamos a lista seguinte, de modo a ser inserida na base de dados a que já estamos ligados:

listaValores = [  ('Vermelho', 'Benfica'),  ('Azul', 'Campeão'),  ('Verde', 'Sporting')] # no pun intended :)

O código do método executemany() será:

cursor.executemany("INSERT INTO tabela1 (campo1, campo2) VALUES (%s, %s)", listaValores)

A vantagem de usar este método está na performance (bem como na organização do código). De facto, só temos que aceder uma vez à base de dados, em vez de múltiplos acessos com vários execute().

O outro método relevante na interacção com as bases de dados é o método fetchone(). Até aqui, tínhamos apenas interagido com a base de dados numa relação unidireccional (utilizador -> base de dados). Contudo, o método fetchone(), bem como os associados fetchmany() e fetchall(), permitem-nos tornar essa interacção bidireccional, tornando possível ao utilizador recuperar informação inserida na base de dados. A sintaxe do comando é relativamente simples. Após um execute() com uma string de SQL que envolva um SELECT e que nos devolva N resultados, podemos aceder aos resultados usando uma das três funções – fetchone(), fetchmany(), fetchall() – consoante queiramos um, vários, ou todos os resultados. Para definirmos o número de resultados a ver no fetchmany() usamos outro método do cursor: arraysize. Este, aceita um valor inteiro que, por defeito, é 1. Segue-se um exemplo:

resultado = cursor.fetchone()

Este método devolve uma tupla de resultados, contendo uma linha por cada resultado, e caso o SQL não devolva resultados, o método devolve None. Simples e eficaz. Para aceder ao conteúdo da linha podemos ou imprimir a variável linha ou, caso queiramos uma coluna em específico, aceder tal como a uma lista: resultado[0], resultado[1], etc.

Outro método útil é o rowcount(). Este devolve o número de linhas afectadas/produzidas pelo último execute() (ou executemany()). Exemplificando, um SELECT que devolva um resultado de uma linha, tem um rowcount() igual a 1, enquanto um INSERT que afecte, digamos, 3 linhas, tal como o caso dos clubes lá em cima, dá um rowcount() igual a 3.

Por fim, um último método que deve ser sempre chamado no final de todos os acessos à base de dados, é o commit. Chama-se tendo como classe-mãe a connection e sem argumentos. Depois deste commit, fecha-se o acesso à base de dados, pelo método close, também derivado da classe connection. Pondo estas ideias na prática:

connection.commit()
connection.close()

Conclusão

Como vimos, interagir com bases de dados em MySQL através de scripts de Python torna-se, graças à MySQLlib, como dizem os ingleses, “a walk in the park”, ou, em bom português, “como tirar o doce a uma criança”. Uma vantagem interessante desta biblioteca, é que o seu código é exactamente igual ao usado por outras bibliotecas que acedem a outros provedores de bases de dados, como a SQLite ou o PostgreSQL. Deixo, em jeito de despedida, links para bibliotecas em Python para fazer ligação a outras bases de dados, bem como o desafio de lhes aplicarem este código e, ou me dizerem que funciona, ou me enviarem hate-mails porque não funciona!

PostgreSQL:

SQLite:

  • sqlite3 (incluída com o Python 2.5)

Publicado na edição 14 (PDF) da Revista PROGRAMAR.