Apuntes_Python/01_curso/Modulo_4
2022-12-24 22:41:20 -03:00
..
4-1a_bases_de_datos.md init Apuntes Python 2022-12-24 22:41:20 -03:00
4-1b_database.db init Apuntes Python 2022-12-24 22:41:20 -03:00
4-2a_structured_query_language.sql init Apuntes Python 2022-12-24 22:41:20 -03:00
4-2b_consultas.sql init Apuntes Python 2022-12-24 22:41:20 -03:00
4-2c_multi_table_query.sql init Apuntes Python 2022-12-24 22:41:20 -03:00
4-2d_SQL_Joins.png init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3a_libreria_SQLite3.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3b_conexiones_a_bd.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3c_cursores.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3d_insert_db.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3d_prueba.db init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3e_select_db.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3f_update_db.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-3g_delete_db.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4a_ORM_mapeo_relacional_de_objetos.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4b_add_sqlalchemy.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4c_query_sqlalchemy.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4d_objetos_relacionados.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4e_relaciones_entre_objetos.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4f_relacion_muchos_a_muchos.py init Apuntes Python 2022-12-24 22:41:20 -03:00
4-4g_activ_sistema_escuela.py init Apuntes Python 2022-12-24 22:41:20 -03:00
README.md init Apuntes Python 2022-12-24 22:41:20 -03:00

Ir a: Repositorio, Modulo 3

Modulo 4 - Python basico

Bases de Datos

Indice


Bases de Datos

Por lo general un programa necesita cargar y guardar datos externos al mismo.

Si los datos son pocos y sin estructura, se pueden utilizar archivos.

Si se necesita manejar grandes volumenes de datos, y además estos datos
están relacionados, usar archivos es inviable, poco eficiente, y requieren
código mas complejo.

El tipo de base de datos mas utilizado en la actualidad son las bases
de datos relacionales
(Edgar Frank Codd, IBM, sn Jose, California 1970).

Las bases de datos pueden considerarse como un conjunto de relaciones o tabas
que contienen registros o filas y cada registro contiene campos (dato especifico).

Esquemas y Datos

  • Esquema:
    Definición de la estructura de la base de datos, almacena principalmente;
    nombre de cada tabla, nombre de cada columna, y tipo de dato de cada columna.

  • Datos:
    Son el contenido de la base de datos en un momento dado

Las bases de datos relacionales utilizan lenguaje SQL (Structured Query Languaje)

Se utilizará SQLite, por ser muy liviano.

  • SQLite3
  • SQLiteBrowser

Funcionalidad básica SQLiteBrowser

  • Database Structure : Modificar la estructura de la base de datos

  • Browser Data : Buscar, insertar o eliminar datos en una tabla

  • Edit Pragmas : Perminte modificar parametros de la DB (opcs. avanzadas)

  • Execute SQL : Ejecutar consultas SQL

El programa permite Importar y Exportar tablas, file import/export

El modelo Relacional

Está basado en la lógica de predicados y la teoría de conjuntos.

Primary Key :
Asegura que cada registro de una tabla sea único.
No se permite tener dos registros con la misma clave primaria en una misma tabla.

La llave primaria puede ser un campo o una combinación de estos, que identifiica
cada fila de una tabla de forma única.
No pueden existir dos filas que tengan la misma clave primaria.

Son valores únicos, no nulos.

   Ej. de posibles claves primarias:
   - DNI    - ISBM  - Patente
   - Nro. de tarjeta 

Foreign Key :
La clave foránea identifica una columna o grupo de columnas en una tabla (tabla
hija o referendo) que se refiere a una columna o grupo de columnas en otra tabla
(tabla maestra o referenciada, puediendo ser la misma tabla).

Una clave foránea es una limitación referencial entre dos tablas.
Clave utilizada para vincular o relacionar dos tablas

Una tabla puede contener múltiples claves foráneas referenciando tablas diferentes.

La base de datos hace cumplir las restricciones de referencia.

Se debe garantizar la integridad de los datos, sus respectivas tablas,
y filas referenciadas al actualizar o eliminar.

Integridad de Datos

El sistema de gestión de base de datos relacionales, preserva la integridad
de los datos almacenados (a medida de lo posible).

Restricciones de integridad

Integridad de dominio:
Validación de las restricciones requeridas para una determinada columna de la tabla
ej.

  • Datos Requeridos: valor de columna Not Null
  • Validacion: Asegura que los tipos de datos insertados correspondan al especificado

Integridad de entidad:
Comprobación de unicidad del valor de la clave primaria, al insertar/modificar un registro

Integridad referencial
Asegura la integridad entre las llaves foránea y primaria. (tabla refereciada/tabla referendo)

Actualizaciones de BDs que pueden corromper la integridad referencial

  • Incongruencia entre clave foránea con clave primaria al insertar una fila en la tabla referendo.

  • Incongruencia entre clave foránea con clave primaria al actualizar la clave foránea,
    a un valor inexsitente de clave primaria en la tabla refenciada.

  • Borrando una fila referenciada. Las filas de la tabla referendo quedan huérfanas.

  • Actualización de clave primaria en la tabla referenciada, cuando existe(n) referencia(s)
    a la fila modificada. Las filas de la tabla referendo quedan huérfanas.

Especificación SQL año 2003

Asegurando la integridad referencial

En caso de actualizar o eliminar registros de una tabla rederenciada (padre),
se definen 5 acciónes referenciales diferentes:

  • CASCADE
    Cuando se elminen/actualizen las filas de una tabla padre, se eliminan/actualizan
    las respectivas filas de la tabla hija. eliminacion o actualización en cascada.

  • RESTRICT
    Impide la modificación del valor de una columna de la tabla padre,
    cuando está refereciada al valor de una fila en una tabla hija.

  • NO ACTION
    Similar a RESTRICT. Pero la comprobación de integridad se realiza
    despues de intentar modificar la tabla (UPDATE o DELETE)

  • SET DEFAULT
    Cambia a un valor predefinido, el valor de referencia afectado

  • SET NULL
    Cambia a NULL el valor de referencia afectado.
    No debe estar definido NOT NULL en la columna hija.

Indices

Estructura de datos que mejora la velocidad de las operaciones, permitiendo un
rápido acceso a los registros de una tabla, de una BD.

Pueden ser definidos como únicos, actuando como restricción,
previniendo filas idénticas en el índice.
Pueden ser creados a partir de una o más columnas.
El indice ocupa espacio en disco, por lo que puediera ralentizar un INSERT,
UPDATE o DELETE.

Detalle de implementación: Los índices son construidos generalmente sobre árboles B, B+ o B*


Structured Query Language

Diseñado para administrar y consultar sistemas de gestión de bases
de datos relacionales.
Maneja álgebra y cálculo relacional para realizar consultas.

SQL es un lenguaje de definición, manipulación y contról de datos.

Permite:

  • Inserción INSERT
  • Consulta SELECT
  • Modificación UPDATE
  • Borrar DELETE

