Hace unas semanas decidí que quería crear mi primer paquete Python y comencé a indagar sobre cómo empezar.

Mientras intentaba encontrar algún tutorial sencillo para principiantes, me sentí un poco perdido y empecé a ponerme nervioso. Por eso decidí escribir este artículo. Quería crear una guía que describiera el proceso de creación de mi primer paquete Python.

¿Qué es un paquete Python?

Antes de comenzar, debemos comprender qué es un paquete Python.

Un paquete Python es un directorio que contiene un conjunto de módulos y un archivo de dependencias llamado __init__.py. Este archivo puede estar completamente vacío. Es necesario para marcar el directorio en el disco como un paquete Python.

Aquí tienes un ejemplo de un directorio de paquete:

Ejemplo de directorio de paquete

package
    __init__.py
    module_a.py
    module_b.py
    module_c.py

__init__.py es el archivo de dependencias que ayuda a Python a encontrar los módulos disponibles en el directorio del paquete. Si eliminamos este archivo, Python no podrá importar los módulos.

Paquetes vs. Módulos

Ahora ya sabes que los paquetes Python crean un directorio estructurado con varios módulos dentro, ¿pero qué pasa con los módulos? Aclarémoslo para asegurarnos de que entendemos la diferencia entre un paquete y un módulo:

  • Módulo: Siempre es un único archivo Python (por ejemplo, turtle.py). Contiene lógica, es decir, clases, funciones y constantes.
  • Paquete: En esencia, también es un módulo, pero puede contener muchos otros módulos y subpaquetes.

Estructura de un Paquete

Sin embargo, los paquetes no solo contienen módulos. También incluyen scripts de alto nivel, documentación y pruebas. A continuación, se muestra un ejemplo de cómo se puede estructurar un paquete Python estándar:

Ejemplo de estructura de paquete

package_name/
    docs/
    scripts/
    src/
        package_a
            __init__.py
            module_a.py
        package_b
            __init__.py
            module_b.py
    tests/
        __init__.py
        test_module_a.py
        test_module_b.py
    LICENSE.txt
    CHANGES.txt
    MANIFEST.in
    README.txt
    pyproject.toml
    setup.py
    setup.cfg

Veamos para qué sirve cada archivo en este árbol:

  • package_name: Representa el paquete principal.
  • docs: Contiene la documentación sobre cómo utilizar el paquete.
  • scripts/: Tus scripts de alto nivel.
  • src: Aquí es donde va tu código. Contiene paquetes, módulos, subpaquetes, etc.
  • tests: Aquí puedes almacenar tus pruebas unitarias.
  • LICENSE.txt: Contiene el texto de la licencia (por ejemplo, MIT).
  • CHANGES.txt: Registra los cambios realizados en cada versión.
  • MANIFEST.in: Donde colocas las instrucciones sobre qué archivos adicionales (sin código) quieres incluir.
  • README.txt: Contiene una descripción del paquete (en formato markdown).
  • pyproject.toml: Un archivo diseñado para registrar sistemas de compilación.
  • setup.py: Contiene el script de compilación para tus sistemas de compilación correspondientes.
  • setup.cfg: Un archivo de configuración para tus sistemas de compilación.

Ten en cuenta que podemos añadir archivos de prueba al paquete principal de dos maneras. Podemos dejarlos en el nivel superior, como hicimos anteriormente, o colocarlos dentro del paquete, como se muestra a continuación:

Ejemplo de pruebas independientes

package_name/
    __init__.py
    module_a.py
    module_b.py
    test/
        __init__.py
        test_module_a.py
        test_module_b.py

En mi opinión, el enfoque de tener las pruebas en el nivel superior puede ser muy útil, especialmente cuando estas pruebas implican leer y escribir en otros archivos externos.

¿Qué utilizar: setup.cfg o setup.py?

setup.py y setup.cfg son herramientas de organización de paquetes predeterminadas en PyPI, setuptools, pip y la biblioteca estándar de Python.

Son scripts de configuración y compilación para setuptools. Ambos le indican a setuptools cómo compilar e instalar el paquete.

El archivo mencionado contiene información como la versión, los paquetes y los archivos que se deben incluir, así como cualquier otro requisito.

Aquí tienes un ejemplo de un archivo setup.py que utiliza algunos argumentos de setup().

setup.py

import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="package-name",
    version="0.0.1",
    author="author",
    author_email="author@example.com",
    description="short package description",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="package URL",
    project_urls={
        "Bug Tracker": "package issues URL",
    },
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    package_dir={"": "src"},
    packages=setuptools.find_packages(where="src"),
    python_requires=">=3.6"
)

El archivo setup.cfg tiene un aspecto completamente diferente. Normalmente, solo contiene dos claves obligatorias: command y options.

