Escrevendo Scripts MySQL com Python DB-API

Paul DuBois paul (at) kitebird dot com
Traduzido por Ielton Ferreira Carneiro Pinto


  1. Instalação do MySQLdb
  2. Um pequeno script DB-API
  3. Um script DB-API mais extenso
  4. Notas de portabilidade
  5. Recursos
  6. Apêndice
  7. Reconhecimento


A Python é uma das mais populares linguagens de programação Open Source, deve isso largamente a sua própria expressividade assim como a variedade de módulos de suporte que estão disponíveis para estender suas capacidades. Um desses módulos é o DB-API, que, como o nome implica, fornece uma API (interface de programação de aplicativos) para banco de dados. DB-API é projetado para ser relativamente independente dos detalhes específicos de qualquer engine dado, para ajuda-lo escrever scripts de acesso a banco de dados que são portáveis entre engines.

O design do DB-API é similar aquele usado por Perl e módulos DBI Ruby, a classe PHP PEAR DB, e a interface Java JDBC: Ela usa uma arquitetura de dois níveis na qual o nível superior fornece uma interface abstrata que é similar para todos engines de banco de dados suportados, e o nível baixo consistente de drivers para engines específicos que controlam detalhes de dependências do engine. Isso significa, naturalmente, que para usar DB-API para escrever scripts Python, você deve ter um driver para seu sistema de banco de dados particular. Para MySQL, o DB-API fornece acesso ao banco de dados por meio do driver MySQLdb. Esse artigo começa discutindo a instalação do driver (no caso de você não ter o MySQLdb), então move-o para o inicio de como escrever scripts DB-API.

1. Instalação do MySQLdb

Para escrever scripts MySQL que usam DB-API, o próprio Python deve ser instalado. Isso será quase que certamente verdade se você estiver usando Unix, mas é menos provável para Windows. Os instaladores para cada plataforma podem ser encontrados no Web site da Python (veja a seção dos "recursos" na extremidade deste artigo).

Em seguida, verifique que sua versão do Python é 1.5.2 ou mais velha, e que o módulo MySQLdb está instalado. Você pode checar ambas exigências rodando o Python no modo interativo apartir da linha de comando no prompt (algo como % para Unix ou C:\ para Windows):

 $ python 
 >>> import sys 
 >>> sys.version 
 '1.5.2 (#1, Aug 25 2000, 09:33:37)  [GCC 2.96 20000731 (experimental)]' 
 >>> import MySQLdb

Supondo que você tem uma versão bem recente do Python e que nenhum erro ocorreu quando você emite a declaração import MySQLdb, você está pronto para começar a escrever scripts com acesso a banco de dados e você pode pular a próxima seção. Entretanto, se você receber o seguinte erro, você precisa obter e instalar o MySQLdb primeiro:

>>> import MySQLdb
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ImportError: No module named MySQLdb

Para obter MySQLdb, visite a seção "Recursos" para ver onde buscar uma distribuição apropriada para seu sistema. Os binários pré-compilados estão disponíveis para várias plataformas (RedHat Linux, Debian GNU/Linux Linux, Windows), ou você pode instalar apartir dos arquivos fonte. Se você usa uma distribuição binária, instale-o usando o procedimento de instalação do pacote usual de sua plataforma. Para construir e instalar apartir dos arquivos fonte, vá para o diretório raiz da distribuição MySQLdb e emita os seguintes comandos. (Sob Unix, é provável que você precisará executar o segundo comando como root de modo que os arquivos do driver possam ser copiadas dentro da sua instalação do Python.)

 $ python setup.py build
 $ python setup.py install

Se o script setup.py falhar porque ele não pode achar o módulo Distutils, você precisará satisfazer um pré-requisito adicional para instalar o Distutils. (MySQLdb suporta Python 2.4, mas o Distutils é incluso somente com Python como da versão 1.6) A seção "Recursos" indica onde obter esse módulo. Se você encontrar outro problema, cheque o arquivo README incluso com a distribuição MySQLdb.

2. Um pequeno script DB-API

Scripts que acessam MySQL através do DB-API usando MySQLdb executam geralmente as seguintes etapas:

O resto dessa seção apresenta um pequeno script DB-API que explica os elementos básicos dessas etapas. As seções posteriores discutem aspectos específicos da escrita de script em mais detalhes.

2.1. Escrevendo o script