Además de la Creación CREATE y Modificación UPDATE
de esquemas (schemas) y control de acceso a los datos.

Tipos de datos básicos SQL

Variable Tipo Tipo de dato
VARCHAR Cadena de caracteres
INTEGER Enteros con o sin signo
DATE Fecha (año, mes y día)
TIME Hora (horas, minutos y segundos)
-- Creación de una tabla

CREATE TABLE nombre_tabla (
    nombre_columna1 tipo_de_dato,
    nombre_columna2 tipo_de_dato,
    nombre_columna3 tipo_de_dato,
    ....
);

Restricciones SQL

Restricción Instrucción
NOT NULL Impide que el dato sea nulo.
UNIQUE Impide que los datos se repitan.
PRIMARY KEY Identifica cada registro de una tabla de forma única.
FOREIGN KEY Identifica un registro de otra tabla de forma única.
CHECK Asegura el cumplimiento de una condición para todos los valores en una columna.
DEFAULT Valor insertado por defecto, al no especificar ninguno.
INDEX Utilizados para crear y devolver datos de forma eficiente.

Creacion de tabla

ej.

CREATE TABLE    categoria_libro (
    CODIGO INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    NOMBRE TEXT NOT NULL
);

-- Creación de una tabla a partir de otra
--CREATE TABLE nueva_tabla AS
--  SELECT columna1, columna2, ...
--  WHERE ...;

-- ej.
CREATE TABLE copia_libro AS
    SELECT ISBN, TITULO FROM libro

Modificando el esquema de una tabla

ALTER TABLE

Agregar, eliminar o modificar, columnas en una tabla:
ALTER TABLE nombre_tabla nombre_columna tipo_de_dato

Agregar o eliminar, resticciones de columnas:
ALTER TABLE nombre_tabla ALTER COLUMN

-- Agregar una columna
-- ALTER TABLE nombre_tabla ADD nombre_columna tipo_de_dato;
--ej.
ALTER TABLE libro
ADD categoria_id INTEGER REFERENCES categoria_libro(ID);

ALTER TABLE libro
ADD precio INTEGER;

-- Borrar una columna
--ALTER TABLE nombre_tabla DROP COLUMN nombre_columna;
--ej.
ALTER TABLE copia_libro
DROP COLUMN TITULO;
-- SQlite no ofrece esta funcionalidad

-- Modificar tipo de dato de columna
--ALTER TABLE nombre_tabla ALTER COLUMN nombre_columna tipo_de_dato;
--ej.
ALTER TABLE libro
ALTER COLUMN precio REAL;
-- SQlite no ofrece esta funcionalidad

-- Agregar una restricción
ALTER TABLE libro
ADD CONSTRAINT chk_price CHECK (precio>=0);
-- SQlite no ofrece esta funcionalidad

-- Borrar tabla
--DROP TABLE nombre_tabla;
DROP TABLE copia_libro;

-- Borrar datos de una tabla
--TRUNCATE TABLE table_name;
-- SQlite no ofrece esta funcionalidad

Sentencias SQL

SELECT Selecciona datos desde una db, estos son guardados en una tabla,
llamada conjunto de resultado. WHERE, filtra los registros según condición dada.

--SELECT columna1, columna2, ...
--FROM nombre_tabla;
--ej.
SELECT * FROM libro;
-- * = ALL

SELECT DISTINCT solo selecciona los valores diferentes.

--SELECT DISTINCT columna1, columna2, ...
--FROM nombre_tabla;
--ej.
SELECT DISTINCT categoria_id FROM libro;

WHERE

--SELECT columna1, columna2, ...
--FROM nombre_tabla
--WHERE condición;
--ej.
SELECT * FROM libro WHERE precio < 100;

AND , OR , NOT

--SELECT columna1, columna2, ...
--FROM nombre_tabla
--WHERE condición1 AND condición2 OR condición3 ...;
--ej.
SELECT * FROM libro where precio < 90 OR precio > 10 AND NOT precio = 150;

ORDER BY

--SELECT columna1, columna2, ...
--FROM nombre_tabla
--ORDER BY column2, column1, ... ASC|DESC;
--ej.
SELECT * FROM libro ORDER BY precio;
SELECT * FROM libro ORDER BY precio DESC;

LIMIT

SELECT * FROM libro LIMIT 1;

INSERT

--INSERT INTO nombre_tabla (columna1, columna2, columna3, ...)
--	VALUES (valor1, valor2, valor3, ...);
--ej.
INSERT INTO categoria_libro (NOMBRE) VALUES ('SCI');

INSERT INTO categoria_libro VALUES (4, 'Terror');

UPDATE

--UPDATE nombre_taba
--SET comumna1 = valor1, columna2 = valor2, ...
-- WHERE concición;
--ej.
UPDATE categoria_libro SET NOMBRE = 'Acción' WHERE CODIGO = 4;

DELETE

--DELETE FROM nombre_tabla WHERE condición;
--ej.
DELETE FROM categoria_libro WHERE CODIGO = 4;

--BORRAR REGISTROS DE LA TABLA
--DELETE FROM nombre_tabla

Join

LEFT JOIN Obtener datos de varias tablas relacionadas.

--SELECT nombre_columna(s)
--FROM tabla1
--LEFT JOIN tabla2
--ON tabla1.nombre_columna = tabla2.nombre_columna;
--ej
SELECT * FROM libro LIB LEFT JOIN categoria_libro COD ON LIB.categoria_id = COD.CODIGO

INNER JOIN Selecciona resgistros con valores coincidentes en ambas tablas.

--SELECT nombre_columna(s)
--FROM tabla1
--INNER JOIN tabla2
--ON tabla1.nombre_columna = tabla2.nombre_columna;
--ej.
SELECT * FROM libro L INNER JOIN categoria_libro C ON L.categoria_id = C.CODIGO;

Diferencias INNER JOIN & LEFT JOIN

  • LEFT JOIN
    Une la tabla de la izq. con los valores filtrados de la otra tabla.

  • INNER JOIN
    Filta los registros según condición, para AMBAS tablas.

--ej. agregado libro sin categoría.
INSERT INTO libro (ISBN, TITULO, DESCRIPCION, AUTOR, precio)
VALUES ('1111', 'Prueba', 'Prueba', 1, 50);

SELECT * FROM libro L LEFT JOIN categoria_libro C ON L.categoria_id = C.CODIGO;

Sql Joins Sql Joins


Manejo de Bases de Datos en Python

Librería SQLite3 (parte de la biblioteca estandar de Python)

Hecha en C, provee una BD liviana basada en un archivo, se guarda en el disco,
y no requiere de un proceso aparte, permite el acceso a la BD usando una variante
no standard de lenguaje SQL.

También permite trabjabar con una base de datos en memoria. Por ejemplo.

import sqlite3

# Conexión a base de datos
conn = sqlite3.connect(':memory:')

# Cursor
cursor = conn.cursor()

# Crear tabla
cursor.execute("""CREATE TABLE moneda
                    (ID integer primary key, nombre text, simbolo text)""")

# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")

# Guardar cambios
conn.commit()

# Consultar monedas
query = "SELECT * FROM moneda"

# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchall()
print(monedas)

# Cerrar conexión a la base de datos
conn.close()

Tipos de Datos Sql-Python

Tipo Python Tipo SQLite
None Null
int INTEGER
float REAL
str TEXT
bytes BLOB

SQLite puede almacenar tipos de datos adicionales usando adaptadores.

SQLite3 convierte tipos de SQLite a diferentes tipos de Python por medio de conversores.

Ejemplo adaptador

import sqlite3

class Point:
    def __init__(self, x, y):
        self.x, self.y = x,y

    def __conform__(self, protocol):
        if protocol is sqlite3.PrepareProtocol:
            return "%f,%f" % (self.x, self.y)

con = sqlite3.connect(":memory:")
cur = con.cursor()
p = Point(5.2, -3.5)
cur.execute("select ?", (p,))
print(cur.fetchone()[0])

# Conversores y adaptadores...

Apunte migración

--Crear BD
BEGIN TRANSACTION;

CREATE TABLE IF NOT EXISTS 'moneda' (
    'codigo' TEXT,
    'nombre' text,
    'symbol' text,
    PRIMARY KEY('codigo')
);

COMMIT;

--Agregar campo 'factor'
BEGIN TRANSACTION;

ALTER TABLE 'moneda'
ADD 'factor' REAL;

COMMIT;

Conexiones a base de datos

import sqlite3
import hashlib

# Conexión a base de datos
conn = sqlite3.connect(':memory:')

"Cursor: Objeto cursor"
cursor = conn.cursor()

# Crear tabla
cursor.execute("""CREATE TABLE moneda
                    (ID integer primary key, nombre text, simbolo text)""")

"commit(): hace un commit de la transancción"
conn.commit()

excecute() : Atajo que crea un cursor y ejecuta la consulta (metodo excecute del cursor,
devuelve el cursor), execute many, lo mismo pero con múliples consultas.

# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")

"rollback(): revertir cambios, al último commit"
conn.rollback()

# Consultar monedas
query = "SELECT * FROM moneda"

# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchall()
print(monedas)

# Cerrar conexión a la base de datos
conn.close()

Create Function

Permite crear una función personalizada, para ser usada dentro las sentencias SQL,
usando el nombre de la función.
Puede devolver cualquier valor soportado por SQLite:
BYTE, STRING, INT, FLOAT, NONE.

Create Agregate

Permite crear una función de agregación, para esto de debe definir una clase
que implemente los metodos step y finalize.
finalize, devuelve el resultado de la agregación, puede devolver cualquier tipo
de dato soportado por SQLite.

Ejemplo crear función

def md5sum(t):
    return hashlib.md5(t).hexdigest()

conn = sqlite3.connect(':memory:')

# create_function()
conn.create_function("md5", 1, md5sum)
cursor = conn.cursor()

# (?) es reemplazado por "a md5 hash"
cursor.execute("select md5(?)", (b"a md5 hash",))
print(cursor.fetchone()[0])

conn.close()

Ejemplo crear Agregación

class MiSum:

    def __init__(self):
        self.cont = 0

    def step(self, valor):
        self.cont += valor

    def finalize(self):
        return self.cont


conn = sqlite3.connect(':memory:')
conn.create_aggregate("misum", 1, MiSum)
cursor = conn.cursor()
cursor.execute("CREATE TABLE test(i)")
cursor.execute("INSERT INTO test(i) VALUES (1)")
cursor.execute("INSERT INTO test(i) VALUES (2)")
cursor.execute("SELECT MiSum(i) FROM test")
print(cursor.fetchone()[0])

conn.close()

Cursores

Estructura de contról, usada para recorrer los registros (y potencial el procesamiento)
de los registros del resultado de una consulta.

Metodos pricipales

  • execute:
    Ejecuta una consulta SQL, puede estar parametrizada.
    Soporta todos los tipods de 'playholders' para reemplazo
    por variables. Parametros (?) y playjolders con nombre.

  • execute many:
    Ejecuta un conjunto de parametros personalizados.

  • execute script:
    Ejecuta múltiples consultas. Ejecuta un commit antes.

  • fetchone:
    Extrae la siguiente fila del conjunto resultado de la consulta
    Devuelve una secuencia, o none si no quedan datos.

  • fetchmany:
    Extrae el sgte. conjunto de filas de la respuesta, devolviendo
    una lista, si no hay datos, devuelve una lista vacía.
    Se especifica el nro de filas a extraer o por defecto toma el
    valor de la variable 'array size' del cursor.

  • fetchall:
    Extrae todas las filas que quedan "sin consumir", en el conjunto
    resultado de la consulta. Devuelve una lista.

  • close:
    Cierra el cursor, queda inutilizado, Se levanta una excepción
    ProgrammingError si se intenta usar.

  • atributo Connection:
    Solo lectura, provee la conexión a la base de datos utilizada por el cursor.

ej.

import sqlite3

# Conexión a la base de datos
conn = sqlite3.connect(":memory:")

# Cursor
cursor = conn.cursor()

# Crear tabla
cursor.execute("""CREATE TABLE moneda
                    (ID integer primary key, nombre text, simbolo text)""")

# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")

# Guardar cambios
conn.commit()

# Consultar monedas
query = "SELECT * FROM moneda"

print("Fetch one:")
# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchone()
print(monedas)

print(cursor.fetchone())
print(cursor.fetchone())

print("Fetch all:")
monedas = cursor.execute(query).fetchall()
print(monedas)

conn.close()

Guardar objeto en base de datos

Base de datos 4-3d_prueba.db

import sqlite3

DB_PATH = './4-3d_prueba.db'

class AdminMoneda(object):

    def __init__(self, database=None):
        if not database:
            database = ':memory:'
        self.conn = sqlite3.connect(database)
        self.cursor = self.conn.cursor()

    def insert(self, obj):
        query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
        self.cursor.execute(query)
        self.conn.commit()


class Moneda(object):
    """
    Modelo Moneda
    """

    objetos = AdminMoneda(DB_PATH)

    def __init__(self, codigo, nombre, simbolo):
        self.codigo = codigo
        self.nombre = nombre
        self.simbolo = simbolo

    def __repr__(self):
        return u'{}'.format(self.nombre)


clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
eur = Moneda(codigo='USD', nombre='Dolar', simbolo='€')

Moneda.objetos.insert(clp)
Moneda.objetos.insert(usd)
Moneda.objetos.insert(eur)

Consultar Objetos

De una base de datos, desde Python

import sqlite3

DB_PATH = './4-3d_prueba.db'

class MonedaNoExiste(Exception):
    pass


