Finished: basic exercises. Started: BS4 exercise 1.

This commit is contained in:
Jose
2026-02-15 05:17:23 +01:00
parent a9effed3b0
commit 2dbbf387d6
18 changed files with 573 additions and 91 deletions

View File

@@ -1,25 +0,0 @@
from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
import re
URL = "https://www.vinissimus.com/es/vinos/tinto/?cursor=0"
def main():
req = Request(
URL,
headers={
"User-Agent": "Mozilla/5.0 (compatible; Konqueror/3.5.8; Linux)"
}
)
doc = BeautifulSoup(
urlopen(req),
"lxml"
)
for child in doc.find_all("div", class_="list large"):
name = child.find("h2", class_=["title"])
print(name)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
def init_ssl():
import os, ssl
if (not os.environ.get('PYTHONHTTPSVERIFY', '') and
getattr(ssl, '_create_unverified_context', None)):
ssl._create_default_https_context = ssl._create_unverified_context

View File

@@ -0,0 +1,6 @@
from pathlib import Path
URL = "https://www.vinissimus.com/es/vinos/tinto/?cursor=0"
DATA_DIR = Path(__file__).parent.parent / "data"
CSV_PATH = DATA_DIR / "books.csv"
DB_PATH = DATA_DIR / "books.bd"

View File

