16. Introducción a Bases de Datos y ORM
Introducción a base de datos
Una base de datos es un sistema organizado que permite almacenar y gestionar grandes volúmenes de información. Los datos se estructuran típicamente en tablas dentro de bases de datos relacionales, donde cada tabla representa una entidad del mundo real (como "Usuarios", "Productos" o "Pedidos") y las filas representan instancias específicas de esas entidades.
Por ejemplo, en una tabla de "Usuarios", cada fila podría contener información como el nombre, el correo electrónico y la fecha de registro de un usuario.
Tipos de Bases de Datos
Relacionales
- Utilizan tablas para estructurar los datos.
- Basadas en SQL (Structured Query Language) para realizar operaciones sobre los datos.
- Ejemplos: MySQL, PostgreSQL, SQLite.
No Relacionales (NoSQL)
- Almacenan datos de forma más flexible, como en documentos, gráficos o claves-valor.
- Ejemplos: MongoDB, Cassandra.
Conectar Aplicaciones con Bases de Datos
Tradicionalmente, para interactuar con una base de datos relacional, se escriben consultas SQL directamente. Estas consultas permiten realizar operaciones como:
- Crear un nuevo registro (
INSERT
). - Leer información (
SELECT
). - Actualizar un registro existente (
UPDATE
). - Eliminar un registro (
DELETE
).
Si bien escribir SQL directamente ofrece control total sobre las operaciones, también presenta desafíos:
- Falta de legibilidad: El código que mezcla lógica de negocio con consultas SQL puede volverse difícil de mantener.
- Errores comunes: Errores de sintaxis o problemas de inyección SQL son riesgos habituales.
- Complejidad de relaciones: Manejar relaciones entre tablas mediante SQL puede ser tedioso y propenso a errores.
ORM (Object-Relational Mapping)
¿Qué es un ORM?
Un ORM (Object-Relational Mapping o Mapeo Objeto-Relacional) es una técnica en programación que permite interactuar con bases de datos relacionales utilizando objetos en lugar de escribir consultas SQL directamente. En Python, los ORMs son herramientas que convierten las tablas de una base de datos y sus registros en objetos de Python, y viceversa.
Un ORM sirve como un puente entre el modelo de datos relacional (tablas y columnas) y el paradigma orientado a objetos (clases y atributos).
¿Por qué usar un ORM?
Abstracción del SQL
- Permite realizar operaciones CRUD (Crear, Leer, Actualizar y Eliminar) sin necesidad de escribir consultas SQL directamente.
- Esto simplifica la gestión de la base de datos para los desarrolladores que no están familiarizados con SQL.
Código más legible y mantenible
Las operaciones con la base de datos se escriben en Python, lo que hace que el código sea más uniforme y fácil de entender.
Independencia de la base de datos
Los ORMs generalmente soportan múltiples sistemas de bases de datos (MySQL, PostgreSQL, SQLite, etc.), lo que facilita cambiar de un sistema a otro con cambios mínimos en el código.
Reducción de errores
Evita errores comunes en consultas SQL como problemas de sintaxis, inyecciones SQL y errores de conversión de datos.
Facilidad en la gestión de relaciones
Maneja relaciones entre tablas (uno a uno, uno a muchos, muchos a muchos) de manera más intuitiva utilizando referencias entre objetos.
Automatización
Proporciona herramientas para generar automáticamente esquemas de base de datos a partir de modelos definidos en Python.
Desventajas de usar un ORM
Rendimiento
En escenarios complejos, las consultas generadas por un ORM pueden no estar optimizadas, lo que puede afectar el rendimiento.
Curva de aprendizaje
Aunque un ORM simplifica las operaciones, aprender a usarlo correctamente puede requerir tiempo, especialmente en proyectos grandes.
Menos control sobre consultas complejas
En casos avanzados, escribir consultas SQL a mano puede ser más eficiente o necesario.
Sobrecarga
Los ORMs agregan una capa adicional de abstracción, lo que puede aumentar la complejidad en ciertos contextos.
CRUD
Un CRUD es un acrónimo que representa las cuatro operaciones básicas que se pueden realizar sobre una base de datos o cualquier sistema de almacenamiento de datos: Crear (Create), Leer (Read), Actualizar (Update), y Eliminar (Delete). Estas operaciones son fundamentales en el desarrollo de aplicaciones que interactúan con datos almacenados, como sistemas web, móviles o de escritorio.
ORM PeeWee
Peewee es un ORM (Object Relational Mapper) ligero y fácil de usar, ideal para proyectos pequeños y medianos. Ofrece soporte para bases de datos populares como SQLite, MySQL y PostgreSQL, y es conocido por su simplicidad y rendimiento.
Ventajas de Peewee
- Ligero y fácil de aprender: Ideal para principiantes o proyectos que no necesitan funcionalidades avanzadas.
- Soporte para múltiples bases de datos: SQLite, MySQL, PostgreSQL.
- Buenas herramientas de migración: Incluye un sistema para gestionar cambios en los modelos y esquemas de bases de datos.
- Flexibilidad: Permite personalizar y ejecutar consultas SQL directamente si es necesario.
Instalación
Primero, instala Peewee usando pip:
pip install peewee
Dependiendo de la base de datos que usaras necesitas instalar dependencias adicionales, usuario, password, servicios, permisos y puertos de conexión.
Primeros pasos con Peewee
- Definir un modelo Un modelo en Peewee representa una tabla en la base de datos, donde cada atributo es una columna.
- Conectar a la base de datos Puedes crear una conexión a una base de datos SQLite directamente desde Peewee.
- Ejecutar operaciones CRUD Peewee proporciona métodos para Crear, Leer, Actualizar y Eliminar registros.
Ejemplo de ORM en Python con Peewee
Definiendo Modelos
from peewee import SqliteDatabase, Model, CharField
# Conexión a la base de datos SQLite
db = SqliteDatabase('ejemplo.db')
# Modelo de ejemplo
class Usuario(Model):
nombre = CharField(max_length=100)
email = CharField(unique=True)
class Meta:
database = db # Asocia el modelo con la base de datos
# Crear la tabla
db.connect()
db.create_tables([Usuario])
Creando CRUD
Crear un nuevo registro
Usuario.create(nombre="Juan", email="juan@example.com")
Leer registros
usuarios = Usuario.select()
for usuario in usuarios:
print(usuario.nombre, usuario.email)
Actualizar un registro
usuario = Usuario.get(Usuario.nombre == "Juan")
usuario.email = "nuevo_email@example.com"
usuario.save()
Eliminar un registro
usuario = Usuario.get(Usuario.nombre == "Juan")
usuario.delete_instance()
SQLite
SQLite es un sistema de gestión de bases de datos relacional ligero y autónomo, diseñado para ser simple, eficiente y fácil de usar. Es uno de los sistemas de bases de datos más utilizados en el mundo debido a su portabilidad y bajo costo en términos de configuración y mantenimiento.
Características Principales de SQLite
- Autónomo y ligero
- SQLite no requiere un servidor separado para funcionar. Es simplemente un archivo en el disco que almacena la base de datos.
- Su tamaño es pequeño, generalmente menor a 1 MB.
- Integrado
- Está integrado en muchas plataformas y lenguajes de programación, como Python, Android, iOS, y más.
- Portabilidad
- Una base de datos SQLite es un archivo único que se puede mover fácilmente entre sistemas operativos.
- Soporte para SQL
- SQLite soporta la mayor parte de los comandos y características estándar de SQL.
- Uso local
- Ideal para aplicaciones que necesitan una base de datos embebida, como software de escritorio, prototipos o almacenamiento local de aplicaciones móviles.
¿Dónde se usa SQLite?
- Aplicaciones móviles
- SQLite es el sistema de base de datos predeterminado para plataformas como Android e iOS.
- Prototipos y desarrollo rápido
- Es ideal para probar conceptos o desarrollar proyectos pequeños donde un sistema de bases de datos más complejo no es necesario.
- Almacenamiento local
- Usado en aplicaciones de escritorio o web que requieren almacenamiento de datos local sin depender de un servidor.
- Herramientas y sistemas embebidos
- Muchos sistemas embebidos, como dispositivos IoT, utilizan SQLite debido a su bajo peso y simplicidad.
Comparación
Característica | SQLite Directamente | SQLite con Peewee (ORM) |
---|---|---|
Legibilidad del código | Media | Alta |
Manejo de relaciones | Complejo | Simplificado |
Escritura manual de SQL | Necesaria | No necesaria |
Facilidad de mantenimiento | Baja | Alta |
DB Browser for SQLite
DB Browser for SQLite (DB4S) is a high quality, visual, open source tool designed for people who want to create, search, and edit SQLite or SQLCipher database files. DB4S gives a familiar spreadsheet-like interface on the database in addition to providing a full SQL query facility.
Como usar Peewee ORM
Creando el modelo
Primero debemos crear un modelo del objeto que vamos a almacenar en la base de datos. Una vez queda nuestro modelo listo, ya podemos usarlo.
from peewee import * # importamos todo lo necesario de la librería peewee
from datetime import datetime # la vamos a usar para asignar cuando se crea el registro
db = SqliteDatabase("components.db") #le damos el nombre a la base de datos que se creará
class Component(Model):
id = AutoField() # creamos el campo que tendrá el registro
name = CharField() # creamos el campo que tendrá el registro
code = CharField(unique=True) # creamos el campo que tendrá el registro
description = CharField() # creamos el campo que tendrá el registro
quantity = IntegerField(default=0) # creamos el campo que tendrá el registro
power = FloatField(null=True) # creamos el campo que tendrá el registro
date_register = DateTimeField(default=datetime.now)# creamos el campo que tendrá el registro
def __str__(self):
return f"id: {self.id} - code: {self.code} - name: {self.name} - date: {self.date_register}"
class Meta:
database = db
db_table = "component"
# para conocer mas sobre los campos que se pueden crear ir a https://docs.peewee-orm.com/en/latest/peewee/models.html
Referencia: https://docs.peewee-orm.com/en/latest/peewee/models.html
Creando tabla y verificando su existencia
Segundo. Tenemos que crear la tabla para poder agregar registros a la base de datos.
if __name__ == "__main__":
Component.create_table() # creamos la tabla, primero debe existir para insertar datos.
Necesitamos siempre verificar si la tabla en donde vamos a insertar un registro nuevo, existe, de lo contrario se debe crear.
if __name__ == "__main__":
if not Component.table_exists():
Component.create_table()
Insertando datos
Ya que se comprobar la base de datos y sus tablas podemos insertar datos.
if __name__ == "__main__":
if not Component.table_exists():
Component.create_table()
Component.create(name="resistencia R10", code="r10", description=f"Resistencia de 10 ohms", quantity=5) # creando componente e insertando en db
Component.create(name="capacitor 4.7u", code="c4.7u", description=f"capacitor de 4.7uF a 50V", quantity=1) # creando componente e insertando en db
Component.create(name="microcontrolador ATMEGA328P", code="ATMEGA328P", description=f"microcontrolador ATMEGA328P AVR 8-bits PDIP28") # creando componente e insertando en db
Obteniendo todos los registros de la DB
components = Component.select() # aquí obtenemos todos los datos de la db
for component in components: # vamos a recorrer todos los registros e imprimir su contenido
print(component)
Obteniendo datos individuales
Con relación a obtención de los registros individuales, siempre se debe tener un valor en el registro que sea único, por eso siempre se define un id
único, el cual sabemos que no se repite en ninguno mas.
Para este caso, definimos en el modelo id = AutoField()
, con esto se indica que sera único e incremental. Y sera el campo que nos ayuda a obtener ese registro.
La forma de obtener un registro es con el método Model.get( filtro )
.
if not Component.table_exists():
Component.create_table()
component = Component.get(Component.id == "2")
print(component) # Resultado: code: c4.7u - name: capacitor 4.7u - date: 2025-01-12 17:59:06.432133
Actualizando registros
Para actualizar un registro, primero hay que extraerlo, se modifica el campo del objeto y se salva.
component = Component.get(Component.id == "1") # buscamos el registro
component.power = "1/2W" # aquí es donde modificamos el dato
component.save() # guardamos el cambio en la db
Eliminando registro
Se debe obtener el registro y aplicar el método delete_instance()
para eliminar
component = Component.get(Component.id == "3")
component.delete_instance()
Ejercicios
Toma en consideraciones los siguientes puntos,
- Se toma como obvio pero debes agregar el campo de
id
. - Define los modelos con las clases de Peewee.
- Usa los métodos como
create()
,select()
,get()
, ydelete_instance()
para las operaciones CRUD. - Integra validaciones o menús interactivos en la consola para practicar con entradas dinámicas.
Ejercicio 1: Gestión de una Lista de Tareas
Crea una aplicación simple para gestionar una lista de tareas. Las tareas deben tener un título, una descripción y un estado (pendiente o completada).
Requisitos:
- Crear una base de datos SQLite llamada tareas.db.
- Diseñar un modelo llamado Tarea con los campos:
- titulo (texto, obligatorio, único).
- descripcion (texto, opcional).
- estado (booleano, False por defecto).
- Implementar las siguientes operaciones:
- Agregar una nueva tarea.
- Listar todas las tareas.
- Marcar una tarea como completada.
Ejemplo de vista
Tareas pendientes:
1. Comprar comida (Pendiente)
2. Terminar proyecto (Pendiente)
Tareas completadas:
1. Limpiar la casa
Answer
task.py
from peewee import *
from datetime import datetime
db = SqliteDatabase("task.db")
class Task(Model):
id = AutoField()
name = CharField()
description = CharField(null=True)
state = BooleanField(default=False)
date_register = DateTimeField(default=datetime.now)
def __str__(self):
return f"id:{self.id}, name: {self.name}, state:{self.state}"
class Meta:
database = db
db_table = "tasks"
from task import Task
class TaskController:
"""Class helper to controller the CRUD
"""
def __init__(self):
if not Task.table_exists():
Task.create_table()
def create_task(self, name, description):
Task.create(name=name, description=description)
def change_state(self, id):
task = Task.get(Task.id == id)
if task:
task.state = True
task.save()
def show_tasks(self):
text_complete = "Tasks Done:\n"
text_incomplete = "Task Incomplete:\n"
count_complete = 1
count_incomplete = 1
for task in Task.select():
if task.state:
text_complete += f"{count_complete}.- {task.name } - description: {task.description} - (COMPLETE)\n"
count_complete += 1
else:
text_incomplete += f"{count_incomplete}.- [{task.id}] {task.name } - description: {task.description} - (NOT COMPLETE)\n"
count_incomplete += 1
print(text_complete)
print(text_incomplete)
if __name__ == "__main__":
opt = 0
controller = TaskController()
while True:
print("APP TASK")
print("1. Add Task")
print("2. Change status Task")
print("3. Show Task")
opt = int(input())
if opt == 1:
print("ADD TASK")
name = None
while not name: # evita que el nombre de la tarea quede vació
name = input("Name task [no puede quedar vacio]: ")
description = input("Description task [opcional]: ")
controller.create_task(name=name, description=description)
elif opt == 2:
print("CHANGE STATUS")
id = int(input("Give ID task to change: "))
controller.change_state(id)
elif opt == 3:
controller.show_tasks()
Ejercicio 2: Sistema de Gestión de Estudiantes
Crea una base de datos para gestionar estudiantes en un curso. Los estudiantes tienen un nombre, un apellido, y un promedio (float).
Requisitos:
- Crear una base de datos SQLite llamada estudiantes.db.
- Diseñar un modelo Estudiante con los campos:
- nombre (texto, obligatorio).
- apellido (texto, obligatorio).
- promedio (float).
- Implementar las siguientes funciones:
- Agregar nuevos estudiantes.
- Mostrar todos los estudiantes ordenados por su promedio de forma descendente.
- Eliminar un estudiante por su nombre y apellido.
Ejemplo de vista
Estudiantes registrados:
1. Ana Pérez, Promedio: 95.0
2. Juan Gómez, Promedio: 85.5
Ejercicio 3: Biblioteca de Libros
Crea una aplicación que permita gestionar una pequeña biblioteca de libros.
Requisitos:
- Crear una base de datos SQLite llamada biblioteca.db.
- Diseñar dos modelos:
- Autor con los campos:
- nombre (texto, obligatorio).
- Libro con los campos:
- titulo (texto, obligatorio).
- autor (relación con el modelo Autor).
- año_publicacion (entero).
- Implementar las siguientes funcionalidades:
- Agregar un nuevo autor.
- Agregar un nuevo libro y asociarlo con un autor.
- Mostrar todos los libros disponibles junto con su autor.
- Buscar libros publicados por un autor específico.
Ejemplo de vista
Libros disponibles:
1. "Cien años de soledad" por Gabriel García Márquez (1967)
2. "El amor en los tiempos del cólera" por Gabriel García Márquez (1985)
Referencia: https://docs.peewee-orm.com/en/latest/