class AdminMoneda(object):

    def __init__(self, database=None):
        if not database:
            database = ':memory:'
        self.conn = sqlite3.connect(database)
        self.cursor = self.conn.cursor()

    def insert(self, obj):
        query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
        self.cursor.execute(query)
        self.conn.commit()

    def get(self, codigo):
        query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
        self.cursor.execute(query)
        datos = self.cursor.fetchone()
        if not datos:
            raise MonedaNoExiste("No existe la moneda código []".format(codigo))
        return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])

    def filtro(self, **kwargs):
        codigo = kwargs.get('codigo')
        nombre = kwargs.get('nombre')
        simbolo = kwargs.get('simbolo')

        condicion = ' WHERE '
        agregar_y = False
        agregar_condicion = False

        if codigo:
            condicion += 'codigo="{}"'.format(codigo)
            agregar_condicion = True
            agregar_y = True
        if nombre:
            if agregar_y:
                condicion += ' AND '
            condicion += 'nombre="{}"'.format(nombre)
            agregar_condicion = True
            agregar_y = True
        if simbolo:
            if agregar_y:
                condicion += ' AND '
            condicion += 'simbolo="{}"'.format(simbolo)
            agregar_condicion = True

        query = 'SELECT * FROM moneda'
        if agregar_condicion:
            query += condicion

        self.cursor.execute(query)
        resultado = self.cursor.fetchall()

        monedas = []
        for datos in resultado:
            moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
            monedas.append(moneda)

        return monedas

class Moneda(object):
    """
    Modelo Moneda
    """

    objetos = AdminMoneda(DB_PATH)

    def __init__(self, codigo, nombre, simbolo):
        self.codigo = codigo
        self.nombre = nombre
        self.simbolo = simbolo

    def __repr__(self):
        return u'{}'.format(self.nombre)

ej. 4-3d

# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)

print(Moneda.objetos.get(codigo='CLP'))

# Moneda.objetos.get(codigo='ART')
print(Moneda.objetos.filtro(codigo='EUR'))
print(Moneda.objetos.filtro(nombre='Dolar'))
print(Moneda.objetos.filtro(simbolo='€'))

print(Moneda.objetos.filtro())

Actualizar Objetos

De una base de datos, desde Python

import sqlite3

DB_PATH = './4-3d_prueba.db'

class MonedaNoExiste(Exception):
    pass


class AdminMoneda(object):

    def __init__(self, database=None):
        if not database:
            database = ':memory:'
        self.conn = sqlite3.connect(database)
        self.cursor = self.conn.cursor()

    def insert(self, obj):
        query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
        self.cursor.execute(query)
        self.conn.commit()

    def get(self, codigo):
        query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
        self.cursor.execute(query)
        datos = self.cursor.fetchone()
        if not datos:
            raise MonedaNoExiste("No existe la moneda código []".format(codigo))
        return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])

    def filtro(self, **kwargs):
        codigo = kwargs.get('codigo')
        nombre = kwargs.get('nombre')
        simbolo = kwargs.get('simbolo')

        condicion = ' WHERE '
        agregar_y = False
        agregar_condicion = False

        if codigo:
            condicion += 'codigo="{}"'.format(codigo)
            agregar_condicion = True
            agregar_y = True
        if nombre:
            if agregar_y:
                condicion += ' AND '
            condicion += 'nombre="{}"'.format(nombre)
            agregar_condicion = True
            agregar_y = True
        if simbolo:
            if agregar_y:
                condicion += ' AND '
            condicion += 'simbolo="{}"'.format(simbolo)
            agregar_condicion = True

        query = 'SELECT * FROM moneda'
        if agregar_condicion:
            query += condicion

        self.cursor.execute(query)
        resultado = self.cursor.fetchall()

        monedas = []
        for datos in resultado:
            moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
            monedas.append(moneda)

        return monedas

    def update(self, old_obj, obj):
        updated = False
        add_comma = False

        query = 'UPDATE moneda SET '

        if old_obj.nombre != obj.nombre:
            query += 'nombre="{}"'.format(obj.nombre)
            updated = True
            add_comma = True
        if old_obj.simbolo != obj.simbolo:
            if add_comma:
                query += ', '
            query += 'simobolo="{}"'.format(obj.simbolo)
        if updated:
            query += ' WHERE codigo="{}"'.format(obj.codigo)
            self.cursor.execute(query)
            self.conn.commit()

    def save(self, obj):
        try:
            old_obj = self.get(codigo=obj.codigo)
        except MonedaNoExiste:
            self.insert(obj)
        else:
            self.update(old_obj, obj)

class Moneda(object):
    """
    Modelo Moneda
    """

    objetos = AdminMoneda(DB_PATH)

    def __init__(self, codigo, nombre, simbolo):
        self.codigo = codigo
        self.nombre = nombre
        self.simbolo = simbolo

    def __repr__(self):
        return u'{}'.format(self.nombre)

ej. 4-3d

# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)

ej. 4-3e

# print(Moneda.objetos.get(codigo='CLP'))
#
# Moneda.objetos.get(codigo='ART')
# print(Moneda.objetos.filtro(codigo='EUR'))
# print(Moneda.objetos.filtro(nombre='Dolar'))
# print(Moneda.objetos.filtro(simbolo='€'))
#
# print(Moneda.objetos.filtro())

peso_cl = Moneda.objetos.get(codigo='CLP')
Moneda.objetos.save(peso_cl)

print(peso_cl.codigo)
print(peso_cl.nombre)
print(peso_cl.simbolo)

peso_cl.nombre = 'Pesos (CL)'
Moneda.objetos.save(peso_cl)
peso_cl = Moneda.objetos.get(codigo='CLP')
print(peso_cl.codigo)
print(peso_cl.nombre)
print(peso_cl.simbolo)

peso_uru = Moneda(codigo='UYU', nombre='Pesos Uruguayos', simbolo='$')
Moneda.objetos.save(peso_uru)

pesos_uru = Moneda.objetos.get(codigo='UYU')
print(peso_uru.codigo)
print(peso_uru.nombre)
print(peso_uru.simbolo)

Borrar Objetos

De una base de datos, desde Python

import sqlite3

DB_PATH = './4-3d_prueba.db'

class MonedaNoExiste(Exception):
    pass