@@ -0,0 +1,124 @@
import sqlite3
from pathlib import Path
class DBAttr:
def __init__(self, name, type_, modifier=""):
self.name = name
self.type_ = type_
self.modifier = modifier
def sql(self):
parts = [self.name, self.type_]
if self.modifier:
parts.append(self.modifier)
return " ".join(parts)
class DBManager:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, path):
self.path = Path(path)
self.conn = sqlite3.connect(self.path)
self.conn.row_factory = sqlite3.Row
def create_table(self, table_name, attributes: list[DBAttr]):
columns_sql = ",\n ".join(attr.sql() for attr in attributes)
query = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
{columns_sql}
);
"""
try:
with self.conn:
self.conn.execute(query)
except Exception as e:
print("Error creating table:", e)
def get_all(self, table_name):
try:
cursor = self.conn.execute(f"SELECT * FROM {table_name};")
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print("Error selecting:", e)
return []
def get_by(self, table_name, column, value):
try:
query = f"SELECT * FROM {table_name} WHERE {column} = ?;"
cursor = self.conn.execute(query, (value,))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print("Error selecting:", e)
return []
def insert(self, table_name, data: dict):
keys = ", ".join(data.keys())
placeholders = ", ".join("?" for _ in data)
values = tuple(data.values())
query = f"""
INSERT INTO {table_name} ({keys})
VALUES ({placeholders});
"""
try:
with self.conn:
self.conn.execute(query, values)
except Exception as e:
print("Error inserting:", e)
def update(self, table_name, data: dict, where_column, where_value):
set_clause = ", ".join(f"{key} = ?" for key in data.keys())
values = list(data.values())
values.append(where_value)
query = f"""
UPDATE {table_name}
SET {set_clause}
WHERE {where_column} = ?;
"""
try:
with self.conn:
self.conn.execute(query, tuple(values))
except Exception as e:
print("Error updating:", e)
def delete(self, table_name, where_column, where_value):
query = f"DELETE FROM {table_name} WHERE {where_column} = ?;"
try:
with self.conn:
self.conn.execute(query, (where_value,))
except Exception as e:
print("Error deleting:", e)
def exists(self, table_name, where_column, where_value):
query = f"SELECT 1 FROM {table_name} WHERE {where_column} = ? LIMIT 1;"
try:
cursor = self.conn.execute(query, (where_value,))
return cursor.fetchone() is not None
except Exception as e:
print("Error checking existence:", e)
return False
def count(self, table_name):
try:
cursor = self.conn.execute(f"SELECT COUNT(*) as total FROM {table_name};")
return cursor.fetchone()["total"]
except Exception as e:
print("Error counting:", e)
return 0
def close(self):
self.conn.close()

View File

@@ -0,0 +1,35 @@
from bs4 import BeautifulSoup
import re
from db import DBManager, DBAttr
from ui import WinesUI
from req import Requester
from __ssl import init_ssl
from config import *
init_ssl()
dbm = DBManager(DB_PATH)
req = Requester()
def create_tables():
wines_attr = [
DBAttr("name", "TEXT", "NOT NULL"),
DBAttr("price", "INTEGER", "NOT NULL"),
DBAttr("origin", "TEXT", "NOT NULL"),
DBAttr("cellar", "TEXT", "NOT NULL"),
DBAttr("type", "TEXT", "NOT NULL")
]
types_attr = [
DBAttr("type", "TEXT")
]
dbm.create_table("wines", wines_attr)
dbm.create_table("types", types_attr)
def main():
pass
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,10 @@
from urllib.request import urlopen, Request
class Requester():
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (compatible; Konqueror/3.5.8; Linux)"
}
def get(self, url):
return urlopen(Request(url, self.headers))

View File

@@ -0,0 +1,80 @@
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter.scrolledtext import ScrolledText
class WinesUI():
def __init__(self, root, title = "AII"):
self.root = root
self.root.title(title)
self.root.geometry("900x600")
# Menu Principal
self.menu = tk.Menu(self.root)
self.root.config(menu=self.menu)
# Menu Datos
datos_menu = tk.Menu(self.menu, tearoff=0)
datos_menu.add_command(label="Cargar", command=lambda: self.callback("cargar"))
datos_menu.add_separator()
datos_menu.add_command(label="Salir", command=self.root.quit)
self.menu.add_cascade(label="Datos", menu=datos_menu)
# Menu Listar
listar_menu = tk.Menu(self.menu, tearoff=0)
listar_menu.add_command(label="Completo", command=lambda: self.callback("listar_completo"))
listar_menu.add_command(label="Ordenado", command=lambda: self.callback("listar_ordenado"))
self.menu.add_cascade(label="Listar", menu=listar_menu)
# Menu Buscar
buscar_menu = tk.Menu(self.menu, tearoff=0)
buscar_menu.add_command(label="Título", command=lambda: self.callback("buscar_titulo"))
buscar_menu.add_command(label="Editorial", command=lambda: self.callback("buscar_editorial"))
self.menu.add_cascade(label="Buscar", menu=buscar_menu)
# Callback externo desde el punto de entrada
self.callback = None
def show_list(self, books, fields, title="Listado"):
mw = tk.Toplevel(self.root)
mw.title(title)
listbox = tk.Listbox(mw, width=80, height=20)
listbox.pack(side="left", fill="both", expand=True)
scrollbar = tk.Scrollbar(mw)
scrollbar.pack(side="right", fill="y")
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
for book in books:
row = " | ".join(str(book[field]) for field in fields)
listbox.insert("end", row)
def ask_text(self, label, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
entry = ttk.Entry(mw)
entry.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(entry.get()), mw.destroy()]).pack(pady=10)
def ask_spinbox(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
spinbox = ttk.Spinbox(mw, values=options, state="readonly", width=40)
spinbox.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(spinbox.get()), mw.destroy()]).pack(pady=10)
def ask_radiobutton(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
sv = tk.StringVar(value=options[0])
for option in options:
tk.Radiobutton(mw, text=option, variable=sv, value=option).pack(anchor="w")
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(sv.get()), mw.destroy()]).pack(pady=10)
def info(slef, message):
messagebox.showinfo("Información", message)

View File

@@ -1,4 +1,4 @@
ISBN;title;author;year;publisher isbn;title;author;year;publisher
913154;The Way Things Work: An Illustrated Encyclopedia of Technology;C. van Amerongen (translator);1967;"Simon & Schuster" 913154;The Way Things Work: An Illustrated Encyclopedia of Technology;C. van Amerongen (translator);1967;"Simon & Schuster"
1010565;Mog's Christmas;Judith Kerr;1992;Collins 1010565;Mog's Christmas;Judith Kerr;1992;Collins
1046438;Liar;Stephen Fry;Unknown;Harpercollins Uk 1046438;Liar;Stephen Fry;Unknown;Harpercollins Uk
1 ISBN isbn title author year publisher
2 913154 The Way Things Work: An Illustrated Encyclopedia of Technology C. van Amerongen (translator) 1967 Simon & Schuster
3 1010565 Mog's Christmas Judith Kerr 1992 Collins
4 1046438 Liar Stephen Fry Unknown Harpercollins Uk

View File

@@ -0,0 +1,124 @@
import sqlite3
from pathlib import Path
class DBAttr:
def __init__(self, name, type_, modifier=""):
self.name = name
self.type_ = type_
self.modifier = modifier
def sql(self):
parts = [self.name, self.type_]
if self.modifier:
parts.append(self.modifier)
return " ".join(parts)
class DBManager:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, path):
self.path = Path(path)
self.conn = sqlite3.connect(self.path)
self.conn.row_factory = sqlite3.Row
def create_table(self, table_name, attributes: list[DBAttr]):
columns_sql = ",\n ".join(attr.sql() for attr in attributes)
query = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
{columns_sql}
);
"""
try:
with self.conn:
self.conn.execute(query)
except Exception as e:
print("Error creating table:", e)
def get_all(self, table_name):
try:
cursor = self.conn.execute(f"SELECT * FROM {table_name};")
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print("Error selecting:", e)
return []
def get_by(self, table_name, column, value):
try:
query = f"SELECT * FROM {table_name} WHERE {column} = ?;"
cursor = self.conn.execute(query, (value,))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print("Error selecting:", e)
return []
def insert(self, table_name, data: dict):
keys = ", ".join(data.keys())
placeholders = ", ".join("?" for _ in data)
values = tuple(data.values())
query = f"""
INSERT INTO {table_name} ({keys})
VALUES ({placeholders});
"""
try:
with self.conn:
self.conn.execute(query, values)
except Exception as e:
print("Error inserting:", e)
def update(self, table_name, data: dict, where_column, where_value):
set_clause = ", ".join(f"{key} = ?" for key in data.keys())
values = list(data.values())
values.append(where_value)
query = f"""
UPDATE {table_name}
SET {set_clause}
WHERE {where_column} = ?;
"""
try:
with self.conn:
self.conn.execute(query, tuple(values))
except Exception as e:
print("Error updating:", e)
def delete(self, table_name, where_column, where_value):
query = f"DELETE FROM {table_name} WHERE {where_column} = ?;"
try:
with self.conn:
self.conn.execute(query, (where_value,))
except Exception as e:
print("Error deleting:", e)
def exists(self, table_name, where_column, where_value):
query = f"SELECT 1 FROM {table_name} WHERE {where_column} = ? LIMIT 1;"
try:
cursor = self.conn.execute(query, (where_value,))
return cursor.fetchone() is not None
except Exception as e:
print("Error checking existence:", e)
return False
def count(self, table_name):
try:
cursor = self.conn.execute(f"SELECT COUNT(*) as total FROM {table_name};")
return cursor.fetchone()["total"]
except Exception as e:
print("Error counting:", e)
return 0
def close(self):
self.conn.close()