Use um editor de textos para criar um arquivo chamado server_version.py que contém o seguinte script. Esse script usa MySQLdb para interagir com o servidor MySQL, entretanto na forma relativamente rudimentar -- todo ele pergunta o servidor por sua string de versão:

   # server_version.py - Obtém e mostra a versão do servidor de banco de dados

import MySQLdb

conn = MySQLdb.connect (host = "localhost", user = "testuser", passwd = "testpass", db = "test") cursor = conn.cursor () cursor.execute ("SELECT VERSION()") row = cursor.fetchone () print "server version:", row[0] cursor.close () conn.close ()

A declaração import diz ao Python que o script precisa usar o código no módulo MySQLdb. Essa declaração deve anteceder a qualquer tentativa de conexão ao servidor MySQL. Então a conexão é estabilizada invocando o método connect() do driver MySQLdb e especificando os parâmetros apropriados da conexão. Esses incluem o hostname onde o servidor está rodando, o usuário e a senha da sua conta MySQL, e o nome do banco de dados que você quer usar. A sintaxe da lista do argumento do connect() varia entre os drivers; para MySQLdb, os argumentos permitidos são dados no formato nome = valor, que tem a vantagem que você pode especifica-lo em qualquer ordem. O script server_version.py faz uma conexão com o servidor MySQL no localhost para acessar o banco de dados test com o nome de usuário testuser e a senha testpass.

   conn = MySQLdb.connect (host = "localhost",
                           user = "testuser",
                           passwd = "testpass",
                           db = "test")

Se a chamada connect() suceder, ela retorna um objeto da conexão que serve como base para interação adicional com MySQL. Se a chamada falhar, ela levanta uma exceção. (O script server_version.py não negocia essa exceção, assim um erro nesse ponto termina o script. A manipulação de erro é coberta mais tarde nesse artigo.)

Depois que o objeto da conexão foi obtido, o script server_version.py invoca seu método cursor() para criar um objeto cursor para processar as consultas. O script usa esse cursor para emitir uma declaração SELECT VERSION(), que retorna uma string contendo a informação da versão do servidor:

   cursor = conn.cursor ()
   cursor.execute ("SELECT VERSION()")
   row = cursor.fetchone ()
   print "server version:", row[0]
   cursor.close ()

O método execute() do objeto cursor envia a consulta para o servidor e o flechone() recupera uma linha como uma tupla. Para a consulta mostrada aqui, a tupla contém um único valor, que imprime o script. (se nenhuma linha estiver disponível, o flechone() realmente retornará o valor None; o server_version.py alegremente supõe que isso não acontecerá, uma suposição que você normalmente não deve fazer. Nos exemplos posteriores, iremos lidar com esse caso.) Os objetos do cursor execute() pode ser usados para enviar multiplas consultas, mas o server_version.py na precisa do cursor depois de pegar a string da versão, então ele se fecha.

Finalmente, o script invoca o método close() do objeto da conexão para desconectar do servidor.

   conn.close ()

Depois disso, conn torna-se inválido e não deve ser usado para acessar o servidor.

2.2. Executando o script

Para executar o script server_version.py, chame o Python na linha de comando e diga para ele o nome do script. Você deve ver um resultado como esse:

   $ python server_version.py
   server version: 4.0.9-gamma-log

Isso indicar que a versão do servidor MySQL é 4.0.9; os sufixos -gamma e -log dizem-nos o nível de estabilidade da distribuição e que query logging está habilitado. (Você pode ver outros sufixos que aqueles mostrados aqui. Por exemplo, se você tem debugging habilitado, você verá um sufixo -debug.)

É possível definir o script de modo que ele possa ser executado pelo nome sem invocar o Python explicitamente. No Unix, adicione #! na linha inicial, que especifica o caminho completo do interpretador Python. Isso diz ao sistema que o programa deve executar o script. Por exemplo, se o Python estiver em /usr/bin/python no seu sistema, adicione a seguinte como a primeira linha do seu script:

   #!/usr/bin/python

Então use chmod para fazer o script executável, e você e poderá executa-lo diretamente:

   
  $ chmod +x server_version.py
  $ ./server_version.py

(O primeiro ./ diz explicitamente ao seu interpretador que o script está localizado no diretório atual. Muitas contas Unix são definidas para não procurar o diretório atual ao procurar pro comandos.)

