Distribución de librerías Python con setup.py
En este tutorial se explicará cómo distribuir una librería Python a través
del repositorio PyPi usando para esto el archivo setup.py
.
Durante el desarrollo de un proyecto de software Python es común implementar utilidades o librerías que facilitan la construcción de la aplicación. Estas utilidades, en la mayoría de los casos, son reutilizadas en más de un proyecto.
En algún punto, el equipo de desarrollo se da cuenta que es muy probable que estas utilidades puedan ayudar a personas y proyectos al exterior de la organización. Es en este momento dónde el equipo de desarrollo decide publicar las utilidades para que otras personas y organizaciones se puedan beneficiar de ellas.
Surge entonces el problema sobre cómo distribuir el código de dichas utilidades, de tal forma que el equipo de desarrollo pueda continuar implementando nuevas características y que las demás organizaciones puedan acceder a las ultimas versiones de la forma mas eficiente.
Es aquí dónde el repositorio de paquetes Python, llamado PyPi, juega un papel importante. Este repositorio contiene una miríada de librerías y utilidades listas para ser descargadas mediante pip y utilizadas en cualquier proyecto de software Python.
Implementación de la librería Python
El primer paso consiste en tener una librería lista para ser distribuida. En este caso se ha preparado una utilidad muy sencilla llamada StarWars Ipsum que permite agregar a cualquier proyecto, texto de relleno basado en la introducción de cada una de las películas de la saga Star Wars. Por el momento solo está disponible la introducción del episodio IV en inglés.
El código fuente completo está disponible en Github y Bitbucket.
La utilidad proporciona dos funciones: paragraphs()
y html()
. La función
paragraphs()
retorna un listado con los párrafos leídos del archivo de texto
a_new_hope.txt
. La función html()
retorna código HTML válido del texto
leído, listo para ser incluido en una página web.
import markdown
import os
PARENT_DIR = os.path.dirname(os.path.dirname(__file__))
def paragraphs():
paragraphs = []
filename = os.path.join(PARENT_DIR, 'data', 'a_new_hope.txt')
with open(filename, 'r') as file_data:
paragraph = ''
for line in file_data:
if line == '\n':
paragraphs.append(paragraph)
paragraph = ''
else:
paragraph.append(line.replace('\n', ' '))
return paragraphs
def html():
html = ''
filename = os.path.join(PARENT_DIR, 'data', 'a_new_hope.txt')
with open(filename, 'r') as file_data:
html = markdown.markdown(file_data.read())
return html
El archivo setup.py
Al observar el archivo setup.py
, es posible ver la información
necesaria para que el repositorio PyPi pueda clasificar la librería
correctamente y para que pip pueda instalarla de forma apropiada.
distutils es método tradicional para distribuir librerías y utilidades Python. Este tutorial contiene información sobre setuptools, una implementación mejorada de distutils.
from setuptools import find_packages, setup
setup(
name='starwars-ipsum',
version='0.0.1',
author='Jose Miguel Venegas Mendoza',
author_email='jvenegas@rukbottoland.com',
description=('A simple utility that generates placeholder text from Star '
'Wars intros.'),
long_description=get_readme(),
license='BSD',
keywords='starwars utilities ipsum',
url='',
packages=find_packages(),
package_data={
'starwars_ipsum': ['*.txt']
},
install_requires=['markdown==2.6.5'],
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2.7',
'Topic :: Utilities'
]
)
La función setup()
necesita saber algunos datos básicos sobre la librería
tales como el nombre, la versión, el autor y la descripción. Estos datos por
los general son cadenas de texto.
Sin embargo, es posible utilizar funciones que retornen una o varias cadenas de
texto. Tal es el caso del atributo long_description
al que se le ha asignado
la función get_readme()
.
def get_readme():
readme = ''
try:
import pypandoc
readme = pypandoc.convert('README.md', 'rst')
except (ImportError, IOError):
with open('README.md', 'r') as file_data:
readme = file_data.read()
return readme
Debido a que PyPi utiliza reStructuredText en lugar de Markdown para leer el texto en los archivos README y el archivo README de la utilidad esta escrito en Markdown, se hace necesario utilizar la librería pandoc para convertir el formato a reStructuredText para que PyPi lo pueda interpretar correctamente.
Continuando con la descripción de la función setup()
, el atributo packages
se utiliza para indicarle a pip la ubicación de los paquetes que la librería
proporciona para poder instalarlos apropiadamente.
En este caso el valor podría ser simplemente packages=['starwars_ipsum']
pero
en su lugar se utiliza la función find_packages()
, la cual busca
automáticamente todos los paquetes existentes dentro del directorio de la
librería.
En el atributo package_data
se indican los archivos adicionales que
necesita la librería para funcionar correctamente. Debido a que la utilidad
lee archivos de texto con el contenido de la introducción de las películas, se
hace necesario incluir todos los archivos de texto *.txt
.
Estos archivos se deben asignar por paquete, de ahí que se definan mediante un diccionario de datos cuya clave es el nombre del paquete.
Debido a que la utilidad provee una función para convertir el texto a código
HTML listo para ser incluido en una página web, es necesario utilizar una
librería llamada markdown
para que realice la conversión.
Se dice entonces que la utilidad depende de la librería markdown
y por lo
tanto debe ser instalada al mismo tiempo que la utilidad, para que funcione
óptimamente. Estas relaciones de dependencia se asignan a la variable
install_requires
, la cual es un listado de dependencias que se definen
usando el formato requirements.txt.
El atributo classifiers
es un listado de cadenas de texto que se utiliza
para describir con mas detalle la librería o utilidad. Es posible ver en este
caso que la utilidad esta en un estado de desarrollo Alpha, que su licencia
es de tipo BSD, que el lenguaje de programación usado para su elaboración es
Python 2.7 y que esta clasificada como una utilidad.
El listado completo de classifiers puede consultarse aquí.
Registrando la librería Python en PyPi
Actualmente existen dos repositorios PyPi: uno principal y otro de prueba. El repositorio de prueba fue creado para proporcionar un ambiente adecuado a los desarrolladores que desean probar el sistema de distribución o que desean probar la integración de varias librerías antes de enviarlas al repositorio principal.
Para poder enviar la librería o utilidad al repositorio PyPi principal, es indispensable crear una cuenta en https://pypi.python.org/pypi. Para hacerlo en el repositorio de prueba se debe crear una cuenta en https://testpypi.python.org/pypi.
Una vez registrada una cuenta en cada repositorio, se debe crear un archivo en
el directorio home de la maquina donde se encuentra la librería, llamado
.pypirc
. Este archivo debe contener las credenciales de acceso a los
repositorios:
[distutils]
index-servers=
pypi
pypitest
[pypi]
repository = https://pypi.python.org/pypi
username = <nombre de usuario>
password = <contraseña>
[pypitest]
repository = https://testpypi.python.org/pypi
username = <nombre de usuario>
password = <contraseña>
Finalmente, para envíar la librería al repositorio deseado, ejecutamos los siguientes comandos en una terminal:
>> Para envíar la libreria al repositorio principal
$ python setup.py register -r pypi
$ python setup.py sdist upload -r pypi
>> Para envíar la libreria al repositorio de prueba
$ python setup.py register -r pypitest
$ python setup.py sdist upload -r pypitest
En este punto, la librería o utilidad ya debe ser visible en la página web del repositorio.
Instalación de la librería Python
Simplemente usamos pip para instalar la librería de la siguiente manera:
$ pip install starwars-ipsum