Representar artículos con un modelo de datos

Ciencia y tecnologíaRepresentar artículos con un modelo de datos

Hasta ahora has utilizado diccionarios para representar artículos. Funcionan pero permiten escribir claves incorrectas y repartir las reglas entre muchas funciones. Hoy crearás un modelo pequeño con dataclass y una única función de construcción validada. Este será el primer archivo definitivo del proyecto: guárdalo como modelo.py dentro de proyecto.

Cuándo ayuda una clase sencilla

Una dataclass genera métodos habituales para objetos que agrupan datos. Al declarar los campos obtienes un constructor y una representación legible sin escribir todo a mano. No necesitas introducir herencia ni una jerarquía de clases. El objetivo es que Articulo tenga tres propiedades reconocibles: codigo, nombre y cantidad.

Las anotaciones str e int documentan los tipos esperados pero Python no las convierte por sí solo en validación de ejecución. Si llamas directamente al constructor con datos incorrectos puedes crear un objeto incoherente. Por eso las entradas del programa pasarán siempre por crear_articulo. El constructor directo quedará reservado a datos ya controlados, como las filas de nuestra propia base.

El archivo modelo.py completo

Copia el bloque conservando la sangría. Este módulo no lee archivos, no pide datos y no imprime. Sus funciones reciben valores y comunican errores concretos. Al mantenerlo independiente podremos probarlo sin preparar CSV ni bases de datos.

@dataclass(frozen=True) class Articulo: codigo: str nombre: str cantidad: int

def crear_articulo(codigo, nombre, cantidad): if not isinstance(codigo, str) or not isinstance(nombre, str): raise ValueError("Código y nombre deben ser textos") codigo = codigo.strip().upper() nombre = nombre.strip() permitidos = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-" if not 1 <= len(codigo) <= 12: raise ValueError("Código: entre 1 y 12 caracteres") if any(c not in permitidos for c in codigo): raise ValueError("Código: solo letras ASCII, números y guion") if not 1 <= len(nombre) <= 80: raise ValueError("Nombre: entre 1 y 80 caracteres") if any(ord(c) < 32 or ord(c) == 127 for c in nombre): raise ValueError("El nombre debe ocupar una sola línea") if nombre[0] in "=+-@": raise ValueError("El nombre no puede comenzar por =, +, – o @") if isinstance(cantidad, str): texto = cantidad.strip() if not 1 <= len(texto) <= 7 or any(c not in "0123456789" for c in texto): raise ValueError("Cantidad: escribe dígitos enteros no negativos") cantidad = int(texto) if type(cantidad) is not int or not 0 <= cantidad <= 1000000: raise ValueError("Cantidad: entero entre 0 y 1000000") return Articulo(codigo, nombre, cantidad) «`

Leer las decisiones del modelo

frozen=True impide reasignar normalmente los campos después de crear el objeto. Como nuestros campos son textos y un entero no tenemos estructuras mutables interiores que compliquen esta protección. Si más adelante añadieras una lista ese detalle necesitaría revisión. Una dataclass congelada no convierte automáticamente en inmutables todos los objetos que contiene.

El nombre rechaza caracteres de control después de strip. Esto impide incluir tabuladores o saltos de línea interiores que estropeen la salida de terminal. También rechaza ciertos caracteres iniciales asociados a fórmulas en hojas de cálculo. Es una regla conservadora para nuestro intercambio educativo; no sustituye a revisar las opciones de importación de otros programas.

La cantidad admite un entero real o un texto formado por dígitos ASCII después de quitar espacios exteriores. El límite de un millón es una decisión de alcance para el taller ficticio. No es un límite de los enteros de Python. Mantendremos esa misma restricción al crear la tabla para que el almacenamiento sea coherente.

Por qué se comprueba el tipo exacto

En Python los booleanos tienen una relación de herencia con los enteros. Una comprobación amplia con isinstance podría aceptar True como una unidad. Aquí no queremos que una respuesta lógica pase por cantidad. type(cantidad) is int expresa esa decisión concreta. No es una regla universal para todas las funciones numéricas pero sí encaja en este contrato.

Tampoco admitimos float aunque tenga apariencia entera como 3.0. La interfaz y los archivos entregarán texto o enteros explícitos. Aceptar más formatos no siempre mejora la aplicación: también obliga a explicar más conversiones. Para ampliar el contrato primero escribe qué entradas nuevas quieres admitir y añade pruebas que las distingan.

Una única puerta para las entradas

La interfaz utilizará crear_articulo cuando reciba un alta. El lector CSV hará lo mismo por cada registro. Así no tendrás dos versiones de la regla sobre nombres vacíos o cantidades máximas. Si cambia el límite revisarás el modelo, la restricción de base de datos y sus pruebas de forma coordinada.

Una clase no elimina la necesidad de diseño. Podrías seguir creando objetos directamente en cualquier lugar y saltarte las comprobaciones. La estructura del programa debe respetar la frontera que acabas de definir. En el proyecto final solo la lectura de filas ya almacenadas utilizará Articulo directamente y la base conservará restricciones propias.

Errores habituales

Copiar validaciones parecidas en app e intercambio hace que se separen con el tiempo. Una puede aceptar cero y la otra rechazarlo. Otro error es creer que escribir cantidad: int realiza una conversión automática. Las anotaciones ayudan a personas y herramientas pero no reemplazan estas comprobaciones.

Evita añadir métodos a la clase por costumbre. Por ahora Articulo representa datos y crear_articulo aplica reglas de entrada. No necesita conectarse a SQLite ni exportarse a sí mismo. Mantener esas tareas en módulos específicos facilita probarlas y cambiar su implementación sin convertir el modelo en una pieza que lo sabe todo.

Una comprobación adicional

Antes de reutilizar el modelo desde otro archivo ejecuta una prueba de importación. Importar modelo no debe mostrar mensajes ni crear archivos. Si ocurre alguna de esas acciones revisa si has dejado una demostración al nivel superior. La independencia del módulo tiene un efecto práctico: puedes construir artículos en una prueba sin preparar el resto de la aplicación. También puedes comparar dos objetos con los mismos campos y obtener igualdad aunque sean instancias diferentes. Esa comparación facilitará verificar los datos recuperados de un CSV o de SQLite sin depender de su ubicación en memoria.

Lo que deberías recordar

  • Las anotaciones no validan por sí solas.
  • Todas las entradas pasarán por crear_articulo.
  • La dataclass agrupa datos sin encargarse del almacenamiento.
  • Los límites del dominio son decisiones explícitas.

Ejercicio práctico

Crea modelo.py y construye un artículo con « cu001 », « Cuaderno » y «3». Comprueba que el objeto resultante contiene CU001, Cuaderno y el entero 3. Intenta después asignar otra cantidad al mismo objeto y observa que la dataclass congelada lo impide.

Prepara pruebas para nombre vacío, código con espacio interior, cantidad negativa, True y 1000001. Todos deben rechazarse. Añade los límites válidos cero y un millón. Escribe cuál de estas reglas responde al lenguaje y cuál has elegido para el proyecto.

Compara crear_articulo con llamar directamente a Articulo. Anota por qué el constructor no debe utilizarse con entradas externas. Guarda las pruebas porque este archivo se incorporará sin cambios a la aplicación final. Si modificas una regla registra también qué comprobación deberás actualizar en el almacenamiento.

Siguiente: Guardar el inventario en SQLite.

Volver al índice de Programar con Python.

Últimos posts

DEJA UNA RESPUESTA

Por favor ingrese su comentario!
Por favor ingrese su nombre aquí

 

Artículos más vistos

Horóscopo diario
Menú diario