class AdminMoneda(object):

    def __init__(self, database=None):
        if not database:
            database = ':memory:'
        self.conn = sqlite3.connect(database)
        self.cursor = self.conn.cursor()

    def insert(self, obj):
        query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
        self.cursor.execute(query)
        self.conn.commit()

    def get(self, codigo):
        query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
        self.cursor.execute(query)
        datos = self.cursor.fetchone()
        if not datos:
            raise MonedaNoExiste("No existe la moneda código []".format(codigo))
        return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])

    def filtro(self, **kwargs):
        codigo = kwargs.get('codigo')
        nombre = kwargs.get('nombre')
        simbolo = kwargs.get('simbolo')

        condicion = ' WHERE '
        agregar_y = False
        agregar_condicion = False

        if codigo:
            condicion += 'codigo="{}"'.format(codigo)
            agregar_condicion = True
            agregar_y = True
        if nombre:
            if agregar_y:
                condicion += ' AND '
            condicion += 'nombre="{}"'.format(nombre)
            agregar_condicion = True
            agregar_y = True
        if simbolo:
            if agregar_y:
                condicion += ' AND '
            condicion += 'simbolo="{}"'.format(simbolo)
            agregar_condicion = True

        query = 'SELECT * FROM moneda'
        if agregar_condicion:
            query += condicion

        self.cursor.execute(query)
        resultado = self.cursor.fetchall()

        monedas = []
        for datos in resultado:
            moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
            monedas.append(moneda)

        return monedas

    def update(self, old_obj, obj):
        updated = False
        add_comma = False

        query = 'UPDATE moneda SET '

        if old_obj.nombre != obj.nombre:
            query += 'nombre="{}"'.format(obj.nombre)
            updated = True
            add_comma = True
        if old_obj.simbolo != obj.simbolo:
            if add_comma:
                query += ', '
            query += 'simobolo="{}"'.format(obj.simbolo)
        if updated:
            query += ' WHERE codigo="{}"'.format(obj.codigo)
            self.cursor.execute(query)
            self.conn.commit()

    def save(self, obj):
        try:
            old_obj = self.get(codigo=obj.codigo)
        except MonedaNoExiste:
            self.insert(obj)
        else:
            self.update(old_obj, obj)

    def delete(self, obj):
        query = 'DELETE FROM moneda WHERE codigo="{}"'.format(obj.codigo)
        self.cursor.execute(query)
        self.conn.commit()


class Moneda(object):
    """
    Modelo Moneda
    """

    objetos = AdminMoneda(DB_PATH)

    def __init__(self, codigo, nombre, simbolo):
        self.codigo = codigo
        self.nombre = nombre
        self.simbolo = simbolo

    def __repr__(self):
        return u'{}'.format(self.nombre)

ej. 4-3d

# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)

ej. 4-3e

# print(Moneda.objetos.get(codigo='CLP'))
#
# Moneda.objetos.get(codigo='ART')
# print(Moneda.objetos.filtro(codigo='EUR'))
# print(Moneda.objetos.filtro(nombre='Dolar'))
# print(Moneda.objetos.filtro(simbolo='€'))
#
# print(Moneda.objetos.filtro())

ej. 4-3f

# peso_cl = Moneda.objetos.get(codigo='CLP')
# Moneda.objetos.save(peso_cl)
#
# print(peso_cl.codigo)
# print(peso_cl.nombre)
# print(peso_cl.simbolo)
#
# peso_cl.nombre = 'Pesos (CL)'
# Moneda.objetos.save(peso_cl)
# peso_cl = Moneda.objetos.get(codigo='CLP')
# print(peso_cl.codigo)
# print(peso_cl.nombre)
# print(peso_cl.simbolo)
#
# peso_uru = Moneda(codigo='UYU', nombre='Pesos Uruguayos', simbolo='$')
# Moneda.objetos.save(peso_uru)
#
# pesos_uru = Moneda.objetos.get(codigo='UYU')
# print(peso_uru.codigo)
# print(peso_uru.nombre)
# print(peso_uru.simbolo)

pesos_uru = Moneda.objetos.get(codigo='UYU')
Moneda.objetos.delete(pesos_uru)
# pesos_uru = Moneda.objetos.get(codigo='UYU')

ORM

Object Relational Mapping Mapeo Relacional de Objetos

Técnica de programación para convertir tipos de datos utilizados entre un lenguaje
de POO y una base de datos relacional como motor de persistencia.

Hay librerías que desarrollan el mapeo relacional de objetos, SQLAlchemy es uno de
los ORM más utilizados en Python.

Tambíen es posible crear propias herramientas ORM.

ORM permite cambiar de motor de DB mapeando automáticamente, ademas de armar
las consultas SQL, evitando así el uso del lenguaje SQL.

Instalación: $ pip install SQLAlchemy

Mapeo de los Modelos

Usando el ORM de SQLAlchemy.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence

engine = create_engine('sqlite:///:memory:')
Base = declarative_base()

class Author(Base):

    __tablename__ = 'author'

    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)

print(Author.__table__, end='\n'*2)

# Creacción del esquema
Base.metadata.create_all(engine)
author = Author(firstname="Juanita", lastname='Leon')
print(author.firstname)
print(author.lastname)
print(author.id)

$ python -i

Python 3.8.2 (default, Jul 16 2020, 14:00:26) 
[GCC 9.3.0] on linux

>>> runfile('./4-3h_ORM_mapeo_relacional_de_objetos.py', wdir='./')

Author.__table__
    Table('author', MetaData(bind=None), Column('id', Integer(), table=<author>, 
    primary_key=True, nullable=False, default=Sequence('author_id_seq', 
    metadata=MetaData(bind=None))), Column('firstname', String(), table=<author>),
    Column('lastname', String(), table=<author>), schema=None)

author.firstname
    'Juanita'

author.lastname
    'Leon'
    
author.id   

Guardar Objetos

En la base de datos con ORM de SQLAlchemy

Mapeo de los Modelos

Con la base de datos, usando el ORM de SQLAlchemy

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.orm import sessionmaker


Base = declarative_base()

class Author(Base):
    __tablename__ = 'author'

    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)

Creacción del esquema CREATE_ALL

engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

Agregando objetos a DB, flush automatico al hacer una consulta
posterior a SESSION.ADD

author = Author(firstname="Juanita", lastname='Leon')
session.add(author)
our_author = session.query(Author).filter_by(firstname='Juanita').first()
# son la misma instancia
print(author is our_author, '\n')

Agregar lista de objetos a la DB, SESION.ADD_ALL

session.add_all([Author(firstname='Joél Ez', lastname='Silva'),
                 Author(firstname='Jorge', lastname='Olivares')])

SESSION.NEW
Aun no estan guardados en la base, pero están en la sesión.

print(session.new)
# Modificando objeto en sesión, ver con SESSION.DIRTY
author.firstname = 'Anita'
print(session.dirty, '\n')

SESSION.COMMIT
Enviar todos los cambios a la BD. Se devuelve la conexión al pull de conexiones.
Una vez insertados. Todos identificadores y valores por defecto generados por
la BD están disponibles en instancia.

session.commit()
print(author.id, '\n')

ROLLBACK
Podemos hacer rollback de todos los cambios no incluidos en un commit.

author.firstname = 'Ruben'
another_author = Author(firstname='Uriel', lastname='Alelias')
session.add(another_author)
res = session.query(Author).filter(Author.firstname.in_(['Ruben', 'Uriel'])).all()
print(res, '\n')

session.rollback()
print(author.firstname)
# another_author no está en sesión, producto del rollback
print(another_author in session)

Consultar objetos SQLAlchemy

De la base de datos, con ORM de SQLAlchemy

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import aliased


engine = create_engine('sqlite:///:memory:')
Base = declarative_base()

class Author(Base):
    __tablename__ = 'author'

    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)


Base.metadata.create_all(engine)
author = Author(firstname="Juanita", lastname='Leon')

