Tag: Python

Solucionar error “failed to create process” en herramientas instaladas de Python

Hoy me encontraba revisando dependencias de las herramientas, librerías y frameworks que tenía en Python y me he encontrado con la herramienta pipdeptree, una herramienta que nos permite enumerar las herramientas, librerías y frameworks al estilo del comando “pip freeze” pero mostrando además cada una de las dependencias que tiene con otras librerías.

Como suele ser la costumbre realicé las instalación con el clásico:

pip install pipdeptree

La instalación resultó satisfactoria pero al ejecutar la herramienta devuelve un error:

$ pipdeptree

failed to create process.

El error “failed to create process” se produce por un problema de la ruta en el script de ejecución. Concretamente por culpa de espacios en la ruta donde tenemos instalados Python y donde se instalan los paquetes mediante el comando “pip“. Si eres de los que tiene instalado Python en “Archivos de Programa” o “Program File”, vas a tener este problema si además estás usando una versión de “setuptools” anterior a la versión 24.3.1 del 23 de Julio del 2016.

Para ver la versión de “setuptools” que estás usando actualmente ejecuta el siguiente comando:

easy_install --version

Si tienes una versión anterior y no quieres actualizar, puedes corregir el error si vas a la carpeta “Scripts” dentro de tu instalación de Python y buscas el archivo “pipdeptree-script.py” que es el que ejecuta la herramienta gracias al binario “pipdeptree.exe” que lo invoca:

#!d:\program files (x86)\python35-32\python.exe
# EASY-INSTALL-ENTRY-SCRIPT: 'pipdeptree==0.10.1','console_scripts','pipdeptree'
__requires__ = 'pipdeptree==0.10.1'
import re
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(
        load_entry_point('pipdeptree==0.10.1', 'console_scripts', 'pipdeptree')()
    )

Para corregir el problema basta con añadir unas comillas a la ruta de la primera línea:

#!"d:\program files (x86)\python35-32\python.exe"

Si no tienes inconveniente alguno en actualizar “setuptools”, basta con que ejecutes el comando de actualización estándar de “pip“:

pip install --upgrade setuptools

Obviamente la segunda solución es la más recomendable, pero la explicación de la primera puede servir para solucionar errores similares en otras herramientas si se presentan, y si la actualización no funciona o no es una opción.

Solucionar problema de Autoridad de Certificación en los binarios .EXE generados con PY2EXE

Últimamente he empezado a utilizar la herramienta py2exe para poder mejorar la distribución de mis desarrollos en Python en sistemas Windows, mediante la generación de un ejecutable .exe autocontenido que evite tener que pedir al usuario que instale Python y las correspondientes librerías.

La herramienta py2exe puede ser instalada de manera sencilla con el comando:

pip install py2exe

Y puedes compilar rápidamente cualquier programa en Python realizando un fichero setup.py simple como este:

from distutils.core import setup
import py2exe

setup(console=['mi_programa.py'])

El cual deberemos ejecutar desde una consola de comandos de la siguiente forma:

python setup.py install

Al ejecutarlo nos resolverá todas las dependencias y dejará la versión autcontenida con el ejecutable mi_programa.exe en la carpeta dist.

Si trabajas con servicios o mínimamente con conexiones seguras haciendo peticiones, seguro que acabas usando la librería requests o puede que la librería con la que trabajes, la use como base para hacer conexiones seguras con SSL. Si intentas usar estas librerías que trabajan con certificados SSL te encontrarás con el siguiente error cuando ejecutas tu binario .exe:

requests.exceptions.SSLError: [Errno 2] No such file or directory

El cual viene dado porque cuando se empaqueta todo, el certificado de la Autoridad de Certificación no se incluye al ser un fichero que no es de Python. A causa de esto, cuando se empaquetan todas la librerías y se llama de manera relativa al certificado de la librería desde nuestro empaquetado con nuestro binario .exe de py2exe, éste no se encuentra porque ninguna parte.