No Windows, a linha #! é desnecessária (embora seja inofensivo, então você não precisa remove-lo se você escreve o script em sistemas Unix e então move-lo para um sistema Windows). Para sistemas baseados em Windows NT, você pode definir a associação de nome de arquivo de modo que os scripts .py sejam associados ao Python. Em vez de usar chmod para fazer os scripts executáveis, abra o item Opções de Pastas no Painel de Controle e selecione a aba Tipos de Arquivos. Isso permite a você definir a associação para arquivos terminados em .py para dizer ao Windows executa-los com Python. Então você pode executar o script pelo nome:

   C:\> server_version.py

Se você instalar o ActiveState Python no Windows, o instalador do ActiveState irá definir a associação de arquivos automaticamente como parte do processo.

3. Um script DB-API mais extenso

O script server_version.py tem um pequeno número de falhas. Por exemplo, ele não pega exceções ou indica o que deu errado se ocorrer um erro, e ele não possibilita que a consulta rode sem poder retornar qualquer resultado. Essa seção mostra como endereçar essas emições usando um script mais elaborado, o animal.py, que usa uma tabela (animal) contendo nomes e categorias:

   CREATE TABLE animal
   (
       name CHAR(40),
       category CHAR(40)
   )

Se você ler o artigo PEAR DB disponível no sítio da Kitebird (veja em "Recursos"), você pode reconhecer esta tabela e algumas das consultas emitidas pelo script animal.py; foram usados nesse artigo demasiadamente.

O script animal.py inicia como esse (incluindo a linha #!, se você pretende rodar esse script em sistemas Unix):

   #!/usr/bin/python
   # animal.py - cria a tabela animal e obtém informações dela

import sys import MySQLdb

Como com server_version.py, o script importa o MySQLdb, mas ele também importa o módulo sys para usar na manipulação de erro. O animal.py usa sys.exit() para retornar 0 ou 1 para indicar a finalização normal ou que um erro ocorreu.)

3.1. Manipulação de Erro

Depois de importar os módulos requisitados, o animal.py estabelece uma conexão ao servidor usando a chamada connect(). Para permitir a possibilidade de falha na conexão (por exemplo, de modo que você possa indicar a razão para a falha), isso é necessário para pegar as exceções. Para manipular as exceções no Python, coloque seu código em um bloco try e inclua um bloco except que contém o código de manipulação de erro. A sequência resultante da conexão parece com isso:

   try:
       conn = MySQLdb.connect (host = "localhost",
                               user = "testuser",
                               passwd = "testpass",
                               db = "test")
   except MySQLdb.Error, e:
       print "Error %d: %s" % (e.args[0], e.args[1])
       sys.exit (1)

A linha except nomeia uma classe de exceção (MySQLdb.Error nesse exemplo) para obter a informação do erro específico no banco de dados que o MySQLdb pode fornecer, assim como a variável e que armazena a informação. Se uma exceção ocorrer, o MySQLdb disponibiliza essa informação em e.args, uma tupla de elemento duplo, que contém o código númerico de erro e uma string descrevendo o erro. O bloco except mostra no exemplo, imprime ambos valores e sai.

Qualquer declaração de banco de dados relacionados podem ser colocadas em uma extrutura similar a try/except para pegar e relatar erros; para brevidade, a seguinte discussão não mostra o código de manipulação de exceções. (O texto completo de animal.py é listado no apêndice.)

3.2. Métodos para enviar consultas

A seção seguinte de animal.py cria um objeto cursor e usa-o para emitir consultas que definem e ocupem a tabela animal:

   cursor = conn.cursor ()
   cursor.execute ("DROP TABLE IF EXISTS animal")
   cursor.execute ("""
           CREATE TABLE animal
           (
               name CHAR(40),
               category CHAR(40)
           )
       """)
   cursor.execute ("""
           INSERT INTO animal (name, category)
           VALUES
               ('cobra', 'réptil'),
               ('sapo', 'anfíbio'),
               ('atum', 'peixe'),
               ('quaxinim', 'mamífero')
       """)
   print "%d linhas foram inseridas" % cursor.rowcount

Note que esse código não inclui a checagem de erros. (Lembre que isso será colocado em um bloco try; os erros travarão as exceções que são pegas e manipuladas no bloco except correspondente, que permite que o fluxo principal do código leia mais fácil.) As consultas realizam as seguintes ações:

Cancela a tabela animal caso ela exista, e começa com uma limpa.

