Consolida una línea de comandos profesional en Python: implementa un método de lectura en el service, usa csv.DictReader para convertir registros en objetos tipo diccionario y muestra la salida con click.echo para un comportamiento consistente en cualquier sistema operativo. Aquí verás cómo pasar de imprimir cadenas sueltas a una interfaz limpia y estable.
¿Cómo implementar el método list_clients en el service?
Para obtener los clientes desde la “tabla” (archivo), se abre el recurso en modo lectura, se crea un DictReader con los nombres de campo del esquema del cliente y se convierte el reader (iterable) a una lista. Así, el service devuelve una colección lista para iterar en la consola. La idea clave: el reader es iterable y la función global list() lo materializa en memoria.
import csv
CLIENT_SCHEMA =["uid","name","company","email","position"]# del client schemaclassClientService:def__init__(self, table_path): self._table_path = table_path # referencia a la "tabla"deflist_clients(self):# no recibe argumentos externoswithopen(self._table_path, mode="r")as f:# abrir en lectura reader = csv.DictReader(f, fieldnames=CLIENT_SCHEMA)returnlist(reader)# convertir iterable a lista
¿Qué modos de apertura usar y por qué?
r: lectura. Abre el archivo existente para leer datos.
a: append. Agrega al final sin borrar contenido.
w: write. Escribe desde cero, sobreescribe el archivo.
¿Cómo mostrar la lista en la consola con Click?
El comando list no requiere argumentos ni opciones adicionales. Se inicializa el service con el contexto y el nombre de la tabla, se obtiene la lista de clientes y se imprimen headers y filas. Se reemplaza print por click.echo porque print varía entre sistemas y click.echo garantiza salida consistente.
import click
@click.command(name="list")@click.pass_contextdeflist_command(ctx):# inicializar el service con contexto y nombre de la tabla service = ClientService(table_path=ctx.obj["table_name"]) clients_list = service.list_clients()# headers y separador click.echo("ID name company email position") click.echo("-"*60)# formateo de salida por filafor client in clients_list: line ="{uid} {name} {company} {email} {position}".format( uid=client["uid"], name=client["name"], company=client["company"], email=client["email"], position=client["position"],) click.echo(line)
¿Cómo formatear la salida de cada registro?
Construye un string con {} y .format(...) para cada campo.
Usa claves del diccionario: uid, name, company, email, position.
Controla espacios para columnas legibles.
¿Cómo validar el flujo completo desde la CLI?
La verificación es directa: invoca el grupo de comandos, llama list, crea otro cliente y vuelve a listar para confirmar que se agregan registros.
Ejecutar el grupo: pvi clients. Muestra los comandos disponibles.
Listar existentes: pvi clients list. Debe aparecer el primer cliente.
Crear otro con create: nombre “Tom”, compañía “Tom Inc.”, correo “tom arroba tom”, posición “CEO”.
Limpiar pantalla y listar de nuevo: pvi clients list. Ahora aparecen dos clientes.
Este avance muestra cómo separar lógica de negocio (clases y services) de la interfaz de consola con Click. Es un hito: se pasa de concatenar cadenas a una estructura robusta, cercana a estándares de producción.
¿Te gustaría añadir filtros, paginación o un ancho fijo por columna? Comparte tus ideas y retos en los comentarios.
++Tablas con mejor formato++
¡Hola a todos! Les comparto una mejora que le hice al código para que la tabla tenga un mejor formato.
Utilicé un módulo llamado Tabulate, que pueden encontrar en la página oficial de PyPI o solo instalarlo con $ pip install tabulate.
El código del método list queda así:
from tabulate import tabulate
@clients.command()@click.pass_contextdef list(ctx):"""List all clients""" client_service =ClientService(ctx.obj['clients_table']) clients_list = client_service.list_clients() headers =[field.capitalize()for field inClient.schema()] table =[]for client inclients_list: table.append([client['name'], client['company'], client['email'], client['position'], client['uid']])print(tabulate(table, headers))
El resultado:
Les recomiendo leer un poco más acerca del funcionamiento del módulo aquí.
Gran aporte, Gracias ✌
Gracias, esto me parece muchísimo mejor :D
Deberían compartir el código por que el mio no funciona y no tengo con que compararlo....
import uuid
classClient:def__init__(self, name, company, email, position, uid=None): self.name = name
self.company = company
self.email = email
self.position = position
self.uid = uid or uuid.uuid4()#Genera ID únicosdefto_dict(self):returnvars(self)@staticmethod# estático es que se puede ejecutar sin necesidad de una instancia de clasedefschema():return['name','company','email','position','uid']
Y estamos igual y aún así no me funciona
Muy interesante, lástima que a muchos no nos haya servido.
Al ejecutar no aparece nada. :|
Un poco de calma, sobretodo al principio, hubiese sido de mucha ayuda.
Qué diferencia hay entre:
mode=‘a’ y mode=‘w’?
Uno añade y el otro escribe, pero si escribes no estás añadiendo?
Write ('w') lo que hace es sobrescribir el archivo (limpiar el archivo y escribir sobre él).
Por esto, a mi entender, en la otra versión de platzi ventas creabamos una nueva tabla y borrábamos la anterior tmp.
Append ('a'), en cambio, te permite evitar el borrar el archivo y poder añadir o escribir al final del archivo.
Justamente como dices, el modo w (write) sobreescribe el archivo, por lo que no era necesario crear un archivo auxiliar .tmp, quedando así el método ++_save_clients_to_storage++:
for client inclient_list: name, company, email, position, id =list(client.values()) # Guarda los valores.print(f"{id} | {name} | {company} | {email} | {position}")
Gracias!
Con PrettyTable también es posible formatear la tabla en la consola.
from prettytable importPrettyTable@clients.command()@click.pass_contextdef list(ctx):"""List all clients""" client_service =ClientService(ctx.obj['clients_table']) clients = client_service.list_clients() table =PrettyTable() table.field_names=[c.upper()for c inClient.schema()]for client inclients: table.add_row([ client['name'], client['company'], client['email'], client['position'], client['uid'],]) click.echo(table)
Asi se veria usando PrettyTable.
pip install prettytable
!example
Gracias por compartir mejor que tabulate
Me generó una inquietud y es que el profesor colocó una función en el archivo la cual tiene como nombre list y pues se supone que es una keyword reservada de Python, lo extraño es que no le haya fallado.
Aunque sea una palabra reservada python no pone error, solo sobre escribe la función. Digamos, puedes decir:
list =9
y no hay error, pero ahora list es una variable, y no la función que conocemos, por tanto si luego decimos:
a =(1,2,3)list(a)
Esto genera un el siguiente error:
TypeError:'int' object is not callable
Ya que list es entero, no una función.
Aunque python permite sobre escribir sus propias funciones, no es bueno hacerlo.
Al hacer eso lo que está haciendo es convertir la variable a una lista, como cuando tenemos un texto y lo queremos convertir a un entero.
meaning_of_life =int("42")
🙋Aqui:
👉Archivos de la clase 43:
al ejecutar mi programa me manda el siguiente error :
File “C:\platzi-ventas\lib\site-packages\click\decorators.py”, line 71, in _make_command
raise TypeError('Attempted to convert a callback into a '
TypeError: Attempted to convert a callback into a command twice.
Alguien sabe a que se refiere ?
Coloca tu código para que te podamos ayudar :)
Tienes un error en uno de tus decoradores, revisa si tienes los mismos que el maestro, puede ser un typo, a lo mejor te confundiste de nombre de variable, etc.
Fantástico el uso del Framework Click para la creación de API's
La función click.echo(), que en muchos sentidos funciona como la declaración print o función de Python. La principal diferencia es que funciona igual en Python 2 y 3, detecta inteligentemente los flujos de salida mal configurados y nunca fallará (excepto en Python 3)
Está muy bueno conocer más comando de Click que son prácticos y realmente aplicables :D
Para las personas que quieran practicar con una clase para mejorar la visualización de la data, les comparto este package
https://pypi.org/project/beautifultable/
Tanto su documentación como varios ejemplos de uso aparecen allí.
Implementacion inicial de lista de clientes
se modificos 2
services.py
se agrego funcion list_clients //metodo para leer csv a un objeto lista usando
//el metodo estatico de clase client
command.py
se modifico el metodo list_clients
se intancia la clase ClientServices con los paremetro nombre de la tabla de contexto
en la variable client_service se hace referencia a nuestra clase Client_service
//trae el listado de clientes
en la variable lista client_list se accesa al metodo de la variable client_service
instanciada al metodo list_clients
//se crea encabezado de datos a mostrar
//iteramos a lo largo de nuestra lista de clientes
Commands.py
importclickfrom clients.servicesimportClientServicesfrom clients.modelsimportClient@click.group()def clients():""" Manages the clients lifecycle""" pass
@clients.command()@click.option('-n','--name', type=str, prompt=True, help='The client name')@click.option('-c','--company', type=str, prompt=True, help='The client Company')@click.option('-e','--email', type=str, prompt=True, help='The client email')@click.option('-p','--position', type=str, prompt=True, help='The client position')@click.pass_contextdef create(ctx, name, company, email, position):"""Create a new client """ client =Client(name, company, email, position) client_service =ClientServices(ctx.obj['clients_table']) client_service.create_client(client)@clients.command()@click.pass_contextdef list_clients(ctx):"""List all clients""" client_services =ClientServices(ctx.obj['clients_table']) client_list = client_services.list_clients() click.echo(' ID | NAME | COMPANY | EMAIL | POSITION') click.echo('*'*50)for client inclient_list:print('{uid} | {name} | {company} | {email} | {position}'.format( uid = client['uid'], name = client['name'], company = client['company'], email = client['email'], position = client['position']))@clients.command()@click.pass_contextdef update(ctx, client_name):"""Update a client """@clients.command()@click.pass_contextdef delete(ctx, client_name):"""Delete a client""" pass
all = clients
ImportError: cannot import name
nose si es la version de python :C pero no me funciona
Hola
¿Qué versión de Python estás utilizando? Puedes compartir el repositorio o integrar el código el mensaje.
Hola, no he podido ejecutar el programa, cuando escribo pv --help o también pv clients list, pv clients create, etc no se me ejecuta nada ¿Alguien podría ayudarme?
Podes probar instalando pv, para eso se ejecuta sudo apt-get install pv
Hola podrías poner un screenshoot de lo que te sale cuando tratas de ejecutar el programa.
Seria bueno saber que sistema operativo usas y si tuviste algún problema al instalar el paquete Cick.
Saludos
Hola, desde hace algunas clases que no he podido ejecutar el programa, cuando escribo pv --help o también pv clients list, pv clients create, etc no se me ejecuta nada ¿Alguien podría ayudarme?
Aparece que estoy en el entorno virtual y todo.
Como te respondí en otro consulta que hiciste. Trata de revisar si tu código es igual al que escribió el profesor o intenta a hacer todo de nuevo. Creando nuevos archivos con su entorno virtual, en otra carpeta para que no tengas conflictos.
Pueden ser dos cosas, puede que alguno te tus archivos no esté igual que el que vamos haciendo a lo mejor algún typo pero si no te está dando error, entonces el problema está en la instalación del --editable.
Ingresa a tu carpeta y borra la carpeta que se llama algo así como egg.info--editable (es algo así), de paso también borra la carpeta venv.
Una vez tengas todo eso limpio, instala de nuevo el entorno virtual, instala la librería de click con: pip install click
Después instala el editable con: pip install --editable .
(Importante el punto al final)
Y listo, aveces movemos los archivos lo las carpetas y puede fallar alguno de estos 2. Yo tenía el mismo problema y así lo solucioné
Hola :
Al correr pv clients list, me muestra los datos del id de manera diferente al profesor, ya que a el le sale una serie de números y letras en el id y a mi me muestra un modulo como lo muestro a continuación :
ya ni debes ver esto, pero ya la culpa era de tener un archivo aparte llamado uuid.py y si no comparten el código difícil.
A alguien le sucedio que no le funcionara con click.echo() y si lo imprima con print nomas. Porque a mi me sucede eso. Tengo python 3.9. Cuando coloco con click.echo() me sale lo siguiente:
", line 298, in echo
file.write(out) # type: ignore
AttributeError: 'str' object has no attribute 'write'
¡Hola! :)
Si pudieras compartirnos tu código sería más sencillo encontrar la solución. Puedes copiar y pegar utilizando el botón de </> código.
¡Saludos!
Hola ODCenteno gracias por la rapida atencion, lo que sucedia era que colocaba lo siguiente
click.echo(click.style(' ID | NOMBRE | DEPARTAMENTO | CONTACTO | EMAIL ','\n','*'*100)
por lo que me saliá error, esto leyendo documentación, aun no encontré como colocar el salto de linea y los 100 asteriscos, porque la coma es lo que me da el error. Pero ya encontre otras funcionalidades de click que están buenas, por ejemplo:
click.echo(click.style(' ID | NOMBRE | DEPARTAMENTO | CONTACTO | EMAIL ', bg='blue'))
al agregarle click.style se le pueden dar colores a las letras con fg, o con bg se reslata(como con un marcado) todo el texto, en este caso lo puse en azul. Voy a seguir buscando como colocar en la misma linea de codigo el salto de linea. Gracias
TypeError: create_client() takes 1 positional argument but 2 were given
El error dice que estás enviando 2 argumentos cuando espera 1.
A mi me apareció el mismo error, mi problema fue que tenia una coma en vez de un punto en un print("{}",format) -> print("{}".format) espero lo hallas resuelto.
No entiendo el concepto del "contexto" en python en esencialmente lineas como esta:
@click.pass_context
client_service = ClientService(ctx.obj['clients_table'])
El contexto es el que nos da el decorador @click.pass_context.
Pero que funcion cumple?
Me parece que tiene que ver con el archivo csv que creamos, cada que pasamos el contexto estaremos pasando ese archivo donde podremos acceder a los datos que ya teníamos antes, al "contexto"