View File

@@ -0,0 +1,23 @@
import csv
class FileReader:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, delimiter=";"):
self.delimiter = delimiter
def read(self, file):
results = []
try:
with open(file, encoding="utf-8-sig") as f:
reader = csv.DictReader(f, delimiter=self.delimiter)
for row in reader:
results.append(dict(row))
except Exception as e:
print(f"Error leyendo archivo: {e}")
return results

View File

@@ -0,0 +1,82 @@
from pathlib import Path
from tkinter import Tk
from files import FileReader
from db import DBManager, DBAttr
from ui import BooksUI
DATA_DIR = Path(__file__).parent.parent / "data"
CSV_PATH = DATA_DIR / "books.csv"
DB_PATH = DATA_DIR / "books.bd"
dbm = DBManager(DB_PATH)
fr = FileReader()
def create_tables():
book_attrs = [
DBAttr("isbn", "INTEGER", "PRIMARY KEY"),
DBAttr("title", "TEXT", "NOT NULL"),
DBAttr("author", "TEXT"),
DBAttr("year", "DATE"),
DBAttr("publisher", "TEXT")
]
dbm.create_table("books", book_attrs)
def main():
create_tables()
root = Tk()
ui = BooksUI(root)
books = fr.read(CSV_PATH)
for book in books:
print(book)
def handle_action(action):
match(action):
case "cargar":
books = fr.read(CSV_PATH)
count = 0
for book in books:
book["isbn"] = int(book["isbn"])
if not dbm.exists("books", "isbn", book["isbn"]):
dbm.insert("books", book)
count += 1
ui.info(f"{count} libros almacenados.")
case "listar_todo":
books = dbm.get_all("books")
ui.show_list(books, ["isbn", "title", "author", "year"])
case "listar_ordenado":
def sort(attr):
books = dbm.get_all("books")
def key_fn(x):
v = x[attr]
if isinstance(v, int):
return v
elif isinstance(v, str) and v.isdigit():
return int(v)
else:
return float('inf')
books.sort(key=key_fn)
ui.show_list(books, ["isbn", "title", "author", "year"])
ui.ask_radiobutton("Ordenar por: ", ["isbn", "year"], sort)
case "buscar_titulo":
def search_title(title):
books = [book for book in dbm.get_all("books") if title.lower() in book["title"].lower()]
ui.show_list(books, ["isbn", "title", "author", "year"])
ui.ask_text("Buscar por título: ", search_title)
case "buscar_editorial":
publishers = list({book["publisher"] for book in dbm.get_all("books")})
publishers.sort()
def search_publisher(publisher):
books = [book for book in dbm.get_all("books") if book["publisher"] == publisher]
ui.show_list(books, ["title", "author", "publisher"])
ui.ask_spinbox("Selecciona editorial: ", publishers, search_publisher)
ui.callback = handle_action
root.mainloop()
dbm.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,80 @@
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter.scrolledtext import ScrolledText
class BooksUI():
def __init__(self, root, title = "AII"):
self.root = root
self.root.title(title)
self.root.geometry("900x600")
# Menu Principal
self.menu = tk.Menu(self.root)
self.root.config(menu=self.menu)
# Menu Datos
datos_menu = tk.Menu(self.menu, tearoff=0)
datos_menu.add_command(label="Cargar", command=lambda: self.callback("cargar"))
datos_menu.add_separator()
datos_menu.add_command(label="Salir", command=self.root.quit)
self.menu.add_cascade(label="Datos", menu=datos_menu)
# Menu Listar
listar_menu = tk.Menu(self.menu, tearoff=0)
listar_menu.add_command(label="Completo", command=lambda: self.callback("listar_completo"))
listar_menu.add_command(label="Ordenado", command=lambda: self.callback("listar_ordenado"))
self.menu.add_cascade(label="Listar", menu=listar_menu)
# Menu Buscar
buscar_menu = tk.Menu(self.menu, tearoff=0)
buscar_menu.add_command(label="Título", command=lambda: self.callback("buscar_titulo"))
buscar_menu.add_command(label="Editorial", command=lambda: self.callback("buscar_editorial"))
self.menu.add_cascade(label="Buscar", menu=buscar_menu)
# Callback externo desde el punto de entrada
self.callback = None
def show_list(self, books, fields, title="Listado"):
mw = tk.Toplevel(self.root)
mw.title(title)
listbox = tk.Listbox(mw, width=80, height=20)
listbox.pack(side="left", fill="both", expand=True)
scrollbar = tk.Scrollbar(mw)
scrollbar.pack(side="right", fill="y")
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
for book in books:
row = " | ".join(str(book[field]) for field in fields)
listbox.insert("end", row)
def ask_text(self, label, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
entry = ttk.Entry(mw)
entry.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(entry.get()), mw.destroy()]).pack(pady=10)
def ask_spinbox(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
spinbox = ttk.Spinbox(mw, values=options, state="readonly", width=40)
spinbox.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(spinbox.get()), mw.destroy()]).pack(pady=10)
def ask_radiobutton(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
sv = tk.StringVar(value=options[0])
for option in options:
tk.Radiobutton(mw, text=option, variable=sv, value=option).pack(anchor="w")
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(sv.get()), mw.destroy()]).pack(pady=10)
def info(slef, message):
messagebox.showinfo("Información", message)