Cria a tabela animal.

Insere alguns dados dentro da tabela e relata o número de linhas adicionadas.

Cada consulta é emitida invocando o método execute() do objeto do cursor. As primeiras duas consultas não produzem resultado, mas a terceira produz um contagem indicando o número de linhas inseridas. (algumas interfaces de banco de dados fornecem essa contagem como valor de retorno da chamada de execução da consulta, mas isso não é válido para DB-API.)

A tabela animal é iniciada nesse ponto, então nós podemos emitir consultas SELECT para recuperar informações dela. Como com as declarações precedentes, as consultas SELECT são emitidas usando execute(). Entretanto, ao contrário das declarações tais como DROP ou INSERT, as consultas SELECT geram um resultado definido que você deve recuperar. Isso é, o execute() emite somente a consulta, ela não retorna o resultado estabelecido. Você pode usar o fetchone() para pegar as linhas uma de cada vez, ou fetchall para pegar todas elas de uma vez. o animal.py usa ambas abordagens. Aqui esta como usar o fetchone() para recuperar uma linha de cada vez:

   cursor.execute ("SELECT name, category FROM animal")
   while (1):
       row = cursor.fetchone ()
       if row == None:
           break
       print "%s, %s" % (row[0], row[1])
   print "%d linhas foram retornadas" % cursor.rowcount

O fetchone() retorna a próxima linha do resultado definido como uma tupla, ou valor None se nenhuma linha estiver disponível. O loop verifica isso e sai quando o resultado está completo. Para cada linha retornada, a tupla contém dois valores (isso é, quantas colunas a consulta SELECT pediu), que o animal.py imprime. A declaração print mostrada acima acessa um elemento da tupla. Entretanto, porque são usados em ordem de ocorrência dentro da tupla, a declaração print poderia por sorte ter sido escrita como esta:

   print "%s, %s" % row

Depois de mostrar o resultado da consulta, o script também imprime o número de linhas retornadas (disponível como o valor do atributo rowcount).

O fetchall retorna o resultado completo definindo imediatamente como uma tupla das tuplas, ou como uma tupla vazia se o resultado obtido estiver vazio. Para acessar as linhas individuais da tupla, repita completamente a linha definida que o fetchall retorna:

   cursor.execute ("SELECT name, category FROM animal")
   rows = cursor.fetchall ()
   for row in rows:
       print "%s, %s" % (row[0], row[1])
   print "%d linhas foram retornadas" % cursor.rowcount

Esse código imprime a contagem de linhas acessando rowcount, apenas como para o loop fetchone(). Outra maneira para determinar a contagem de linhas quando você usa fetchall() é pegando o comprimento do valor que ele retorna:

   print "%d linhas foram retornadas" % len (rows)

Os loops fetch mostrados aqui recuperam distantes linhas como tuplas. É também possível buscar linhas como dicionários, que permitem valores de colunas serem acessadas pelo nome. O Seguinte código mostrará como fazer isso. Note que o acesso do dicionário requer um diferente tipo de cursor, deste modo o exemplo fecha o cursor e obtem um novo que usa uma diferente classe de cursor:

   cursor.close ()
   cursor = conn.cursor (MySQLdb.cursors.DictCursor)
   cursor.execute ("SELECT name, category FROM animal")
   result_set = cursor.fetchall ()
   for row in result_set:
       print "%s, %s" % (row["name"], row["category"])
   print "%d linhas foram retornadas" % cursor.rowcount

MySQLdb tem suporte a capacidade de nomes reservados que permitem você vincular valores de dados a marcas especiais dentro de uma string de consulta. Isso fornece uma alternativa para encaixar os valores diretamente dentro da consulta. O mecanismo de nomes reservados manipula citações adicionais em torno dos valores de dados, e escapa qualquer caracter especial que ocorrer dentro dos valores. Os seguintes exemplos demonstram uma consulta UPDATE que muda cobra para tartaruga, primeiro usando valores literais e então usando espaços reservados. A consulta de valores literal se parece com isso:

   cursor.execute ("""
               UPDATE animal SET name = 'tartaruga'
               WHERE name = 'cobra'
           """)
   print "%d linhas foram atualizadas" % cursor.rowcount

