Guías sobre temas de bases de datos y programación
Antes de sumergirnos en el ejemplo CRUD con Python y SQLite, digamos que queremos crear una aplicación que maneje los datos del cliente. La aplicación creada debe tener la funcionalidad para crear, actualizar, leer y eliminar clientes. Tenemos que guardar esos datos del cliente en una base de datos, para ello debemos tener una estructura definida de un cliente y asi podamos realizar las operaciones CRUD anteriores desde la base de datos.
Primero que nada, creemos una clase de Cliente en un archivo nombrado customer.py
# customer.py
class Customer:
def __init__(self, first_name, last_name, phone, address, city):
self.first_name = first_name
self.last_name = last_name
self.phone = phone
self.address = address
self.city = city
@property
def email(self):
return '{}.{}@gmail.com'.format(self.first_name, self.last_name)
@property
def fullname(self):
return '{} {}'.format(self.first_name, self.last_name)
def __repr__(self):
return "Customer('{}', '{}', '{}', '{}', '{}')".format(
self.first_name,
self.last_name,
self.phone,
self.address,
self.city)
@property
es un decorador incorporado para el uso de captadores y definidores en Python orientado a objetos. He definido dos métodos para el correo electrónico del cliente y el nombre completo del cliente y los marqué como decoradores. Usando el método, puedo obtener la representación del objeto real de la clase de cliente (__repre__).
Creamos un archivo crud.py para ir viendo las operaciones del CRUD con Python con SQLite . Toda la funcionalidad relacionada con la base de datos va aquí. En primer lugar, tenemos que importar la biblioteca estándar SQLite3 a nuestro código para que podamos trabajar con las operaciones de la base de datos.
Necesitamos crear un objeto de conexión para representar nuestra base de datos. En este caso, nuestra base de datos tendrá el nombre de customer.db y usando el método connect()
podemos pasar un la ubicación de un archivo y si no existe lo crea. También podemos hacer una base de datos en memoria.
Este ejemplo muestra una base de datos en memoria:
import sqlite3
connection = sqlite3.connect(':memory:')
Ahora veamos un ejemplo para una conexión a base de datos customer.db:
import sqlite3
connection = sqlite3.connect('customer.db')
Si ejecutamos este código, creará un archivo customer.db en nuestro directorio. Incluso si el archivo ya existía. no obtendremos errores la próxima vez que ejecutemos el mismo código.
Un cursor nos permite interactuar con la base de datos a través de comandos SQL, podemos crear un cursor llamando al método .cursor()
de nuestro objeto de conexión.
import sqlite3
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
Con el objeto cursor ahora podemos llamar al método .execute()
para ejecutar comandos SQL.
Usando el cursor, vamos a crear una tabla de clientes. Tendrá los siguientes campos (columnas):
El comando SQL que queremos ejecutar entrará en el método execute() del objeto cursor. Utilizando comillas triples para envolver el comando SQL. Utilizaremos los “docstring” para escribir valores de cadena en varias líneas.
## sqlite_demo.py
import sqlite3
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers(
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(12) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100),
city VARCHAR(50))
""")
connection.commit()
connection.close()
Ahora si ejecutamos nuestro script ‘crud.py’
python crud.py
Si no obtenemos ningún error, eso significa que hemos creado la tabla de clientes correctamente.
Si vuelve a ejecutar el mismo código, obtendríamos un error de que ya existe la tabla en la base de datos. Es por ello que le agregamos la cláusula IF NOT EXISTS
.
Ahora que ya hemos creado la tabla de la base de datos, agreguemos datos a la tabla de clientes. Para eso podemos usar el comando SQL “INSERT INTO table_name VALUES(val, val2, ...)
”.
## crud.py
import sqlite3
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers(
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(12) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100),
city VARCHAR(50))
""")
cursor.execute("""
INSERT INTO customers (first_name, last_name, phone, email, address, city)
VALUES
('marco', 'contreras', '+56984687949', 'marco_contreras@gmail.com', 'av. los mojo-jojos', 'townsville')
""")
connection.commit()
connection.close()
Ahora si ejecutamos nuevamente nuestro script y no obtenemos ningún error, eso significa que hemos creado la tabla de clientes correctamente y además hemos insertado un nuevo registro. Para verlo podemos abrir el archivo con un programa como DbBrowser:
Después de revisar la base de datos, asegúrese de comentar las líneas de código del INSERT INTO
, de lo contrario cada vez que ejecutemos el script se llevará a cabo la inserción de la misma información.
Ahora podemos consultar la tabla sin ningún programa externo. En el método execute() usaremos el comando “SELECT”:
## crud.py
import sqlite3
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers(
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(12) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100),
city VARCHAR(50))
""")
# Comentamos este bloque para no se inserte nuevamente
# ====================================================
# cursor.execute("""
# INSERT INTO customers (first_name, last_name, phone, email, address, city)
# VALUES
# ('marco', 'contreras', '+56984687949', 'marco_contreras@gmail.com', 'av. los mojo-jojos', 'townsville')
# """)
cursor.execute("SELECT * FROM customers")
print(cursor.fetchall())
connection.commit()
connection.close()
Después del método execute()
, obtendremos un resultado de consulta para que podamos iterar para encontrar el resultado deseado. hay algunos métodos diferentes para iterar el resultado de la consulta.
Método | Funcionalidad |
---|---|
fetchone() |
obtendrá la siguiente fila en nuestro resultado y devolverá un registro. Si no hay más registro disponibles, devuelve None. |
fetchmany(number) |
fetchmany(number) devolverá un número especificado de filas como una lista. Si no hay más registro disponibles, devuelve una lista vacía. |
fetchall() |
devolverá las filas restantes como una lista que queda. Si no hay filas, devolverá una lista vacía. |
Primero, importamos la clase de cliente al archivo Customer.py. Ahora, tenemos que crear instancias de la clase de cliente. Crearemos dos instancias y las nombraremos como customer1 y customer2:
import sqlite3
from customer import Customer
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers(
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(12) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100),
city VARCHAR(50))
""")
customer_1 = Customer('marco', 'contreras', '+569-84687949', 'av suecia 327', 'coquimbo')
customer_2 = Customer('marcelo', 'riveros', '+569-89587949', 'av dinamarca 387', 'santiago')
Por ejemplo, digamos que quiero guardar los valores del objeto customer_1 en la base de datos. Si usamos el fomato de cadena, podemos utilizar las llaves como marcadores de posición. Luego usando el método format()
, completará esos valores en el marcador de posición correspondiente.
import sqlite3
from customer import Customer
connection = sqlite3.connect('customer.db')
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers(
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(12) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100),
city VARCHAR(50))
""")
customer_1 = Customer('marco', 'contreras', '+569-84687949', 'av suecia 327', 'coquimbo')
customer_2 = Customer('marcelo', 'riveros', '+569-89587949', 'av dinamarca 387', 'santiago')
cursor.execute("INSERT INTO customers VALUES ('{}', '{}', {}, '{}', '{}', '{}')".format(
customer_1.first_name,
customer_1.last_name,
customer_1.phone,
customer1.email,
customer_1.city,
customer_1.country))
connection.commit()
connection.close()
Como podemos observar no fue necesario pasar el email al momento de instanciar la clase, ya que este campo podemos obtenerlo desde el método email de la clase que creamos y lo podemos usar como una propiedad gracias al decorador @property
Nota: utilizar el formato de cadena no es buena práctica ya que se considera muy propenso a la inyección de SQL.
Hay dos formas de insertar objetos en una base de datos correctamente. Dado que el enfoque de formato de cadena es propenso a la inyección SQL, es mejor usar cualquiera de los siguientes métodos.
?
# import statements
# create connection object
# create database table
customer_1 = Customer('marco', 'contreras', '+569-84687949', 'av suecia 327', 'coquimbo')
cursor.execute("INSERT INTO customers VALUES (?,?,?,?,?,?)",
(customer_1.first_name, customer_1.last_name,
customer_1.phone, customer_1.email,
customer_1.address, customer_1.city))
connection.commit()
Una cosa a tener en cuenta aquí, ya no usamos la función format()
. En el método de ejecución, pase el segundo argumento como la tupla de valores de objeto que queremos guardar en la base de datos.
Veamos otro método para lograr la misma funcionalidad que antes.
En lugar de usar los “?
” como marcador de posición, usamos los dos puntos “:
” con un nombre que describe el marcador de posición. En el método de ejecución, tenemos que pasar un diccionario como segundo argumento. Las claves del diccionario serán los nombres de los marcasdores de posición y los valores serán los que pasamos de los atributos del objeto. Ej:
Marcador | par clave/valor |
---|---|
:first | {‘first’: customer_1.first_name} |
Usamos corchetes para denotar un diccionario.
# import statements
# create connection object
# create database table
customer_1 = Customer('marco', 'contreras', '+569-84687949', 'av suecia 327', 'coquimbo')
cursor.execute("INSERT INTO customers VALUES (:first, :last, :phone, :email, :city, :country)",
{
'first':customer_1.first_name,
'last':customer_1.last_name,
'phone':customer_1.phone,
'email':customer_1.email,
'address': customer_1.address,
'city': customer_1.city
})
connection.commit()
connection.close()
Para demostrar el ejemplo de Python y SQLite para un CRUD, crearé cuatro funciones en el archivo crud.py.
create_customer(customer)
get_customers(city)
update_city(customer, city)
delete_customer(customer)
Las funciones anteriores son sencillas y hacen lo que han definido.
Nota: A veces es tedioso comprometer nuestras posibilidades cuando hemos realizado las operaciones CRUD en la base de datos. Una forma sencilla de resolver ese problema es utilizar los administradores de contexto de Python. Los administradores de contexto se utilizan para configurar y eliminar recursos automáticamente. Por ejemplo; el objeto de conexión se puede utilizar como administrador de contexto para confirmar y deshacer transacciones automáticamente.
La palabra clave with
se utiliza para definir un administrador de contexto. Los administradores de contexto se pueden escribir usando clases o funciones con la ayuda de decoradores.
La función create_customer(customer)
guardará un registro de cliente en la base de datos.
# import statements
# create connection object
# create database table
def create_customer(customer):
with connection:
cursor.execute("INSERT INTO customers VALUES(:first, :last, :phone, :email, :address, :city)",
{
'first': customer.first_name,
'last':customer.last_name,
'phone':customer.phone,
'email':customer.email,
'address':customer.address,
'city':customer.city
})
En el código anterior, el método execute()
del cursor se envuelve dentro del bloque with
. para que no necesitemos una declaración commit()
después de eso.
La función get_customers(city)
aceptará la ciudad del cliente y devolverá un conjunto de resultados.
# import statements
# create connection object
# create database table
def get_customers(city):
cursor.execute("SELECT * FROM customers WHERE city=:city", {'city':city})
return cursor.fetchall()
Nota: es posible que se pregunte por qué no hay ningún bloque
with
aquí. Bueno, para la consultaSELECT
no necesitamos la funcióncommit()
. Debido a que no necesita estar dentro de un administrador de contexto, pero si en el caso INSERTAR, ACTUALIZAR, ELIMINAR, entonces necesitamos un bloquewith
.
La función update_customers(customer, city)
actualizará la ciudad del cliente y se realizará según el nombre y apellido del cliente proporcionado.
# import statements
# create connection object
# create database table
def update_city(customer, city):
with connection:
cursor.execute("""UPDATE customers SET city=:city
WHERE first_name=:first AND last_name=:last""",
{
'first': customer.first_name,
'last': customer.last_name,
'city': city
})
La función delete_customers(customer)
Si el nombre y apellido del cliente dado coinciden con los registros existentes de la base de datos, todos los registros con ese nombre y apellido se eliminarán de la base de datos.
# import statements
# create connection object
# create database table
def delete_city(customer):
with connection:
cursor.execute("""DELETE FROM customers
WHERE first_name=:first AND last_name=:last""",
{'first': customer.first_name, 'last': customer.last_name})
Muy bien, hemos implementado toda la funcionalidad CRUD. Ahora ejecutemos nuestro código para ver el resultado.
Primero que nada, necesitamos crear objetos de la clase Customer. Para ello tenemos 4 objetos de prueba:
# import statements
# create connection object
# create database table
# create crud function
cliente1 = Customer('marco', 'contreras', '+569-84687949', 'av suecia 327', 'coquimbo')
cliente2 = Customer('marcelo', 'riveros', '+569-89587949', 'av dinamarca 237', 'coquimbo')
cliente3 = Customer('pedro', 'pascal', '+569-89087949', 'av japón 387', 'antofagasta')
cliente4 = Customer('ignacio', 'guerrero', '+569-89089849', 'av china 127', 'antofagasta')
Luego insertar los clientes en la base de datos usando el método create_customer(customer)
e invocamos la función una vez por cada cliente.
# import statements
# create connection object
# create database table
# create crud function
create_customer(cliente1)
create_customer(cliente2)
create_customer(cliente3)
create_customer(cliente4)
Ahora veamos cómo actualizar un cliente y eliminarlo. Tienes que usar las funciones update_city(customer,city) y delete_customer(customer) para eso.
# import statements
# create connection object
# create database table
def create_customer(customer):
with connection:
cursor.execute("INSERT INTO customer VALUES (:first, :last, :age, :city, :country)",
{'first':customer.first_name, 'last':customer.last_name,
'age':customer.age, 'city':customer.city, 'country':customer.country})
def get_customers(city):
cursor.execute("SELECT * FROM customer WHERE city=:city", {'city':city})
return cursor.fetchall()
def update_city(customer, city):
with connection:
cursor.execute("""UPDATE customer SET city=:city
WHERE first_name=:first AND last_name=:last""",
{'first':customer.first_name, 'last':customer.last_name, 'city':city})
def delete_customer(customer):
with connection:
cursor.execute("DELETE FROM customer WHERE first_name=:first AND last_name=:last",
{'first':customer.first_name,'last':customer.last_name})
customer_1 = Customer('john', 'doe', 30, 'perth', 'Australia')
customer_2 = Customer('sara', 'migel', 25, 'perth', 'Australia')
create_customer(customer_1)
create_customer(customer_2)
update_city(customer_1,'sydney')
delete_customer(customer_2)
print(get_customers('perth'))
print(get_customers('sydney'))
connection.close()