distutils: ¿Cómo pasar un parámetro definido por el usuario a setup.py?

Por favor, indíqueme cómo pasar un parámetro definido por el usuario desde la línea de comandos y el archivo de configuración setup.cfg al script setup.py de distutils. Quiero escribir un script setup.py, que acepte los parámetros específicos de mi paquete. Por ejemplo:

python setup.py install -foo myfoo 

Gracias,
Mher

Como Setuptools / Distuils están horriblemente documentados, tuve problemas para encontrar la respuesta a esto. Pero al final me topé con este ejemplo. Además, esta pregunta similar fue útil. Básicamente, un comando personalizado con una opción se vería así:

 from distutils.core import setup, Command class InstallCommand(Command): description = "Installs the foo." user_options = [ ('foo=', None, 'Specify the foo to bar.'), ] def initialize_options(self): self.foo = None def finalize_options(self): assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!' def run(self): install_all_the_things() setup( ..., cmdclass={ 'install': InstallCommand, } ) 

Aquí hay una solución muy simple, todo lo que tiene que hacer es filtrar sys.argv y manejarlo usted mismo antes de llamar a la setup(..) distutils setup(..) . Algo como esto:

 if "--foo" in sys.argv: do_foo_stuff() sys.argv.remove("--foo") ... setup(..) 

La documentación sobre cómo hacer esto con distutils es terrible, finalmente encontré esta: la guía para el embalaje de los autoestopistas , que utiliza sdist y sus user_options . Me parece que la referencia de extensión no es particularmente útil.

Aunque se parece a la forma “correcta” de hacerlo con distutils (al menos la única que puedo encontrar que está vagamente documentada). No pude encontrar nada en --with y – --without interruptores mencionados en la otra respuesta.

El problema con esta solución de distutils es que está demasiado involucrado para lo que estoy buscando (lo que también puede ser el caso para usted). Agregar docenas de líneas y subclasificar a sdist es simplemente incorrecto para mí.

Sí, es 2015 y todavía falta la documentación para agregar comandos y opciones en ambas setuptools y distutils .

Después de unas pocas horas frustrantes, descubrí el siguiente código para agregar una opción personalizada al comando de install de setup.py :

 from setuptools.command.install import install class InstallCommand(install): user_options = install.user_options + [ ('custom_option=', None, 'Path to something') ] def initialize_options(self): install.initialize_options(self) self.custom_option = None def finalize_options(self): #print('The custom option for install is ', self.custom_option) install.finalize_options(self) def run(self): global my_custom_option my_custom_option = self.custom_option install.run(self) # OR: install.do_egg_install(self) 

Vale la pena mencionar que install.run () comprueba si se llama “de forma nativa” o si ha sido parcheado:

 if not self._called_from_setup(inspect.currentframe()): orig.install.run(self) else: self.do_egg_install() 

En este punto usted registra su comando con la setup :

 setup( cmdclass={ 'install': InstallCommand, }, : 

Realmente no se pueden pasar parámetros personalizados al script. Sin embargo, las siguientes cosas son posibles y podrían resolver su problema:

  • las características opcionales se pueden habilitar usando --with-featurename , las características estándar se pueden deshabilitar usando --without-featurename . [AFAIR esto requiere herramientas de configuración]
  • puede usar variables de entorno, sin embargo, estas deben set en Windows, mientras que el prefijo funciona en linux / OS X ( FOO=bar python setup.py ).
  • puede extender los nombres con sus propios cmd_class es que pueden implementar nuevas características. También se pueden cambiar, por lo que puede usar eso para cambiar las variables en su script. ( python setup.py foo install ) ejecutará el comando foo antes de ejecutar la install .

Espero que ayude de alguna manera. En general, sugeriría que proporcione un poco más de información sobre qué debe hacer exactamente su parámetro adicional, quizás haya una mejor solución disponible.

Tal vez usted sea un progtwigdor no sazonado como yo que todavía tuvo problemas después de leer todas las respuestas anteriores. Por lo tanto, puede encontrar otro ejemplo potencialmente útil ( y para abordar los comentarios en respuestas anteriores sobre cómo ingresar los argumentos de la línea de comando ):

 class RunClientCommand(Command): """ A command class to runs the client GUI. """ description = "runs client gui" # The format is (long option, short option, description). user_options = [ ('socket=', None, 'The socket of the server to connect (eg '127.0.0.1:8000')', ] def initialize_options(self): """ Sets the default value for the server socket. The method is responsible for setting default values for all the options that the command supports. Option dependencies should not be set here. """ self.socket = '127.0.0.1:8000' def finalize_options(self): """ Overriding a required abstract method. The method is responsible for setting and checking the final values and option dependencies for all the options just before the method run is executed. In practice, this is where the values are assigned and verified. """ pass def run(self): """ Semantically, runs 'python src/client/view.py SERVER_SOCKET' on the command line. """ print(self.socket) errno = subprocess.call([sys.executable, 'src/client/view.py ' + self.socket]) if errno != 0: raise SystemExit("Unable to run client GUI!") 

 setup( # Some other omitted details cmdclass={ 'runClient': RunClientCommand, }, 

Lo anterior está probado y desde algún código lo escribí. También he incluido docstrings un poco más detallados para que las cosas sean más fáciles de entender.

En cuanto a la línea de comandos: python setup.py runClient --socket=127.0.0.1:7777 . Una verificación rápida mediante el uso de sentencias de impresión muestra que, de hecho, el método de ejecución recoge el argumento correcto.

Otros recursos que me parecieron útiles ( más y más ejemplos):

Comandos personalizados distutils

https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html

Usé con éxito una solución alternativa para usar una solución similar a la sugerencia de totaam. Terminé haciendo estallar mis argumentos adicionales de la lista sys.argv:

 import sys from distutils.core import setup foo = 0 if '--foo' in sys.argv: index = sys.argv.index('--foo') sys.argv.pop(index) # Removes the '--foo' foo = sys.argv.pop(index) # Returns the element after the '--foo' # The foo is now ready to use for the setup setup(...) 

Se podría agregar una validación adicional para asegurar que las entradas sean buenas, pero así es como lo hice

Una forma rápida y fácil similar a la dada por totaam sería usar argparse para capturar el argumento -foo y dejar los argumentos restantes para la llamada a distutils.setup (). Usar argparse para esto sería mejor que iterar a través de sys.argv manualmente imho. Por ejemplo, agregue esto al comienzo de su setup.py:

 argparser = argparse.ArgumentParser(add_help=False) argparser.add_argument('--foo', help='required foo argument', required=True) args, unknown = argparser.parse_known_args() sys.argv = [sys.argv[0]] + unknown 

El argumento add_help=False significa que aún puede obtener la ayuda regular de setup.py usando -h (siempre que se --foo ).