Para solucionarlo es tan sencillo como proporcionar un certificado válido del tipo cacert.pem en la variable de entorno de Python REQUESTS_CA_BUNDLE de nuestro programa. Pero para resolverlo, vamos a hacerlo de manera elegante parcheando dicha variable sólo si es necesario, para poder seguir tirando de los certificados de las propias librería mientras desarrollamos.

Para ello vamos a instalar certifi, una librería que nos facilita una serie de Certificados Raíz que nos van a permitir validar la integridad de certificados, tanto de SSL, como de TSL de los servicios a los que nos conectemos.

pip install py2exe

Ahora vamos a modificar un poco nuestro setup.py:

from distutils.core import setup
import py2exe
import certifi

setup(console=['mi_programa.py'], data_files=[certifi.where()])

Hemos añadido únicamente el import de certifi y en los parámetros de setup hemos añadido el fichero de certificado que nos devuelve certifi gracias al método where. Este fichero se copiará en dist al mismo nivel que nuestro .exe.

Por último añadiremos en mi_programa.py al inicio de nuestro programa el siguiente código:

cacert_path = os.path.join(os.getcwd(), 'cacert.pem')
if os.path.exists(cacert_path):
    os.environ['REQUESTS_CA_BUNDLE'] = cacert_path

El código genera la ruta hasta certificado cacert.pem, usando el directorio de trabajo que será dist. Esa ruta se busca si existe entre las rutas que maneja Python para resolver las librerías y dependencias. Si se ejecuta desde el entorno de desarrollo, encontrará el de la propia librería que lo este usando, sino parcheará añadirá la ruta para que coja certificado que hemos copiado en dist.

De esta forma no sólo se soluciona el problema del certificado de la Autoridad de Certificación, sino que el parche se aplica selectivamente copiando fichero de certificado necesario. Esto es importante porque los certificados pueden cambiar y basta con volver a generar los binarios .exe con py2exe, teniendo las librerías actualizadas y con los certificados en regla, para que el ejecutable creado también los tenga.

Integración de PyLint

Habitualmente se genera código que funciona, y que no por ello quiere decir que esté bien codificado. Cuando me refiero a bien codificado, me refiero a que no existan secciones de código que no se usan, que los comportamientos del código sean deterministas, que se codifique evitando generar brechas de seguridad, que cumpla unas reglas de codificación que aseguren una buena comprensión del código, que se genere documentación del código… Por ello, algo que funcione no es sinónimo de que esté bien codificado (simplemente funciona).

En mi día a día suelo utilizar habitualmente Python y el estupendo IDE PyCharm para desarrollar mi trabajo. También tengo que orientar a otras personas que se están formando en este lenguaje, y eso conlleva una supervisión del código generado, no en el sentido de revisión que asegure la funcionalidad, sino el de asegurar una serie de buenas praxis en la codificación. Se trata del paso anterior a cualquier tipo de Testing.

Hablando con un compañero surgió el tema de asegurar unas buenas praxis para el lenguaje Python de personas interinas en la empresa que acaban de empezar, que desarrollan sus prácticas, su proyecto PFG (Proyecto Fin de Grado) o PFM (Proyecto Fin de Master). Estas buenas praxis no son otra cosa que la extensión del Zen de Python promulgado por Tim Peters:


  • Bello es mejor que feo.
  • Explícito es mejor que implícito.
  • Simple es mejor que complejo.
  • Complejo es mejor que complicado.
  • Plano es mejor que anidado.
  • Disperso es mejor que denso.
  • La legibilidad cuenta.
  • Los casos especiales no son tan especiales como para quebrantar las reglas.
  • Aunque lo práctico gana a la pureza.
  • Los errores nunca deberían dejarse pasar silenciosamente.
  • A menos que hayan sido silenciados explícitamente.
  • Frente a la ambigüedad, rechaza la tentación de adivinar.
  • Debería haber una -y preferiblemente sólo una- manera obvia de hacerlo.
  • Aunque esa manera puede no ser obvia al principio a menos que usted sea holandés.
  • Ahora es mejor que nunca.
  • Aunque nunca es a menudo mejor que ya mismo.
  • Si la implementación es difícil de explicar, es una mala idea.
  • Si la implementación es fácil de explicar, puede que sea una buena idea.
  • Los espacios de nombres (namespaces) son una gran idea ¡Hagamos más de esas cosas!

