practices find_packages best python namespaces distribution extend

find_packages - ¿Poniendo paquetes de python separados en el mismo espacio de nombres?



python project structure (5)

Estoy desarrollando un marco de Python que tendría "complementos" escritos en paquetes separados. Es decir:

import myframework from myframework.addons import foo, bar

Ahora, lo que estoy tratando de arreglar es que estos complementos se puedan distribuir por separado desde el framework central y se myframework.addons inyectar en el espacio de nombres myframework.addons .

Actualmente mi mejor solución a esto es la siguiente. Se implementaría un complemento (muy probablemente en {python_version}/site-packages/ like así:

fooext/ fooext/__init__.py fooext/myframework/ fooext/myframework/__init__.py fooext/myframework/addons/ fooext/myframework/addons/__init__.py fooext/myframework/addons/foo.py

El fooext/myframework/addons/__init__.py tendría el código de extensión pkgutil path:

import pkgutil __path__ = pkgutil.extend_path(__path__, __name__)

El problema es que para que esto funcione, PYTHONPATH necesita tener fooext/ in, sin embargo, lo único que tendría es el directorio de instalación principal (lo más probable es que los site-packages mencionados anteriormente).

La solución a esto es tener código adicional en myframework/addons/__init__.py que myframework/addons/__init__.py sys.path y busque cualquier módulo con un subpaquete myframework, en cuyo caso lo agrega a sys.path y todo funciona.

Otra idea que tuve fue escribir los archivos de complemento directamente en myframework/addons/ install location, pero luego eso haría que el espacio de desarrollo y despliegue difiera.

¿Hay una mejor manera de lograr esto o tal vez un enfoque diferente al problema de distribución anterior por completo?


Parece que lo que está buscando se puede lograr bastante bien con los ganchos de importación.

Esta es una forma de escribir código de carga personalizado, que puede asociarse con un paquete (o en su caso un marco) para realizar la carga de todos los subpaquetes y módulos, en lugar de utilizar el mecanismo de carga predeterminado de python. Luego puede instalar el cargador en paquetes de sitio como un paquete base o bajo su marco.

Cuando se encuentra que un paquete está asociado con el cargador (que simplemente puede estar codificado en la ruta relativa si es necesario), siempre usará el cargador para cargar todos los complementos, por ejemplo. Esto tiene la ventaja de no requerir ningún toque de la PYTHONPATH, que generalmente vale la pena mantener lo más corto posible.

La alternativa a esto es usar los archivos de inicio para redirigir la llamada de importación de un submódulo al que desea que recoja, pero esto es un poco complicado.

Puede encontrar más información sobre los ganchos de importación aquí:

http://www.python.org/dev/peps/pep-0302/


Setuptools tiene la capacidad de buscar paquetes de "puntos de entrada" (funciones, objetos, lo que sea) por nombre. Trac usa este mecanismo para cargar sus complementos , y funciona bien.


¿Hay una mejor manera de lograr esto o tal vez un enfoque diferente al problema de distribución anterior por completo?

Posiblemente. La configuración del paquete / módulo de Python es generalmente difícil de manipular dinámicamente como esta, pero su sistema de objeto / clase es abierto y extensible de una manera bien definida. Cuando los módulos y paquetes no tienen las características que necesita para encapsular su proyecto, puede usar clases en su lugar.

Por ejemplo, podría tener la funcionalidad de extensión en un paquete completamente diferente, pero permitirle inyectar clases en su marco básico a través de una interfaz específica. p.ej. myframework / _ _ init _ _.py que contiene un contenedor de aplicaciones básicas:

class MyFramework(object): """A bare MyFramework, I only hold a person''s name """ _addons= {} @staticmethod def addAddon(name, addon): MyFramework._addons[name]= addon def __init__(self, person): self.person= person for name, addon in MyFramework._addons.items(): setattr(self, name, addon(self))

Entonces podría tener la funcionalidad de extensión en un myexts / helloer.py, que guarda una referencia a su instancia de clase ''owner'' o ''outer'' MyFramework:

class Helloer(object): def __init__(self, owner): self.owner= owner def hello(self): print ''hello ''+self.owner.person import myframework myframework.MyFramework.addAddon(''helloer'', Helloer)

Entonces, si solo "importas myframework", solo obtienes la funcionalidad básica. Pero si también "importas myexts.helloer", también tienes la posibilidad de llamar a MyFramework.helloer.hello (). Naturalmente, también puede definir protocolos para que los complementos interactúen con el comportamiento básico del marco y entre sí. También puede hacer cosas como clases internas que una subclase del framework puede anular para personalizar sin tener que aplicar parche de monos que podrían afectar otras aplicaciones, si necesita ese nivel de complejidad.

El comportamiento de encapsulación como este puede ser útil, pero normalmente es un trabajo molesto adaptar el código de nivel de módulo que ya tiene para ajustarse a este modelo.



Hay una configuración completamente nueva para los espacios de nombres. Echa un vistazo a los paquetes de espacios de nombres de Packaging . En resumen, tiene tres opciones, en función de la compatibilidad con versiones anteriores que desea que tenga su código. También hay un PEP relevante, que reemplaza a los mencionados en otras respuestas: PEP 420 .