Lista 10-23: Lista completa de la nueva agenda

##############################################################################
# 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 10\10.23 - Lista completa de la nueva agenda.py
# Descripción: Lista completa de la nueva agenda
##############################################################################

import sys
import pickle
from functools import total_ordering


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)


@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()


@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 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 Teléfonos(ListaÚnica):
    def __init__(self):
        super().__init__(Teléfono)


class TiposTeléfono(ListaÚnica):
    def __init__(self):
        super().__init__(TipoTeléfono)


class DatoAgenda:
    def __init__(self, nombre):
        self.nombre = nombre
        self.teléfonos = Teléfonos()

    @property
    def nombre(self):
        return self.__nombre

    @nombre.setter
    def nombre(self, valor):
        if type(valor) != Nombre:
            raise TypeError("nombre debe ser una instancia de la clase Nombre")
        self.__nombre = valor

    def investigaciónTeléfono(self, teléfono):
        posición = self.teléfonos.investigación(Teléfono(teléfono))
        if posición == -1:
            return None
        else:
            return self.teléfonos[posición]


class Agenda(ListaÚnica):
    def __init__(self):
        super().__init__(DatoAgenda)
        self.tiposTeléfono = TiposTeléfono()

    def adicionaTipo(self, tipo):
        self.tiposTeléfono.adiciona(TipoTeléfono(tipo))

    def investigaciónNombre(self, nombre):
        if type(nombre) == str:
            nombre = Nombre(nombre)
        for datos in self.lista:
            if datos.nombre == nombre:
                return datos
            else:
                return None

    def ordena(self):
        super().ordena(lambda dato: str(dato.nombre))


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()

    @staticmethod
    def pide_nombre_archivo():
        return(input("Nombre del archivo: "))

    def __init__(self):
        self.agenda = Agenda()
        self.agenda.adicionaTipo("Celular")
        self.agenda.adicionaTipo("Residencia")
        self.agenda.adicionaTipo("Trabajo")
        self.agenda.adicionaTipo("Fax")
        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.menú.adicionaopción("Graba", self.graba)
        self.menú.adicionaopción("Lee", self.lee)
        self.menú.adicionaopción("Ordena", self.ordena)
        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):
        dato = self.agenda.investigaciónNombre(nombre)
        return dato

    def nuevo(self):
        nuevo = AppAgenda.pide_nombre()
        if nulo_o_vacío(nuevo):
            return
        nombre = Nombre(nuevo)
        if self.investigación(nombre) != None:
            print("¡Nombre ya existe!")
            return
        registro = DatoAgenda(nombre)
        self.menú_teléfonos(registro)
        self.agenda.adiciona(registro)

    def borra(self):
        if len(self.agenda) == 0:
            print("Agenda vacía, nada a borrar")
        nombre = AppAgenda.pide_nombre()
        if(nulo_o_vacío(nombre)):
            return
        p = self.investigación(nombre)
        if p != None:
            self.agenda.remove(p)
            print("Borrado. La agenda ahora tiene solo: %d registros" % len(self.agenda))
        else:
            print("Nombre no encontrado.")

    def altera(self):
        if len(self.agenda) == 0:
            print("Agenda vacía, nada a alterar")
        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)
        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(Telé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:
            AppAgenda.muestra_datos(e)
        print("-" * 60)

    def lee(self, nombre_archivo=None):
        if nombre_archivo == None:
            nombre_archivo = AppAgenda.pide_nombre_archivo()
        if nulo_o_vacío(nombre_archivo):
            return
        with open(nombre_archivo, "rb") as archivo:
            self.agenda = pickle.load(archivo)
            self.ultimo_nombre = nombre_archivo

    def ordena(self):
        self.agenda.ordena()
        print("\nAgenda ordenada\n")

    def graba(self):
        if self.ultimo_nombre != None:
            print("Último nombre utilizado fue '%s'" % self.ultimo_nombre)
            print("Digite enter en caso de que quiera utilizar el mismo nombre")
            nombre_archivo = AppAgenda.pide_nombre_archivo()
            if nulo_o_vacío(nombre_archivo):
                if self.ultimo_nombre != None:
                    nombre_archivo = self.ultimo_nombre
                else:
                    return
            with open(nombre_archivo, "wb") as archivo:
                pickle.dump(self.agenda, archivo)

    def ejecute(self):
        self.menú.ejecute()

if __name__ == "__main__":
    app = AppAgenda()
    if len(sys.argv) > 1:
        app.lee(sys.argv[1])
    app.ejecute()
Haga clic aquí para bajar el archivo