setup.cfg

[command]
option = value

Aquí tienes un ejemplo de un archivo setup.cfg que utiliza algunas opciones y metadatos.

setup.cfg

[metadata]
name = basicpkgcn
version = 1.0.0
author = your name
author_email = your email
description = A simple Python package
long_description = file: README.md, LICENSE.txt
long_description_content_type = text/markdown
url = https://gitlab.com/codigonautas/basicpkgcn
project_urls =
    Bug Tracker = https://gitlab.com/codigonautas/basicpkgcn/-/issues
    repository = https://gitlab.com/codigonautas/basicpkgcn
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent

[options]
package_dir =
    = src
packages = find:
python_requires = >=3.6

[options.packages.find]
where = src

Ambos archivos, setup.py y setup.cfg, están diseñados únicamente para setuptools. Además, el archivo setup.cfg se puede mover de forma segura a pyproject.toml.

La idea es que, si en algún momento decides cambiar el sistema de compilación, por ejemplo, a flit o poetry, todo lo que tienes que hacer es cambiar la entrada del sistema de compilación (por ejemplo, setuptools) en pyproject.toml por algo como flit o poetry, en lugar de escribir todo de nuevo.

Independientemente del archivo de configuración que elijas ( pyproject.toml, setup.cfg o setup.py), debes acompañarlo.

✔️
La Guía para trabajar con paquetes Python dice que lo mejor es utilizar el archivo setup.cfg, ya que es estático, está bien escrito, es fácil de leer y ayuda a evitar errores de codificación.

Cómo crear tu primer paquete Python

Ahora es el momento de crear un sencillo paquete Python. Usaremos setuptools como sistema de compilación, y setup.cfg y pyproject.toml nos ayudarán a configurar nuestro proyecto.

Configurar los archivos del paquete

Vamos a crear un paquete bastante simple, y debemos estructurar nuestro directorio añadiendo los archivos de dependencias necesarios para poder distribuir el paquete. La estructura del paquete será la siguiente:

Estructura de nuestro paquete

setup.cfg

basicpkgcn/
    src/
        divide
            __init__.py
            by_three.py
        multiply
            __init__.py
            by_three.py
    tests/
        __init__.py
        test_divide_by_three.py
        test_multiply_by_three.py
    LICENSE.txt
    README.txt
    pyproject.toml
    setup.cfg

Nuestro paquete principal consta de dos paquetes: uno para dividir números entre tres y otro para multiplicar números por tres.

En este caso, estamos omitiendo algunos archivos, como CONTEXT.txt, MANIFEST.in y el directorio docs/, para simplificar la tarea. Sin embargo, necesitamos el directorio test/ para añadir nuestras pruebas unitarias para verificar el comportamiento del paquete.

Añadir algo de lógica a nuestros módulos

Como siempre, dejaremos el archivo __init__.py vacío. A continuación, debemos añadir algo de lógica a nuestros módulos para poder realizar diferentes operaciones.

Tomemos el paquete que se encarga de dividir números. Añadiremos el siguiente contenido al archivo divide.by_three.py para poder dividir números entre tres:

by_three.py

def divide_by_three(num):
    return num / 3

Añadiremos una lógica similar al archivo multiply.by_three.py, que se encuentra dentro del paquete que se encarga de multiplicar números. Sólo que en este caso, queremos poder multiplicar números por tres:

by_three.py

def multiply_by_three(num):
    return num * 3

No tengas miedo de añadir otros paquetes y módulos para otras acciones. Por ejemplo, puedes añadir paquetes para realizar sumas y restas de números.

Probar nuestros módulos

Lo mejor es añadir pruebas automatizadas a los programas que creamos. En este caso, utilizaremos unittest para probar nuestros módulos y el comportamiento del paquete.

Añadiremos el siguiente código al archivo test_divide_by_three.py, situado en el directorio test/:

test_divide_by_three.py

import unittest
from divide.by_three import divide_by_three 

class TestDivideByThree(unittest.TestCase):

    def test_divide_by_three(self):
        self.assertEqual(divide_by_three(12), 4)

unittest.main()

Para ejecutar las pruebas automatizadas, importamos TestCase de unittest. Después, importamos nuestro método divide_by_three() del módulo by_three, que se encuentra en el paquete que se encarga de dividir números.

Si eliminamos el archivo __init__.py aquí, Python ya no podrá encontrar nuestros módulos.

El método .assertEqual() se utiliza para comprobar si dos valores ( divide_by_three(12) y 4) son iguales. Y el método unittest.main() se ha implementado para ejecutar todas nuestras pruebas.

La lógica para test_multiply_by_three.py es similar:

test_multiply_by_three.py

import unittest
from multiply.by_three import multiply_by_three

