Cómo estructurar una app CLI con orientación a objetos
Resumen
Diseña una arquitectura clara en Python con orientación a objetos para un CLI: separa interfaz, lógica de negocio y modelos. Aquí verás cómo modelar un cliente, generar IDs con UUID4, convertir objetos a diccionario y persistir datos en CSV con un servicio dedicado. Todo con código conciso y listo para crecer.
¿Cómo organizar la arquitectura de la aplicación con paquetes y módulos?
En aplicaciones reales, separar responsabilidades acelera el desarrollo y evita errores. Se propone dividir en tres capas: interfaz (comandos del CLI), lógica de negocio (servicios) y abstracciones (modelos/objetos con los que se interactúa).
¿Qué estructura de archivos y paquetes usar en clientes?
Paquete clients con el módulo commands.
Script inicial para ejecutar el CLI.
setup.py para instalar la aplicación en el sistema.
Nuevos archivos: models.py y services.py dentro de clients.
¿Qué papel cumple la interfaz de CLI en comandos?
Define cómo el software interactúa con el exterior.
En CLI, son los comandos que el usuario ejecuta.
Si fuese web: la interfaz gráfica. Si fuera API: los endpoints abiertos.
¿Dónde ubicar la lógica de negocio y las abstracciones?
Lógica de negocio en servicios: abrir y escribir archivos, operaciones de lista y actualización.
Abstracciones en modelos: objetos como el cliente representados con clases.
¿Cómo modelar al cliente con clases, self, UUID y schema?
El objeto cliente concentra datos y utilidades para persistir. Se resalta el uso de self, la generación de UID con el módulo estándar de Python y una representación tipo diccionario para escribir en CSV.
Clase cliente con init y primer parámetro self en métodos de instancia.
Atributos: name, company, email, position y UID.
UID opcional: si no viene, se genera con UUID4 (estándar de la industria).
Conversión a diccionario con vars para escribir a disco en CSV.
Decorador @staticmethod para exponer el schema (columnas) sin instanciar la clase.
Nota sobre lógica booleana: se menciona el uso de all y que None evalúa a falso.
# models.pyimport uuid
classCliente:def__init__(self, name, company, email, position, uid=None): self.name = name
self.company = company
self.email = email
self.position = position
# si llega uid se usa; de lo contrario se genera uno único self.uid = uid or uuid.uuid4()defto_dict(self):# representación del objeto como diccionario, útil para CSVreturnvars(self)@staticmethoddefschema():# columnas que representan al objeto en datos tabularesreturn["name","company","email","position","uid"]
¿Cómo implementar el servicio de clientes y escribir en CSV?
La clase de servicio centraliza la lógica de negocio: sabe dónde guardar, en qué modo abrir y cómo transformar datos a filas tabulares. Así, los comandos solo orquestan y el servicio hace el trabajo.
¿Qué responsabilidades tiene ClientService en la creación?
Recibe el nombre de la tabla: el archivo CSV destino.
Abre el archivo en modo append para añadir nuevas filas.
Usa csv.DictWriter con fieldnames obtenidos de Cliente.schema() sin instanciar.
Estas últimas clases… resultaron muy confusas… Yo soy desarrollador en .Net y Java, y creo que el profesor en lo referente a la programación orientada a objetos y la creación de paquetes presenta muchos conceptos con muy poca explicación; por ejemplo el uso del decorador @staticmethod para indicar que una clase es estática lo introduce sin una explicación muy clara.
Estoy parcialmente de acuerdo, creo que una sección del curso enfocada a dar un vistazo mas a fondo a aspectos de programación funcional y orientada a objetos no hubiera estado de mas en este curso, sin embargo es un curso de Python, y no de programación sobre estos paradigmas. Igual espero que tomen en cuenta estas cosas, ya que a pesar de que uno puede investigar por si solo, deberían de tratar dejar bien claro todo lo que se ve en el curso por igual. Sobre el profesor la verdad ninguna queja, me parece que lo ha hecho bien, desconozco si los profesores son los que planean el curso, pero de ser así solo seria el detalle de profundizar mas.
muy de acuerdo contigo @chincapie, hay algunos conceptos que estan en el aire, y tu comentario es constructivo para con la clase, pero decir que es "un completo asco", de ninguna manera suma como aporte, no soy del equipo de platzi, pero creo que el otro comentario no aporta en nada, en fin, lo mio solo es un comentario, por lo demas el curso(a mi parecer) es muy bueno. Saludos.
Platzi debería tener una opción para calificar cada clase, con esto les seria mas facil hacer seguimiento a las clases que no están funcionando o con las que la comunidad no se siente conforme.
Una idea genial, la verdad que con el tamaño de la empresa y la metodología de estudio pienso que es imprescindible esa opción.
¡Hola @Miltonsanabria! Voy a llevar tu propuesta al equipo, me parece una genial idea. Por el momento, ¿qué crees que falló con esta clase? 🤔
Considero que si se realizara una explicación previa a entrar al código, como se hace en otros videos de este mismo curso, se entendería de manera más sencilla.
De igual forma si nos pusiera en pizarrón (tablet) el diseño de la solución y cómo se va a implementar, igual también ayudaría mucho en el entendimiento, ya que es este caso se salta de un archivo a otro, creando funciones sin nosotros conocer pq.
No es claro. Es muy dificil de seguir, parece que tiene mucho afan para explicar.
Es preferible una clase larga pero con buena pedagogía que esto así
Arrancó lindo el curso, pero llegó un momento que pensó que lo más importante es manejar un sistema UNIX y no el lenguaje en sí.. éste curso fue en picada hacia abajo.
Esta clase parece una carrera contra el tiempo. Creo que podría tomarse un poco mas de tiempo explicando los pasos, sobre todo cuando construye las clases.
¡Hola @datalex! Tu feedback nos sirve mucho para mejorar. En cada actualización de cursos de Platzi siempre vas a encontrar más calidad, y no va a ser distinto con la de Python 😉
Vi que mucha gente se esta perdiendo en estas clases, primero, tranquilos, no se rindan, yo estoy igual que ustedes, recien arranco con la programación y algunos ejercicios o conceptos no me salen!
Asi que aca dejo mi aporte:
1)Repasar repasar y repasar
2)Vuelvan el video una y otra vez
Pd:Aca les dejo mi codigo, se que es una mala practica comentar todas las lineas pero quiza les pueda ayudar.
models.py:
import uuid
classClient: #Crea el objeto cliente
def __init__(self, name, company, email, position, uid=None): #Inicializa las instancias del objeto Cliente self.name= name
self.company= company
self.email= email
self.position= position
self.uid= uid or uuid.uuid4() #Si no recibe como parametro la variable "uid", devuelve el IDUUID4(), que se utiliza para generar ID'S en generral.defto_dict(self):returnvars(self) #Chequea lo que regresa el metodo __dict__ y nos realiza un diccionario de nuestro objeto
@staticmethod #Metodo estaticos dentro de nuestra clase, es un metodo que se puede ejecutar sin la necesidad de una instancia de clase
def schema(): #No hace falta pasar por parametro SELF porque el decorador @staticmethod nos permite hacerlo.return['name','company','email','position','uid'] #devuelve una lista de las variables que existen en el objeto CLIENT'
'''def dict(self): #Intentar el metodo to_dict con este metodo haber si funciona
returnvars(self)''```
services.py:
'''Logica de negocio,
#Aqui vamos a guardar todo lo que signifique cada funcion, update, list, create, etc'''
#Para no perderse aqui, es necesario que tengan bien claro los conceptos de la clase "Manejo de archivos con python"
def create_client(self, client):
with open(self.table_name, mode='a') as f: #Abre el archivo en modo "a = append", añade un cliente al archivo csv, cada vez que se crea uno
writer = csv.DictWriter(f, fieldnames=Client.schema()) #Referencia del WRITER
writer.writerow(client.to) #Escribir una nueva fila tabular dentro de nuestro archivo csv```
PD2:aclaro que recien soy principiante en esto y es por ahora mi forma de entender, me gustaria que alguien pueda corregirme si algo esta mal
Está genial tu aporte!
.
¡No se rindan compañerxs!
No es claro, muy confuso y dificil de seguir!!!
Hola! Te entiendo en ocasiones hay cursos pesados, me ha pasado con este curso también, lo que yo hago es tratar de ver la clase y tomar notas para entender lo que dice el profe. Recuerda que estos cursos tienen mucha información en poco tiempo pero la ventaja es que los podemos ver cuantas veces queramos.
Cuéntanos también, ¿Qué es lo que te confunde y hace que no puedas seguir?
Yo pienso que el profesor demuestra conocimientos profundos en todo momento, de temas muy complicados, al igual que a ustedes me resulta confuso pero trato de apoyame en libros al repecto e investigacion en internet, no es la idea ya que al adquirir un curso esperas que todo este claro pero, al entender las bases investigar y ensayar todo va tomando forma de a poquitos, el curso bueno, el profe conocimientos profundos y definidos, yo pondria el pero en la estructuracion del curso y propongo se sumen mas notas de apoyo escritas al curso y apoyo de herramientas como jupyter que harian mucho mas facil esto que solo videos, no hay forma de aprender sin ensayar y solo video lo hace dificil.
¡Hola Felix! Agradezco tu feedback, ten sabido que es muy valioso para mejorar la próxima actualización del curso. Lo vamos a tener en cuenta 💪🏼
Para los que se pregunten el comando que el profesor usa para mostrar la estructura de archivos tiene una opcion -I donde indica que carpetas quiere ignorar. Para el caso espesifico de él esta ignorando _ _ pycache _ _, que es donde se guardan los archivos compilados por python; esta ignorando *.egg-info, que es donde se guarda la información de la instalación del programa y esta ignorando la carpeta venv, que es donde estan los archivos y carpetas relativos al ambiente virtual. Para mi caso tambien estoy ignorando la carpeta que crea vscode para que la estructura de archivos me quede más limpia y legible.
.
El comando resultante queda realmente largo para escribirlo constantemente, es recomendable guardarlo en un alias, bien sea un alias temporal para la sesion abierta del terminal o escribirlo en el archivo .bashrc que esta en el home.
Uff excelente aporte, deduje lo que mencionas después de ver que pasaba al solo usar tree dentro del entorno virtual y ¡PUM! un chorizote de árbol. Luego ya con lo implementado por el profe es más sencillo.
.
Saludos
El problema no esta en el profesor, ya que explica y se le entiende perfectamente; el problema radica en el contenido y estructuración del curso que presenta vacíos y confusiones entre cada clase.
Entonces si es el profesor, porque el es el encargado de definir la structures del curso.
No siempre es el profesor… muchas veces le toca hacer como lo dicta la empresa pese a que este mal.
Le falta estructurar las clases por que da saltos de información demasiado grandes y es complicado seguir el paso si no se tienen conocimientos de programación
¡Hola @Dreyksauron! Tu feedback nos sirve mucho para mejorar, ten sabido que lo vamos a tener en cuenta para la próxima actualización del curso 💪🏼
Investigando un poco di con que existen classmethods y staticmethods y la diferencia me cuesta mucho entenderla. Aquí un link sobre dicha diferencia, pero está en inglés y me cuesta entenderlo con temas tan complejos.(). Apoyo 😦
basicamente @staticmethod puedes llamarlo sin inizializar la clase ejmplo nameClass.nameFunction() y es inmutable
y @classmethod no puedes llamar la funcion sin inizializar la clase
@staticmethod = Metodos de Clase.
@classmethod = Metodos de instancia.
Considero que la clase no estuvo a la altura de lo que pretende explicar, así que les voy a explicar lo que entendí sobre un tema que se pasó por alto. Si me equivoco en algo espero puedan corregirme:
@classmethods y @staticmethods son decoradores que se suele usar sobre las funciones de una clase.
Cuando trabajamos con clases, los métodos regulares (las funciones de toda la vida) pasan automáticamente una instancia como primer argumento (la llamamos self).
@classmethod automáticamente pasa una instancia referida a la clase como primer argumento (la llamamos cls y se comporta algo parecido a self) ejemplo:
#suponemos que estamos dentro de una clase y estamos definiendo sus métodos
@classmethod
def from_string(cls ,emp_string): #notese el cls
first, last, pay = emp_strsplit('-')
return cls(first,last,pay) #regreso los mismos valores que contiene el init de la clase, es como si cls emulara ser la clase
#no es requisito indispensable retornar cls, se pueden hacer otras cosas sin retornar.
#no se enrieden en los detalles del código, lo importante es el comportamiento de @classmethod y dejo link del vídeo al final para que puedan ver el ejemplo completo.
@staticmethod no pasa ninguna instancia automáticamente, casi son como los métodos regulares, pero guardan alguna conexión lógica con la clase.
Los métodos regulares necesitan usar el self para cumplir algún objetivo, @classmethod necesita el cls para cumplir algún objetivo, mientras que a @staticmethod lo vamos a usar en aquellos métodos (funciones) en los que no se requiera ni de self ni de cls. Un ejemplo es el que usa David en el código de la clase.
Este tema lo entendí gracias a: https://www.youtube.com/watch?v=rq8cL2XMM5M
Está en ingles, les sugiero que lo vean ya que seguramente explica mejor que yo el tema y van a poder ver un ejemplo completo. Y si no entiendes ingles, espero haberte ayudado con mi explicación del tema. Saludos
tuve un error aquí:
first, last, pay = emp_strsplit(’-’)
se me olvidó el punto del metodo split
first, last, pay = emp_str.split(’-’)
¡Hola Jesús! Agradecemos tu feedback, es algo que intentaremos solucionar para la próxima actualización del curso 👌🏼
Se subió mucho la dificultad en estas últimas clases... al principio pensé que era lo mismo que las anteriores pero en estas últimas me han puesto a abrir el IDE y tratar de seguir el paso... Y sinceramente esto es lo que espero de un curso más avanzado, pero como sugerencia, los primeros 3 módulos los dejaría en el curso básico, que esto es una repetición práctica de lo mismo que se vio como en 3 cursos anteriores, y haber enfocado a desarrollar puntos más avanzados como los que otros compañeros comentan, si algunos como yo estamos haciendo el path de Backend con Python y Django tendrán la noción de lo que hablo...
En fin este curso tiene ya 3 años, asi que espero que conforme se vayan actualizando los cursos vayan refinando más la estructura de los cursos... Ej.
Curso Básico... (Tipos de variables, funciones, etc... lo que se ve en los cursos de Facundo)
Curso práctico de lo básico (Iniciar con un proyecto como se hizo en este con lo básico)
Curso Intermedio... (Aquí ya hablar de comprehensions, clases, etc.. y no repetir lo anterior)
Curso práctico Intermedio (Modificar el proyecto inicial con los nuevos conocimientos, lo mismo que se hizo aquí)
Curso Avanzado... (Aquí ver design patterns, arquitectura de proyectos, etc..)
Curso de Testing...
Cursos de Frameworks
Saludos
Creo que tendré que repasar y hacer los últimos 11 videos del curso una y otra vez jajaja.
Puedes practicar todos los días haciendo ejercicios y repasando, son temas complejos y no se aprenden de una sentada :D
Luego de mirar esta serie de vídeos complicados varias veces pude entender de qué va! A seguir! 😁
David Aroesti podrías anexar una descripción de que hace cada elemento del árbol de la API???, para tener mas claridad de como funciona cada scrip y módulo, dentro del sistema general
¡Hola Haxel! Tendremos en cuenta tu feedback para una futura actualización del curso :)
Para hacer un buen repaso de los conceptos de python es un buen curso pero de acá para adelante es mejor buscar por otros lados, ya se pierden los conceptos que se llevaban con tiempo y la curva de aprendizaje tiene saltos importantes ... encima les falta explicar los errores constantes que se presentan.
Python no solo es un lenguaje orientado a objetos, sino un lenguaje formado por objetos.
Cuando creas una funcion en python con la palabra recervada "def" estas creando una variable del tipo "function" que ocupa un espacio de memoria determinado. Aunque esto suena tan trivial es muy importante para entender los decoradores.
El hecho de que esto sea así es que permite que los decoradores funcionen.
Veamos
from datetime import datetime, timedelta
def ultimo_dia_mes():'''
con esta funcion obtienes el ultimo dia del mes actual
:return: datetime
'''
hoy = datetime.now() mes =int(hoy.month) periodo =int(hoy.year)if mes ==12: mes =1 periodo +=1else: mes +=1returndatetime(year=periodo, month=mes, day=1)-timedelta(days=1)
si escribes el nombre de una definicion en la shell de python te regresara algo como esto
>>> ultimo_dia_mes
<function ultimo_dia_mes at 0x7fed01d60668>>>>
indicandote el tipo y la direccion de memoria de esa definicion
>>>type(ultimo_dia_mes)<type 'function'>>>>
Y como puedes ver si pides el tipo de esa definicion, python te dirá que es del tipo function. Por lo que las funciones son objetos.
Pero cuando le agregas los parentecis entonces estas llamando a un método llamado execute (no me consta en que en python 3 se llame igual) del objeto function que lo que hace es ejecutar de manera secuencial las lineas de codigo que agregaste a la definicion.
Con esto aclarado veamos los decoradores.
Los decoradores son funciones (objetos) que en su método execute (el metodo que manda a ejecutar las linea de codigo secuencial que mencioné antes) tiene como parametro otra funcion (otro objeto por cierto) esto con el objetivo de poder ejecutar código definido en el decorador y luego decidir si ejecutar el de la funcion original.
import math
# este será el decorador, veamos como se construye
def solo_numeros_positivos(funcion_matematica_original_aplicada_al): # funcion handler. # esta funcion debe tener la misma cantidad de parametros
# que las funciones donde se va a usar el decorador
def validar_positivo(numero):if not numero ==0: # si el numero no es cero, ejecuto la funcion original sobre el numero
returnfuncion_matematica_original_aplicada_al(numero)else: # pero sino regreso un error indicando que solo acepto numeros positivos
raise(BaseException("solo numeros positivos por favor"))
# ahora esta funcion que acabamos de definir dentro del decorador es la que regresamos
# pero sin los parentecis, ya que queremos regresar la definicion de la funcion
# y no ejecutarla
return validar_positivo
@solo_numeros_positivos
def mitad(numero):return numero /2
@solo_numeros_positivos
def logaritmo(numero):return math.log(numero)
ahora si ejecutas mitad o logaritmo y pones de parametro "0", entonces no se ejecutara el codigo dentro de mitad ni logaritmo sino que se ejecuatara el raise con el error de "solo numeros positivos por favor"