zen_symbol

Si el código cumple con estas directrices se dice que es código “pythonico“. Esta filosofía ha hecho que Python lleve en su ADN esa características de legibilidad y transparencia.

Toda esta introducción nos lleva a las Python Enhancement Proposal (PEP) como formalismo de estas buenas praxis, siendo el Zen de Python la PEP20. Pero la pregunta es: ¿cómo me aseguro cumplir estas buenas praxis?, ¿y qué otros las cumplan?, ¿y qué además la forma de evaluarlas sea la misma? La respuesta es un revisor automático como es el caso de PyLint, que asegura una codificación estandarizada alineada con lo que se entiende con un código pythonico, la corrección de errores, la detección de código duplicado o que no se usa… en definitiva asegurar la buena praxis durante la codificación.

PyLint es un revisor automático ampliamente usado e integrado en otros servicios de mayor envergadura como es el caso de Codacy. Sitio de donde tomé la idea de coger un revisor local que se pudiese ejecutar desde el propio equipo, pero que a la vez se pudiese configurar de manera sencilla en el proyecto, y por supuesto integrarlo en mi IDE PyCharm.

Como primer paso, lo que hice fue instalarlo de manera sencilla con el comando “pip“:

pip install pylint

Lo primero tras instalarlo fue hacer la prueba de ejecutarlo contra código que ya tenía evaluado por Codacy para poder comprobar lo estricto que era el revisor por defecto. Para mi mayor pena, el proyecto que arrojaba una calificación de B en Codacy, se quedaba en un 2.5 sobre 10. Inmediatamente me dí cuenta de que la vara de medir era extremadamente estricta por defecto, con lo que exporté los parámetros de revisión con los que Codacy revisaba mi código, ya que entendía que era una revisión más coherente.

Acto seguido revisé aquellas parametrizaciones que correspondían a PyLint y las migré a un fichero “pylintrc” que puede albergar la información de configuración de PyLint. Para ello ejecuté el siguiente comando, obteniendo la plantilla del “pylintrc“:

pylint --generate-rcfile > .pylintrc

Una vez obtenida la plantilla del fichero de configuración, posteriormente modifiqué la sección “MESSAGES CONTROL” para deshabilitar todas las reglas de revisión por defecto y añadir las que yo deseaba, y que además coincidían con las de revisión en Codacy.

disable=all
enable= E0001,E0100,E0101,E0102,E0103,E0104,E0105,E0106,E0107,E0110,
        E0113,E0114,E0115,E0116,E0117,E0108,E0202,E0203,E0211,E0236,
        E0238,E0239,E0240,E0241,E0301,E0302,E0601,E0603,E0604,E0701,
        E0702,E0703,E0704,E0710,E0711,E0712,E1003,E1102,E1111,E0112,
        E1120,E1121,E1123,E1124,E1125,E1126,E1127,E1132,E1200,E1201,
        E1205,E1206,E1300,E1301,E1302,E1303,E1304,E1305,E1306,
        C0123,C0200,C0303,C1001,
        W0101,W0102,W0104,W0105,W0106,W0107,W0108,W0109,W0110,W0120,
        W0122,W0124,W0150,W0199,W0221,W0222,W0233,W0404,W0410,W0601,
        W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300,
        W1301,W1302,W1303,,W1305,W1306,W1307
        R0102,R0201,R0202,R0203

La serie de códigos que añado son una normalización de distintas reglas aplicables en PyLint para revisar el código. Podéis disponer del fichero completo con las reglas desde el siguiente Gist:

Con ese fichero “pylintrc” ya es posible portar la configuración para el revisor PyLint en cualquier proyecto. Sólo debemos añadirlo a la raíz de cualquier proyecto Python.

Anteriormente he comentado que uso PyCharm como IDE, y claro está, que ejecutar esta herramienta por línea de comandos, no resulta lo ideal. No os preocupéis porque existe una integración bastante directa de PyLint en PyCharm. Para ello solo tenéis que ir a File –> Settings –> Tools –> External Tools en vuestro PyCharm y añadir la siguiente configuración haciendo click en el símbolo + de color verde:

