Boîte à outils
Sont présentés dans cette page divers outils permettant d'aider au développement en local d'une documentation créée avec pyodide-mkdocs-theme
.
Notez que de nombreuses choses présentées ici sont spécifiques aux IDEs de la famille de VSC (VS-codium devrait pouvoir les utiliser également). Il est en général possible de porter la plupart des fonctionnalités à d'autres IDEs, mais il vous faudra trouver la documentation nécessaire.
mkdocs.yml
: auto-complétion & validation#
Le thème propose un jeu de schémas de validation du contenu du fichier mkdocs.yml
, en reprenant les fonctionnalités proposées par mkdocs-material
et en les mettant à jour avec les spécificités du thème.
Ils sont donc utilisables dans des éditeurs comme VSC, et permettent d'avoir les suggestions d'auto-complétion, les descriptions des options, des liens vers les pages de la documentation et les valeurs par défaut pour le plugin du thème, pyodide_macros
, en plus des fonctionnalités proposées par les schémas de mkdocs-material.
Mise en place#
La procédure est très similaire à celle proposée par mkdocs-material
.
Concernant VSC (ou assimilé), il convient donc d'ouvrir (ou de créer) le fichier .vscode/settings.json
dans le projet, puis d'y ajouter le contenu suivant :
{
"yaml.schemas": {
"https://gitlab.com/frederic-zinelli/pyodide-mkdocs-theme/-/raw/main/yaml_schemas/schema.json": "mkdocs.yml"
},
"yaml.customTags": [
"!ENV scalar",
"!ENV sequence",
"!relative scalar",
"tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg",
"tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji",
"tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format",
"tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify"
],
}
Remarques#
Concernant markdown_extensions:pymdownx.tabbed.slugify
#
La syntaxe yaml n'étant pas totalement équivalente à du json, aucune solution n'a été trouvée jusqu'à présent pour valider la configuration de l'option slugify
de l'extension markdown pymdownx.tabbed
quand elle utilise des arguments supplémentaires (1) .
- Ce qui est le réglage recommandé, entre autre par mkdocs-material.
Cette ligne restera donc en rouge dans le fichier, malheureusement, à moins d'utiliser la configuration par défaut pour slugify
:
Concernant la validation des plugins
#
Ces schémas héritent des mêmes défauts et avantages que ceux de material
.
En l'occurrence, si la validation a bien lieu pour les plugins, vous ne verrez en général aucune ligne soulignée en rouge dans la section plugins
du fichier mkdocs.yml
, sauf pour les erreurs de syntaxe et les noms invalides de plugins eux-mêmes.
Par contre, les configurations invalides peuvent être identifiées à la disparition des info-bulles d'un plugin, quand on survole ses options :
Accélérer le travail en serve
#
Les temps de rendu deviennent assez vite problématiques lors du développement, surtout si beaucoup d'appels de macros sont utilisés et que le nombre de pages devient conséquent.
Il y a deux stratégies simples pour rendre le travail plus fluide :
Changer le mode d'exécution#
mkdocs serve --dirty
L'option dirty
de la commande ci-dessus active un mode de travail dans lequel seulement les pages qui viennent d'être modifiées sont rendus à nouveau.
Avantages | Inconvénients |
---|---|
|
|
|
|
|
exclude_docs
#
La propriété exclude_docs
du fichier mkdocs.yml
permet d'exclure certains fichiers du rendu. Ceci peut être mis à profit pour ne rendre que la page en cours de travail et ainsi réduire drastiquement la charge de calcul.
Avantages | Inconvénients |
---|---|
|
|
|
La propriété exclude_docs
est une chaîne multilignes d'expressions régulières, où l'ordre de déclaration des expressions est primordial :
- Les exclusions d'abord.
- Les inclusions ensuite, en préfixant les expressions avec
!
.
Exemple :
exclude_docs: |
**/*_REM.md
**/*.py
**/*.md
!custom/config.md
La ligne **/*.md
exclut toutes les pages markdown du rendu, tandis que la suivante inclut uniquement la page custom/config.md
.
Contraintes d'utilisation :
- Ne pas oublier le caractère
|
sur la ligneexclude_docs: |
: il permet de travailler en mode multilignes, justement. - Les motifs ne tiennent pas compte de la profondeur:
!index.md
inclut tous les fichiersindex.md
, quelle que soit leur localisation dans ledocs_dir
. - Ne pas mettre de commentaires dans les différentes lignes, car ils ne seraient en fait pas des commentaires, mais feraient partie intégrante de l'expression régulière de cette ligne.
Cela peut donc en fait être mis à profit pour désactiver temporairement certaines règles, en plaçant le caractère#
en début de ligne !
Déboguer les codes Python#
Mise en place & lancement (VSC)#
-
Vérifier que les extensions classiques pour python sont installées dans VSC (ou assimilé). À priori les deux suivantes devraient suffire :
ms-python.python
(microsoft)ms-python.debugpy
(microsoft)
-
Créer ou mettre à jour le fichier
.vscode/launch.json
avec le code ci-dessous :{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Mkdocs", "type": "debugpy", "request": "launch", "module": "mkdocs", "args": [ "serve", "-a", "localhost:8000" ], "jinja": false, "justMyCode": true }, { "name": "Python Debugger: Current File", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", "jinja": false, "justMyCode": true } ] }
La première configuration permet de lancer un
mkdocs serve
en mode de débogage. Avec donc possibilité d'utiliser des points d'arrêts, de s'arrêter automatiquement en cas d'erreur, ...La seconde permet de lancer un fichier de manière isolée, ce qui peut permettre de tester certains fichiers python utilisés pour les IDEs, terminaux, ... en dehors du site construit (s'ils contiennent du code compatible : voir plus bas).
-
Sélectionner le profil d'exécution voulu dans le panneau du débogueur puis lancer les exécutions, en appuyant sur F5 ou sur le bouton "lecture", en vert dans l'image :
Les options jinja
et justMyCode
des profils de déboguage
-
justMyCode
:Si cette option est à
true
, le débogueur ne s'arrêtera que dans vos fichiers.Cette notion concerne en particulier les arrêts automatiques sur les erreurs levées : si cette option est passée à
false
, le débogueur s'arrêtera aussi sur une erreur levée dans les modules utilisés par l'interpréteur python. Cela peut notamment permettre d'explorer les raisons d'une erreur dans mkdocs, le thème lui-même, ou encore un autre plugin utilisé dans le projet...Garder cependant en tête que le code s'exécute encore plus lentement, quand cette option est à
false
. -
jinja
:Si à
true
, les rendus des templates Jinja sont visibles dans la pile d'appels du débogueur et il est possible de les explorer, voire même de stopper le débogueur sur les erreurs levées lors des rendus de ces templates.Cela peut parfois se révéler utile, mais c'est en général très difficile à comprendre/utiliser et il vaut mieux laisser la valeur à
false
.
Accélérer le débogage#
Le problème essentiel du débogage est que les temps d'exécutions sont souvent multipliés par un facteur 5 à 10.
C'est donc ici que la propriété mkdocs.yml:exclude_docs
se révèle particulièrement utile pour pouvoir cibler la ou les pages problématiques et ne rendre que celles-ci.
Déboguer vos macros#
- Profil du débogueur : "MkDocs".
justMyCode: true
- Utiliser
exclude_docs
pour cibler la ou les pages problématiques. - F5
Déboguer mkdocs, le thème ou d'autres plugins#
- Profil du débogueur : "MkDocs".
justMyCode: false
- Utiliser
exclude_docs
pour cibler la ou les pages problématiques. - F5
Tester/déboguer les .py
de la documentation#
Il s'agit ici de tester les fichiers python utilisés pour les macros IDE
, terminal
, ... Deux approches sont possibles :
1. Exécution via Pyodide#
Il s'agit plutôt de tester les codes, sans pouvoir mettre à profit des fonctionnalités de débogage à proprement parler :
Lancer un serve
, aller sur la page en question et utiliser l'IDE lui-même ou le bouton pour tester la correction depuis le navigateur (en vert, dans l'image ci-dessous) :
- Permet de tester le code dans le même contexte d'exécution que l'utilisateur.
- Lent à mettre en place (
exclude_docs
peut aider). -
La visibilité ou non des modifications faites au fichier python depuis le navigateur n'est pas toujours évidentes. Elle dépend notamment des points suivants :
- En mode
serve --dirty
, il faut modifier le fichier python et enregistrer le fichier markdown source pour que les modifications soient intégrées au build. - Si la modifications concerne la section
PYODIDE:code
, il faut en plus réinitialiser l'IDE pour voir apparaître les changements (sinon, c'est la version précédemment enregistrée dans le localStorage qui est utilisée).
- En mode
-
À partir de la version 2.3.0, le thème propose de générer automatiquement une page pour tester tous les IDEs du site, permettant notamment de vérifier que le code présent dans la section
corr
d'un IDE passe bien les tests de validations.
Cette fonctionnalité vient avec diverses options permettant d'affiner la façon de tester un IDE (ne pas faire le test, le faire mais considérer une erreur comme un succès, ...).Ceci permet de tester les codes dans le même environnement que l'utilisateur, de manière semi-automatisée.
2. Exécuter un fichier python en local (VSC, ...)#
L'idée est cette fois de pouvoir travailler plus vite, et éventuellement avec les outils de débogage de votre IDE.
Cependant, l'environnement n'est alors plus le même que celui de l'utilisateur, ce qui génère un nombre certains de contraintes et ne garantit pas que ce qui tourne en local tournera dans pyodide exactement de la même façon (notamment en ce qui concerne l'articulation des codes des différentes sections entre elles).
Les tenants et aboutissants sont discutés à la section suivante, ainsi que les outils permettant de faciliter l'articulation entre les deux types d'environnements, mais voici la configuration du débogueur à utiliser :
- Profil du débogueur : "Current File"
justMyCode: true
- Ouvrir le fichier que l'on veut tester dans l'éditeur
- F5
Lancer les .py
de la documentation en local#
Le thème propose quelques outils pour faciliter le lancement des fichiers python de la documentation en local, de façon à pouvoir en tester le code en dehors du site construit ou d'un mkdocs serve
.
Comme évoqué précédemment, il y a cependant un certain nombre de limitations.
Argument -C
du script
Avec cet argument, un fichier est créé directement à la racine du projet, en écrasant sans avertissement tout autre fichier du même nom que celui du fichier à copier.
toolbox.py
et pyodide_plot.py
#
La première chose à faire est "d'installer" ces deux fichiers, fournis par le thème, à la racine du projet.
Le plus simple pour cela est d'utiliser le script PMT :
python -m pyodide_mkdocs_theme --toolbox --plot -C
Ou en version plus condensée :
python -m pyodide_mkdocs_theme -tPC
Ces fichiers permettent de mimer ou réimplanter certaines fonctionnalités utilisées dans l'environnement de Pyodide.
Pour les mettre à profit, il suffit d'ajouter ce code tout en haut du fichier python de la documentation que l'on veut ensuite exécuter localement :
# --- PYODIDE:ignore --- #
from toolbox import *
Comment ça marche ?
-
Comme décrit dans la page concernant les IDEs, les sections
ignore
des fichiers python sont ignorées quand les IDE sont créés pour l'environnement de Pyodide. Donc ce code n'a aucun impact sur les IDEs du site construit. -
À contrario, le contenu des sections
ignore
est du code python à part entière lorsqu'un de ces fichiers est exécuté en local. -
Le fichier
toolbox.py
étant à la racine du projet, il est dans le répertoire de travail (cwd
) de l'interpréteur python et peut donc être importé depuis un fichier lancé localement.
toolbox.py
vs matplotlib
Le fichier toolbox.py
importe pyodide_plot.py
, qui lui-même importe matplotlib
.
Si vous n'en avez pas l'utilité et que vous ne souhaitez pas installer matplotlib
, supprimez ou passez en commentaire la ligne d'import dans toolbox.py
.
Contenu du fichier toolbox.py
"""
Utilities to fake some PMT tools so that a python file from the documentation becomes
runnable in the local environment.
WHAT IT DOES:
* `auto_run` works exactly as it does in pyodide, except for the variables deletions.
* All other objects/functions act as sinks, swallowing any kind of access/calls
without doing anything (hence, not returning any value either).
HOW TO USE IT:
Just put the following snippet at the very top of the python file you wanna test:
```python
# --- PYODIDE:ignore --- #
# To put at the very top of the python file to test
from toolbox import *
```
This file and the `pyodide_plot.py` one are also available through one of these PMT's scripts:
python -m pyodide_mkdocs_theme --toolbox --plot
python -m pyodide_mkdocs_theme --toolbox --plot -C
python -m pyodide_mkdocs_theme -tPC
"""
from pyodide_plot import PyodidePlot
mermaid_figure = lambda _: print
def terminal_message(_, *a, **kw):
print(*a, end='\n' if kw.get('new_line',True) else '')
def auto_run(func): func()
auto_run.clean = lambda: None # sink
@auto_run
def __fake_js_import_and_loaders():
global pyodide_uploader, pyodide_downloader
from unittest.mock import Mock
from functools import wraps
pyodide_uploader = Mock()
pyodide_downloader = Mock()
js = Mock()
src_import = __import__
@wraps(src_import)
def fake_js_import(chain:str, *a, **kw):
"""
If an import related to the js Pyodide module is done, a Mock object will
be returned. All other imports work the usual way.
"""
if chain.startswith('js'):
return js
return src_import(chain, *a, **kw)
if isinstance(__builtins__, dict):
__builtins__['__import__'] = fake_js_import
else:
__builtins__.__import__ = fake_js_import
# Because there is no Pyodide around, triggering the deletion:
del __fake_js_import_and_loaders
Contenu du fichier pyodide_plot.py
import matplotlib
import matplotlib.pyplot as plt
from typing import Callable, Sequence
from unittest.mock import Mock
js = Mock()
class PyodidePlot:
"""
Helper class, to draw figures from pyodide runtime, into a specified html element.
If no argument is provided, the default `div_id` is used (arguments configuration of
the plugin).
```python
import matplotlib.pyplot as plt
PyodidePlot().target()
plt.plot(xs, ys, ...)
plt.title("...")
plt.show()
```
---
In case you want to target more than one figure in the same page, pass the html id of the
div tag to target to the `PyodidePlot` constructor:
```python
fig1 = PyodidePlot("figure_id1")
fig2 = PyodidePlot("figure_id2")
fig1.target() # Draw in "figure_id1"
plt.plot(...)
fig2.target() # Draw in "figure_id2"
plt.plot(...)
```
---
"""
def __init__(self, div_id:str=''):
self.div_id = div_id or js.config().argsFigureDivId
def __getattr__(self, prop:str):
return getattr(plt, prop)
def target(self, keep_fig=False):
"""
Close any previously created figure, then setup the current run to draw in
the div tag targeted by the current instance.
If keep_fig is set to True, the automatic `Figure N` will be kept, above the
drawn figure.
"""
for _ in plt.get_fignums():
plt.close()
div = js.document.getElementById(self.div_id)
if not div:
raise ValueError("Couldn't find the target object: #"+self.div_id)
js.document.pyodideMplTarget = div
div.textContent = ""
if not keep_fig:
plt.gcf().canvas.manager.set_window_title('')
return plt
refresh = target # backward compatibility
def plot_func(
self,
func:Callable,
rng:Sequence,
fmt:str=None,
title:str=None,
*,
show:bool=True,
keep_figure_num: bool = False,
**kw
):
"""
Draw an automatic graph for the given function on the given range, then "show"
automatically the resulting graph in the correct figure element in the page.
Arguments:
func: Callable, func(x) -> y
rng: Sequence of xs
fmt: Curve formatting (just like `pyplot.plot`)
title: If given, will be added as title of the graph.
show: Call `pyplot.show()` only if `True`. This allows to customize the graph
before applying show manually.
keep_figure_num: if False (by default), the `Figure N` above the drawing is
automatically removed.
"""
self.target(keep_figure_num)
xs = list(rng)
ys = [*map(func, rng)]
args = (xs,ys) if fmt is None else (xs,ys,fmt)
out = plt.plot(*args, **kw)
if title:
plt.title(title)
if show:
plt.show()
return out
def plot(self, *args, keep_figure_num:bool=False, **kw):
"""
Generic interface, strictly equivalent to `pyplot.plot`, except the `PyodidePlot`
instance will automatically apply the drawing to the desired html element it is
related to.
_Use specifically this method to "plot"_ ! You then can rely on `pyplot` to finalize
the figure as you prefer.
"""
self.target(keep_figure_num)
out = plt.plot(*args, **kw)
return out
Une fois les deux fichiers python placés à la racine du projet et le code ajouté dans le fichier python de la documentation que l'on souhaite tester, cette "boîte à outils" offre les fonctionnalités suivantes :
-
Le décorateur
@auto_run
est utilisable en local.
La méthodeauto_run.clean
est également présente, mais n'a aucun effet. -
Les codes utilisant
import js
(ou toute autre forme d'import de ce package Pyodide) ne lèvent pas d'erreurs, mais leur logique devient inopérante : l'objetjs
importé est unMock
.
À noter que :-
Les accès à des propriétés (quelles qu'elles soient et "récursivement") renvoient toujours un objet
Mock
. Les comparaisons avec des valeurs renverront donc toujoursFalse
, par exemple.
Il est possible d'augmenter les objets Mock avec d'autres fonctionnalités, si vous vous en sentez le courage. -
Les appels de méthodes ne lèvent aucune erreur, mais n'ont plus aucun effet, non plus.
Les méthodes sont également des Mocks, qui "absorbent" tout bonnement n'importe quel appel de fonction).
-
-
La fonction
mermaid_figure
, pour gérer les graphes Mermaid, est également utilisable.
Son seul effet est d'afficher le code mermaid dans la console. -
L'objet
PyodidePlot
, utilisé pour gérer les figures créées avecmatplotlib
est utilisable en local.
Il est fonctionnel et les figures seront effectivement affichées dans une fenêtre isolée, comme lors des exécutions habituelles depuis une exécution en local. Il faut par contre quematplotlib
soit déjà installé dans l'environnement local. -
Les fonctions
pyodide_uploader
etpyodide_downloader
sont disponibles sous forme de Mocks et n'ont donc pas d'effet.
Rappel:pyodide_uploader_async
n'est pas utilisable car asynchrone, et c'est la seule des fonctions d'échanges de fichiers qui renverrait normalement des résultats utilisables pour l'exécution en cours.
Bibliothèques python#
-
Concernant les bibliothèques externes, qui sont installées automatiquement dans l'environnement Pyodide, il suffit de les installer dans l'environnement local au préalable.
-
Pour les bibliothèques personnalisées du projet, si elles ont été créées à la racine du projet, elles sont donc importables pour les exécutions locales sans rien avoir à faire de particulier.
Les impossibilités#
Section d'environnement utilisant du code async
Il est impossible d'exécuter en local un fichier python contenant des sections d'environnement (env
, env_term
, post_term
ou post
) avec des appels de fonctions asynchrones :
# --- PYODIDE:env --- #
fichier = "image.jpg"
await copy_from_server(fichier) # <<< HERE !
Raison
En python, il est interdit d'utiliser le mot clef await
en dehors d'une fonction async
.
async def awaitable(...):
""" do something async"""
await other_async_tool(...) # <-- OK !
await awaitable(...) # <-- SyntaxError !
Cette restriction s'applique évidemment aux exécutions locales.
Ces codes fonctionnent par contre dans l'environnement Pyodide car il y a toute une logistique autour qui fait que, quand lancés avec les bons outils (pyodide.runPythonAsync
), tout se passe comme si le code en question était lancé depuis l'intérieur d'une fonction async def
, justement.
Ceci implique que tous les exercices utilisant les outils suivants ne pourront pas être exécutés en local :
- Requêtes http/ftp/... et utilisation de
copy_from_server
- Interaction asynchrones avec l'utilisateur :
pyodide_uploader_async