class TestMultiplyByThree(unittest.TestCase):

    def test_multiply_by_three(self):
        self.assertEqual(multiply_by_three(3), 9)

unittest.main()

Para ejecutar las pruebas, escribe lo siguiente en el terminal/línea de comandos:

Para Linux:

python3 tests/test_divide_by_three.py

Para Windows:

py tests/test_divide_by_three.py

Para ejecutar la prueba del módulo que se encarga de multiplicar números, debes hacer lo mismo. Si las pruebas se ejecutan correctamente, verás lo siguiente:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Prueba de módulo exitosa
Prueba de módulo exitosa

Si quieres añadir paquetes y módulos adicionales, intenta añadir algunos métodos de unittest. ¡Es un buen ejercicio para ti!

Configurar los metadatos con setup.cfg

Ahora tenemos que añadir un archivo de configuración para setuptools. Como ya hemos mencionado, este archivo de configuración le indicará a setuptools cómo compilar e instalar nuestro paquete.

Así que debemos añadir los siguientes metadatos y opciones a nuestro archivo setup.cfg. No olvides cambiar el nombre, ya que ya he subido un paquete con ese nombre a TestPyPI. También cambia la información adicional, es decir, el autor, la dirección de correo electrónico y las URL del proyecto, para que tu paquete se distribuya con tu información.

setup.cfg

[metadata]
name = basicpkgcn
version = 1.0.0
author = your name
author_email = your email
description = A simple Python package
long_description = file: README.md, LICENSE.txt
long_description_content_type = text/markdown
url = https://gitlab.com/codigonautas/basicpkgcn
project_urls =
    Bug Tracker = https://gitlab.com/codigonautas/basicpkgcn/-/issues
    repository = https://gitlab.com/codigonautas/basicpkgcn
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent

[options]
package_dir =
    = src
packages = find:
python_requires = >=3.6

[options.packages.find]
where = src

En cuanto a las opciones, deja los valores predeterminados. El parámetro package_dir especifica la ubicación del directorio raíz del paquete, donde se almacenan los paquetes, los módulos y todos los archivos fuente de Python.

Con la clave packages, podemos enumerar nuestros paquetes manualmente, es decir, [divide, multiply]. Pero si queremos especificar todos los paquetes, podemos simplemente escribir find: y especificar dónde se deben buscar estos paquetes con [options.packages.find], donde la clave where se asigna al nombre del directorio raíz del paquete.

Asegúrate de añadir la clave classifiers al archivo de configuración (siempre debes hacerlo). Con ella podemos añadir información importante, como la versión de Python y el sistema operativo con el que es compatible tu paquete.

Crear pyproject.toml

Usaremos setuptools como sistema de compilación. Para informar a pip o a cualquier otra herramienta de compilación sobre nuestro sistema de compilación, necesitaremos dos variables (véase a continuación).

Usaremos build-system.require para añadir todo lo que necesitamos para compilar el paquete, y system.build-back-end especifica el objeto que se encargará de la compilación.

Así que vamos a añadir el siguiente contenido al archivo pyproject.toml:

pyproject.toml

[build-system]
requires = ['setuptools>=42']
build-backend = 'setuptools.build_meta'

Ten en cuenta que si quieres cambiar el sistema de compilación, por ejemplo, a flit o poetry, puedes mover de forma segura todos los parámetros de configuración del archivo setup.cfg a pyproject.toml. Esta función te ahorrará tiempo.

Crear README.md

Es muy importante crear un buen archivo README.md. Vamos a añadir una descripción a nuestro paquete y algunas instrucciones para instalarlo.

README.md

# `basicpkgcn`

The `basicpkgcn` is a simple testing example to understand the basics of developing your first Python package. 

Además, podemos añadir información sobre cómo utilizarlo:

from multiply.by_three import multiply_by_three
from divide.by_three import divide_by_three

multiply_by_three(9)
divide_by_three(21)

No dudes en añadir cualquier información que pueda ayudar a otros desarrolladores a entender para qué sirve tu paquete, cómo instalarlo y cómo usarlo.

Ten en cuenta que nuestro archivo de configuración cargará README.md y se añadirá cuando se distribuya nuestro paquete.

Añadir una licencia

Es muy importante añadir una licencia a tu proyecto para que los usuarios sepan cómo pueden utilizar tu código. Vamos a elegir la licencia MIT para nuestro paquete y añadiremos el siguiente contenido al archivo LICENSE.txt:

LICENSE.txt

MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

No olvides cambiar [year] por el año actual y [fullname] por tu nombre o los nombres de los titulares de los derechos de autor.

Crear archivos de distribución

