guide - python software structure
¿Cómo protejo mi base de código de Python para que los invitados no puedan ver ciertos módulos, pero todavía funciona? (3)
Estamos empezando un nuevo proyecto en Python con unos pocos algoritmos patentados y bits de lógica delicados que nos gustaría mantener en privado. También tendremos algunos forasteros (miembros selectos del público) trabajando en el código. No podemos otorgar acceso a los extraños a pequeños fragmentos de código privados, pero nos gustaría que una versión pública funcione lo suficientemente bien para ellos.
Digamos que nuestro proyecto, Foo, tiene un módulo, bar
, con una función, get_sauce()
. Lo que realmente ocurre en get_sauce()
es secreto, pero queremos que una versión pública de get_sauce()
un resultado aceptable, aunque incorrecto.
También administramos nuestro propio servidor de Subversion, por lo que tenemos control total sobre quién puede acceder a qué.
Enlaces simbólicos
Lo primero que pensé fue en el enlace simbólico: en lugar de bar.py
, proporcione bar_public.py
a todo el mundo y bar_private.py
a desarrolladores internos. Desafortunadamente, la creación de enlaces simbólicos es un trabajo tedioso y manual, especialmente cuando realmente habrá cerca de dos docenas de estos módulos privados.
Más importante aún, hace que la gestión del archivo Authz de Subversion sea difícil, ya que para cada módulo que queremos proteger debe agregarse una excepción en el servidor. Alguien puede olvidarse de hacer esto y verificar accidentalmente los secretos ... Entonces el módulo está en el repositorio y tenemos que reconstruir el repositorio sin él y esperar que un extraño no lo descargue mientras tanto.
Múltiples repositorios
El siguiente pensamiento fue tener dos repositorios:
private
└── trunk/
├── __init__.py
└── foo/
├── __init__.py
└── bar.py
public
└── trunk/
├── __init__.py
└── foo/
├── __init__.py
├── bar.py
├── baz.py
└── quux.py
La idea es que solo los desarrolladores internos podrán realizar compras private/
public/
. Los desarrolladores internos establecerán su PYTHONPATH=private/trunk:public/trunk
, pero todos los demás simplemente establecerán PYTHONPATH=public/trunk
. Entonces, tanto los de adentro como los de fuera pueden from foo import bar
y obtener el módulo correcto, ¿verdad?
Intentemos esto:
% PYTHONPATH=private/trunk:public/trunk python
Python 2.5.1
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo.bar
>>> foo.bar.sauce()
''a private bar''
>>> import foo.quux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named quux
No soy un experto en Python, pero parece que Python ya se ha decidido por el módulo foo
y las búsquedas relativas a eso:
>>> foo
<module ''foo'' from ''/path/to/private/trunk/foo/__init__.py''>
Ni siquiera borrar foo
ayuda:
>>> import sys
>>> del foo
>>> del sys.modules[''foo'']
>>> import foo.quux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named quux
¿Me puede dar una mejor solución o sugerencia?
Utilice algún tipo de sistema de complemento y mantenga sus complementos para usted, pero también tenga complementos disponibles públicamente que se envíen con el código abierto.
Los sistemas de complementos abundan. Usted puede hacer fácilmente simples muertos. Si quieres algo más avanzado, prefiero Zope Component Architecture, pero también hay opciones como setuptools entry_points, etc.
Cuál usar en su caso sería una buena segunda pregunta.
Así que crea un directorio llamado secret
y ponlo en tu repositorio privado de Subversion. En secret
ponga su bar.py
propiedad. En __init__.py
del paquete foo
público pon algo así como:
__path__.insert(0,''secret'')
Esto significará para los usuarios que tienen el repositorio privado y por lo tanto el directorio secret
que obtendrán bar.py
como foo.bar
como secret
es el primer directorio en la ruta de búsqueda. Para otros usuarios, Python no encontrará el secret
y se verá como el siguiente directorio en __path__
y así cargará el bar.py
normal de foo
.
Por lo tanto, se verá algo como esto:
private
└── trunk/
└── secret/
└── bar.py
public
└── trunk/
├── __init__.py
└── foo/
├── __init__.py
├── bar.py
├── baz.py
└── quux.py
Aquí hay una solución alternativa que noté al leer los documentos para Flask :
flaskext/__init__.py
El único propósito de este archivo es marcar el paquete como paquete de espacio de nombres. Esto es necesario para que múltiples módulos de diferentes paquetes PyPI puedan residir en el mismo paquete de Python:
__import__(''pkg_resources'').declare_namespace(__name__)
Si desea saber exactamente qué está sucediendo allí, revise los documentos de distribución o configuración que explican cómo funciona esto.