¿Cómo protejo mi base de código Python para que los invitados no puedan ver ciertos módulos pero aún así funciona?
-
22-07-2019 - |
Pregunta
Estamos comenzando un nuevo proyecto en Python con algunos algoritmos patentados y partes sensibles de lógica que nos gustaría mantener en privado. También tendremos algunos extraños (miembros selectos del público) trabajando en el código. No podemos otorgar acceso a los extraños a los pequeños y privados bits de código, 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 sucede en get_sauce ()
es secreto, pero queremos que una versión pública de get_sauce ()
devuelva un resultado aceptable, aunque incorrecto.
También ejecutamos nuestro propio servidor Subversion para tener control total sobre quién puede acceder a qué.
Symlinks
Mi primer pensamiento fue la simulación - & nbsp; En lugar de bar.py
, proporcione bar_public.py
a todos y bar_private.py
a interno solo desarrolladores. Desafortunadamente, crear enlaces simbólicos es tedioso, trabajo manual, especialmente cuando realmente va a haber alrededor de dos docenas de estos módulos privados.
Más importante aún, hace que la administración del archivo Subversion authz sea difícil, ya que para cada módulo que queremos proteger se debe agregar una excepción en el servidor. Alguien podría olvidar hacer esto y accidentalmente registrar 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 pagar tanto private /
como public /
. Los desarrolladores internos establecerán su PYTHONPATH = private / trunk: public / trunk
, pero todos los demás solo establecerán PYTHONPATH = public / trunk
. Entonces, tanto los de adentro como los de afuera pueden desde foo import bar
y obtener el módulo correcto, ¿verdad?
Probemos 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 sobre el módulo foo
y busca en relación con eso:
>>> foo
<module 'foo' from '/path/to/private/trunk/foo/__init__.py'>
Ni siquiera eliminar 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
¿Puede proporcionarme una mejor solución o sugerencia?
Solución
Cree un directorio llamado secret
y póngalo en su repositorio privado de Subversion. En secret
ponga su propiedad bar.py
. En el __init__.py
del paquete público foo
ponga algo como:
__path__.insert(0,'secret')
Esto significará para los usuarios que tienen el repositorio privado y, por lo tanto, el directorio secret
obtendrán el bar.py
propietario como foo.bar
como secret
es el primer directorio en la ruta de búsqueda. Para otros usuarios, Python no encontrará secret
y se verá como el siguiente directorio en __path__
y, por lo tanto, cargará el bar.py
normal de foo
.
Entonces se verá más o menos así:
private
└── trunk/
└── secret/
└── bar.py
public
└── trunk/
├── __init__.py
└── foo/
├── __init__.py
├── bar.py
├── baz.py
└── quux.py
Otros consejos
Use algún tipo de sistema de complementos y mantenga sus complementos para usted mismo, pero también tenga complementos disponibles públicamente que se envían con el código abierto.
Los sistemas de complementos abundan. Usted mismo puede hacer fácilmente los más simples. Si desea algo más avanzado, prefiero la arquitectura de componentes de Zope, pero también hay opciones como setuptools entry_points, etc.
Cuál usar en su caso sería una buena segunda pregunta.
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 Python:
__import__('pkg_resources').declare_namespace(__name__)
Si desea saber exactamente qué está sucediendo allí, consulte los documentos de herramientas de distribución o configuración que explican cómo funciona esto.