Aller au contenu

Les bibliothèques python dans Pyodide et le thème#

Packages natifs#

L'environnement de Pyodide propose la quasi intégralité des packages qui viennent avec une installation locale de python. Il y a cependant quelques exceptions notables.

Voici ceux qui ne sont pas disponibles ou ne fonctionnent pas dans l'environnement de pyodide :

  • turtle
  • turtledemo
  • tkinter
  • multiprocessing
  • threading
  • sockets
  • requests
  • urllib

Pour plus d'informations à ce sujet, se référer à la documentation de Pyodide.

Packages de PyPI#

Tous les packages de PyPI qui mettent à disposition un format .whl ("wheel") peuvent être automatiquement téléchargés et installés par le thème, via les fonctionnalités proposées par Pyodide.

installation messages

Notez cependant que le processus est relativement long, surtout pour des packages comme numpy ou matplotlib...

Pour cette raison, un message est affiché dans les terminaux, annonçant à l'utilisateur le début et la fin des installations de packages.

Voir aussi le commentaire concernant la sécurité.

Bibliothèques personnalisées #

Packages & __init__.py

Pour des informations sur les packages python, se référer à la documentation en ligne sur python.org.

Si vous avez besoin de mutualiser du code pour différents exercices, le thème propose la logistique pour automatiser cela.



Des archives .zip Ă  la racine durant un serve...

Pour chaque bibliothèque déclarée, une archive .zip temporaire sera créée à la racine du projet, lors du build. Elles seront supprimées automatiquement en fin de build.

Si une erreur a été levée et que la ou les archives sont toujours présentes, elles seront écrasées puis supprimées au prochain build. Il n'est pas nécessaire de gérer manuellement ces fichiers.

Guide rapide#

  1. Créer un dossier py_libs à la racine du projet (ou tout autre nom souhaité).

  2. Y ajouter un fichier __init__.py.
    Ceci fait que le dossier py_libs devient un package python.

  3. Ajouter au dossier autant de fichiers python et/ou sous-dossiers que voulu.
    Chaque sous-dossiers doit contenir son propre fichier __init__.py.

  4. Dans le fichier mkdocs.yml, référencer le dossier de la bibliothèque danspyodide_macros.build.python_libs.

  5. Dans les fichiers python des exercices, importer le code comme n'importe quel autre module/bibliothèque python : from py_libs import ....
    Le thème identifie s'il s'agit d'une bibliothèque personnalisée, et utilise alors la méthode d'installation appropriée.

Exemple :

projet
├── docs
│   ├── POO
│   │   ├── exercice1
│   │   │   ├── index.md
│   │   │   ├── exo.py
│   │   │   └── exo_REM.md
│   ... ...
├── py_libs
│   ├── __init__.py
│   ├── structures_lineaires.py
│   └── arbres.py
...
plugins:
    ...
    - pyodide_macros:
        build:
            python_libs:
                - py_libs
# Depuis l'intérieur même d'un package, les imports peuvent relatifs...
from .structures_lineaires import Pile

# ...ou en spécifiant le chemin complet (import absolu):
from py_libs.structures_lineaires import File

class BSTree:
    ...
# --- PYODIDE:env --- #

# Depuis l'extérieur d'un package les imports doivent être faits
# avec les chemins complets (absolus) :
from py_libs.structures_lineaires import Pile, File
from py_libs.arbres import BSTree as ABR

...

Avantages & inconvénients#

  • Placer les packages Ă  la racine du projet permettent d'exĂ©cuter les fichiers pythons de la documentation en local, si nĂ©cessaire, sans modifier le code par rapport Ă  la version en ligne ou celle disponible en utilisant mkdocs serve.

  • Les imports peuvent ĂŞtre rĂ©alisĂ©s depuis n'importe quelle section d'un exercice, donc aussi depuis une des sections de tests ou par l'utilisateur lui-mĂŞme. Ceci peut en partie ĂŞtre mitigĂ© en modifiant les python_libs grâce aux fichiers .meta.pmt.yml ou aux mĂ©tadonnĂ©es dans les entĂŞtes de pages (voir ici).