Session = sessionmaker(bind=engine)
session = Session()
session.add(author)
session.add_all([Author(firstname='Joél Ez', lastname='Silva'),
                 Author(firstname='Jorge', lastname='Olivares')])
session.commit()

Querys SQLAlchemy

Query #1: Devuelve los Autores ordenados por ID.

for instance in session.query(Author).order_by(Author.id):
    print(instance.firstname, instance.lastname)

#  Juanita Leon
#  Joél Ez Silva
#  Jorge Olivares

Query #2: Devuelve el Nombre y Apellido de cada Autor.

for firstname, lastname in session.query(Author.firstname, Author.lastname):
    print(firstname, lastname)

#  Juanita Leon
#  Joél Ez Silva
#  Jorge Olivares

Query #3: Devuelve el Autor, y su primer nombre.

for row in session.query(Author, Author.firstname).all():
    print(row.Author, row.firstname)

#  Juanita Leon Juanita
#  Joél Ez Silva Joél Ez
#  Jorge Olivares Jorge

Query #4: Devuelve los Autores, asignando una etiqueta al campo firstname.

for row in session.query(Author.firstname.label('firstname_label')).all():
    print(row.firstname_label)

#  Juanita
#  Joél Ez
#  Jorge

Query #5: Devuelve el Autor y su primer nombre, definiendo un alias de la tabla.

author_alias = aliased(Author, name='author_alias')
for row in session.query(author_alias, author_alias.firstname).all():
    print(row.author_alias)

#  Juanita Leon
#  Joél Ez Silva
#  Jorge Olivares

Query #6: Busqueda de todos los Autores, filtrados por slices, del 2 al 3.

for an_author in session.query(Author).order_by(Author.id)[1:3]:
    print(an_author)

#  Joél Ez Silva
#  Jorge Olivares

Query #7: Filtrado por autores con nombre 'Joél Ez'

for name, in session.query(Author.firstname).filter_by(firstname='Joél Ez'):
    print(name)

#  Joél Ez

Query #8 Filtrado por apellido del Autor.

for name, in session.query(Author.firstname).filter(Author.lastname == 'Silva'):
    print(name)

# Joél Ez

Query #9: Filtrado por nombre de autor y apellido.

for an_author in session.query(Author).\
                 filter(Author.firstname == 'Jorge').\
                 filter(Author.lastname == 'Olivares'):
    print(an_author)

# Jorge Olivares

Query #10: Cantidad de autores con el nombre 'Juanita'.

print(session.query(Author).filter(Author.firstname == 'Juanita').count())

# 1

Construyendo relación entre objetos

  • Crear columna clave foránea hacia la tabla del modelo a relacionar.
  • Crear relación con el modelo utilizando el constructor de relaciones llamado relationship.
  • La relación es bidireccional.

Ej. relación uno a muchos (un autor muchos libros)

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Author(Base):

    __tablename__ = 'author'
    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)
    # books = relationship("Book", order_by="Book.id", back_populates="author")
    # CASCADE
    books = relationship("Book", order_by="Book.id", back_populates="author",
                         cascade="all, delete, delete-orphan")

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)


class Book(Base):

    __tablename__ = 'book'
    id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
    isbn = Column(String)
    title = Column(String)
    description = Column(String)
    author_id = Column(Integer, ForeignKey('author.id'))
    author = relationship("Author", back_populates="books")

    def __repr__(self):
        return "{}".format(self.title)

Trabajando con Objetos Relacionados se puede solicitar libros por autor o al autor por sus libros.

engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# crea un autor
j_rowling = Author(firstname='Joanne', lastname='Rowling')
j_rowling.books = [Book(isbn='9788498387087',
                        title='Harry Potter y la Piedra Filosofal',
                        description='La vida de Harry Potter cambia para siempre el ...'),
                   Book(isbn='9788498382679',
                        title='Harry Potter y la camara secreta',
                        description='Tras derrotar una vez mas a lord Voldemort, ...')]

print(j_rowling.books[1])
print(j_rowling.books[1].title)
session.add(j_rowling)
session.commit()

j_rowling = session.query(Author).filter_by(firstname='Joanne').one()
print(j_rowling.books)

Consultando Objetos Relacionados

Devuelve el autor y libro para este isbn. Los imprimimos en pantalla.


for an_author, a_book in session.query(Author, Book).filter(Author.id==Book.author_id).\
                                                     filter(Book.isbn=='9788498387087').\
                                                     all():
    print(an_author)
    print(a_book)

Devuelve el autor del libro con este isbn

print(session.query(Author).join(Book).filter(Book.isbn=='9788498387087').all())

Devuelve los autores de los libros poniendo la condición explícitamente.

print(session.query(Author).join(Book, Author.id==Book.author_id).all())

Devuelve los autores de los libros especificando la relación de izquierda a derecha.

print(session.query(Author).join(Author.books).all())

Devuelve los autores de los libros para una relación específica.

print(session.query(Author).join(Book, Author.books).all())

Devuelve los autores de loslibros utilizando un string.

print(session.query(Author).join('books').all())

Devuelve el nombre del autor del libro utilizando un filtro exists. Es decir,
si existe algún libro del autor.

print(session.query(Author).join('books').all())
stmt = exists().where(Book.author_id == Author.id)
for name,in session.query(Author.firstname).filter(stmt):
    print(name)

Devuelve el nombre del autor del libro utilizando un filtro any. Es decir,
si el autor tiene algún libro.

print(session.query(Author).join('books').all())
for name, in session.query(Author.firstname).filter(Author.books.any()):
    print(name)

Devuelve el nombre del autor del libro utilizando un filtro like, parecido a any solo
que se le puede definir una condición, en este caso que el apellido del autor contenga
la subcadena de texto 'Row'.

for name, in session.query(Author.firstname).filter(Author.books.any(Author.lastname.like('%Row%'))):
    print(name)

Devuelve los libros donde el autor no se llame Joanne.

print(session.query(Book).filter(~Book.author.has(Author.firstname=='Joanne')).all())

Borrando Objetos

Al borrar un autor el/los libros no se borran. Quedando sin autor, objetos huefanos

session.delete(j_rowling)
print(session.query(Author).filter_by(firstname='Joanne').count())
print(session.query(Book).\
            filter(Book.isbn.in_(['9788498387087','9788498382679'])).\
            count())

session.rollback()

Para borrar el objeto padre y los objetos hijos, definir el atributo cascade con
la opción “all, delete, delete-orphan

    books = relationship("Book", order_by="Book.id", back_populates="author",
                         cascade="all, delete, delete-orphan")
del j_rowling.books[1]
print(session.query(Book).filter(Book.isbn.in_(['9788498387087',
                                                '9788498382679'])).count())

session.delete(j_rowling)
print(session.query(Author).filter_by(firstname='Joanne').count())
print(session.query(Book).filter(Book.isbn.in_(['9788498387087',
                                                '9788498382679'])).count())

Relaciones entre objetos

Crear relaciones entre modelos

