##############################################################################
# Parte del libro Introducción a la programación con Python
# Autor: Nilo Ney Coutinho Menezes
# Editora Novatec (c) 2015 - ISBN 978-85-7522-250-8
# Primera edición - Mayo/2016
# Sitio: http://www.librodepython.com
#
# Archivo: lista\capítulo 11\11.31 - Agenda con banco de datos completo.py
# Descripción: Agenda con banco de datos completo
##############################################################################
import sys
import sqlite3
import os.path
from functools import total_ordering
BANCO = """
create table tipos(id integer primary key autoincrement,
descripción text);
create table nombres(id integer primary key autoincrement,
nombre text);
create table teléfonos(id integer primary key autoincrement,
id_nombre integer,
número text,
id_tipo integer);
insert into tipos(descripción) values ("Celular");
insert into tipos(descripción) values ("Fijo");
insert into tipos(descripción) values ("Fax");
insert into tipos(descripción) values ("Casa");
insert into tipos(descripción) values ("Trabajo");
"""
def nulo_o_vacío(texto):
return texto == None or not texto.strip()
def valida_franja_entero(pregunta, inicio, fin, estándar=None):
while True:
try:
entrada = input(pregunta)
if nulo_o_vacío(entrada) and estándar != None:
entrada = estándar
valor = int(entrada)
if inicio <= valor <= fin:
return(valor)
except ValueError:
print("Valor inválido, favor digitar entre %d y %d" %
(inicio, fin))
def valida_franja_entero_o_blanco(pregunta, inicio, fin):
while True:
try:
entrada = input(pregunta)
if nulo_o_vacío(entrada):
return None
valor = int(entrada)
if inicio <= valor <= fin:
return(valor)
except ValueError:
print("Valor inválido, favor digitar entre %d y %d" %
(inicio, fin))
class ListaÚnica:
def __init__(self, elem_class):
self.lista = []
self.elem_class = elem_class
def __len__(self):
return len(self.lista)
def __iter__(self):
return iter(self.lista)
def __getitem__(self, p):
return self.lista[p]
def indiceVálido(self, i):
return i >= 0 and i < len(self.lista)
def adiciona(self, elem):
if self.investigación(elem) == -1:
self.lista.append(elem)
def remove(self, elem):
self.lista.remove(elem)
def investigación(self, elem):
self.verifica_tipo(elem)
try:
return self.lista.index(elem)
except ValueError:
return -1
def verifica_tipo(self, elem):
if type(elem) != self.elem_class:
raise TypeError("Tipo inválido")
def ordena(self, clave=None):
self.lista.sort(key=clave)
class DBListaÚnica(ListaÚnica):
def __init__(self, elem_class):
super().__init__(elem_class)
self.borrados = []
def remove(self, elem):
if elem.id is not None:
self.borrados.append(elem.id)
super().remove(elem)
def limpia(self):
self.borrados = []
@total_ordering
class Nombre:
def __init__(self, nombre):
self.nombre = nombre
def __str__(self):
return self.nombre
def __repr__(self):
return "<Clase {3} en 0x{0:x} Nombre: {1} Clave: {2}>".format(
id(self), self.__nombre, self.__clave, type(self).__name__)
def __eq__(self, otro):
return self.nombre == otro.nombre
def __lt__(self, otro):
return self.nombre < otro.nombre
@property
def nombre(self):
return self.__nombre
@nombre.setter
def nombre(self, valor):
if nulo_o_vacío(valor):
raise ValueError("Nombre no puede ser nulo ni en blanco")
self.__nombre = valor
self.__clave = Nombre.CreaClave(valor)
@property
def clave(self):
return self.__clave
@staticmethod
def CreaClave(nombre):
return nombre.strip().lower()
class DBNome(Nombre):
def __init__(self, nombre, id_=None):
super().__init__(nombre)
self.id = id_
@total_ordering
class TipoTeléfono:
def __init__(self, tipo):
self.tipo = tipo
def __str__(self):
return "({0})".format(self.tipo)
def __eq__(self, otro):
if otro is None:
return False
return self.tipo == otro.tipo
def __lt__(self, otro):
return self.tipo < otro.tipo
class DBTipoTeléfono(TipoTeléfono):
def __init__(self, id_, tipo):
super().__init__(tipo)
self.id = id_
class Teléfono:
def __init__(self, número, tipo=None):
self.número = número
self.tipo = tipo
def __str__(self):
if self.tipo != None:
tipo = self.tipo
else:
tipo = ""
return "{0} {1}".format(self.número, tipo)
def __eq__(self, otro):
return self.número == otro.número and (
(self.tipo == otro.tipo) or (
self.tipo == None or otro.tipo == None))
@property
def número(self):
return self.__número
@número.setter
def número(self, valor):
if nulo_o_vacío(valor):
raise ValueError("Número no puede ser None o en blanco")
self.__número = valor
class DBTeléfono(Teléfono):
def __init__(self, número, tipo=None, id_=None, id_nombre=None):
super().__init__(número, tipo)
self.id = id_
self.id_nombre = id_nombre
class DBTeléfonos(DBListaÚnica):
def __init__(self):
super().__init__(DBTeléfono)
class DBTiposTeléfono(ListaÚnica):
def __init__(self):
super().__init__(DBTipoTeléfono)
class DBDadoAgenda:
def __init__(self, nombre):
self.nombre = nombre
self.teléfonos = DBTeléfonos()
@property
def nombre(self):
return self.__nombre
@nombre.setter
def nombre(self, valor):
if type(valor) != DBNome:
raise TypeError("nombre debe ser una instancia de la clase DBNome")
self.__nombre = valor
def investigaciónTeléfono(self, teléfono):
posición = self.teléfonos.investigación(DBTeléfono(teléfono))
if posición == -1:
return None
else:
return self.teléfonos[posición]
class DBAgenda:
def __init__(self, banco):
self.tiposTeléfono = DBTiposTeléfono()
self.banco = banco
nuevo = not os.path.isfile(banco)
self.conexión = sqlite3.connect(banco)
self.conexión.row_factory = sqlite3.Row
if nuevo:
self.crea_banco()
self.cargaTipos()
def cargaTipos(self):
for tipo in self.conexión.execute("select * from tipos"):
id_ = tipo["id"]
descripción = tipo["descripción"]
self.tiposTeléfono.adiciona(DBTipoTeléfono(id_, descripción))
def crea_banco(self):
self.conexión.executescript(BANCO)
def investigaciónNombre(self, nombre):
if not isinstance(nombre, DBNome):
raise TypeError("nombre debe ser del tipo DBNome")
encontrado = self.conexión.execute("""select count(*)
from nombres where nombre = ?""",
(nombre.nombre,)).fetchone()
if(encontrado[0] > 0):
return self.carga_por_nombre(nombre)
else:
return None
def carga_por_id(self, id):
consulta = self.conexión.execute(
"select * from nombres where id = ?", (id,))
return carga(consulta.fetchone())
def carga_por_nombre(self, nombre):
consulta = self.conexión.execute(
"select * from nombres where nombre = ?", (nombre.nombre,))
return self.carga(consulta.fetchone())
def carga(self, consulta):
if consulta is None:
return None
nuevo = DBDadoAgenda(DBNome(consulta["nombre"], consulta["id"]))
for teléfono in self.conexión.execute(
"select * from teléfonos where id_nombre = ?",
(nuevo.nombre.id,)):
ntel = DBTeléfono(teléfono["número"], None,
teléfono["id"], teléfono["id_nombre"])
for tipo in self.tiposTeléfono:
if tipo.id == teléfono["id_tipo"]:
ntel.tipo = tipo
break
nuevo.teléfonos.adiciona(ntel)
return nuevo
def lista(self):
consulta = self.conexión.execute(
"select * from nombres order by nombre")
for registro in consulta:
yield self.carga(registro)
def nuevo(self, registro):
try:
cur = self.conexión.cursor()
cur.execute("insert into nombres(nombre) values (?)",
(str(registro.nombre),))
registro.nombre.id = cur.lastrowid
for teléfono in registro.teléfonos:
cur.execute("""insert into teléfonos(número,
id_tipo, id_nombre) values (?,?,?)""",
(teléfono.número, teléfono.tipo.id,
registro.nombre.id))
teléfono.id = cur.lastrowid
self.conexión.commit()
except:
self.conexión.rollback()
raise
finally:
cur.close()
def actualiza(self, registro):
try:
cur = self.conexión.cursor()
cur.execute("update nombres set nombre=? where id = ?",
(str(registro.nombre), registro.nombre.id))
for teléfono in registro.teléfonos:
if teléfono.id is None:
cur.execute("""insert into teléfonos(número,
id_tipo, id_nombre)
values (?,?,?)""",
(teléfono.número, teléfono.tipo.id,
registro.nombre.id))
teléfono.id = cur.lastrowid
else:
cur.execute("""update teléfonos set número=?,
id_tipo=?, id_nombre=?
where id = ?""",
(teléfono.número, teléfono.tipo.id,
registro.nombre.id, teléfono.id))
for borrado in registro.teléfonos.borrados:
cur.execute("delete from teléfonos where id = ?", (borrado,))
self.conexión.commit()
registro.teléfonos.limpia()
except:
self.conexión.rollback()
raise
finally:
cur.close()
def borra(self, registro):
try:
cur = self.conexión.cursor()
cur.execute("delete from teléfonos where id_nombre = ?",
(registro.nombre.id,))
cur.execute("delete from nombres where id = ?",
(registro.nombre.id,))
self.conexión.commit()
except:
self.conexión.rollback()
raise
finally:
cur.close()
class Menú:
def __init__(self):
self.opciones = [["Salir", None]]
def adicionaopción(self, nombre, función):
self.opciones.append([nombre, función])
def exhibe(self):
print("====")
print("Menú")
print("====\n")
for i, opción in enumerate(self.opciones):
print("[{0}] - {1}".format(i, opción[0]))
print()
def ejecute(self):
while True:
self.exhibe()
elija = valida_franja_entero("Elija una opción: ",
0, len(self.opciones) - 1)
if elija == 0:
break
self.opciones[elija][1]()
class AppAgenda:
@staticmethod
def pide_nombre():
return(input("Nombre: "))
@staticmethod
def pide_teléfono():
return(input("Teléfono: "))
@staticmethod
def muestra_datos(datos):
print("Nombre: %s" % datos.nombre)
for teléfono in datos.teléfonos:
print("Teléfono: %s" % teléfono)
print()
@staticmethod
def muestra_datos_teléfono(datos):
print("Nombre: %s" % datos.nombre)
for i, teléfono in enumerate(datos.teléfonos):
print("{0} - Teléfono: {1}".format(i, teléfono))
print()
def __init__(self, banco):
self.agenda = DBAgenda(banco)
self.menú = Menú()
self.menú.adicionaopción("Nuevo", self.nuevo)
self.menú.adicionaopción("Altera", self.altera)
self.menú.adicionaopción("Borra", self.borra)
self.menú.adicionaopción("Lista", self.lista)
self.ultimo_nombre = None
def pide_tipo_teléfono(self, estándar=None):
for i, tipo in enumerate(self.agenda.tiposTeléfono):
print(" {0} - {1} ".format(i, tipo), end=None)
t = valida_franja_entero("Tipo: ", 0,
len(self.agenda.tiposTeléfono) - 1, estándar)
return self.agenda.tiposTeléfono[t]
def investigación(self, nombre):
if type(nombre) == str:
nombre = DBNome(nombre)
dato = self.agenda.investigaciónNombre(nombre)
return dato
def nuevo(self):
nuevo = AppAgenda.pide_nombre()
if nulo_o_vacío(nuevo):
return
nombre = DBNome(nuevo)
if self.investigación(nombre) != None:
print("¡Nombre ya existe!")
return
registro = DBDadoAgenda(nombre)
self.menú_teléfonos(registro)
self.agenda.nuevo(registro)
def borra(self):
nombre = AppAgenda.pide_nombre()
if(nulo_o_vacío(nombre)):
return
p = self.investigación(nombre)
if p != None:
self.agenda.borra(p)
else:
print("Nombre no encontrado.")
def altera(self):
nombre = AppAgenda.pide_nombre()
if(nulo_o_vacío(nombre)):
return
p = self.investigación(nombre)
if p != None:
print("\nEncontrado:\n")
AppAgenda.muestra_datos(p)
print("Digite enter en caso de que no quiera alterar el nombre")
nuevo = AppAgenda.pide_nombre()
if not nulo_o_vacío(nuevo):
p.nombre.nombre = nuevo
self.menú_teléfonos(p)
self.agenda.actualiza(p)
else:
print("¡Nombre no encontrado!")
def menú_teléfonos(self, datos):
while True:
print("\nEditando teléfonos\n")
AppAgenda.muestra_datos_teléfono(datos)
if(len(datos.teléfonos) > 0):
print("\n[A] - alterar\n[D] - borrar\n", end="")
print("[N] - nuevo\n[S] - salir\n")
operación = input("Elija una operación: ")
operación = operación.lower()
if operación not in ["a", "d", "n", "s"]:
print("Operación inválida. Digite A, D, N o S")
continue
if operación == 'a' and len(datos.teléfonos) > 0:
self.altera_teléfonos(datos)
elif operación == 'd' and len(datos.teléfonos) > 0:
self.borra_teléfono(datos)
elif operación == 'n':
self.nuevo_teléfono(datos)
elif operación == "s":
break
def nuevo_teléfono(self, datos):
teléfono = AppAgenda.pide_teléfono()
if nulo_o_vacío(teléfono):
return
if datos.investigaciónTeléfono(teléfono) != None:
print("Teléfono ya existe")
tipo = self.pide_tipo_teléfono()
datos.teléfonos.adiciona(DBTeléfono(teléfono, tipo))
def borra_teléfono(self, datos):
t = valida_franja_entero_o_blanco(
"Digite la posición del número a borrar, enter para salir: ",
0, len(datos.teléfonos) - 1)
if t == None:
return
datos.teléfonos.remove(datos.teléfonos[t])
def altera_teléfonos(self, datos):
t = valida_franja_entero_o_blanco(
"Digite la posición del número a alterar, enter para salir: ",
0, len(datos.teléfonos) - 1)
if t == None:
return
teléfono = datos.teléfonos[t]
print("Teléfono: %s" % teléfono)
print("Digite enter en caso de que no quiera alterar el número")
nuevoteléfono = AppAgenda.pide_teléfono()
if not nulo_o_vacío(nuevoteléfono):
teléfono.número = nuevoteléfono
print("Digite enter en caso de que no quiera alterar el tipo")
teléfono.tipo = self.pide_tipo_teléfono(
self.agenda.tiposTeléfono.investigación(teléfono.tipo))
def lista(self):
print("\nAgenda")
print("-" * 60)
for e in self.agenda.lista():
AppAgenda.muestra_datos(e)
print("-" * 60)
def ejecute(self):
self.menú.ejecute()
if __name__ == "__main__":
if len(sys.argv) > 1:
app = AppAgenda(sys.argv[1])
app.ejecute()
else:
print("Error: nombre del banco de datos no informado")
print(" agenda.py nombre_del_banco")