Il y a un revers conséquent à cette médaille.

  • Les imports marchent depuis n'importe oĂą sur le site.
    Sauf si des restrictions ont été mises en place, un utilisateur ayant connaissance de l'existence d'un package fournissant une classe Pile peut très bien l'importer depuis un autre exercice où il est sensé implanter lui-même la classe, si aucune restriction d'imports n'est mise en place.
    Concernant la mise en place de restrictions de bibliothèques, préférer la configuration des python_libs via les métadonnées, plutôt que l'argument SANS des macros.

  • Le contenu de ces fichiers N'EST PAS cachĂ©.
    Une fois une bibliothèque installée, les fichiers sont disponibles à la racine du système de fichier de Pyodide. Un utilisateur ayant la présence d'esprit de fouiller le disque virtuel avec os.listdir() pourra découvrir l'intégralité du contenu de l'environnement, afficher leur contenu, ...

Responsabiliser les utilisateurs vis-Ă -vis des restrictions...

Plutôt que de tenter (en vain) de tout verrouiller, il vaut sans doute mieux responsabiliser les utilisateurs, afin que le runtime ne devienne pas complètement imbuvable pour avec des restrictions dans tous les sens.
Encore une fois, le thème est conçu dans l'optique de créer des sites d'entraînement, pas des sites d'évaluation.

Configuration#

Il est possible de définir plusieurs dossiers de bibliothèques personnalisées, via l'option python_libs du plugin :

plugins:
    - pyodide_macros:
        build:
            python_libs:
                - lib1
                - libxyz
                - ...

Chaque dossier constitue une bibliothèque différente, qui devrait être indépendante des autres.

Contraintes

  1. Seuls des dossiers sont acceptés, pour les items de l'option python_libs.
  2. Tous les dossiers doivent être placés à la racine du projet.
  3. Importer un unique élément d'une bibliothèque installe tout le contenu de cette bibliothèque.

Conseil

  1. Utiliser différents dossiers permet de ne pas charger tous les fichiers pour un seul exercice.
  2. Préférer une organisation plutôt "horizontale" des packages, avec peu de profondeur, sans quoi il est très vite pénible d'écrire les importations...

Problèmes de dépendances#

Les bibliothèques utilisées depuis le code de python_libs ne sont pas installées automatiquement durant les exécutions. Cela signifie qu'il peut être nécessaire d'importer les dépendances des python_libs dans le code important lui-même la bibliothèque.

Par exemple, pour utiliser les modules p5 du thème et numpy depuis une bibliothèque personnalisée, elle-meme importée depuis une section env, il faut faire :

# --- PYODIDE:env --- #
import p5, numpy
import my_lib_using_p5
# ...

Sécurité #

Le problème#

Installations de paquets automatiques depuis Pyodide

Le thème réalise les installations de bibliothèques manquantes automatiquement, sans demander de confirmation à l'utilisateur.

Une attaque particulièrement classique de hackers malveillants est d'uploader des packages sur PyPI avec des fautes de frappes dans les noms, en attendant ensuite qu'un malheureux fasse une requête avec la-dite faute de frappe. Par exemple, request au lieu de requests (nota: exemple pris au hasard, rien de factuel !).

L'environnement de PMT est donc sujet à ce type de problèmes et il serait bon que les utilisateurs soient conscients du fait.

Il faut cependant relativiser la sévérité de la "faille" :

  • L'environnement n'est Ă  priori pas conventionnel, et donc pas une cible privilĂ©giĂ©e pour ce type d'attaque.
  • L'environnement est sensĂ© ĂŞtre isolĂ© de la machine...
  • ... mais gardez en tĂŞte qu'il est possible d'accĂ©der au DOM et donc au navigateur depuis la couche python. Donc il est Ă  priori possible de rĂ©cupĂ©rer certaines informations de ce cĂ´tĂ© (assertion non vĂ©rifiĂ©e de ma part !).

Une solution#

S'il faut vraiment limiter les accès à PyPI, le thème propose une option de configuration qui permet de limiter les installations autorisées via PyPI :

plugins:

    - pyodide_macros:
        build:
            limit_pypi_install_to:
                - numpy
                - PIL

Plus d'informations dans la page concernant la configuration du plugin.

Aller plus loins#

Bibliothèques personnalisées et méta#