Ej. se muestra una relación uno a muchos (un autor muchos libros):

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()
engine = create_engine('sqlite:///:memory:')

class Author(Base):

    __tablename__ = 'author'
    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)
    """
    Se crea una relación con el modelo Book, ordenado por book.id y
    con referencia hacia atrás "author".
    """
    # books = relationship("Book", order_by="Book.id", back_populates="author")
    # CASCADE
    books = relationship("Book",
                         order_by="Book.id",
                         back_populates="author",
                         cascade="all, delete, delete-orphan")

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)


class Book(Base):

    __tablename__ = 'book'
    id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
    isbn = Column(String)
    title = Column(String)
    description = Column(String)
    """
    Se crea la columna de tipo entero, ForeignKey de author.id
    """
    author_id = Column(Integer, ForeignKey('author.id'))
    """
    Se crea la relación con el modelo Author, con una 
    referencia hacia atrás a "books".
    """
    author = relationship("Author", back_populates="books")

    def __repr__(self):
        return "{}".format(self.title)


Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

j_rowling = Author(firstname='Joanne', lastname='Rowling')
print(j_rowling.books)

j_rowling.books = [Book(isbn='9788498387087',
                        title='Harry Potter y la Piedra Filosofal',
                        description='La vida de Harry Potter cambia para siempre el ...'),
                   Book(isbn='9788498382679',
                        title='Harry Potter y la camara secreta',
                        description='Tras derrotar una vez mas a lord Voldemort, ...')]

print(j_rowling.books[1])
print(j_rowling.books[1].title)

session.add(j_rowling)
session.commit()

j_rowling = session.query(Author).filter_by(firstname='Joanne').one()
print(j_rowling.books)

Querys 2

Devuelve Autor y Libro para el isbn especificado

for an_author, a_book in session.query(Author, Book).\
                                 filter(Author.id == Book.author_id).\
                                 filter(Book.isbn == '9788498387087').\
                                 all():
    print(an_author, '\n', a_book)

Query #2: Devuelve el Autor del libro con isbn 9788498387087.

      session.query(Author).join(Book).filter(Book.isbn == '9788498387087').all())

Query #3: Devuelve los autores de los libros condición explicita.

      session.query(Author).join(Book, Author.id == Book.author_id).all())

Query #4: Devuelve los autores y los libros, especificando la relación de izq. a derecha.

      session.query(Author).join(Author.books).all())

Query #5: Devuelve los Autores de los libros para una relación específica.

      session.query(Author).join(Book, Author.books).all())

Query #6: Busqueda de todos los Autores, usando un string.

      session.query(Author).join('books').all())

Query #7: Filtrado por autores con nombre 'Joél Ez'").

stmt = exists().where(Book.author_id == Author.id)
for name, in session.query(Author.firstname).filter(stmt):
    print(name)
    stmt = exists().where(Book.author_id == Author.id)

>>> NameError: name 'exists' is not defined

Query #8: Devuelve el nombre del autor del libro usando un filtro any.

for name, in session.query(Author.firstname).filter(Author.books.any()):
    print(name)

Query #9: Devuelve el nombre del autor, usando un filtro like, se puede
definir una condición. Por ejm. que el apellido contenga la cadena 'Row'.

for name, in session.query(Author.firstname).\
                 filter(Author.books.any(Author.lastname.like('%Row%'))):
    print(name)

Query #10: Devuelve los libros donde el autor no se llame 'Joanne'.

      session.query(Book).filter(~Book.author.has(Author.firstname == 'Joanne')).all(),

Borrar objetos de la BD

session.delete(j_rowling)

print("Autores con nombre Joanne :",
      session.query(Author).filter_by(firstname='Joanne').count())

print("Libros según isbn 9788498387087 y 9788498382679 :",
      session.query(Book).filter(Book.isbn.in_(['9788498387087', '9788498382679'])).count())


Relación muchos a muchos

Agregar una tabla para asociar objetos de dos modelos. La tabla de asociación se indica
en el argumento secondary de la relación.

from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
association_table = Table('association', Base.metadata,
                          Column('left_id', Integer, ForeignKey('left.id')),
                          Column('right_id', Integer, ForeignKey('right.id')))

class Parent(Base):

    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary=association_table,
                                     back_populates="parents")


class Child(Base):

    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Parent", secondary=association_table,
                                     back_populates="children")
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, \
                       Sequence, ForeignKey, Table, Text
from sqlalchemy.orm import sessionmaker, relationship


Base = declarative_base()
# association table
book_categories = Table('book_categories',
                        Base.metadata,
                        Column('book_id', ForeignKey('book.id'), primary_key=True),
                        Column('category_id', ForeignKey('book_category.id'), primary_key=True))

class Author(Base):

    __tablename__ = 'author'
    id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
    firstname = Column(String)
    lastname = Column(String)

    books = relationship("Book", order_by="Book.id", back_populates="author")

    def __repr__(self):
        return "{} {}".format(self.firstname, self.lastname)


class BookCategory(Base):

    __tablename__ = 'book_category'
    id = Column(Integer, Sequence('book_category_id_seq'), primary_key=True)
    name = Column(String)

    books = relationship('Book', secondary=book_categories,
                                 back_populates='categories')

    def __repr__(self):
        return "{}".format(self.name)


class Book(Base):

    __tablename__ = 'book'
    id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
    isbn = Column(String)
    title = Column(String)
    description = Column(String)

    author_id = Column(Integer, ForeignKey('author.id'))

    author = relationship("Author", back_populates="books")

    categories = relationship('BookCategory', secondary=book_categories,
                                              back_populates='books')

    def __repr__(self):
        return "{}".format(self.title)


engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

j_rowling = Author(firstname='Joanne', lastname='Rowling')
session.add(j_rowling)

j_rowling = session.query(Author).filter_by(firstname='Joanne').one()

book = Book(isbn='9788498387087',
            title='Harry Potter y la Piedra Filosofal',
            description='La vida de Harry Potter cambia para siempre el ...')

book.categories.append(BookCategory(name='Aventura'))
book.categories.append(BookCategory(name='Accion'))

book.author = j_rowling

print(
    session.query(Book).filter(Book.categories.any(name='Aventura')).all()
    , '\n',
    session.query(Book).filter(Book.author == j_rowling).\
                        filter(Book.categories.any(name='Aventura')).all()
)

# [Harry Potter y la Piedra Filosofal] 
# [Harry Potter y la Piedra Filosofal]

Actividad sistema escuela

Realizar un sistema para una escuela.

  • Este sistema permite registrar nuevos alumnos, profesores y cursos.

  • Un alumno es asignado a un curso y un curso puede tener asociado más de un profesor.

  • Los profesores tienen un horario que indica cuando están en cada curso.

  • El horario asociará un curso y un profesor para un día de la semana (Lunes, Martes,
    Miércoles, Jueves, Viernes, Sábado, Domingo), una hora desde, y una hora hasta.

  • El sistema permitirá exportar los alumnos que pertenecen a un curso, el horario de
    cada profesor y el horario del curso.

