Apache Cordova y Brython
Crear aplicaciones para Android utilizando el Framework Cordova y Brython
(Python en el WebView)
INTRODUCCIÓN
Una forma de crear aplicaciones para Android con Python es utilizar el Framework Apache Cordova.
Cordova es un framework que permite crear aplicaciones multiplataforma utilizando HTML, CSS y JavaScript. La interfaz gráfica de la aplicación será un webview, y Cordova expone una API para interactuar con el dispositivo (permite instalar plugins).
Gracias a Brython, una implementación del interprete de Python para navegadores web escrita en JavaScript, es posible utilizar el Framework Cordova para crear aplicaciones Android con Python.
Lo mas aburrido es la instalación de todos los componentes necesarios. Sin embargo, una vez configurado el entorno de desarrollo, crear aplicaciones con Cordova y Brython resulta muy sencillo y divertio. A continuación tienes algunos ejemplos de lo que se puede hacer:
Ejemplos de Aplicaciones creadas con Cordova + Brython
Dado Brython (Play Store) - Lanza el dado. Puedes descargarlo y probarlo de la Play Store o puedes crearlo tú mismo.
Remote PC Input (Play Store) - Controla el PC desde tu dispositivo Android. Puedes descargarlo y probarlo de la Play Store.
El servidor para conectar con el PC está disponible en sourceforge (solo para windows).Calculadora - Calculadora para realizar operaciones matemáticas básicas. Puedes desarrollarla tú mismo.
Cartas Munchkin - Visor de Cartas del juego Munchkin.
En este artículo vamos a ver todo lo necesario para desarrollar aplicaciones con Python aprovechando la mayor ventaja que tiene JavaScript (el ser un lenguaje que se ejecuta en el navegador y por tanto fácil de implementar en cualquier plataforma).
¡¡No te irás de aquí sin crear un apk que ejecute tu código python!!
Sin mas dilación empecemos con la parte mas aburrida: la instalación del entorno de desarrollo.
INSTALACIÓN
Unicamente explicaré cómo realizar la instalación en Ubuntu (también he probado la misma instalación en el subsistema de linux para windows y funciona perfectamente):
Instalar Java Development Kit 8 (JDK8):
sudo add-apt-repository ppa:openjdk-r/ppa sudo apt update sudo apt install openjdk-8-jdk
Abre el archivo ~/.bashrc y añade lo siguiente al final:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH=$PATH:$JAVA_HOME/bin
Guarda los cambios y ejecuta
source ~/.bashrc
para aplicar los cambios.Instalar Android Studio, Gradle y el SDK Platform 28:
Android Studio:
sudo apt install snapd sudo snap install android-studio --classic
Abre el archivo ~/.bashrc y añade lo siguiente al final:
export ANDROID_HOME=$HOME/Android/Sdk export ANDROID_SDK_ROOT=$HOME/Android/Sdk export PATH=$PATH:$ANDROID_HOME/tools export PATH=$PATH:$ANDROID_HOME/platform-tools
Guarda los cambios y ejecuta
source ~/.bashrc
para aplicar los cambios.Gradle:
sudo apt install gradle
SDK Platform 28:
Abre Android Studio ejecutando
android-studio
, ve a configuración (esquina inferior derecha) y abre el SDK Manager:
Instala el SDK Platform 28, que corresponde a Android 9.0 (Pie):
Instalar NodeJS y npm:
NodeJS:
sudo apt install python-software-properties sudo apt install curl curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt install nodejs
npm:
sudo apt install npm
Instalar Cordova:
sudo npm install -g cordova sudo chown -R $USER:$(id -gn $USER) $HOME/.config
Instalar Brython:
python3 -m pip install brython
CREAR UNA APLICACIÓN
Vamos a crear una aplicación sencilla. Lo primero será crear el proyecto Cordova, en el que tenemos que instalar Brython. Borraremos los archivos innecesarios que se crean por defecto y copiaremos una plantilla. Finalmente crearemos nuestro APK que instalaremos en nuestro dispositivo:
cordova create miapp
Ejecuta este comando en el directorio que quieras. Se creará un subdirectorio llamado, en este caso, "miapp" que contiene el proyecto.
Entra al directorio del proyecto "miapp" mediante
cd miapp
para poder ejecutar el siguiente comando:cordova platform add android
Hemos añadido la plataforma Android a nuestro proyecto. Recordemos que aunque estamos interesados en crear aplicaciones para Android con Brython, el propósito del framework Apache Cordova es crear aplicaciones multiplataforma.
Dentro de nuestro proyecto, el directorio www es en el que se encuentran los archivos HTML, CSS y JavaScript (o Python) con los que crearemos nuestra aplicación. Entra mediante cd www
y elimina los archivos .html, .css y .js que se crearon por defecto: rm index.html css/index.css js/index.js
.
Entra al directorio vacío js mediante cd js
e instala Brython: python3 -m brython --install
Elimina los archivos innecesarios que se crearon durante la instalación de Brython: rm *.html *.txt
Vuelve al directorio www con cd ..
y crea un archivo index.html, copia la siguiente plantilla:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
<title>Cordova-Brython-App</title>
<script src="js/brython.js"></script>
<script src="js/brython_stdlib.js"></script>
<script type="text/javascript">
function startBrython(){
document.addEventListener('deviceready', brython, false);
}
</script>
</head>
<body onload='startBrython();'>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/python3">
from browser import document, alert
from browser.html import *
document <= H1('Brython Ready! :)')
alert('Brython Ready!')
</script>
</body>
</html>
Lo mas importante a tener en cuenta en nuestro archivo index.html es lo siguiente:
Etiqueta meta http-equiv="Content-Security-Policy"
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'">
Define la política de seguridad de contenido en su atributo content. Para mas información echa un vistazo a cordova-plugin-whitelist.
- 'self' - La propia aplicación. Sólo permitimos cargar recursos locales (css, imágenes, scripts, etc). Si quieres cargar recursos de un servidor web debes declararlo.
- 'unsafe-inline' - Habilita la ejecución de JavaScript en línea.
- 'unsafe-eval' - Habilita la función JS eval(), esencial para ejecutar Brython.
Enlazamos Brython
<script src="js/brython.js"></script> <script src="js/brython_stdlib.js"></script>
Enlazamos el interprete (brython.js) y la librería estándar de Brython (brython_stdlib.js) de la manera habitual.
Ejecutamos brython()
No podemos ejecutar la función brython en respuesta al evento onload de la etiqueta body, como habría sido habitual creando una página web con Brython.
En Cordova, todo script debe ejecutarse después de que se dispare el evento deviceready, de modo que dentro de la etiqueta head creamos un script en el que definimos la función startBrython:
function startBrython(){ document.addEventListener('deviceready', brython, false); }
Al dispararse el evento deviceready se ejecutará la función brython. Ahora solo falta que la función startBrython se ejecute una vez se haya cargado body:
<body onload='startBrython();'>
Enlazamos cordova.js
<script type="text/javascript" src="cordova.js"></script>
Aunque cordova.js no exista en el direcorio www no te preocupes, ya que al compilar la aplicación se incluirá automáticamente en el APK.
Nuestro Script Brython
from browser import document, alert from browser.html import * document <= H1('Brython Ready! :)') alert('Brython Ready!')
Esta es nuestra aplicación propiamente dicha, ¡lo que nos interesa!.
Si quieres desarrollar tu aplicación mediante un script .py independiente (en vez de escribir código Python en index.html) no es posible enlazarlo en el documento de la manera habitual.
Tampoco es posible cargar un archivo .py mediante la sentencia import, y no es posible hacerlo mediante la función open para luego utilizar run_script.
Sin embargo, puedes cargar un script .py externo mediante una petición XMLHttpRequest para ejecutarlo con run_script:
from browser import window, run_script req = window.XMLHttpRequest.new() req.onload = lambda *args:run_script(req.repsponseText) req.open('GET', 'app.py', False) # False == carga síncrona, True == asíncrona (False si vamos a cargar varios scripts que dependen unos de otros) req.send()
Puedes simplificar este script utilizando el módulo ajax de brython_stdlib.js:
from browser import ajax, run_script ajax.get('app.py', blocking=True, oncomplete=lambda req:run_script(req.text)) # blocking si vamos a cargar varios scripts que dependen unos de otros
Ahora simplemente debes crear un archivo app.py en el directorio www para que sea cargado y ejecutado por index.html.
UNA FORMA DE IMPORTAR MÓDULOS
Con el método anterior hemos conseguido ejecutar código fuente mediante la función run_script
(también podríamos haber utilizado exec
), pero no hemos importado nuestro archivo app.py como un módulo, sino que lo hemos ejecutado como un script independiente.
Personalmente, aunque investigué mucho sobre como importar módulos casi nunca es necesario y basta con utilizar el sencillo y efectivo método anterior. En cualquier caso, importar módulos es posible y vamos a verlo a continuación.
La sentencia import
no funciona para brython en cordova, ya que solicita el script con un query-string para asegurar que se obtiene la última versión del módulo, es decir, para evitar obtener el módulo del cache del navegador. Cordova no es un servidor web y para recuperar recursos locales debe hacerse una petición ajax en la que se indica el path del recurso sin ningún argumento adicional. Por el mismo motivo tampoco funcionan los scripts type="text/python" en los que se especifica el atributo src ni la función open
.
Aunque la sentencia import
no funciona podemos utilizar el módulo types para crear e importar módulos.
A continuación defino la clase py_import que puedes utilizar para importar cualquier script en el directorio www o en sus subdirectorios:
import os, types
from browser import ajax
class py_import:
def __init__(self, script, location=''):
name = os.path.splitext(script)[0]
self.module = types.ModuleType(name)
ajax.get(os.path.join(location, script), blocking=True, oncomplete=self.__load)
exec(self.source, self.module.__dict__)
def __load(self, req):
self.source = req.text
def __call__(self):
return self.module
# Importar www/app.py
app = py_import('app.py')()
# Importar www/mismodulos/modulo1.py
modulo1 = py_import('modulo1.py', location='mismodulos')()
No es necesario definir la clase py_import en todos los scripts en los que quieras importar otros módulos. Puedes definir la clase en el archivo index.html y guardarla como atributo del objeto window:
# En el archivo index.html
# dendro de un <script type='text/python3'></script>
import os, types
from browser import window, ajax
class py_import:
def __init__(self, script, location=''):
name = os.path.splitext(script)[0]
self.module = types.ModuleType(name)
ajax.get(os.path.join(location, script), blocking=True, oncomplete=self.__load)
exec(self.source, self.module.__dict__)
def __load(self, req):
self.source = req.text
def __call__(self):
return self.module
# Exponemos la clase py_import
window.py_import = py_import
# Importamos www/app.py
app = py_import('app.py')()
Ahora puedes utilizar la clase py_import en todos tus scripts para importar módulos desde módulos:
# www/app.py
from browser import window
py_import = window.py_import
# Importamos www/mismodulos/modulo1.py desde www/app.py
modulo1 = py_import('modulo1.py', location='mismodulos')()
COMPILAR LA APLICACIÓN (APK)
Ejecuta los siguientes comandos:
cordova prepare android
cordova compile android
Puedes simplificar la compilación en un único comando:
cordova build android
Tu APK se encuentra en el directorio miapp/platforms/android/app/build/outputs/apk/debug y se llama app-debug.apk.
Puedes instalarlo en tu dispositivo Android y comprobarás que funciona perfectamente
Aunque app-debug.apk funciona perfectamente, se trata de una aplicación compilada en modo depuración (debug).
Una aplicación compilada en modo debug tiene el indicador de depuración habilitado, es decir, puede depurarse. Además, se ha compilado con la clave de firma de depuración predeterminada, de modo que no puede ser publicada en Google Play Store.
### Diferencias entre compilación de depuración (*debug*) y de lanzamiento (*release*) --------------------------------------------------------------------------------------
Las principales diferencias entre una compilación de depuración y una de lanzamiento son la indicador de depuración y las claves de firma:
Para las compilaciones de depuración (debug), el archivo APK se firmará con el indicador de depuración habilitado y con las claves de firma de depuración predeterminadas.
Para las compilaciones de lanzamiento (release) el indicador de depuración es desactivado para que no se pueda depurar la aplicación lanzada, y hay que especificar explícitamente las claves con las que se va a firmar.
La reducción de código (ProGuard) se puede activar para versiones de lanzamiento (y también para versiones de depuración, pero no tiene sentido y se desaconseja). La activación ProGuard debe hacerse explícitamente y está deshabilitada por defecto.
No obstante, estas características por defecto pueden ser modificadas en un archivo build.json.
### Compilación de Lanzamiento: *app-release.apk* -------------------------------------------------
Para realizar una compilación de lanzamiento (release) necesitas:
Un almacén de claves: Para nosotros será un archivo con extensión .keystore que puede estar en cualquier directorio del sistema de archivos.
Un archivo de configuración llamado build.json en el directorio raíz del proyecto cordova.
#### 1. Crear el almacén de claves (keystore):
Para crear el keystore de tu aplicación tienes que utilizar la utilidad keytool: Abre un terminal en el directorio ráiz de tu proyecto cordova miapp y presta atención al siguiente comando:
echo y | keytool -genkeypair -dname "cn=José María, ou=tecnobillo1, o=tecnobillo, c=ES" -alias miapp -keypass claveLlave -keystore miapp.keystore -keyalg RSA -keysize 2048 -storepass claveAlmacen -validity 20000
-dname
- Identificador único para la aplicación en el .keystore:cn
- Nombre completo de la persona u organización que genera el .keystore. Yo he puesto mi nombre: José María.ou
- Unidad organizativa que crea el proyecto, es una subdivisión de la organización. Yo he puesto tecnobillo1.o
- Organización propietaria de todo el proyecto. Yo he puesto tecnobillo.c
- Código corto del país. En mi caso es ES porque vivo en España.
-alias
- He indicado el mismo nombre que el directorio de la aplicación.-keypass
- Es la clave secreta que solamente deberías conocer tú. Yo he indicado claveLlave en el ejemplo.-keystore
- Mi almacén de claves es un archivo miapp.keystore.-storepass
- Clave del .keystore. Yo he puesto claveAlmacen.-validity
- Número de días de validez del keystore. Yo he indicado 20000, que serían casi 55 años. Indica un número lo suficientemente grande para que cubra todo el ciclo de vida previsto para la aplicación.
### 2. Crear el archivo de configuración (build.json):
El archivo de configuración debe contener información correcta en relación al keystore creado previamente. Un ejemplo acorde con miapp.keystore es el siguiente:
{
"android": {
"release": {
"keystore": "miapp.keystore",
"storePassword": "claveLlave",
"alias": "miapp",
"password": "claveAlmacen",
"keystoreType": ""
}
}
}
Ten en cuenta que he indicado miapp.keystore como path del almacén de claves porque he creado este archivo en el directorio raíz del proyecto. Si lo has creado en otro directorio debes indicar el path absoluto del keystore.
El archivo build.json también puede definir la configuración relativa a la compilación debug. Por ejemplo build.json podría lucir así:
{
"android": {
"release": {
"keystore": "miapp.keystore",
"storePassword": "claveLlave",
"alias": "miapp",
"password": "claveAlmacen",
"keystoreType": ""
},
"debug": {
"keystore": "miapp.keystore",
"storePassword": "claveLlave",
"alias": "miapp",
"password": "claveAlmacen",
"keystoreType": ""
}
}
}
En este caso la configuración es la misma para release y debug, cosa que no tiene por que ser así e incluso no debería ser así, pero es válido.
### 3. Compilar app-release.apk
Ya puedes compilar la versión de lanzamiento de tu aplicación, firmada con el keystore que has creado anteriormente. En el directorio del proyecto cordova miapp abre un terminal y ejecuta:
cordova prepare android
cordova compile android -release
El archivo app-release.apk se ha generado en el directorio miapp/platforms/android/app/build/outputs/apk/release. Puedes simplificar la compilación en un único comando:
cordova build android -release
Si ya instalaste app-debug.apk en tu dispositivo tienes que desinstalarla antes de instalar app-release.apk, ya que como son la misma aplicación con diferente keystore, el sistema android no te permitirá realizar la instalación de ambas aplicaciones.
¡¡ MISIÓN CUMPLIDA !!
DEPURACIÓN EN BRYTHON
En Brython los errores en tiempo de ejecución se imprimen por defecto en la consola del navegador, pero cuando creas el archivo APK con Cordova y lo instalas en un dispositivo, al ejecutar la aplicación no es posible abrir la consola del navegador (webview) y ver lo que está sucediendo, de modo que si tu código tiene errores resulta muy difícil localizarlos y corregirlos.
SALIDA DE ERROR ESTÁNDAR
No obstante, puedes modificar la salida de error estándar en tu script mientras desarrollas y pruebas la aplicación en tu dispositivo (app-debug.apk). Por ejemplo, puedes añadir el siguiente código al inicio de tu script:
import sys
from browser import document, alert
class Err:
def write(self, err):
document <= P(err)
alert(err)
sys.stderr = Err()
Así, si se produce un error podrás visualizarlo en el documento y además se mostrará una alerta. Al finalizar tu aplicación (app-release.apk), cuando estés seguro/a de que no hay errores, puedes eliminar este trozo de código.
El archivo config.xml
El archivo config.xml es el archivo de configuración de nuestra aplicación. Se encuentra en el directorio raíz del proyecto (fuera del directorio www). Por defecto se ve así:
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>HelloCordova</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.
</description>
<author email="[email protected]" href="http://cordova.io">
Apache Cordova Team
</author>
<content src="index.html" />
<plugin name="cordova-plugin-whitelist" spec="1" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
</platform>
</widget>
Dominio inverso de la aplicación:
Se especifíca como valor del atributo id de la etiqueta widget. Para nuestro ejemplo cambiaré io.cordova.hellocordova por site.tecnobillo.miapp. El dominio inverso de la aplicación es una convención para designar el PACKAGE_NAME (nombre de paquete).
Nombre de la aplicación:
Se especifíca como contenido de la etiqueta name. Para nuestro ejemplo cambiaré HelloCordova por miapp.
Descripción:
Se especifíca como contenido de la etiqueta description.
Autor:
Se especifíca como contenido de la etiqueta author. Puedes indicar tu email y sitio web como valores de los atributos email y href respectivamente.
Contenido:
Indica cual es el documento HTML principal de la aplicación, el que se cargará al abrir la aplicación. Se especifíca como valor del atributo src de la etiqueta content.
Icono:
Puede especificarse en la etiqueta icon, indicando el path del icono como valor del atributo src. Por ejemplo:
<icon src="icon.png" />
Como puedes observar, se añade por defecto el *plugin whitelist*. Este *plugin* permite declarar permisos para el acceso a diferentes *URLs* mediante las etiquetas __access__ y __allow-intent__. Por otra parte, observa las etiquetas __platform__: lo que se declare dentro de una etiqueta *platform* solo se aplicará a la plataforma en cuestión.
En nuestra aplicación de ejemplo solamente necesitamos acceso a recursos locales de la aplicación, de modo que podemos prescindir de whitelist. Además, como estamos desarrollando la app solamente para Android no necesitamos las etiquetas platform para definir distinciones entre plataformas.
Nuestro config.xml puede quedar así (modifícalo a tu gusto):
<?xml version='1.0' encoding='utf-8'?>
<widget id="site.tecnobillo.miapp" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>miapp</name>
<description>
Aplicación de ejemplo desarrollada con Apache Cordova y Brython.
</description>
<author email="[email protected]" href="https://tecnobillo.site">
José María Sánchez Ruiz (Tecnobillo)
</author>
<content src="index.html" />
</widget>
Podrías haber incluido cierta información al crear el proyecto desde el terminal en vez de editarlo ahora:
En vez de crear el proyecto mediante:
cordova create miapp
Podrías haber ejecutado:
cordova create miapp site.tecnobillo.miapp miapp
En este caso se habría creado un proyecto en el directorio miapp, y en el archivo config.xml se habria añadido automáticamente el dominio inverso de la aplicación (site.tecnobillo.miapp) y el nombre de la aplicación (miapp).
### Algunas aclaraciones
En realidad no hemos eliminado el plugin whitelist ni las plataformas android e ios, solamente hemos borrado su configuración en config.xml.
A partir de Cordova 9.0.0 se recomienda elimiar las plataformas y plugins del archivo config.xml, a partir de esta versión no se sincronizan los archivos config.xml y package.json.
La configuración de plugins y plataformas debe hacerse en el archivo package.json.
Para eliminar completamente el plugin whitelist ejecutarías:
cordova plugin remove cordova-plugin-whitelist
Para reinstalar el plugin whitelist:
cordova plugin add cordova-plugin-whitelist
Es conveniente reinstalar los plugins si modificas el dominio inverso de la aplicación en config.xml, ya que cuando ejecutas cordova prepare android
se crea un archivo platforms/android/android.json en el que se especifican los plugins instalados. Estos plugins tienen una propiedad PACKAGE_NAME que toma el valor del dominio inverso de la aplicación (el dominio inverso de la aplicación es un convenio para designar el nombre del paquete).
Si creaste el proyecto mediante
cordova create miapp
, añadiste la plataforma android y posteriormente modificaste el archivo config.xml, en el archivo platforms/android/android.json figura lo siguiente:{ "installed_plugins" : { "cordova-plugin-whitelist": { "PACKAGE_NAME" : "io.cordova.hellocordova" } } }
Pero si lo reinstalas después de modificar config.xml mediante
cordova plugin remove cordova-plugin-whitelist
ycordova plugin add cordova-plugin-whitelist
, en el archivo platforms/android/android.json aparecerá la declaración del plugin whitelist instalado correctamente:{ "installed_plugins" : { "cordova-plugin-whitelist": { "PACKAGE_NAME" : "site.tecnobillo.miapp" } } }
Si abres el archivo package.json verás que no se muestra actualizada la información de la aplicación (lo que cambiaste en config.xml). No te preocupes, al compilar el archivo apk tendrá prioridad la configuración en config.xml sobre package.json.
Cuando ejecutas cordova prepare android
se instalan los plugins declarados en package.json que no estén ya instalados. Sin embargo, no es necesario modificar el archivo package.json para instalar plugins, ya que cuando instalas un plugin desde el terminal se agrega automáticamente a package.json.
Compilar Brython - Interesante pero poco práctico
Brython no está pensado para ser distribuido compilado, sino para transpilar el código python al vuelo. No obstante, es posible compilar el código Brython a JavaScript antes de crear el archivo APK.
Cuando se desarrolla una página web con Brython no tiene mucho sentido hacer esto, ya que el script .js generado es mucho mas pesado que el original .py, así que el coste de recuperar el archivo a través de una petición HTTP por parte del navegador no compensa el tiempo que se ahorra en interpretar el código brython original.
Pero en una aplicación creada con Apache Cordova y Brython esta práctica resulta interesante, ya que los scripts son cargados localmente.
Si quieres "trasterar un poco" puedes echar un vistazo a Brython Compiler para Windows, una aplicación que creé con este propósito. Si tu aplicación está formada por varios módulos realmente tendrás problemas, pero de vez en cuando está bien "cacharrear un poco".
Una opción de optimización mas realista es minificar el código. Si desarrollas una aplicación y piensas distribuirla probablemente prefieras que el código esté "obfuscado". También suele ser deseable eliminar los comentarios del código (que en mi caso pueden ser incluso vergonzosos).
Para ello te recomiendo probar python_minifier, que puedes instalar en CPython mediante:
python -m pip install python-minifier
Casi siempre funciona perfectamente pero en ocasiones (en mi caso, al utilizar funciones lambda o decoradores) el código minificado da error. Te invito a que lo pruebes, una vez instalado puedes utilizarlo como en el siguiente ejemplo:
import python_minifier
src = """
def saludar(alguien):
'''Función que saluda a alguien'''
print(f'Hola {alguien}')
# Un comentario random
saludar('tecnobillo')
"""
minified_src = python_minifier.minify(src)
with open('minified_script.py', 'w') as f:
f.write(minified_src)
# RESULTADO:
# def saludar(alguien):'Función que saluda a alguien';print(f"Hola {alguien}")
# saludar('tecnobillo')
Como puedes observar, el código de salida es mas compacto, y los comentarios de una sola línea son eliminados. Los comentarios de comillas triples no se eliminan sino que se convierten en strings de una sola línea (esto es porque python_minifier no sabe si son comentarios o strings multilínea).