pycharm_external-tool_pylint

Poned el nombre y una descripción para la herramienta y desmarcad “Synchronize files after execution“. En “Program” buscad dentro de la carpeta de instalación de Python en el subdirectorio Scripts el ejecutable “pylint.exe“, si sois usuarios Windows,  y en el directorio “/usr/bin” si sois usuarios de Linux.

Los parámetros que se introducen son la ubicación del fichero de configuración de PyLint (que debiera estar en la raíz del proyecto) y la macro que corresponde al fichero o carpeta que analizaríamos con PyLint. En “Working directory” ponemos la macro que apunta al directorio del proyecto correspondiente al fichero o carpeta que analicemos. Guardamos y para probarlo basta con ir a la raíz del proyecto y ejecutar PyLint con el botón derecho External Tools –> PyLint.

pylint_linkero_review

Como podéis ver, con esta integración tenéis una excelente herramienta que mejorará (si hacéis caso a sus reportes) vuestro código, generando un código mucho más pythonico.

Linkero – Creación de APIs RESTful en Python (parte 1)

Según vas cogiendo experiencia como desarrollador de software te vas dando cuenta del verdadero significado de “No reinventar la rueda“. Debido muchas veces a la falta de tiempo, es casi obligatorio buscar soluciones desde las que partir, o buscar integraciones de software que permitan alcanzar el objetivo marcado.

No obstante la mayoría se suele quedarse ahí y se contentan con reutilizar “piezas”. El siguiente paso lógico y racional para ahorrar trabajo, pasa por diseñar. Por pensar y configurar un diseño que sea lo más generalista posible para que abarque el mayor número de casos de uso. Esta parte de diseño (y preferiblemente buen diseño) no suele ser frecuente entre desarrolladores, ya que suele implicar el gasto de más tiempo para ahorrarlo en un futuro. Debido a eso suele ser complicado trasladar a la dirección o al jefe inmediatamente superior y responsable del desarrollo, la necesidad de invertir más tiempo en un desarrollo para ahorrarlo en un futuro.

Hoy os traigo un caso de esos, en los que se juntan “piezas” y se refuerzan con un diseño para conformar una librería que ayude a un desarrollo más ágil, sujeto a un diseño flexible. Su nombre es Linkero:

https://github.com/RDCH106/linkero

link_slash

Linkero es un framework que permite la creación de manera sencilla de APIs RESTful para peticiones externas. Esta desarollado en Python y su diseño hace hincapié en los siguientes puntos:

  • Propósito general –> Se puede usar para cualquier tipo de API RESTful
  • Modular –> Se pueden agregar las API como si de módulos se tratasen quedando separadas del core de Linkero
  • Escalable –> Se pueden añadir nuevas APIs
  • Seguro –> Permite autenticar las peticiones por usuario y contraseña o mediante token de seguridad
  • Sencillo –> La codificación de las APIS y su securización es fácil de implementar.

La verdad es que originalmente el nombre de Linkero venía de link (unión en inglés) y el sufijo ero (que viene a significar “el  que”), es decir, el que une. Con el tiempo le vi una segunda connotación que tenía relación con el personaje Link de “The Lengedn of Zelda” y el juego de palabras link + hero (unión + héroe).

Como desde hace un tiempo, pienso que el Open Source es una vertiente totalmente Win&Win, decidí liberar el código fuente cuando consideré que el código estaba lo suficientemente maduro para empezar a recoger aportes. Uno de los mejores puntos que tiene el Open Source es su escrutinio público que ayuda a revelar fallos y la aportación de mejoras por otros usuario o empresas que usen dicho código y hayan implementado mejoras. Por contra, existe muchos usuario y sobre todo empresas que usan Open Source, pero no devuelven absolutamente NADA, si la licencia que acompaña el software lo permite. Es lícito hacerlo, no obstante la contrapartida real, es que tras cada mejora en el proyecto principal que se quiera integrar, resulta exponencialmente más difícil, ya que la mejoras introducidas no liberadas, pueden provocar divergencias con las actualizaciones y el diseño oficial.