import csv
import datetime
from sqlalchemy import create_engine, ForeignKey, Column, \
                       Integer, String, Time, Sequence
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Alumno(Base):

    __tablename__ = 'alumno'

    id = Column(Integer, primary_key=True)
    nombre = Column(String, nullable=False)

    curso_id = Column(Integer, ForeignKey('curso.id'))
    curso = relationship('Curso', back_populates='inscrito')

    def __repr__(self):
        return self.nombre


class Profesor(Base):

    __tablename__ = 'profesor'

    id = Column(Integer, primary_key=True)
    nombre = Column(String, nullable=False)
    horario_prof = relationship('Horario', order_by='Horario.hr_ini', back_populates='profesor')

    def __repr__(self):
        return self.nombre


class Curso(Base):

    __tablename__ = 'curso'

    id = Column(Integer, primary_key=True)
    nombre = Column(String, nullable=False)
    inscrito = relationship('Alumno', order_by='Alumno.id', back_populates='curso')
    horario_curso = relationship('Horario', order_by='Horario.hr_ini', back_populates='curso')

    def __repr__(self):
        return self.nombre


class Horario(Base):

    __tablename__ = 'horario'

    id = Column(Integer, Sequence('horario_id_seq'), primary_key=True)
    dia = Column(Integer)
    hr_ini = Column(Time)
    hr_fin = Column(Time)
    curso_id = Column(Integer, ForeignKey('curso.id'))
    profesor_id = Column(Integer, ForeignKey('profesor.id'))

    curso = relationship('Curso', back_populates='horario_curso')
    profesor = relationship('Profesor', back_populates='horario_prof')


class ExportarCSV(object):

    def __init__(self, ruta):
        self.ruta = ruta

    def exprt_horario_prof(self, profesor):
        horario = profesor.horario_prof
        with open(self.ruta, 'w') as archivo:
            writer = csv.writer(archivo)
            for horas in horario:
                writer.writerow([horas.dia, horas.hr_ini, horas.hr_fin, horas.curso.nombre])

    def export_hr_curso(self, curso):
        horario = curso.horario_curso
        with open(self.ruta, 'w') as archivo:
            writer = csv.writer(archivo)
            for horas in horario:
                writer.writerow([horas.dia, horas.hr_ini, horas.hr_fin, horas.profesor])

    def export_alumnos_curso(self, curso):
        inscritos = curso.inscrito
        with open(self.ruta, 'w') as archivo:
            writer = csv.writer(archivo)
            for alumno in inscritos:
                writer.writerow([str(alumno)])


def main(*args, **kwargs):

    engine = create_engine('sqlite:///:memory:')
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    # Creación de profesores
    prof1 = Profesor(nombre='Profesor_1')
    prof2 = Profesor(nombre='Profesor_2')
    prof3 = Profesor(nombre='Profesor_3')
    session.add(prof1)
    session.add(prof2)
    session.add(prof3)

    # Creación de Cursos
    curso1 = Curso(nombre='Curso_1')
    curso2 = Curso(nombre='Curso_2')
    curso3 = Curso(nombre='Curso_3')
    session.add(curso1)
    session.add(curso2)
    session.add(curso3)

    # Creación de Alumnos
    alumno1 = Alumno(nombre='Alumno_1', curso=curso1)
    alumno2 = Alumno(nombre='Alumno_2', curso=curso2)
    alumno3 = Alumno(nombre='Alumno_3', curso=curso3)
    session.add(alumno1)
    session.add(alumno2)
    session.add(alumno3)

    # Creación de objetos Hora
    hora1 = datetime.time(9, 0, 0)
    hora2 = datetime.time(12, 0, 0)
    hora3 = datetime.time(13, 0, 0)
    hora4 = datetime.time(15, 0, 0)

    # Creación de horas para iniciio y termino de clases
    horario1 = Horario(dia=2, hr_ini=hora1, hr_fin=hora2, curso=curso1, profesor=prof1)
    horario2 = Horario(dia=3, hr_ini=hora2, hr_fin=hora3, curso=curso2, profesor=prof2)
    horario3 = Horario(dia=4, hr_ini=hora2, hr_fin=hora3, curso=curso3, profesor=prof3)
    horario4 = Horario(dia=5, hr_ini=hora3, hr_fin=hora4, curso=curso1, profesor=prof3)
    session.add(horario1)
    session.add(horario2)
    session.add(horario3)
    session.add(horario4)
    session.commit()

    # Exportar Datos
    ExportarCSV('Horario_Docente_{}'.format(prof1)).exprt_horario_prof(prof1)
    ExportarCSV('Horario_Docente_{}'.format(prof2)).exprt_horario_prof(prof2)
    ExportarCSV('Horario_Docente_{}'.format(prof3)).exprt_horario_prof(prof3)
    ExportarCSV('Horario_Curso_{}'.format(curso1)).export_hr_curso(curso1)
    ExportarCSV('Horario_Curso_{}'.format(curso2)).export_hr_curso(curso2)
    ExportarCSV('Horario_Curso_{}'.format(curso3)).export_hr_curso(curso3)
    ExportarCSV('Alumnos_Inscritos_{}'.format(curso1)).export_alumnos_curso(curso1)
    ExportarCSV('Alumnos_Inscritos_{}'.format(curso2)).export_alumnos_curso(curso2)
    ExportarCSV('Alumnos_Inscritos_{}'.format(curso3)).export_alumnos_curso(curso3)

if __name__ == "__main__":
    main()
──────────────────────────────────
File: Horario_Docente_Profesor_1
──────────────────────────────────
2,09:00:00,12:00:00,Curso_1
──────────────────────────────────
──────────────────────────────────
File: Horario_Docente_Profesor_2
──────────────────────────────────
3,12:00:00,13:00:00,Curso_2
──────────────────────────────────
──────────────────────────────────
File: Horario_Docente_Profesor_3
──────────────────────────────────
4,12:00:00,13:00:00,Curso_3
5,13:00:00,15:00:00,Curso_1
──────────────────────────────────


──────────────────────────────────
File: Horario_Curso_Curso_1
──────────────────────────────────
2,09:00:00,12:00:00,Profesor_1
5,13:00:00,15:00:00,Profesor_3
──────────────────────────────────
File: Horario_Curso_Curso_2
──────────────────────────────────
3,12:00:00,13:00:00,Profesor_2
──────────────────────────────────
──────────────────────────────────
File: Horario_Curso_Curso_3
──────────────────────────────────
4,12:00:00,13:00:00,Profesor_3
──────────────────────────────────


──────────────────────────────────
File: Alumnos_Inscritos_Curso_1
──────────────────────────────────
Alumno_1
──────────────────────────────────
──────────────────────────────────
File: Alumnos_Inscritos_Curso_2
──────────────────────────────────
Alumno_2
──────────────────────────────────
──────────────────────────────────
File: Alumnos_Inscritos_Curso_3
──────────────────────────────────
Alumno_3
──────────────────────────────────