Ya queda el último paso antes de poder empezar a distribuir nuestro paquete: crear archivos de distribución de nuestro paquete Python. Para ello, debemos actualizar nuestra compilación PyPA y luego crear archivos de distribución del código fuente y de la compilación.

Ejecuta los siguientes comandos en el terminal/línea de comandos desde el mismo directorio donde se encuentra el archivo pyproject.toml:

Para Linux:

python3 -m pip install --upgrade build
python3 -m build

Para Windows:

py -m pip install --upgrade build
py -m build

Una vez finalizado este proceso, se creará un nuevo directorio llamado dist/, que contendrá dos archivos. El archivo .tag.tz es el archivo de distribución del código fuente y el archivo .whl* es el archivo de distribución de la compilación. Estos archivos son los archivos de distribución de nuestro paquete Python, que luego subiremos a Python Package Index e instalaremos con pip.

Cómo subir un paquete a Python Package Index

Python Package Index es donde vamos a subir nuestro paquete. Como nuestro paquete está en fase de prueba y todavía podemos añadirle nuevas funciones de forma experimental, debemos utilizar una versión separada de PyPI llamada TestPyPI. Esta versión se creó específicamente para realizar pruebas y experimentos. Más adelante, cuando tu paquete esté listo y te satisfaga completamente, podrás subirlo a PyPI como un paquete final.

Las siguientes instrucciones nos ayudarán a preparar TestPyPI para subir nuestro paquete:

  1. Ve a TestPyPI y crea una cuenta allí.
  2. Confirma tu dirección de correo electrónico para poder subir paquetes.
  3. Configura tu perfil (añade una foto, etc.).
  4. Ve a la sección de tokens API y crea tu token API para poder subir tus paquetes de forma segura.
  5. En la misma página, establece el ámbito de aplicación como «toda la cuenta».
  6. Copia y guarda el token en un lugar seguro de tu disco.

A continuación, debemos subir nuestros archivos de distribución. Para ello, necesitaremos una herramienta de subida. La herramienta oficial de subida de PyPI es Twine. Por lo tanto, debemos instalar Twine y subir los archivos de distribución al directorio dist/.

Ejecuta los siguientes comandos en el terminal/línea de comandos desde el mismo directorio donde se encuentra el archivo pyproject.toml:

Para Linux:

python3 -m pip install --upgrade twine
python3 -m twine upload --repository testpypi dist/*

Para Windows:

py -m pip install --upgrade twine
py -m twine upload --repository testpypi dist/*

A continuación, escribe __token__ como nombre de usuario y el token que has guardado (incluido el prefijo pypi-) como contraseña. Después, pulsa Intro para subir las distribuciones.

Cómo instalar el paquete Python subido

Ahora es el momento de instalar nuestro paquete. Para instalarlo desde TestPyPI, puedes crear un entorno virtual y utilizar la herramienta pip:

Para Linux:

python3 -m venv env
source env/bin/activate
(env) python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps basicpkgcn

Para Windows:

py -m venv env
.\env\Scripts\activate
(env) py -m pip install --index-url https://test.pypi.org/simple/ --no-deps basicpkgcn

Antes de comprobar si tu paquete funciona correctamente, asegúrate de que tu entorno virtual está activado.

Abre el terminal/línea de comandos y ejecuta el comando python3 (para usuarios de Linux/Mac) o el comando py (para usuarios de Windows). A continuación, introduce el siguiente código para asegurarte de que los paquetes que se encargan de multiplicar y dividir números funcionan correctamente:

from multiply.by_three import multiply_by_three
from divide.by_three import divide_by_three

multiply_by_three(9)
divide_by_three(21)

# Output: 27
# Output: 7

No olvides que, para realizar las operaciones necesarias, debemos importar los métodos correspondientes de nuestros módulos.

💪
¡Enhorabuena! Nuestro paquete funciona correctamente.

A continuación, después de experimentar y probar tu paquete, debes seguir las siguientes instrucciones para subir tu paquete al PyPI real:

  1. Ve a PyPI y crea una cuenta.
  2. Ejecuta el comando twine upload dist/* en el terminal/línea de comandos.
  3. Introduce las credenciales con las que te registraste en PyPI.
  4. A continuación, ejecuta el comando pip install [package_name] para instalar tu paquete.
  5. ¡Enhorabuena! Tu paquete se ha instalado desde el PyPI real.

¿Qué sigue?

Sería fantástico que se te ocurriera una idea sencilla, la tomaras como base y crearas tu primer paquete Python real. En este artículo me he centrado en los aspectos básicos que necesitas para empezar, pero hay mucho más que aprender en el mundo de los módulos y los paquetes.

Espero que mi primera experiencia con el desarrollo de paquetes en Python te haya ayudado a entender lo que necesitas para crear el tuyo propio.

Categorizado en:

Python,