Desde hace tiempo tengo la convicción de que en software, no se debería vender software tal cual, sino servicio, vender soluciones en vez de productos. Un mismo software o producto puede aplicarse para proveer diversidad de soluciones a problemas distintos e integrarse con otras tantas soluciones diferentes para solventar retos mayores. Esto es una visión totalmente personal ya que como Ingeniero Informático, lo he podido comprobar. El software es una herramienta. Yo proporciono soluciones a través de mi herramienta principal que es la creación de software y a su vez también uso software de terceros para proveer soluciones. Aclarada la visión personal y profesional de mi campo, volvamos al tema principal de la entrada 😉 .

Linkero cuenta con el siguiente diseño general:

linkero_general-scheme_800x600

Y hace uso de las siguientes librerías:

El esquema conceptual de dependencias refleja como únicas dependencias para crear una aplicación, el uso de Linkero y el uso de todas las APIs desarrolladas para Linkero que se quieran incluir. Linkero a su vez hace uso de las anteriormente citadas librerías. A su vez hace falta un “MAIN” que cargue las APIs y las ponga en funcionamiento con Linkero

Teniendo en mente el esquema puedes instalarte Linkero siguiendo las indicaciones de la Wiki:

https://github.com/RDCH106/linkero/wiki/instalacion

Si has seguido todos los pasos de la sección de instalación deberías poder probar Linkero usando el ejemplo testBasicAPI ejecutando el “testBasicAPI_main.py” desde Python.  Este ejemplo es una versión simplificada que a efectos ilustrativos no incluye temas de autenticación. Si analizamos el ejemplo, obtendremos la estructura mínima para desarrollar una API con Linkero que no requiera de autenticación.

Primero se importa el core de Linkero:

import core.linkero as linkero

A efectos demostrativos se crea una estructura de tareas pendientes:

# TODOS Data
TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}

Se define una función que maneje las peticiones sobre objetos no existentes y se genera un objeto “parser” para las peticiones, al que se le añade el argumento “task”:

def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        linkero.abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = linkero.reqparse.RequestParser()
parser.add_argument('task')

Posteriormente definimos los métodos que nos permitan manejar los items de las tareas por separado (métodos GET, DELETE, PUT). Los métodos GET y DELETE hacen  uso del método “abort_if_todo_doesnt_exist” para comprobar que se puede realizar la acción sobre el item específico y PUT hace uso del objeto “parser” para obtener el valor a introducir de la petición:

# Todo
# shows a single todo item and lets you delete a todo item
class Todo(linkero.Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201

Es habitual ofrecer métodos que nos permitan manejar conjuntos de items. Por ello se definen métodos como pueden ser GET (obtener todas las tareas) o POST (incluir un grupo de tareas):

# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(linkero.Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

Una vez definida los métodos que permiten operar con la API, se define un método “loadTestBasicAPI” que asocia un patrón de URL a los métodos definidos:

##
## Actually setup the Api resource routing here
##
def loadTestBasicAPI():
    linkero.api.add_resource(TodoList, '/todos')
    linkero.api.add_resource(Todo, '/todos/<todo_id>')

Con la API completamente definida sólo queda cargarla y ejecutarla con Linkero mediante un “main.py” (para nuestro ejemplo es el “testBasicAPI_main.py“). Para ello se importa el core de Linkero y nuestra API (paso 1 y 2), se carga la API con el método “loadTestBasicAPI” (paso 3) y se invoca el método “run” de Linkero (paso 4):

# 1) Linkero Core
import core.linkero as linkero

# 2) APIs developed to use with Linkero
import examples.testBasicAPI

# 3) Load desired APIs
examples.testBasicAPI.loadTestBasicAPI()

# 4) Run Linkero
linkero.run()

Con estos simples pasos es posible realizar una API RESTful, definiendo la interfaz de la misma y su lógica en cómodos ficheros que pueden ser cargados y lanzados por Linkero. En la segunda parte de esta entrada se ahondará en la securización de la API, así como los aspectos que no se han podido tratar en esta primera parte.