Se os valores forem armazenados em variáveis, você pode enviar a mesma consulta usando espaços reservados e vinculando os valores apropriados a eles:

   cur_name = "cobra"
   new_name = "tartaruga"
   cursor.execute ("""
               UPDATE animal SET name = %s
               WHERE name = %s
           """, (new_name, cur_name))
   print "%d linhas foram atualizadas" % cursor.rowcount

Note os pontos seguintes sobre a forma de preceder a chamada execute():

Depois de enviar as consultas, o animal.py fecha o cursor, desconecta do servidor, e sai:

   cursor.close ()
   conn.close ()
   sys.exit (0)

4. Notas de portabilidade

Se você quer portar um script DB-API baseado em MySQLdb para usar com um diferente banco de dados, as seguintes coisas podem causar problemas. As fontes sem portabilidade ocorrem em qualquer lugar que o nome do driver possa ser usado. Estes incluem:

Outro tipo de importabilidade que não envolve o nome do driver consiste no uso dos espaços reservados. A especificação DB-API permite várias sintaxes para espaços reservados, e alguns drivers usam uma sintaxe que difere da suportada pelo MySQLdb.

5. Recursos

Andy Dustman, autor do módulo MySQLdb, tem um site em:

   http://dustman.net/andy/python/

Esse site é o melhor lugar para ler a documentação e FAQ online do MySQLdb. Ele também tem links para distribuições binárias para o Debian e Windows. Para pegar o código fonte ou pacotes RPMs, visite o repositório do MySQLdb na SourceForge em:

   http://sourceforge.net/projects/mysql-python

   http://www.python.org/

   http://www.python.org/sigs/distutils-sig/download.html

   http://www.python.org/sigs/db-sig/

   http://www.kitebird.com/articles/

Você pode procurar instruções para comparar esse artigo para ver como o DB-API e PEAR DB são similares ou diferentes em suas abordagens para acessar o banco de dados.

6. Apêndice

O código fonte completo para o script animal.py é mostrado aqui:

   #! /usr/bin/python
   # animal.py - cria a tabela animal e
   # recupera a informação dela

import sys import MySQLdb

# conecta ao servidor MySQL

try: conn = MySQLdb.connect (host = "localhost", user = "testuser", passwd = "testpass", db = "test") except MySQLdb.Error, e: print "Error %d: %s" % (e.args[0], e.args[1]) sys.exit (1)

# cria a tabela animal e preenche ela

try: cursor = conn.cursor () cursor.execute ("DROP TABLE IF EXISTS animal") cursor.execute (""" CREATE TABLE animal ( name CHAR(40), category CHAR(40) ) """) cursor.execute (""" INSERT INTO animal (name, category) VALUES ('cobra', 'réptil'), ('sapo', 'anfíbio'), ('atum', 'peixe'), ('guaxinim', 'mamífero') """) print "%d linhas foram inseridas" % cursor.rowcount

# realiza um loop de busca usando fetchone()

cursor.execute ("SELECT name, category FROM animal") while (1): row = cursor.fetchone () if row == None: break print "%s, %s" % (row[0], row[1]) print "%d linhas foram retornadas" % cursor.rowcount

# realiza um loop de busca usando fetchall()

cursor.execute ("SELECT name, category FROM animal") rows = cursor.fetchall () for row in rows: print "%s, %s" % (row[0], row[1]) print "%d linhas foram retornadas" % cursor.rowcount

# envia uma consulta que inclue literalmente valores de dados na # string de consulta, então faça a mesma coisa usando os espaços reservados

cursor.execute (""" UPDATE animal SET name = 'turtle' WHERE name = 'snake' """) print "%d linhas foram atualizadas" % cursor.rowcount

cur_name = "cobra" new_name = "tartaruga" cursor.execute (""" UPDATE animal SET name = %s WHERE name = %s """, (new_name, cur_name)) print "%d rows were updated" % cursor.rowcount

# cria um cursor dicionário de modo que os valores da coluna # possam ser acessados pelo nome melhor do que pela posição

cursor.close () cursor = conn.cursor (MySQLdb.cursors.DictCursor) cursor.execute ("SELECT name, category FROM animal") result_set = cursor.fetchall () for row in result_set: print "%s, %s" % (row["name"], row["category"]) print "%d linhas foram retornadas" % cursor.rowcount

cursor.close ()

except MySQLdb.Error, e: print "Error %d: %s" % (e.args[0], e.args[1]) sys.exit (1)

conn.close () sys.exit (0)

7. Reconhecimento