Importación del módulo Python – problema de rutas relativas

Estoy desarrollando mi propio módulo en python 2.7. Reside en ~/Development/.../myModule lugar de ~/Development/.../myModule o /usr/lib/python2.7/site-packages . La estructura interna es:

 /project-root-dir /server __init__.py service.py http.py /client __init__.py client.py 

client/client.py incluye la clase PyCachedClient . Estoy teniendo problemas de importación:

 project-root-dir$ python Python 2.7.2+ (default, Jul 20 2012, 22:12:53) [GCC 4.6.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from server import http Traceback (most recent call last): File "", line 1, in  File "server/http.py", line 9, in  from client import PyCachedClient ImportError: cannot import name PyCachedClient 

No configuré PythonPath para incluir mi project-root-dir , por lo tanto, cuando server.http intenta incluir client.PyCachedClient, intenta cargarlo desde una ruta relativa y falla. Mi pregunta es: ¿cómo debo establecer todas las rutas / configuraciones de una manera buena y pythonica? Sé que puedo ejecutar export PYTHONPATH=... en shell cada vez que abro una consola e bash ejecutar mi servidor, pero creo que no es la mejor manera. Si mi módulo se instalara a través de PyPi (o algo similar), lo tendría instalado en /usr/lib/python... ruta y se cargaría automáticamente.

Apreciaría consejos sobre las mejores prácticas en el desarrollo de módulos de Python.

Flujo de trabajo de desarrollo de My Python

Este es un proceso básico para desarrollar paquetes de Python que incorpora lo que creo que son las mejores prácticas en la comunidad. Es básico: si realmente quieres desarrollar paquetes de Python, todavía hay algo más, y cada uno tiene sus propias preferencias, pero debería servir como una plantilla para comenzar y luego aprender más sobre las piezas involucradas. Los pasos básicos son:

  • Usa virtualenv para el aislamiento
  • setuptools para crear un paquete instalable y administrar dependencias
  • python setup.py develop para instalar ese paquete en modo de desarrollo

virtualenv

Primero, recomendaría usar virtualenv para obtener un entorno aislado para desarrollar su (s) paquete (s). Durante el desarrollo, necesitará instalar, actualizar, degradar y desinstalar las dependencias de su paquete, y no desea

  • sus dependencias de desarrollo para contaminar sus site-packages todo el sistema
  • los site-packages todo el sistema para influir en su entorno de desarrollo
  • conflictos de versiones

Contaminar los site-packages todo el sistema es malo, porque cualquier paquete que instale allí estará disponible para todas las aplicaciones de Python que instaló y que usan el sistema de Python, aunque solo necesitaba esa dependencia para su pequeño proyecto. Y se acaba de instalar en una nueva versión que anuló la de los site-packages todo el sistema y es incompatible con $ {important_app} que depende de ello. Tienes la idea

El hecho de que sus site-packages todo el sistema influyan en su entorno de desarrollo es malo, porque tal vez su proyecto dependa de un módulo que ya tiene en los site-packages de site-packages de Python. Así que olvida declarar correctamente que su proyecto depende de ese módulo, pero todo funciona porque siempre está ahí en su caja de desarrollo local. Hasta que libere su paquete y la gente intente instalarlo o lo empuje a producción, etc. El desarrollo en un entorno limpio lo obliga a declarar sus dependencias correctamente.

Por lo tanto, un virtualenv es un entorno aislado con su propio intérprete de Python y su ruta de búsqueda de módulos. Se basa en una instalación de Python que instaló anteriormente, pero aislada de ella.

Para crear un virtualenv, instale el paquete virtualenv instalándolo en su sistema en Python usando easy_install o pip :

 sudo pip install virtualenv 

Tenga en cuenta que esta será la única vez que instale algo como root (utilizando sudo) en sus paquetes de sitios globales. Todo después de esto sucederá dentro del virtualenv que estás a punto de crear.

Ahora crea un virtualenv para desarrollar tu paquete:

 cd ~/pyprojects virtualenv --no-site-packages foobar-env 

Esto creará un árbol de directorios ~/pyprojects/foobar-env , que es su virtualenv.

Para activar el virtualenv, cd en él y source el bin/activate script :

 ~/pyprojects $ cd foobar-env/ ~/pyprojects/foobar-env $ . bin/activate (foobar-env) ~/pyprojects/foobar-env $ 

Tenga en cuenta el punto inicial . , eso es una taquigrafía para el comando shell de la source . También tenga en cuenta cómo cambia el indicador: (foobar-env) significa que está dentro del virtualenv activado (y siempre tendrá que estar para que el aislamiento funcione). Así que active su env cada vez que abra una nueva pestaña de terminal o sesión SSH, etc.

Si ahora ejecuta python en ese env activado, en realidad usará ~/pyprojects/foobar-env/bin/python como el intérprete, con sus propios site-packages y la ruta de búsqueda de módulo aislada.

Un paquete de herramientas de configuración

Ahora para crear tu paquete. Básicamente, querrá que un paquete setuptools con setup.py declare correctamente los metadatos y las dependencias de su paquete. Puede hacer esto por su cuenta siguiendo la documentación de setuptools o creando un paquete con las plantillas de Paster . Para usar plantillas de Paster, instale PasteScript en su virtualenv:

 pip install PasteScript 

Vamos a crear un directorio de origen para nuestro nuevo paquete para mantener las cosas organizadas (tal vez desee dividir su proyecto en varios paquetes o, más tarde, usar dependencias de la fuente):

 mkdir src cd src/ 

Ahora para crear tu paquete, haz

 paster create -t basic_package foobar 

Y responde a todas las preguntas en la interfaz interactiva. La mayoría son opcionales y simplemente se pueden dejar en el valor predeterminado presionando ENTER.

Esto creará un paquete (o más precisamente, una distribución de herramientas de configuración) llamado foobar . Este es el nombre que

  • la gente usará para instalar su paquete usando easy_install o pip install foobar
  • el nombre que usarán otros paquetes para depender del suyo en setup.py
  • Como se llamará en PyPi

En el interior, casi siempre se crea un paquete de Python (como en “un directorio con un __init__.py ) que se llama igual. No es necesario, el nombre del paquete de Python de nivel superior puede ser cualquier nombre de paquete válido, pero es una convención común. para nombrarlo igual a la distribución. Y es por eso que es importante, pero no siempre fácil, mantener los dos separados. Porque el nombre del paquete de nivel superior de python es lo que

  • personas (o usted) usarán para importar su paquete usando import foobar o from foobar import baz

Entonces, si usaste la plantilla de pegado, ya habrá creado ese directorio para ti:

 cd foobar/foobar/ 

Ahora crea tu código:

 vim models.py 

models.py

 class Page(object): """A dumb object wrapping a webpage. """ def __init__(self, content, url): self.content = content self.original_url = url def __repr__(self): return "" % (self.original_url, len(self.content)) 

Y un client.py en el mismo directorio que usa models.py :

client.py

 import requests from foobar.models import Page url = 'http://www.stackoverflow.com' response = requests.get(url) page = Page(response.content, url) print page 

Declare la dependencia en el módulo de requests en setup.py :

  install_requires=[ # -*- Extra requirements: -*- 'setuptools', 'requests', ], 

Control de versiones

src/foobar/ es el directorio que ahora querrá poner bajo el control de versiones:

 cd src/foobar/ git init vim .gitignore 

.gitignore

 *.egg-info *.py[co] 
 git add . git commit -m 'Create initial package structure. 

Instalando tu paquete como un huevo de desarrollo

Ahora es el momento de instalar su paquete en modo de desarrollo:

 python setup.py develop 

Esto instalará la dependencia de requests y su paquete como un huevo de desarrollo. Así que está vinculado a los paquetes de sitio de tu virtualenv, pero aún vive en src/foobar donde puedes hacer cambios y hacer que se activen inmediatamente en el virtualenv sin volver a instalar tu paquete.

Ahora, para su pregunta original, importe utilizando rutas relativas: Mi consejo es, no lo haga. Ahora que tiene un paquete de herramientas de instalación adecuado, que está instalado e importable, su directorio de trabajo actual no debería importar más. Simplemente haga from foobar.models import Page o similar, declarando el nombre completo donde vive ese objeto. Eso hace que su código fuente sea mucho más legible y visible para usted y para otras personas que leen su código.

Ahora puede ejecutar su código haciendo python client.py desde cualquier lugar dentro de su virtualenv activado. python src/foobar/foobar/client.py funciona igual de bien, su paquete está correctamente instalado y su directorio de trabajo ya no importa.

Si desea ir un paso más allá, incluso puede crear un punto de entrada setuptools para sus scripts de CLI. Esto creará un script bin/something en su virtualenv que puede ejecutar desde el shell.

setuptools console_scripts punto de entrada

setup.py

  entry_points=''' # -*- Entry points: -*- [console_scripts] run-fooobar = foobar.main:run_foobar ''', 

client.py

 def run_client(): # ... 

main.py

 from foobar.client import run_client def run_foobar(): run_client() 

Vuelva a instalar su paquete para activar el punto de entrada:

 python setup.py develop 

Y ahí tienes, bin/run-foo .

Una vez que usted (o alguien más) instale su paquete de verdad, fuera de virtualenv, el punto de entrada estará en /usr/local/bin/run-foo o en algún lugar similar, donde estará automáticamente en $PATH .

Pasos adicionales

  • Creando un lanzamiento de su paquete y cargándolo en PyPi, por ejemplo usando zest.releaser
  • Mantener un registro de cambios y la versión de su paquete
  • Aprender sobre declarar dependencias.
  • Aprenda sobre las diferencias entre distribución, herramientas, herramientas de configuración y herramientas2

Lectura sugerida:

  • La guía del autoestopista para el embalaje
  • El libro de cocina pip

Entonces, tienes dos paquetes , el primero con módulos llamados:

 server # server/__init__.py server.service # server/service.py server.http # server/http.py 

El segundo con nombres de módulos:

 client # client/__init__.py client.client # client/client.py 

Si quiere asumir que ambos paquetes están en su ruta de importación ( sys.path ), y la clase que desea está en client/client.py , entonces en su servidor tiene que hacer:

 from client.client import PyCachedClient 

Usted solicitó un símbolo de client , no client.client , y de su descripción, no es donde se define ese símbolo.

Personalmente, consideraría crear este único paquete (es decir, poner un __init__.py en la carpeta un nivel arriba, y darle un nombre de paquete de Python adecuado), y hacer que el client y el server sean subpaquetes de ese paquete. Entonces (a) podría hacer importaciones relativas si quisiera ( from ...client.client import something ), y (b) su proyecto sería más adecuado para la redistribución, no poniendo dos nombres de paquetes muy generics en el nivel superior de La jerarquía del módulo python.