{ "metadata": { "name": "Capitulo28_Banco_de_dados" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[Python para Desenvolvedores](http://ricardoduarte.github.io/python-para-desenvolvedores/#conteudo)\n", "===================================\n", "2ª edi\u00e7\u00e3o, revisada e ampliada\n", "-----------------------------------\n", "\n", "Cap\u00edtulo 28: Banco de dados\n", "=============================\n", "_____________________________\n", "Sistemas Gerenciadores de Banco de Dados (SGBDs) s\u00e3o reconhecidos por prover uma forma de acesso consistente e confi\u00e1vel para informa\u00e7\u00f5es.\n", "\n", "A maioria dos SGDB atuais s\u00e3o baseados no modelo relacional, no qual as informa\u00e7\u00f5es s\u00e3o representadas na forma de tabelas. Geralmente, estas tabelas podem ser consultadas atrav\u00e9s de uma linguagem especializada para isso, chamada SQL (*Structured Query Language*).\n", "\n", "![Cliente Servidor](files/bpypd_diags20.png)\n", "\n", "Geralmente, os SGBDs utilizam a arquitetura cliente-servidor. Os aplicativos usam a API cliente para poder se comunicar com o servidor, que \u00e9 o respons\u00e1vel por receber as consultas dos clientes, interpretar as senten\u00e7as SQL e recuperar os dados com um tempo de resposta adequado.\n", "\n", "Para fazer isso, o servidor precisa realizar uma s\u00e9rie de outras tarefas, tais como: verificar credenciais, controlar o acesso, gerenciar conex\u00f5es de rede, manter a integridade dos dados, otimizar as consultas e resolver quest\u00f5es de concorr\u00eancia.\n", "\n", "No Python, a integra\u00e7\u00e3o com SGBDs \u00e9 feita na maioria dos casos atrav\u00e9s de um m\u00f3dulo DBI., que usa a API cliente para se comunicar com o banco de dados.\n", "\n", "DBI\n", "---\n", "*Database Interface* (DBI) \u00e9 uma especifica\u00e7\u00e3o que descreve como deve ser o comportamento de um m\u00f3dulo de acesso a sistemas de banco de dados.\n", "\n", "![Diagrama DBI](files/bpypd_diags21.png)\n", "\n", "A DBI define que o m\u00f3dulo deve ter uma fun\u00e7\u00e3o `connect()`, retorna objetos de conex\u00e3o. A partir do do objeto conex\u00e3o, \u00e9 poss\u00edvel obter um objeto cursor, que permite a execu\u00e7\u00e3o de senten\u00e7as SQL e a recupera\u00e7\u00e3o dos dados (uma lista de tuplas com os resultados, por *default*).\n", "\n", "MySQL\n", "-----\n", "O MySQL \u00e9 um SGBD cliente-servidor reconhecido pelo bom desempenho e \u00e9 bastante usado como backend para aplica\u00e7\u00f5es Web.\n", "\n", "Exemplo de acesso atrav\u00e9s de DBI com [MySQL](http://sourceforge.net/projects/mysql-python):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import MySQLdb\n", "\n", "# Cria uma conex\u00e3o\n", "con = MySQLdb.connect(db='test', user='root', passwd='root123')\n", "\n", "# Cria um cursor\n", "cur = con.cursor()\n", "\n", "# Executa um comando SQL\n", "cur.execute('show databases')\n", "\n", "# Recupera o resultado\n", "recordset = cur.fetchall()\n", "\n", "# Mostra o resultado\n", "for record in recordset:\n", " print record\n", "\n", "# Fecha a conex\u00e3o\n", "con.close()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "('information_schema',)\n", "('fouling',)\n", "('mysql',)\n", "('performance_schema',)\n", "('s3p',)\n", "('test',)\n", "('test_s3p',)\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "O resultado \u00e9 composto por uma lista de tuplas com as databases dispon\u00edveis no servidor.\n", "\n", "SQLite\n", "------\n", "A partir da vers\u00e3o 2.5, o Python passou a incorporar em sua distribui\u00e7\u00e3o um m\u00f3dulo DBI para acessar o [SQLite](http://www.sqlite.org/).\n", "\n", "SQLite \u00e9 uma biblioteca *Open Source* escrita em linguagem C, que implementa um interpretador SQL, e prov\u00ea funcionalidades de banco de dados, usando arquivos, sem a necessidade de um processo servidor separado ou de configura\u00e7\u00e3o manual.\n", "\n", "Exemplo:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import sqlite3\n", "\n", "# Cria uma conex\u00e3o e um cursor\n", "con = sqlite3.connect('emails.db')\n", "cur = con.cursor()\n", "\n", "# Cria uma tabela\n", "sql = 'create table emails '\\\n", " '(id integer primary key, '\\\n", " 'nome varchar(100), '\\\n", " 'email varchar(100))'\n", "cur.execute(sql)\n", "\n", "# senten\u00e7a SQL para inserir registros\n", "sql = 'insert into emails values (null, ?, ?)'\n", "\n", "# Dados\n", "recset = [('jane doe', 'jane@nowhere.org'),\n", " ('rock', 'rock@hardplace.com')]\n", "\n", "# Insere os registros\n", "for rec in recset:\n", " cur.execute(sql, rec)\n", "\n", "# Confirma a transa\u00e7\u00e3o\n", "con.commit()\n", "\n", "# Seleciona todos os registros\n", "cur.execute('select * from emails')\n", "\n", "# Recupera os resultados\n", "recset = cur.fetchall()\n", "\n", "# Mostra\n", "for rec in recset:\n", " print '%d: %s(%s)' % rec\n", "\n", "# Fecha a conex\u00e3o\n", "con.close()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "1: jane doe(jane@nowhere.org)\n", "2: rock(rock@hardplace.com)\n" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "A vantagem mais significativa de usar o SQLite \u00e9 a praticidade, principalmente no uso em aplicativos locais para desktops, aonde usar um SGBD convencional seria desnecess\u00e1rio e complicado de manter.\n", "\n", "Firebird\n", "--------\n", "[Firebird](http://www.firebirdsql.org/) \u00e9 um SGBD cliente-servidor leve, por\u00e9m com muitos recursos. Programas em Python podem se comunicar com ele atrav\u00e9s do *driver* DBI [FDB](http://www.firebirdsql.org/en/development-python-driver/).\n", "\n", "Exemplo:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import fdb\n", "\n", "#Para criar a base no Ubuntu com Firebird 2.5\n", "# isql-fb\n", "# create database '/tmp/cds.fdb';\n", "#\n", "# conecta o Firebird\n", "con = fdb.connect(dsn='localhost:/tmp/cds.fdb',\n", " user='sysdba', password='sysdba')\n", "\n", "# Cria um objeto cursor\n", "cur = con.cursor()\n", "\n", "sql = \"create table cds(\"\\\n", "\"nome varchar(20),\"\\\n", "\"artista varchar(20),\"\\\n", "\"ano integer,\"\\\n", "\"faixas integer,\"\\\n", "\"primary key(nome, artista, ano));\"\n", "\n", "# Cria uma tabela\n", "cur.execute(sql)\n", "\n", "# Grava as modifica\u00e7\u00f5es\n", "con.commit()\n", "\n", "dados = [\n", " ('IV', 'Led Zeppelin', 1971, 8),\n", " ('Zenyatt\u00e0 Mondatta', 'The Police', 1980, 11),\n", " ('OK Computer', 'Radiohead', 1997, 12),\n", " ('In Absentia', 'Porcupine Tree', 2002, 12),\n", " ]\n", "\n", "# Insere os registros e faz a interpola\u00e7\u00e3o\n", "insert = \"insert into cds\"\\\n", "\"(nome, artista, ano, faixas) values (?, ?, ?, ?)\"\n", "cur.executemany(insert, dados)\n", "con.commit()\n", "\n", "# Consulta os registros\n", "cur.execute(\"select * from cds order by ano\")\n", "\n", "# Recupera os resultados\n", "for reg in cur.fetchall():\n", " # Formata e imprime\n", " print ' - '.join(str(i) for i in reg)" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "DatabaseError", "evalue": "('Error while connecting to database:\\n- SQLCODE: -923\\n- connection rejected by remote interface', -923, 335544421)", "output_type": "pyerr", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mDatabaseError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;31m# conecta o Firebird\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m con = fdb.connect(dsn='localhost:/tmp/cds.fdb',\n\u001b[1;32m----> 9\u001b[1;33m user='sysdba', password='sysdba')\n\u001b[0m\u001b[0;32m 10\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[1;31m# Cria um objeto cursor\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m/usr/local/lib/python2.7/dist-packages/fdb/fbcore.pyc\u001b[0m in \u001b[0;36mconnect\u001b[1;34m(dsn, user, password, host, port, database, sql_dialect, role, charset, buffers, force_write, no_reserve, db_key_scope, isolation_level, connection_class)\u001b[0m\n\u001b[0;32m 691\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mdb_api_error\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0m_isc_status\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 692\u001b[0m raise exception_from_status(DatabaseError, _isc_status,\n\u001b[1;32m--> 693\u001b[1;33m \"Error while connecting to database:\")\n\u001b[0m\u001b[0;32m 694\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 695\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mconnection_class\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0m_db_handle\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdpb\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msql_dialect\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcharset\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mDatabaseError\u001b[0m: ('Error while connecting to database:\\n- SQLCODE: -923\\n- connection rejected by remote interface', -923, 335544421)" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como o Firebird n\u00e3o requer muita pot\u00eancia e nem muito esfor\u00e7o para administra\u00e7\u00e3o, ele pode ser usado tanto como servidor, quanto ser empacotado junto com um aplicativo *desktop*.\n", "\n", "PostgreSQL\n", "----------\n", "Para sistemas que demandam recursos mais sofisticados do SGBD, o [PostgreSQL](http://www.postgresql.org/) \u00e9 a solu\u00e7\u00e3o *Open Source* mais completa dispon\u00edvel. O software segue a arquitetura cliente-servidor e \u00e9 distribu\u00eddo sob a licen\u00e7a BSD.\n", "\n", "Entre os recursos oferecidos pelo PostgreSQL, destacam-se:\n", "\n", "+ Suporte a consultas complexas.\n", "+ Transa\u00e7\u00f5es.\n", "+ Controle de concorr\u00eancia multi-vers\u00e3o.\n", "+ Tipos de objetos definidos pelo usu\u00e1rio.\n", "+ Heran\u00e7a.\n", "+ *Views*.\n", "+ *Stored Procedures*.\n", "+ *Triggers*.\n", "+ *Full text search*.\n", "\n", "Existem v\u00e1rios m\u00f3dulos que prov\u00eam acesso ao PostgreSQL para o Python, como o [PygreSQL](http://www.pygresql.org/) e o [Psycopg](http://www.initd.org/psycopg/).\n", "\n", "O PygreSQL oferece duas interfaces distintas para acesso a servidores PostgreSQL:\n", "\n", "+ *pgdb*: m\u00f3dulo compat\u00edvel com DBI.\n", "+ *pg*: m\u00f3dulo mais antigo, incompat\u00edvel com DBI.\n", "\n", "Exemplo com *pgdb*:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import pgdb\n", "\n", "# Para bancos de dados locais (via Unix Domain Sockets)\n", "#con = pgdb.connect(database='music')\n", "\n", "# Via TCP/IP\n", "con = pgdb.connect(host='tao', database='music', user='pg', password='pg123')\n", "cur = con.cursor()\n", "\n", "# Cria uma tabela\n", "sql = 'create table tracks '\\\n", " '(id serial primary key, '\\\n", " 'track varchar(100), '\\\n", " 'band varchar(100))'\n", "cur.execute(sql)\n", "\n", "# A interpola\u00e7\u00e3o usa uma nota\u00e7\u00e3o semelhante a do Python\n", "sql = 'insert into tracks values (default, %s, %s)'\n", "\n", "# Dados\n", "recset = [('Kashmir', 'Led Zeppelin'),\n", " ('Starless', 'King Crimson')]\n", "\n", "# Insere os registros\n", "for rec in recset:\n", " cur.execute(sql, rec)\n", "\n", "con.commit()\n", "\n", "# Recupera os registros\n", "cur.execute('select * from tracks')\n", "\n", "# Recupera os resultados\n", "recset = cur.fetchall()\n", "# Mostra\n", "for rec in recset:\n", " print rec\n", "\n", "con.close()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[1, 'Kashmir', 'Led Zeppelin']\n", "[2, 'Starless', 'King Crimson']\n" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exemplo com *pg*:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import pg\n", "# Para bancos de dados locais (via Unix Domain Sockets)\n", "#con = pg.connect('music')\n", "\n", "# Via TCP/IP\n", "con = pg.connect(host='tao', dbname='music', user='pg', passwd='pg123')\n", "\n", "# Realiza uma consulta no banco\n", "qry = con.query('select * from tracks')\n", "\n", "# Pega a lista de campos\n", "flds = qry.listfields()\n", "\n", "# Mostra os resultados\n", "for rec in qry.dictresult():\n", " for fld in flds:\n", " print '%s: %s' % (fld, rec[fld])\n", " print\n", "\n", "con.close()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "id: 1\n", "track: Kashmir\n", "band: Led Zeppelin\n", "\n", "id: 2\n", "track: Starless\n", "band: King Crimson\n", "\n" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exemplo usando o *Psycopg*:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import psycopg2\n", "\n", "# Para bancos de dados locais (via Unix Domain Sockets)\n", "#con = psycopg2.connect(database='music')\n", "\n", "# Via TCP/IP\n", "con = psycopg2.connect(host='tao', database='music',\n", " user='pg', password='pg123')\n", "cur = con.cursor()\n", "\n", "sql = 'insert into tracks values (default, %s, %s)'\n", "recset = [('Siberian Khatru', 'Yes'),\n", " (\"Supper's Ready\", 'Genesis')]\n", "for rec in recset:\n", " cur.execute(sql, rec)\n", "con.commit()\n", "\n", "cur.execute('select * from tracks')\n", "recset = cur.fetchall()\n", "for rec in recset:\n", " print rec\n", "\n", "con.close()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "(1, 'Kashmir', 'Led Zeppelin')\n", "(2, 'Starless', 'King Crimson')\n", "(3, 'Siberian Khatru', 'Yes')\n", "(4, \"Supper's Ready\", 'Genesis')\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como o m\u00f3dulo segue fielmente a especifica\u00e7\u00e3o DBI, o c\u00f3digo \u00e9 praticamente igual ao exemplo usando o m\u00f3dulo *pg*. O *Psycopg* foi projetado com o objetivo de suportar aplica\u00e7\u00f5es mais pesadas, com muitas inser\u00e7\u00f5es e atualiza\u00e7\u00f5es.\n", "\n", "Tamb\u00e9m \u00e9 poss\u00edvel escrever fun\u00e7\u00f5es para PostgreSQL usando Python. Para que isso seja poss\u00edvel, \u00e9 preciso habilitar o suporte ao Python no banco, atrav\u00e9s do utilit\u00e1rio de linha de comando pelo administrador:\n", "\n", " createlang plpythonu \n", "\n", "As linguagens que podem usadas pelo PostgreSQL s\u00e3o chamadas *Procedural Languages* (PL) e o sufixo \u201cu\u201d significa *untrusted*.\n", "\n", "Os tipos dos par\u00e2metros e do retorno da fun\u00e7\u00e3o devem ser definidos durante a cria\u00e7\u00e3o da fun\u00e7\u00e3o no PostgreSQL.\n", "\n", "Exemplo de fun\u00e7\u00e3o:\n", "\n", " create function pformat(band text, track text)\n", " returns text\n", " as $$\n", " return '%s - %s' % (band, track)\n", " $$ language plpythonu;\n", "\n", "O c\u00f3digo em Python est\u00e1 entre os s\u00edmbolos `$$`.\n", "\n", "Sa\u00edda da fun\u00e7\u00e3o (atrav\u00e9s do psql):\n", "\n", " music=> select pformat(track, band) from tracks;\n", " pformat \n", " -------------------------\n", " Kashmir - Led Zeppelin\n", " Starless - King Crimson\n", " Yes - Siberian Khatru\n", " Genesis - Supper's Ready\n", " (4 registros)\n", "\n", "O ambiente de execu\u00e7\u00e3o de Python no PostgreSQL prov\u00ea o m\u00f3dulo *plpy* (importado automaticamente) que \u00e9 uma abstra\u00e7\u00e3o para o acesso aos recursos do SGBD.\n", "\n", "Exemplo com *plpy*:\n", "\n", " create function inibands()\n", " returns setof text\n", " as $$\n", " bands = plpy.execute('select distinct band from tracks order by 1')\n", " return [''.join(filter(lambda c: c == c.upper(), list(band['band']))) for band in bands]\n", " $$ language plpythonu;\n", "\n", "Sa\u00edda da fun\u00e7\u00e3o (atrav\u00e9s do utilit\u00e1rio psql):\n", "\n", " music=> select inibands();\n", " inibands \n", " ----------\n", " KC\n", " LZ\n", " Y\n", " G\n", " (4 registros)\n", "\n", "Fun\u00e7\u00f5es escritas em Python podem ser utilizadas tanto em *Stored Procedures* quanto *Triggers* no PostgreSQL.\n", "\n", "Existem v\u00e1rios projetos que ampliam os recursos do PostgreSQL, como o [PostGis](http://postgis.refractions.net/), que prov\u00ea suporte a informa\u00e7\u00f5es espaciais, usadas em GIS (*Geographic Information Systems*).\n", "\n", "Mapeamento objeto-relacional\n", "----------------------------\n", "*Object-Relational Mapper* (ORM) \u00e9 uma camada que se posiciona entre o c\u00f3digo com a l\u00f3gica da aplica\u00e7\u00e3o e o m\u00f3dulo DBI, com o objetivo de reduzir as dificuldades geradas pelas diferen\u00e7as entre a representa\u00e7\u00e3o de objetos (da linguagem) e a representa\u00e7\u00e3o relacional (do banco de dados).\n", "\n", "![Mapeamento objeto-relacional](files/bpypd_diags22.png)\n", "\n", "Com o uso de um ORM:\n", "\n", "+ A aplica\u00e7\u00e3o se torna independente do SGDB.\n", "+ O desenvolvedor n\u00e3o precisa usar SQL diretamente.\n", "+ A l\u00f3gica para gerenciamento das conex\u00f5es \u00e9 realizada de forma transparente pelo ORM.\n", "\n", "Exemplo de ORM (com [SQLAlchemy](http://www.sqlalchemy.org/)):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from sqlalchemy import *\n", "\n", "# URL => driver://username:password@host:port/database\n", "# No SQLite:\n", "# sqlite:// (mem\u00f3ria)\n", "# sqlite:///arquivo (arquivo em disco)\n", "db = create_engine('sqlite:///progs.db')\n", "\n", "# Torna acess\u00edvel os metadados\n", "metadata = MetaData(db)\n", "\n", "# Ecoa o que SQLAlchemy est\u00e1 fazendo\n", "metadata.bind.echo = True\n", "\n", "# Tabela Prog\n", "prog_table = Table('progs', metadata,\n", " Column('prog_id', Integer, primary_key=True),\n", " Column('name', String(80)))\n", "\n", "# Cria a tabela\n", "prog_table.create()\n", "\n", "# Carrega a defini\u00e7\u00e3o da tabela\n", "prog_table = Table('progs', metadata, autoload=True)\n", "\n", "# Insere dados\n", "i = prog_table.insert()\n", "i.execute({'name': 'Yes'}, {'name': 'Genesis'},\n", " {'name': 'Pink Floyd'}, {'name': 'King Crimson'})\n", "\n", "# Seleciona\n", "s = prog_table.select()\n", "r = s.execute()\n", "\n", "for row in r.fetchall():\n", " print row" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:18,932 INFO sqlalchemy.engine.base.Engine \n", "CREATE TABLE progs (\n", "\tprog_id INTEGER NOT NULL, \n", "\tname VARCHAR(80), \n", "\tPRIMARY KEY (prog_id)\n", ")\n", "\n", "\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:18,933 INFO sqlalchemy.engine.base.Engine ()\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,126 INFO sqlalchemy.engine.base.Engine COMMIT\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,141 INFO sqlalchemy.engine.base.Engine INSERT INTO progs (name) VALUES (?)\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,141 INFO sqlalchemy.engine.base.Engine (('Yes',), ('Genesis',), ('Pink Floyd',), ('King Crimson',))\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,143 INFO sqlalchemy.engine.base.Engine COMMIT\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,253 INFO sqlalchemy.engine.base.Engine SELECT progs.prog_id, progs.name \n", "FROM progs\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "2013-08-16 10:27:19,254 INFO sqlalchemy.engine.base.Engine ()\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "(1, u'Yes')\n", "(2, u'Genesis')\n", "(3, u'Pink Floyd')\n", "(4, u'King Crimson')\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Al\u00e9m dos SQLAlchemy, tamb\u00e9m existem dispon\u00edveis para Python o [SQLObject](http://www.sqlobject.org/) e ORMs que integram *frameworks* maiores, como o Django." ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "" ], "output_type": "pyout", "prompt_number": 1, "text": [ "" ] } ], "prompt_number": 1 } ], "metadata": {} } ] }