Documentazione tecnica delle classi Python, con docstrings, annotazioni di tipo e logica di business completa.
Visualizza la struttura delle classi e le loro relazioni.
Le classi astratte definiscono l'interfaccia comune per Libri e Utenti.
Libro (libro.py)from abc import ABC, abstractmethod
from datetime import date
class Libro(ABC):
"""
Classe astratta che rappresenta un generico libro nella biblioteca.
Definisce gli attributi comuni (titolo, autore, isbn) e impone
l'implementazione del calcolo della scadenza alle sottoclassi.
"""
def __init__(self, titolo, autore, isbn, anno_pubblicazione):
self.titolo = titolo
self.autore = autore
self.isbn = isbn
self.anno_pubblicazione = anno_pubblicazione
@abstractmethod
def calcola_scadenza(self, data_inizio: date) -> date:
"""
Metodo astratto per calcolare la data di scadenza del prestito.
La durata varia in base al tipo di libro (Fisico vs EBook).
"""
pass
def descrizione(self) -> str:
"""Restituisce una stringa formattata con i dettagli principali."""
return f"{self.titolo} - {self.autore} ({self.anno_pubblicazione})"
Utente (utente.py)from abc import ABC, abstractmethod
class Utente(ABC):
"""
Classe astratta per gli utenti della biblioteca.
Gestisce i dati anagrafici e la lista dei prestiti attivi.
"""
def __init__(self, nome, cognome, numero_tessera, email):
self.nome = nome
self.cognome = cognome
self.numero_tessera = numero_tessera
self.email = email
self.lista_prestiti_attivi = []
@abstractmethod
def calcola_limite_prestiti(self) -> int:
"""Restituisce il numero massimo di libri che l'utente può avere in prestito."""
pass
@abstractmethod
def calcola_multa(self, giorni_ritardo: int) -> float:
"""Calcola l'importo della multa in base ai giorni di ritardo."""
pass
def aggiungi_prestito(self, prestito):
"""Aggiunge un oggetto Prestito alla lista dell'utente."""
self.lista_prestiti_attivi.append(prestito)
def rimuovi_prestito(self, prestito):
"""Rimuove un prestito dalla lista (solitamente alla restituzione)."""
if prestito in self.lista_prestiti_attivi:
self.lista_prestiti_attivi.remove(prestito)
Le sottoclassi che estendono i modelli base con logiche specifiche.
LibroFisico (libro_fisico.py)from datetime import date, timedelta
from libro import Libro
class LibroFisico(Libro):
"""
Rappresenta un libro cartaceo.
Caratteristiche: Ha una posizione fisica e richiede manutenzione.
Scadenza prestiti: 30 giorni.
"""
def __init__(self, titolo, autore, isbn, anno_pubblicazione,
numero_pagine, stato_conservazione, posizione_scaffale):
super().__init__(titolo, autore, isbn, anno_pubblicazione)
self.numero_pagine = numero_pagine
self.stato_conservazione = stato_conservazione
self.posizione_scaffale = posizione_scaffale
def calcola_scadenza(self, data_inizio: date) -> date:
"""Aggiunge 30 giorni alla data di inizio."""
return data_inizio + timedelta(days=30)
def effettua_manutenzione(self):
"""Aggiorna lo stato di conservazione del libro."""
self.stato_conservazione = "Ottimo (Restaurato)"
print(f"Manutenzione effettuata su: {self.titolo}")
EBook (ebook.py)from datetime import date, timedelta
from libro import Libro
class EBook(Libro):
"""
Rappresenta un libro digitale.
Caratteristiche: Ha formato file (PDF/EPUB) e dimensione.
Scadenza prestiti: 14 giorni.
"""
def __init__(self, titolo, autore, isbn, anno_pubblicazione,
formato, dimensione_file):
super().__init__(titolo, autore, isbn, anno_pubblicazione)
self.formato = formato
self.dimensione_file = dimensione_file
def calcola_scadenza(self, data_inizio: date) -> date:
"""Aggiunge 14 giorni alla data di inizio."""
return data_inizio + timedelta(days=14)
def scarica(self):
print(f"Scaricamento in corso di '{self.titolo}' ({self.dimensione_file})... Completato.")
StudenteUniversitario (studente_universitario.py)from utente import Utente
class StudenteUniversitario(Utente):
"""
Utente Studente.
Limite prestiti: 5 libri.
Multa giornaliera: 0.50€.
"""
def __init__(self, nome, cognome, numero_tessera, email, numero_matricola, facolta):
super().__init__(nome, cognome, numero_tessera, email)
self.numero_matricola = numero_matricola
self.facolta = facolta
def calcola_limite_prestiti(self) -> int:
return 5
def calcola_multa(self, giorni_ritardo: int) -> float:
return giorni_ritardo * 0.50
Insegnante (insegnante.py)from utente import Utente
class Insegnante(Utente):
"""
Utente Insegnante.
Limite prestiti: 10 libri.
Multa giornaliera: 0.25€ (Agevolata).
"""
def __init__(self, nome, cognome, numero_tessera, email, dipartimento, ore_insegnamento):
super().__init__(nome, cognome, numero_tessera, email)
self.dipartimento = dipartimento
self.ore_insegnamento = ore_insegnamento
def calcola_limite_prestiti(self) -> int:
return 10
def calcola_multa(self, giorni_ritardo: int) -> float:
return giorni_ritardo * 0.25
Le classi che gestiscono il flusso dei dati, le transazioni di prestito e il calcolo delle sanzioni.
Biblioteca (biblioteca.py)La classe "Controller" principale che orchestra utenti e libri.
from datetime import date
from prestito import Prestito
class Biblioteca:
"""
Classe Facade che gestisce l'intero sistema bibliotecario.
Mantiene liste di libri, utenti, prestiti attivi e multe.
"""
def __init__(self, nome):
self.nome = nome
self.lista_libri = []
self.lista_utenti = []
self.lista_prestiti = []
self.lista_multe = []
def aggiungi_libro(self, libro):
"""Aggiunge un nuovo libro al catalogo."""
self.lista_libri.append(libro)
print(f"Libro aggiunto: {libro.titolo}")
def registra_utente(self, utente):
"""Registra un nuovo utente nel sistema."""
self.lista_utenti.append(utente)
print(f"Utente registrato: {utente.nome} {utente.cognome}")
def controlla_disponibilita(self, libro) -> bool:
"""
Verifica se un libro è disponibile.
Return: False se il libro è attualmente in un prestito non restituito.
"""
for prestito in self.lista_prestiti:
if prestito.libro == libro and prestito.data_restituzione is None:
return False
return True
def presta_libro(self, utente, libro, data_inizio):
"""
Effettua un prestito se:
1. Il libro è disponibile.
2. L'utente non ha raggiunto il suo limite di prestiti.
"""
if not self.controlla_disponibilita(libro):
print(f"Errore: Il libro '{libro.titolo}' non è disponibile.")
return None
if len(utente.lista_prestiti_attivi) < utente.calcola_limite_prestiti():
scadenza = libro.calcola_scadenza(data_inizio)
nuovo_prestito = Prestito(libro, utente, data_inizio, scadenza)
self.lista_prestiti.append(nuovo_prestito)
utente.aggiungi_prestito(nuovo_prestito)
print(f"Prestito registrato: '{libro.titolo}' a {utente.nome}. Scadenza: {scadenza}")
return nuovo_prestito
else:
print(f"Errore: {utente.nome} ha raggiunto il limite di prestiti.")
return None
def restituisci_libro(self, prestito, data_restituzione):
"""
Gestisce la restituzione.
Se la data_restituzione è successiva alla scadenza, genera una multa.
"""
if prestito in self.lista_prestiti:
prestito.data_restituzione = data_restituzione
prestito.utente.rimuovi_prestito(prestito)
multa = prestito.genera_multa(data_restituzione)
if multa:
self.lista_multe.append(multa)
print(f"Libro restituito in RITARDO. Generata multa di {multa.importo}€.")
return multa
else:
print(f"Libro '{prestito.libro.titolo}' restituito in orario.")
return None
else:
print("Errore: Prestito non trovato.")
def calcola_multa_utente(self, utente):
"""Somma tutte le multe non pagate di uno specifico utente."""
totale = sum(m.importo for m in self.lista_multe if m.utente == utente and not m.pagata_flag)
print(f"Totale multe da pagare per {utente.nome}: {totale}€")
return totale
Prestito (prestito.py)Classe di associazione che collega un Libro a un Utente per un periodo di tempo.
from datetime import date
from multa import Multa
class Prestito:
"""
Rappresenta la transazione di prestito di un libro a un utente.
Memorizza data inizio, data scadenza e data restituzione effettiva.
"""
def __init__(self, libro, utente, data_inizio, data_scadenza):
self.libro = libro
self.utente = utente
self.data_inizio = data_inizio
self.data_scadenza = data_scadenza
self.data_restituzione = None
def calcola_giorni_ritardo(self, data_restituzione: date) -> int:
"""Restituisce la differenza in giorni tra restituzione e scadenza (se positiva)."""
if data_restituzione > self.data_scadenza:
delta = data_restituzione - self.data_scadenza
return delta.days
return 0
def genera_multa(self, data_restituzione: date):
"""
Crea un oggetto Multa se c'è un ritardo.
Delega il calcolo dell'importo all'utente (strategia polimorfica).
"""
giorni = self.calcola_giorni_ritardo(data_restituzione)
if giorni > 0:
importo = self.utente.calcola_multa(giorni)
return Multa(self.utente, importo, data_restituzione)
return None
Multa (multa.py)class Multa:
"""
Rappresenta una sanzione pecuniaria per un utente.
Traccia se la multa è stata saldata o meno.
"""
def __init__(self, utente, importo, data_creazione):
self.utente = utente
self.importo = importo
self.data_creazione = data_creazione
self.pagata_flag = False
def pagata(self):
"""Segna la multa come pagata."""
self.pagata_flag = True
print(f"Multa di {self.importo}€ saldata dall'utente {self.utente.nome}.")
Il database remoto è gestito tramite mysql-connector-python.
Contiene tabelle per libri, utenti, prestiti e multe.
CREATE TABLE libri (
id INT AUTO_INCREMENT PRIMARY KEY,
titolo VARCHAR(200) NOT NULL,
autore VARCHAR(200) NOT NULL,
isbn VARCHAR(20) UNIQUE NOT NULL,
anno_pubblicazione INT,
disponibile BOOLEAN DEFAULT TRUE,
tipo_libro ENUM('Fisico', 'EBook') NOT NULL,
-- ... altri campi specifici per tipo_libro (es. posizione_scaffale, link_download, ...)
);
CREATE TABLE utenti (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
cognome VARCHAR(100) NOT NULL,
numero_tessera VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100),
tipo_utente ENUM('Studente', 'Insegnante') NOT NULL,
-- ... altri campi specifici per tipo_utente (es. facolta, dipartimento, ...)
data_registrazione TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE prestiti (
id INT AUTO_INCREMENT PRIMARY KEY,
id_libro INT NOT NULL,
id_utente INT NOT NULL,
data_inizio TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
data_fine_prevista TIMESTAMP NOT NULL,
data_fine_effettiva TIMESTAMP NULL,
FOREIGN KEY (id_libro) REFERENCES libri(id),
FOREIGN KEY (id_utente) REFERENCES utenti(id)
);
Il sistema utilizza un Arduino Nano collegato a un modulo NFC RC522.
L'Arduino legge l'UID del tag NFC e lo invia al computer tramite la porta seriale.
L'applicazione Python usa il modulo pyserial e una classe dedicata SerialListener per ricevere questi dati in background.
SerialListener (serial_listener.py)import serial, threading, time
from queue import Queue
from typing import Optional
class SerialListener:
def __init__(self, uid_queue: Queue, port: str = 'COM3', baudrate: int = 9600):
self.uid_queue = uid_queue
self.port = port
self.baudrate = baudrate
self.ser: Optional[serial.Serial] = None
self._running = threading.Event()
self.thread = threading.Thread(target=self.run, daemon=True)
def start(self):
try:
self.ser = serial.Serial(self.port, self.baudrate, timeout=1)
self._running.set()
self.thread.start()
print(f"SerialListener avviato su {self.port}")
except serial.SerialException as e:
print(f"Errore nell'apertura della porta seriale: {e}")
def stop(self):
self._running.clear()
if self.ser:
self.ser.close()
self.thread.join()
print("SerialListener fermato.")
def run(self):
while self._running.is_set():
if self.ser and self.ser.in_waiting:
try:
raw_line = self.ser.readline().decode('utf-8').strip()
if raw_line:
self.uid_queue.put(raw_line) # Inserisce l'UID in una coda
except UnicodeDecodeError:
pass # Ignora linee corrotte
time.sleep(0.1) # Piccola pausa per non sovraccaricare il thread
Di seguito è riportato lo schema di collegamento hardware tra Arduino Nano e il modulo RC522, come descritto nel codice.
⚠️Assicurati di collegare il VCC del modulo RC522 al pin 3.3V dell'Arduino Nano, non al 5V, per evitare di danneggiare il modulo.⚠️
Clicca sul pulsante qui sotto per scaricare il file .ino completo
da caricare sul tuo Arduino Nano.
Per configurare l'ambiente di sviluppo locale:
pip install customtkinter mysql-connector-python pyserial pillow opencv-python.main.py con i dati di accesso corretti al database MySQL remoto (host, user, password, database).COM3 su Windows, /dev/ttyUSB0 su Linux).python main.py.