View File

@@ -1,39 +0,0 @@
import sqlite3
from pathlib import Path
class DBManager:
def __init__(self, path):
self.conn = sqlite3.connect(path)
def init(self):
try:
with self.conn:
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS books (
isbn INTEGER PRIMARY KEY,
title TEXT,
author TEXT,
year DATE,
publisher TEXT
);
"""
)
except Exception as e:
print("Error creating table:", e)
def insert(self, item):
try:
with self.conn:
self.conn.execute(
"""
INSERT INTO books (isbn, title, author, year, publisher)
VALUES (?, ?, ?, ?, ?);
""",
(item.isbn, item.title, item.author, item.year, item.publisher)
)
except Exception as e:
print("Error inserting book:", e)
def close(self):
self.conn.close()

View File

@@ -1,10 +0,0 @@
import csv
from collections import namedtuple
nt = namedtuple("Book", ["isbn", "title", "author", "year", "publisher"])
def read_file(file):
with open(file, encoding="utf-8") as f:
reader = csv.reader(f, delimiter=";")
next(reader)
return [nt(r[0], r[1], r[2], r[3], r[4]) for r in reader]

View File

@@ -1,16 +0,0 @@
from files import read_file
from db import DBManager
from pathlib import Path
DATA = Path(__file__).parent.parent / "data"
def main():
dbm = DBManager(DATA / "books.bd")
dbm.init()
file_path = DATA / "books.csv"
for book in read_file(file_path):
dbm.insert(book)
if __name__ == "__main__":
main()

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
lxml
bs4
requests