<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Bitácora de Vuelo (Publicaciones sobre biblioteca)</title><link>http://blog.taniquetil.com.ar/</link><description></description><atom:link href="http://blog.taniquetil.com.ar/categories/biblioteca.xml" rel="self" type="application/rss+xml"></atom:link><language>es</language><copyright>Contents © 2023 &lt;a href="mailto:facundo@taniquetil.com.ar"&gt;Facundo Batista&lt;/a&gt; CC BY-NC-SA</copyright><lastBuildDate>Mon, 29 May 2023 18:51:47 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Distribuyendo un programa hecho en Python</title><link>http://blog.taniquetil.com.ar/posts/0518/</link><dc:creator>Facundo Batista</dc:creator><description>&lt;p&gt;Más que un análisis completo de las tecnologías para permitir la distribución de programas hechos en Python, este post es casi una receta o colección de anotaciones para seguir un camino. Y es que es un camino que no me fué fácil recorrer, porque la mayoría de los mecanismos para distribuir código Python están pensadas para distribuir &lt;em&gt;bibliotecas&lt;/em&gt; hechas en este lenguaje, y no &lt;em&gt;programas&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;¿Dónde está la diferencia? En dónde van las cosas.&lt;/p&gt;
&lt;p&gt;Antes de seguir: para todo el armado usé &lt;a class="reference external" href="http://docs.python.org/library/distutils.html"&gt;distutils&lt;/a&gt;, que es lo que está en la biblioteca estándar. Le pegué una mirada a otras cosas como setuptools, distribute, etc, pero todas (aunque son más aptas para cosas más complejas) no me solucionaban el problema básico y me complicaban un poco la vida en otros aspectos.&lt;/p&gt;
&lt;section id="donde-van-las-cosas"&gt;
&lt;h2&gt;¿Dónde van las cosas?&lt;/h2&gt;
&lt;p&gt;Volviendo a el lugar en dónde se instala el código Python... si uno quiere distribuir una biblioteca, la respuesta es sencilla: en el directorio de bibliotecas de Python de tu sistema. ¿En dónde particularmente? Bueno, depende de tu sistema; incluso en Linux esto fue cambiando y no es el mismo lugar siempre. En mi máquina tengo &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/lib/python2.6/dist-packages/&lt;/span&gt;&lt;/code&gt;, que en parte apunta a &lt;code class="docutils literal"&gt;/usr/share/pyshared/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Igual, no importa la ubicación exacta: usando distutils (u otras alternativas) las bibliotecas van a parar al lugar correcto sin mayor esfuerzo.&lt;/p&gt;
&lt;p&gt;¿Pero qué pasa si no es una biblioteca sino un programa? El primer detalle es que necesitamos un &lt;em&gt;ejecutable&lt;/em&gt; que arranque nuestro programa. Distutils y amigos tienen esto bastante bien manejado, se les puede especificar un &lt;em&gt;script&lt;/em&gt;, y terminan instalando todo de la siguiente manera:&lt;/p&gt;
&lt;pre class="literal-block"&gt;script -&amp;gt; /usr/bin/&amp;lt;/span&amp;gt;
todo el resto /usr/lib/python2.6/dist-packages/ (o similar)&lt;/pre&gt;
&lt;p&gt;Hasta acá todo bien, ¿no? No. Resulta que nuestro programa tiene imágenes, archivos de audio, etc, y está "mal visto" meter esos archivos "de datos" dentro del directorio de bibliotecas de Python. Entonces, lo que recomiendan por ahí es:&lt;/p&gt;
&lt;pre class="literal-block"&gt;script -&amp;gt; /usr/bin/
archivos de datos -&amp;gt; /usr/share/
código python -&amp;gt; /usr/lib/python2.6/dist-packages/ (o similar)&lt;/pre&gt;
&lt;p&gt;Esto ya no es tan fácil de lograr, porque la distribución de archivos de datos es como un parche en los sistemas de distribución de bibliotecas.&lt;/p&gt;
&lt;p&gt;Además, si nos vamos a poner quisquillosos de no meter archivos de datos en el directorio de bibliotecas, yo pregunto: ¿por qué meter código de nuestro programa, que no es una biblioteca, en el directorio de bibliotecas?&lt;/p&gt;
&lt;p&gt;Entonces me embarqué en el siguiente capricho: quería que la distribución de mi programa vaya a parar a:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;script -&amp;gt; /usr/bin/
todo el resto -&amp;gt; /usr/share/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Los archivos de datos, por supuesto, mezclados con "todo el resto".&lt;/p&gt;
&lt;/section&gt;
&lt;section id="estructura-de-nuestro-programa"&gt;
&lt;h2&gt;Estructura de nuestro programa&lt;/h2&gt;
&lt;p&gt;Primero lo primero, ¿cómo organizamos nuestro proyecto? Yo tengo lo siguiente (simplificado, pueden ver toda la estructura en &lt;a class="reference external" href="http://bazaar.launchpad.net/%7Efacundo/encuentro/trunk/files"&gt;los archivos del proyecto Encuentro&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;un directorio 'bin' donde tengo el script que arranca todo:&lt;/p&gt;
&lt;pre class="literal-block"&gt;bin/encuentro&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;esto es un archivo ejecutable que no hace mucho más que jugar un poco con los directorios y el &lt;code class="docutils literal"&gt;sys.path&lt;/code&gt; para que se encuentre al resto del código Python de nuestro programa (en dos situaciones: cuando ejecutamos &lt;code class="docutils literal"&gt;bin/encuentro&lt;/code&gt; desde el repositorio mientras estamos desarrollando, y cuando está instalado finalmente en el sistema), e inicializar alguna estructura básica y arrancarla, para que comience nuestro programa.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;un directorio con el nombre de nuestro proyecto, con el resto del programa:&lt;/p&gt;
&lt;pre class="literal-block"&gt;encuentro/__init__.py
encuentro/main.py
encuentro/network.py&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;directorios con los archivos de datos, adentro de nuestro proyecto (no por separado), en este caso:&lt;/p&gt;
&lt;pre class="literal-block"&gt;encuentro/ui/main.glade
encuentro/ui/preferences.glade
encuentro/ui/update.glade&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Una vez aclarado eso, quedan dos preguntas sencillas y una complicada por contestar: las sencillas son ¿cómo el script encuentra al resto del programa instalado? y ¿cómo accedemos a los archivos de datos desde nuestro código?.&lt;/p&gt;
&lt;p&gt;La primera es usando una variable que se inyecta en el script en el momento de instalar el programa (ver más abajo el cuándo hacemos eso en &lt;code class="docutils literal"&gt;setup.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;La segunda es accediendo a los archivos de forma relativa al código. Yo tengo esto al principio del programa:&lt;/p&gt;
&lt;pre class="literal-block"&gt;BASEDIR = os.path.dirname(__file__)&lt;/pre&gt;
&lt;p&gt;y luego hago cosas como:&lt;/p&gt;
&lt;pre class="literal-block"&gt;data_file = os.path.join(BASEDIR, 'ui', 'preferences.glade')&lt;/pre&gt;
&lt;p&gt;Finalmente, la pregunta complicada: ¿cómo hacemos para que todo esto funcione?&lt;/p&gt;
&lt;/section&gt;
&lt;section id="distribuyendo-programas"&gt;
&lt;h2&gt;Distribuyendo programas&lt;/h2&gt;
&lt;p&gt;En realidad, la respuesta no es tan complicada una vez que está resuelto (como tantas cosas en la vida).&lt;/p&gt;
&lt;p&gt;Para incluir todos los archivos, en el &lt;code class="docutils literal"&gt;setup.py&lt;/code&gt;, en la llamada a &lt;code class="docutils literal"&gt;setup()&lt;/code&gt; hay que poner:&lt;/p&gt;
&lt;pre class="literal-block"&gt;packages = ["encuentro"],
package_data = {"encuentro": ["ui/*.glade"]},
scripts = ["bin/encuentro"],&lt;/pre&gt;
&lt;p&gt;Fíjense como ahí declaro el paquete donde está mi programa, el script, y los archivos de datos. Pero hay un bug, hasta en Python 2.6 inclusive, que hace que para meter los archivos de datos con eso sólo no alcanza, y hay que declararlos también en el &lt;code class="docutils literal"&gt;MANIFEST.in&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;include encuentro/ui/*.glade&lt;/pre&gt;
&lt;p&gt;Para que todos estos archivos vayan a parar al lugar correcto, hay que hacer algo específico: una clase que acomoda cosas en el proceso de instalación. Pueden ver el detalle de esa clase en &lt;a class="reference external" href="http://bazaar.launchpad.net/%7Efacundo/encuentro/trunk/view/head:/setup.py"&gt;el setup.py de Encuentro&lt;/a&gt;, pero basicamente hace dos cosas:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Construye un directorio donde va a quedar todo con el prefijo indicado, "share" y el nombre del proyecto, y autocorrije el directorio de instalación con eso.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guarda ese directorio de instalación nuevo en los scripts declarados, usando una cadena especial como bandera, de manera que al quedar el script instalado sabe dónde buscar el programa entero.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(importante: no olvidar declarar en la llamada a setup() a esta nueva clase como la clase que será usada para instalar!)&lt;/p&gt;
&lt;p&gt;Finalmente, está bueno probar que todo funca bien. Las pruebas que yo hice fue crear el .tar.gz con &lt;code class="docutils literal"&gt;python setup.py sdist&lt;/code&gt;, descomprimirlo en otro lado que nada que ver y hacer &lt;code class="docutils literal"&gt;python setup.py install &lt;span class="pre"&gt;--prefix=/tmp&lt;/span&gt;&lt;/code&gt; (para que se instale en &lt;code class="docutils literal"&gt;/tmp&lt;/code&gt; y probarlo ahí adentro) y también &lt;code class="docutils literal"&gt;sudo python setup.py install&lt;/code&gt; (para que se instale en el sistema y probarlo así).&lt;/p&gt;
&lt;p&gt;También, luego de hacer todo el proceso de packaging, cuando &lt;em&gt;pbuilder&lt;/em&gt; me dejó el &lt;code class="docutils literal"&gt;.deb&lt;/code&gt;, lo descomprimo y veo que la estructura está correcta y que la variable reemplazada en el script tiene el valor que debería; igual, la prueba de fuego con el &lt;code class="docutils literal"&gt;.deb&lt;/code&gt; es instalarlo con &lt;code class="docutils literal"&gt;dpkg &lt;span class="pre"&gt;-i&lt;/span&gt;&lt;/code&gt; y probar el programa.&lt;/p&gt;
&lt;p&gt;Nota final: ahora me falta armar un &lt;code class="docutils literal"&gt;.exe&lt;/code&gt; para que se pueda ejecutar en Windows, pero eso será otro post.&lt;/p&gt;
&lt;/section&gt;</description><category>biblioteca</category><category>distribuyendo</category><category>distutils</category><category>install</category><category>path</category><category>programa</category><category>Python</category><category>script</category><guid>http://blog.taniquetil.com.ar/posts/0518/</guid><pubDate>Wed, 22 Jun 2011 23:56:32 GMT</pubDate></item></channel></rss>