Les fichiers .meta.pmt.yml ainsi que les entêtes de fichiers markdown peuvent être mis à profit pour empêcher l'utilisateur d'utiliser certaines bibliothèques personnalisées sans requérir à l'argument SANS des différentes macros.

  • La localisation des bibliothèques est toujours renseignĂ©e par rapport au dossier racine du projet, quand les python_libs sont modifiĂ©es dans un fichier .meta.pmt.yml. La localisation des fichiers .meta.pmt.yml n'a donc aucun impact sur la façon de renseigner les noms des python_libs.

  • Il est impĂ©ratif de dĂ©clarer toutes les bibliothèques personnalisĂ©es dans le fichier mkdocs.yml, et ensuite de redĂ©clarer l'option python_libs lĂ  oĂą appropriĂ© dans les fichiers .meta.pmt.yml ou dans les entĂŞtes de pages markdown, pour limiter les bibliothèques effectivement accessibles.
    Ceci présente deux intérêts :

    1. Cela permet d'éviter des imports accidentels de paquets avec des noms similaires sur PyPI :

      Le thème garde une trace de l'intégralité des noms des bibliothèques personnalisées, et si un utilisateur tente d'importer une bibliothèque personnalisée non disponible pour la page en cours, le thème la reconnaîtra et évitera toute requête vers PyPI pour tenter d'installer un paquet automatiquement. (1)

      1. Cela peut paraître anodin, mais gardez en tête que la plupart des noms de packages classiques existent déjà (py-libs, lib, lib2, ...) et l'installation automatique de pyodide installera n'importe quel paquet correspondant à un import demandé par l'utilisateur.

        En l'occurrence, si py_libs n'est pas disponible en temps que bibliothèque personnalisée et que l'utilisateur écrit import py_libs dans son code, micropip va installer py-libs à la place, qui est une bibliothèques existant sur PyPI, mais qui n'a rien à voir avec le thème...

    2. Cela permet également de valider les python_libs déclarées via les méta-données :

      Toutes les python_libs modifiées via les méta données qui n'apparaissent pas dans la déclaration du fichier mkdocs.yml lèveront une erreur.


Exemple

Voici un exemple concret de modification des python_libs via des fichiers .meta.pmt.yml.

Contenu du mkdocs.yml

plugins:
    pyodide_macros:
        build:
            python_libs:
                - linear_adt
                - any_tree
                - ABR
                - ...

Hiérarchie des fichiers

...
├── chapitre_1_ADT_lin
│   ├── .meta.pmt.yml
│   ├── exo_stack.md
│   ...
├── chapitre_2_ADT
│   ├── .meta.pmt.yml
│   ├── exo_tree1.md


On peut alors imaginer interdire toutes les bibliothèques personnalisées dans les exercices du chapitre 1 en utilisant pour le fichier chapitre_1_ADT_lin/.meta.pmt.yml :

build:
    python_libs: []

De même, on pourrait interdire l'accès aux bibliothèques any_tree et ABR mais laisser l'accès aux types linéaires dans les exercices du chapitre 2, en utilisant pour le fichier chapitre_2_ADT/.meta.pmt.yml :

build:
    python_libs:
        - linear_adt

Rangement des bibliothèques#

Il a été dit plus haut que tous les packages doivent être à la racine du projet.

Cependant, si vous utilisez beaucoup de dossiers différents, il peut devenir difficile de s'y retrouver...

Le thème supporte en fait la définition des bibliothèques dans des sous-dossiers, mais cela va avec certaines contraintes dont il faut être conscient.


En prenant l'exemple d'un item other_libs/that_lib ajouté aux python_libs :

  1. Seul le dossier that_lib sera exporté "vers pyodide". Cela signifie que le code python utilisera des imports de la forme:
    from that_lib import ....
  2. Cella implique également qu'il n'est plus possible d'exécuter les fichiers python de la documentation utilisant cette bibliothèque en local : le code nécessaire pour importer leur contenu dans le contexte dans la documentation construite (le site web) ne correspond plus au contexte des exécutions en local.
  3. Pour cette raison, le thème lève une erreur durant le build si une bibliothèque référencée dans un sous-dossier est importable depuis le répertoire de travail du projet. Ceci afin d'éviter d'avoir un code qui fonctionne en local mais qui ne marche plus sur le site construit.
    Concrètement, pour l'exemple de la bibliothèque other_libs/that_lib, cela signifie que le dossier other_libs ne doit pas contenir de fichier __init__.py.
  4. Par ailleurs, une bibliothèque ne peut pas en contenir une autre : une erreur est aussi levée si les python_libs contiennent par exemple les chemins other_libs/lib et other_libs/lib/sub_lib.


À éviter

Cette façon de procéder est déconseillée, pour les raisons évoquées ci-dessus.
Cependant, si le nombre de dossiers de bibliothèques personnalisées commence à devenir trop important pour pouvoir gérer le projet facilement, cela peut rester une solution...