Eel est une petite bibliothèque Python permettant de créer des applications GUI HTML/JS hors ligne simples de type Electron, avec un accès complet aux fonctionnalités et bibliothèques Python.
Eel héberge un serveur Web local, puis vous permet d'annoter des fonctions en Python afin qu'elles puissent être appelées depuis Javascript, et vice versa.
Eel est conçu pour simplifier l'écriture d'applications GUI courtes et simples. Si vous êtes familier avec Python et le développement Web, passez probablement à cet exemple qui sélectionne des noms de fichiers aléatoires dans le dossier donné (ce qui est impossible depuis un navigateur).
Il existe plusieurs options pour créer des applications GUI en Python, mais si vous souhaitez utiliser HTML/JS (afin d'utiliser jQueryUI ou Bootstrap, par exemple), vous devez généralement écrire beaucoup de code passe-partout pour communiquer depuis le client (Javascript ) côté serveur (Python).
L'équivalent Python le plus proche d'Electron (à ma connaissance) est cefpython. C'est un peu lourd pour ce que je voulais.
Eel n'est pas aussi complet qu'Electron ou cefpython - il n'est probablement pas adapté à la création d'applications complètes comme Atom - mais il est très approprié pour créer l'équivalent GUI de petits scripts utilitaires que vous utilisez en interne dans votre équipe.
Pour une raison quelconque, la plupart des meilleures bibliothèques de calcul de nombres et de mathématiques sont en Python (Tensorflow, Numpy, Scipy, etc.), mais la plupart des meilleures bibliothèques de visualisation sont en Javascript (D3, THREE.js, etc.). Espérons qu'Eel facilite leur combinaison en applications utilitaires simples pour vous aider dans votre développement.
Rejoignez les utilisateurs et les responsables d'Eel sur Discord, si vous le souhaitez.
Installer depuis pypi avec pip
:
pip install eel
Pour inclure la prise en charge des modèles HTML, utilisant actuellement Jinja2 :
pip install eel[jinja2]
Une application Eel sera divisée en un frontend composé de divers fichiers de technologie Web (.html, .js, .css) et un backend composé de divers scripts Python.
Tous les fichiers frontend doivent être placés dans un seul répertoire (ils peuvent être divisés en dossiers à l'intérieur si nécessaire).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Supposons que vous placiez tous les fichiers frontaux dans un répertoire appelé web
, y compris votre page de démarrage main.html
, l'application démarre alors comme ceci :
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Cela démarrera un serveur Web avec les paramètres par défaut (http://localhost:8000) et ouvrira un navigateur sur http://localhost:8000/main.html.
Si Chrome ou Chromium est installé, il s'ouvrira par défaut en mode App (avec l'indicateur --app
cmdline), quel que soit le navigateur par défaut du système d'exploitation (il est possible de remplacer ce comportement).
Des options supplémentaires peuvent être transmises à eel.start()
comme arguments de mot-clé.
Certaines options incluent le mode dans lequel se trouve l'application (par exemple « chrome »), le port sur lequel l'application s'exécute, le nom d'hôte de l'application et l'ajout d'indicateurs de ligne de commande supplémentaires.
Depuis Eel v0.12.0, les options suivantes sont disponibles pour start()
:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). Peut également être None
ou False
pour ne pas ouvrir de fenêtre. Par défaut : 'chrome'
'localhost'
)0
pour que le port soit sélectionné automatiquement. Par défaut : 8000
.start()
doit bloquer ou non le thread appelant. Par défaut : True
my_templates
. Par défaut : None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. Défaut: []
None
None
{'size': (200, 100), 'position': (300, 50)}
. Défaut: {}None
app
n'est pas une instance Bottle, vous devrez appeler eel.register_eel_routes(app)
sur votre instance d'application personnalisée.shutdown_delay
secondes, puis vérifie s'il existe désormais des connexions websocket. Sinon, Eel ferme. Dans le cas où l'utilisateur a fermé le navigateur et souhaite quitter le programme. Par défaut, la valeur de shutdown_delay est 1.0
seconde En plus des fichiers du dossier frontend, une bibliothèque Javascript sera servie dans /eel.js
. Vous devez inclure ceci dans toutes les pages :
< script type =" text/javascript " src =" /eel.js " > </ script >
L'inclusion de cette bibliothèque crée un objet eel
qui peut être utilisé pour communiquer avec le côté Python.
Toutes les fonctions du code Python qui sont décorées avec @eel.expose
comme ceci...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
...apparaîtra sous forme de méthodes sur l'objet eel
côté Javascript, comme ceci...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
De même, toutes les fonctions Javascript exposées comme ceci...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
peut être appelé du côté Python comme ceci...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
Le nom exposé peut également être remplacé en passant un deuxième argument. Si votre application réduit JavaScript lors des builds, cela peut être nécessaire pour garantir que les fonctions peuvent être résolues côté Python :
eel . expose ( someFunction , "my_javascript_function" ) ;
Lorsque vous transmettez des objets complexes en tant qu'arguments, gardez à l'esprit qu'en interne, ils sont convertis en JSON et envoyés vers un websocket (un processus qui perd potentiellement des informations).
Voir l'exemple complet dans : examples/01 - hello_world
Rassembler cela dans un Hello, World! Par exemple, nous avons une courte page HTML, web/hello.html
:
<!DOCTYPE html >
< html >
< head >
< title > Hello, World! </ title >
<!-- Include eel.js - note this file doesn't exist in the 'web' directory -->
< script type =" text/javascript " src =" /eel.js " > </ script >
< script type =" text/javascript " >
eel . expose ( say_hello_js ) ; // Expose this function to Python
function say_hello_js ( x ) {
console . log ( "Hello from " + x ) ;
}
say_hello_js ( "Javascript World!" ) ;
eel . say_hello_py ( "Javascript World!" ) ; // Call a Python function
</ script >
</ head >
< body >
Hello, World!
</ body >
</ html >
et un court script Python hello.py
:
import eel
# Set web files folder and optionally specify which file types to check for eel.expose()
# *Default allowed_extensions are: ['.js', '.html', '.txt', '.htm', '.xhtml']
eel . init ( 'web' , allowed_extensions = [ '.js' , '.html' ])
@ eel . expose # Expose this function to Javascript
def say_hello_py ( x ):
print ( 'Hello from %s' % x )
say_hello_py ( 'Python World!' )
eel . say_hello_js ( 'Python World!' ) # Call a Javascript function
eel . start ( 'hello.html' ) # Start (this blocks and enters loop)
Si nous exécutons le script Python ( python hello.py
), alors une fenêtre de navigateur s'ouvrira affichant hello.html
, et nous verrons...
Hello from Python World!
Hello from Javascript World!
...dans le terminal, et...
Hello from Javascript World!
Hello from Python World!
...dans la console du navigateur (appuyez sur F12 pour ouvrir).
Vous remarquerez que dans le code Python, la fonction Javascript est appelée avant même le démarrage de la fenêtre du navigateur - tous les premiers appels comme celui-ci sont mis en file d'attente puis envoyés une fois le websocket établi.
Alors que nous souhaitons considérer notre code comme comprenant une seule application, l’interpréteur Python et la fenêtre du navigateur s’exécutent dans des processus distincts. Cela peut rendre la communication entre eux un peu compliquée, surtout si nous devions toujours envoyer explicitement des valeurs d'un côté à l'autre.
Eel prend en charge deux manières de récupérer les valeurs de retour de l'autre côté de l'application, ce qui permet de garder le code concis.
Pour éviter de rester indéfiniment du côté Python, un délai d'attente a été mis en place pour tenter de récupérer les valeurs du côté JavaScript, qui est par défaut de 10 000 millisecondes (10 secondes). Cela peut être modifié avec le paramètre _js_result_timeout
en eel.init
. Il n'y a pas de délai d'attente correspondant du côté JavaScript.
Lorsque vous appelez une fonction exposée, vous pouvez immédiatement transmettre une fonction de rappel par la suite. Ce rappel sera automatiquement appelé de manière asynchrone avec la valeur de retour lorsque la fonction aura fini de s'exécuter de l'autre côté.
Par exemple, si la fonction suivante est définie et exposée en Javascript :
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Ensuite, en Python, nous pouvons récupérer des valeurs aléatoires du côté Javascript comme ceci :
def print_num ( n ):
print ( 'Got this from Javascript:' , n )
# Call Javascript function, and pass explicit callback function
eel . js_random ()( print_num )
# Do the same with an inline lambda as callback
eel . js_random ()( lambda n : print ( 'Got this from Javascript:' , n ))
(Cela fonctionne exactement de la même manière dans l’autre sens).
Dans la plupart des situations, les appels vers l'autre côté visent à récupérer rapidement certaines données, telles que l'état d'un widget ou le contenu d'un champ de saisie. Dans ces cas, il est plus pratique d'attendre de manière synchrone quelques millisecondes puis de continuer votre code, plutôt que de diviser le tout en rappels.
Pour récupérer de manière synchrone la valeur de retour, ne transmettez simplement rien au deuxième ensemble de parenthèses. Donc en Python on écrirait :
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Vous ne pouvez effectuer des retours synchrones qu'après le démarrage de la fenêtre du navigateur (après avoir appelé eel.start()
), sinon l'appel sera évidemment suspendu.
En Javascript, le langage ne nous permet pas de bloquer en attendant un rappel, sauf en utilisant await
depuis une fonction async
. Le code équivalent du côté Javascript serait donc :
async function run ( ) {
// Inside a function marked 'async' we can use the 'await' keyword.
let n = await eel . py_random ( ) ( ) ; // Must prefix call with 'await', otherwise it's the same syntax
console . log ( "Got this from Python: " + n ) ;
}
run ( ) ;
Eel est construit sur Bottle et Gevent, qui fournissent une boucle d'événements asynchrone similaire à Javascript. Une grande partie de la bibliothèque standard de Python suppose implicitement qu'il existe un seul thread d'exécution - pour gérer cela, Gevent peut « patcher de manière singe » de nombreux modules standard tels que time
. Ce correctif de singe est effectué automatiquement lorsque vous appelez . Si vous avez besoin d'un correctif singe, vous devez import eel
import gevent.monkey
et appeler gevent.monkey.patch_all()
avant d' import eel
. Les correctifs Monkey peuvent interférer avec des choses comme les débogueurs et doivent donc être évités sauf si nécessaire.
Dans la plupart des cas, cela devrait aller en évitant d'utiliser time.sleep()
et en utilisant plutôt les versions fournies par gevent
. Pour plus de commodité, les deux méthodes gevent les plus couramment nécessaires, sleep()
et spawn()
sont fournies directement depuis Eel (pour gagner time
d'importation et/ou également gevent
).
Dans cet exemple...
import eel
eel . init ( 'web' )
def my_other_thread ():
while True :
print ( "I'm a thread" )
eel . sleep ( 1.0 ) # Use eel.sleep(), not time.sleep()
eel . spawn ( my_other_thread )
eel . start ( 'main.html' , block = False ) # Don't block on this call
while True :
print ( "I'm a main loop" )
eel . sleep ( 1.0 ) # Use eel.sleep(), not time.sleep()
... nous aurions alors trois "threads" (greenlets) en cours d'exécution ;
my_other_thread
, affichant à plusieurs reprises "Je suis un fil de discussion"while
finale, affichant à plusieurs reprises "Je suis une boucle principale" Si vous souhaitez regrouper votre application dans un programme pouvant être exécuté sur un ordinateur sans interpréteur Python installé, vous devez utiliser PyInstaller .
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
(par exemple, vous pouvez exécuter python -m eel hello.py web
)dist/
--exclude module_name
. Par exemple, vous pouvez exécuter python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
--onefile --noconsole
pour créer un seul fichier exécutable.Consultez la documentation de PyInstaller pour plus d'options.
Pour les utilisateurs de Windows 10, Microsoft Edge ( eel.start(.., mode='edge')
) est installé par défaut et constitue une solution de secours utile si un navigateur préféré n'est pas installé. Voir les exemples :