**Ir a:** [*Repositorio*](https://gitea.kickto.net/devfzn/Apuntes_Python), [*Modulo 3*](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_3#modulo-3-python-basico) ## Modulo 4 - Python basico # Bases de Datos #### [Indice](#modulo-4-python-basico) - [Bases de Datos](#bases-de-datos) - [Esquemas y datos](#esquemas-y-datos) - [Funcionalidad básica SQLiteBrowser](#funcionalidad-básica-sqlitebrowser) - [El modelo Relacional](#el-modelo-relacional) - [Integridad de Datos](#integridad-de-datos) - [Especificación SQL-2003](#especificación-sql-año-2003) - [Indices](#indices) - [Structured Query Languaje](#structured-query-language) - [Sentencias SQL](#sentencias-sql) - [SQL Joins](#join) - [SQLite - Manejo de BDs en Python](#manejo-de-bases-de-datos-en-python) - [Conexion a base de datos](#conexiones-a-base-de-datos) - [Cursores](#cursores) - [INSERT](#guardar-objeto-en-base-de-datos) - [SELECT](#consultar-objetos) - [UPDATE](#actualizar-objetos) - [DELETE](#borrar-objetos) - [ORM y SQLAlchemy](#orm) - [Mapeo de los modelos](#mapeo-de-los-modelos) - [Guardar Objetos](#guardar-objetos) - [Querys](#querys-sqlalchemy) - [Construyendo relación entre objetos](#construyendo-relación-entre-objetos) - [Consultando objetos relacionados](#consultando-objetos-relacionados) - [Borrando objetos](#borrando-objetos) - [Relaciones entre objetos](#relaciones-entre-objetos) - [Crear relaciones entre modelos](#crear-relaciones-entre-modelos) - [Querys 2](#querys-2) - [Relación muchos a muchos](#relación-muchos-a-muchos) - [Actividad - Sistema escuela](#actividad-sistema-escuela) ---- # 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](https://es.wikipedia.org/wiki/%C3%81rbol-B), [B+](https://es.wikipedia.org/wiki/%C3%81rbol_B%2B) o [B\*](https://es.wikipedia.org/wiki/%C3%81rbol-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)| ```sql -- 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. ```sql 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` ```sql -- 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. ```sql --SELECT columna1, columna2, ... --FROM nombre_tabla; --ej. SELECT * FROM libro; -- * = ALL ``` `SELECT DISTINCT` solo selecciona los valores diferentes. ```sql --SELECT DISTINCT columna1, columna2, ... --FROM nombre_tabla; --ej. SELECT DISTINCT categoria_id FROM libro; ``` `WHERE` ```sql --SELECT columna1, columna2, ... --FROM nombre_tabla --WHERE condición; --ej. SELECT * FROM libro WHERE precio < 100; ``` `AND` , `OR` , `NOT` ```sql --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` ```sql --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` ```sql SELECT * FROM libro LIMIT 1; ``` `INSERT` ```sql --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` ```sql --UPDATE nombre_taba --SET comumna1 = valor1, columna2 = valor2, ... -- WHERE concición; --ej. UPDATE categoria_libro SET NOMBRE = 'Acción' WHERE CODIGO = 4; ``` `DELETE` ```sql --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. ```sql --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. ```sql --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. ```sql --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](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_4/4-2d_SQL_Joins.png) ![Sql Joins](./4-2d_SQL_Joins.png) ---- ## 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. ```python 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 ```python 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 ```sql --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 ```python 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. ```python # 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 ```python 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 ```python 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. ```python 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*](https://gitea.kickto.net/devfzn/IntroPython/raw/branch/master/01_curso/Modulo_4/4-3d_prueba.db) ```python 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 ```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 ```python # 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 ```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 ```python # 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 ```python # 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 ```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 ```python # 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 ```python # 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 ```python # 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. ```python 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 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=, primary_key=True, nullable=False, default=Sequence('author_id_seq', metadata=MetaData(bind=None))), Column('firstname', String(), table=), Column('lastname', String(), table=), 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** ```python 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** ```python 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** ```python 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** ```python 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. ```python 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. ```python session.commit() print(author.id, '\n') ``` **ROLLBACK** Podemos hacer rollback de todos los cambios no incluidos en un commit. ```python 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 ```python 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. ```python 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. ```python 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. ```python 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. ```python 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. ```python 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. ```python 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' ```python 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. ```python 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. ```python 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'. ```python 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) ```python 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***. ```python 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. ```python 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 ```python print(session.query(Author).join(Book).filter(Book.isbn=='9788498387087').all()) ``` Devuelve los autores de los libros poniendo la condición explícitamente. ```python 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. ```python print(session.query(Author).join(Author.books).all()) ``` Devuelve los autores de los libros para una relación específica. ```python print(session.query(Author).join(Book, Author.books).all()) ``` Devuelve los autores de loslibros utilizando un string. ```python 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. ```python 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. ```python 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'*. ```python 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. ```python 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 ```python 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**” ```python books = relationship("Book", order_by="Book.id", back_populates="author", cascade="all, delete, delete-orphan") ``` ```python 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): ```python 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 ```python 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. ```python session.query(Author).join(Book).filter(Book.isbn == '9788498387087').all()) ``` **Query #3**: Devuelve los autores de los libros ***condición explicita***. ```python 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. ```python session.query(Author).join(Author.books).all()) ``` **Query #5**: Devuelve los Autores de los libros para una relación específica. ```python session.query(Author).join(Book, Author.books).all()) ``` **Query #6**: Busqueda de todos los Autores, usando un string. ```python session.query(Author).join('books').all()) ``` **Query #7**: Filtrado por autores con nombre 'Joél Ez'"). ```python 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**. ```python 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'*. ```python 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'*. ```python session.query(Book).filter(~Book.author.has(Author.firstname == 'Joanne')).all(), ``` ### Borrar objetos de la BD ```python 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. ```python 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") ``` ```python # -*- 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. ```python 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 ────────────────────────────────── ```