13 Commits

Author SHA1 Message Date
4914815c8f WIP: la-guerre-des-protocoles.md 2020-12-02 16:30:47 +01:00
f6a5373bff nouvel article 2020-10-19 10:07:53 +02:00
5c82486422 [article] utilisation de JsonPath en Python 2020-07-05 00:33:08 +02:00
f13c69075f [artice] add TL;DR: 2020-06-09 22:16:44 +02:00
7060732b5c [article] On insert-ordered by default dict comparative advantages 2020-06-09 21:50:12 +02:00
cc664b5918 [article] add examples to dynamic enum 2020-06-05 19:57:33 +02:00
feee74665a add stuff to readme 2020-06-04 11:21:31 +02:00
656d093c87 rename 2 posts 2020-06-04 11:07:29 +02:00
57026c7fa5 [article] python dynamic enum 2020-06-02 21:00:13 +02:00
001420ea8d [article] fusermount umount sshfs 2020-05-30 16:50:47 +02:00
354abbdee8 fix svg test2 2020-05-18 16:50:29 +02:00
09e4032a62 fix svg test 2020-05-18 16:48:12 +02:00
a0d7eb3fbf fix images 2020-05-15 17:03:30 +02:00
9 changed files with 676 additions and 6 deletions

View File

@@ -9,7 +9,7 @@
### En-tête
L'en-tête contient les métodonnées de l'article et se présente sous la forme suivante :
L'en-tête contient les métadonnées de l'article et se présente sous la forme suivante :
```
---
@@ -52,9 +52,15 @@ Pour vérifier la cohérence des articles, des scripts sont disponibles dans le
Utilisés sans arguments, ces scripts vérifient l'ensemble des articles. Avec un argument, les scripts ne vérifient que le post dont l'argument est le nom de fichier.
__Il est très fortement recommandé d'exécuter ces scripts de vérification avant chaque commit ou push.__ Pour cela, les [hooks mis à disposision par git](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) peuvent vous être utiles.
## Publication
Pour publier, il suffit de pousser.
Pour publier, il suffit de pousser sur la branche `master`.
Comme seule la branche `master` est affichée sur le site, il est tout à fait possible d'utiliser des branches distinctes à la guise des auteurs (par exemple, pour enregistrer et versionner ses brouillons).
__Note :__ Dans un futur proche, une instance de test du blog sera déployée, et le contenu affiché correspondra à la branche `test`.
# Pour l'administrateur système

View File

@@ -3,6 +3,7 @@
<svg width="14.250cm" height="12.000cm" viewBox="31.811 27.910 46.061 39.910"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="scale(2)">
<rect x="31.861" y="27.960" width="14.150" height="11.900" fill="#FFFFFF" stroke="none" stroke-width="0"/>
<rect x="31.861" y="27.960" width="14.150" height="11.900" fill="none" stroke="#FFFFFF" stroke-width="0.100" />
<rect x="33.530" y="28.893" width="2.034" height="1.525" fill="#B3B3B3" stroke="none" stroke-width="0"/>
@@ -59,4 +60,5 @@ Carol</text>
<polygon fill="none" stroke="#000000" stroke-width="0.100" points="39.623,35.920 39.672,35.363 39.820,35.600 40.098,35.625 "/>
<polygon fill="#000000" stroke="none" stroke-width="0.100" points="42.782,30.775 42.734,31.332 42.586,31.095 42.308,31.070 "/>
<polygon fill="none" stroke="#000000" stroke-width="0.100" points="42.782,30.775 42.734,31.332 42.586,31.095 42.308,31.070 "/>
</svg>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -0,0 +1,82 @@
---
title: Avantages et inconvénients des dictionnaires ordonnés
date: 2020-06-09
author: motius
template: post
tags: python,dict,hashmap,développement
---
Bonjour !
Aujourd'hui, je veux vous parler des dictionnaires en Python, et notamment de leur — relativement — nouvelle propriété d'ordre.
TL;DR: les considérations concernant les dictionnaires ordonnés tourne autour des performances.
On oublie de mentionner que cela facilite le débogage, mais que cela peut aussi cacher un bogue dans l'implémentation d'un algorithme, raison pour laquelle j'ai écrit le fragment de code ci-dessous.
# Un peu de contexte
Si l'on en croit [ce thread StackOverFlow](https://stackoverflow.com/questions/39980323/are-dictionaries-ordered-in-python-3-6), les clefs d'un dictionnaire dans Python3 depuis sa version 3.6 sont _de facto_ ordonnées **dans l'implémentation CPython** (dans l'ordre d'insertion), et Python3 dans sa version 3.7 standardise cet état de fait, ce qui veut dire que les autres implémentations (PyPy, Jython...) devront s'aligner afin de correctement implémenter le nouveau standard, pour assurer la compatibilité du code entre "interpréteurs".
(Je mets le lien StackOverFlow parce qu'il en contient d'autres vers la liste de diffusion de courriel, _etc._)
Si vous vous demandez ce qui a amené à cet état de fait, je vous recommande [la vidéo suivante](https://www.youtube.com/watch?v=p33CVV29OG8), par le curieux Raymond Hettinger.
Avec ça, vous aurez des éléments pour évaluer la pertinence des dictionnaires ordonnées en Python.
Notez, pour ceux qui n'ont pas regardé la documentation, que [Python3 met à disposition un OrderedDict](https://docs.python.org/3/library/collections.html#collections.OrderedDict) déjà disponible dans les versions antiques, antédiluviennes, je veux dire celles pré-3.4 (oui, je trolle, mais à peine).
# Le problème
Voyons un peu le contexte des deux problèmes qui m'ont amené à écrire cet article.
## Scénario 1
J'étais en train de déboguer un logiciel, appelons-le Verifikator, qui faisait des appels API afin de vérifier des données en bases.
Il se trouve que les résultats de Verifikator étaient aléatoires. De temps en temps, il retournait les bons résultats, _i.e._ il indiquait que certaines données en base étaient invalides, et de temps en temps, Verifikator n'indiquait pas d'erreur, alors qu'on pouvait vérifier _a la mano_ qu'il y avait effectivement une erreur en base.
Pour faire simple, la raison pour laquelle Verifikator n'était pas déterministe, c'est qu'il dépendait de l'ordre d'un dictionnaire dont la donnée provenait de l'API de la base de données.
Vous comprenez que j'exagère quand je dit que Verifikator n'était pas déterministe, il l'est heureusement, puisqu'il s'agit d'un système informatique, et qu'on néglige les rayons cosmiques et les bogues système.
Je me permet de rappeler à ceux d'entre vous qui s'étonnent de ce comportement erratique de Verifikator que j'étais en train de le déboguer (qui plus est, j'avais seulement participé à sa conception, pas à son implémentation).
Verifikator tourne en Python3.5, si l'on avait utilisé une version supérieure, on n'aurait pas eu ce problème, notamment, Verifikator aurait soit systématiquement planté, soit systématiquement fonctionné. Ç'eût été mieux afin de pouvoir localiser le bug par dichotomie, je vous avoue que mon état de santé mentale se dégradait à vu d'œil lorsque j'ai commencé à observer ce comportement aléatoire, en cherchant à localiser les lignes fautives par dichotomie.
Je reviens un instant sur cette histoire de dichotomie pour bien faire sentir à quel point c'est fatiguant.
Imaginez un peu, vous essayez de déterminer un point A où le programme est dans un état valide et un point B dans lequel l'état est invalide, puis vous regardez un nouveau point C "au milieu", afin de savoir si ce point C est le nouveau point valide A' ou bien le nouveau point B' pour l'itération suivante. Le problème, c'est que parfois vous pensez que l'état du programme est valide à ce point C, mais que ce n'est pas vrai en général. Vous continuez donc à itérer entre C et B, alors que le problème se trouve entre A et C.
Vous comprenez pourquoi j'apprécie fortement le fait que les dictionnaires soient ordonnés pour le débogage.
Mais. Comme vous imaginez, il y a un mais. Parce qu'il n'y a pas que des avantages aux dictionnaires ordonnés. C'est le sujet du second scénario.
# Scénario 2
Dans d'autres circonstances, j'ai déjà écrit — j'étais l'auteur du code cette fois-là, contrairement à Verifikator — un algorithme qui ne fonctionnait que si le dictionnaire sur lequel il tournait était ordonné. J'utilisais Python3.6 à l'époque, et par conscience professionnelle, j'ai compilé les versions 3.4 à 3.7 de Python afin de vérifier que les tests étaient corrects avec ces interpréteurs. Quelle ne fut pas ma surprise quand je m'aperçus que ce n'était point le cas. Ce n'était même pas le nouvel Python3.7 encore en bêta qui posait problème, mais les version 3.4 et 3.5. J'ai donc réécrit cet algorithme afin qu'il ne dépende plus de l'ordre du dictionnaire d'entrée (très simplement en construisant un OrderedDict à partir du dictionnaire et d'une liste correctement triée).
# Conséquences
À l'époque, je n'avais pas rajouté de code pour tester les fonctions de service du second programme, je m'assurai qu'il fonctionnait avec les 4 versions mineures de Python3 pour lesquelles je développais le programme.
J'ai fait les choses différemment cette fois, puisque j'ai codé cette fonction, qui randomise les clefs d'un dictionnaire plat (fonction non récursive sur d'éventuels dictionnaires en valeurs du dictionnaire passé en argument).
```python
import random as rnd
def shuffle_dict_keys(d: dict) -> dict:
"""shuffle the keys of a dictionary for testing purposes now that
Python dictionaries are insert-ordered. Does not compute inplace.
Does not work recursively.
"""
res = {}
l = list(d)
rnd.shuffle(l)
for k in l:
res[k] = d[k]
return res
```
J'avais en tête [cette planche XKCD](https://xkcd.com/1172/) en écrivant ce code. Non pas que je préfère l'ancien comportement, mais que pouvoir y souscrire de manière optionnelle me permet d'avoir des tests de meilleure qualité, et qu'il a donc fallu queje trouve un contournement afin de retrouver l'ancien comportement dans les cas de tests.
# Conclusion
De manière générale, je suis assez content que les dictionnaires soient ordonnés, mais je ne m'attendais pas à rencontrer de tels écueils, notamment puisque la majorité des conversations que j'avais lues sur le sujet s'attardaient sur les performances de cette nouvelle implémentation, et non sur ce genre de considérations.
Joyeux code !
Motius

View File

@@ -0,0 +1,258 @@
---
title: Créer un enum Python dynamiquement
date: 2020-06-02
author: motius
template: post
tags: python,enum,type,développement
---
Bonjour !
Aujourd'hui, je vous propose un micro tutoriel pour créer un `enum` dynamiquement en Python.
Je vous accorde qu'on ne fait pas ça tous les jours, c'est d'ailleurs la première fois que je suis tombé sur ce cas.
# Un peu de contexte
En général, un `enum`, ça ressemble à ça en Python :
```python
class MonEnumCustom(enum.Enum):
"""docstring
"""
VALIDATION = "VALIDATION"
SEND = "SEND"
```
Si l'`enum` contient un grand nombre de valeurs, chacune correspondant à un état, on peut aisément les formater avec un programme, afin d'écrire un `enum` comme ci-dessus.
Ça fait simplement un `enum` comme ci-dessus, mais en plus gros :
```python
class MonEnumCustom(enum.Enum):
"""docstring
"""
VALIDATION = "VALIDATION"
SEND = "SEND"
COMMIT = "COMMIT"
REVIEW = "REVIEW"
... # une centaine d'états supplémentaires disponibles
```
Mais que faire quand on récupère les valeurs dynamiquement depuis un appel à une API, qui retourne l'ensemble des états possibles sous forme d'une collection, comme ceci :
```python
>>> ls_remote_states = fetch_all_states(...)
>>> print(ls_remote_states)
[
"VALIDATION",
"SEND",
"COMMIT",
"REVIEW",
...
]
```
et que l'on souhaite créer un `enum` à partir de ladite collection afin d'écrire du code qui dépendra de valeurs d'états prise dans l'`enum` comme ci-dessous ?
```python
# pseudo-code
ls_remote_states = fetch_all_states(...)
MonEnumCustom = create_enum_type_from_states(ls_remote_states)
# dans le code
if state is MonEnumCustom.VALIDATION:
print("En cours de validation")
elif state is MonEnumCustom.COMMIT:
...
else:
raise
```
C'est le sujet de la discussion qui suit, qui va présenter les options disponibles, et quelques considérations quant à leurs usages.
# Le scénario
Je souhaitais pouvoir m'interfacer avec un service qui définit un grand nombre (un peu plus d'une centaine) de constantes qu'il me renvoie sous forme d'une collection de chaînes de caractères.
Trois choix s'offraient ainsi à moi :
* utiliser les constantes telles quelles dans une collection (`list`, `dict`...)
**Avantages** :
* rapide & facile
**Inconvénients** :
* sémantiquement pauvre
* la liste des états valides n'est jamais écrite dans le code
* faire un copié collé et du formatage (avec vim, c'est facile et rapide)
**Avantages** :
* rapide & facile
* l'intégralité des valeurs connues de l'`enum` peuvent être lues dans le programme à la seule lecture du code
**Inconvénients** :
* duplique un `enum` dont je ne suis pas responsable
* générer dynamiquement l'`enum`
**Avantages** :
* résout les inconvénients des solutions précédentes
* évite la duplication de code inutile.
Il arrive assez souvent que l'on doive définir un même `enum` à plusieurs endroits dans un système informatique, par exemple en base de donnée dans Postgre, puis en Java / Python, et enfin en Typescript si vous faites du développement sur toute la stack.
Mais dans ce cas-ci, j'aurais redéfini un enum sans que mon code ne soit la source d'autorité sur celui-ci, ce qui contrevient au principe du Single Source of Truth, c'est donc plus gênant que le simple problème de duplication de code.
* Plus facile à adapter lors de l'évolution de l'`enum`.
Ce dernier point est vrai dans mon cas où j'ai l'assurance que l'API ne fera qu'augmenter les états et n'invalidera jamais un état existant.
Dans le cas contraire, il faudra changer les fonctions qui utilisent les états définis dans l'`enum`, avec ou sans la génération d'`enum` automatique.
**Inconvénients** :
* à nouveau, la liste des états valides n'est jamais écrite dans le code
* Un tantinet plus long, surtout si tout ne va pas sur des roulettes du premier coup.
Et si je mentionne les roulettes, c'est que vous imaginez bien que ça n'a pas marché du premier coup (sinon je n'écrirais pas cet article).
# La théorie
Selon la [documentation officielle](https://docs.python.org/3/library/functions.html#type "Documentation Python3 sur type"), il suffit de passer 3 paramètres à la fonction `type` afin de créer une classe dynamiquement.
Si vous voulez mon avis, on est là dans la catégorie des fonctionnalités de Python vraiment puissantes pour le prototypage (à côté de `eval` et `exec`, même si ces derniers devraient être quasiment _interdits_ en production).
Chouette, me dis-je en mon fort intérieur, ceci devrait marcher :
```python
# récupération du dictionnaire des valeurs
d_enum_values: dict = fetch_all_states(...)
# création dynamique de l'enum
MonEnumCustom = type("MonEnumCustom", (enum.Enum,), d_enum_values)
```
# La pratique
Ne vous fatiguez pas, ça ne marche pas. La solution que j'ai trouvée est un contournement dégoûtant (dû justement au module `enum` de Python).
Je contourne les problèmes de création d'`enum` à l'aide d'un mapper qui ressemble à ça :
```python
class EnumMappingHelper(dict): # héritage pour contourner type
def __init__(self, mapping: dict = None):
self._mapping = mapping
def __getitem__(self, item):
if item == "_ignore_": # contournement d'enum
return []
return self._mapping[item]
def __delitem__(self, item):
return item # contournement d'enum
def __getattr__(self, key): # Accès au clefs de l'enum par attribut
return self._mapping[key]
@property
def _member_names(self): # contournement d'enum
return dict(self._mapping)
enum_mapping = EnumMappingHelper(d_enum_values)
MonEnumCustom = type("MonEnumCustom", (enum.Enum,), enum_mapping)
```
Je m'en sors donc en torturant un peu un classe personnalisée que j'appelle `EnumMappingHelper` afin que celle-ci se comporte d'une façon qui soit acceptable à la fois pour la fonction `type` et pour le module `enum`.
Je peux utiliser les variantes de mon `enum` ainsi :
```python
if state is MonEnumCustom.VALIDATION:
print("En cours de validation")
elif state is MonEnumCustom.COMMIT:
print("Validé")
elif state is MonEnumCustom.SEND:
print("Aucune modification possible")
elif ...:
...
else:
raise UnknownEnumStateValue("Code is not in sync with API")
```
# Un petit plus... avec un mixin
Je dispose bien entendu d'une fonction qui permet de mapper un état sous forme de chaîne de caractères à la valeur représentée dans l'`enum` de référence.
Moralement et en très très gros, elle ressemble à ça :
```python
@functools.lru_cache
def map_str_to_custom_enum(s: str) -> MonEnumCustom:
"""docstring
"""
return dict((v.value, v) for v in MonEnumCustom)[s]
```
L'idée étant donc de faire le mapping inverse de celui fourni par '`enum`, _i.e._ de générer automatiquement et efficacement une variante d'`enum` à partir de sa représentation sous forme de chaîne de caractères.
Sauf qu'en réalité, la fonction n'est pas du tout implémentée ainsi.
À la place, j'utilise un mixin, ce qui me permet d'avoir la même fonctionnalité sur tous les `enum`.
La fonction ci-dessus est remplacée par une méthode de classe.
Le cache `lru_cache` est remplacé par un attribut de classe de type dictionnaire, ce qui évite toutes sortes d'inconvénients.
```python
class EnumMixin:
_enum_values = {}
def __init__(self, *args, **kwargs):
self.__class__._enum_values[args[0]] = self
@classmethod
def convert_str_to_enum_variant(cls, value: str):
cls._enum_values
return cls._enum_values(value, object())
```
Avec le mixin de conversion, le code de génération d'`enum` devient :
```python
# inchangé
enum_mapping = EnumMappingHelper(d_enum_values)
# MonEnumCustom hérite en premier du mixin EnumMixin
MonEnumCustom = type("MonEnumCustom", (EnumMixin, enum.Enum), enum_mapping)
```
# Gestion de version de l'API pour les variantes de l'enum
Enfin en ce qui concerne la gestion de version de l'API, il est possible de rajouter un `assert` dans le code qui vérifie toutes les variantes de l'`enum`, afin de lever une exception le plus tôt possible et de ne pas faire tourner du code qui ne serait pas en phase avec la version de l'API utilisée.
```python
# je définis la variable suivante en copiant directement les valeurs
# récupérées par un appel à l'API au moment du développement :
ls_enum_variantes_copie_statiquement_dans_mon_code = [
"VALIDATION",
"SEND",
"COMMIT",
"REVIEW",
... # et une centaine d'états supplémentaires
]
# si la ligne suivante lance une AssertionError, l'API a mis à jour
# les variantes de l'enum
assert fetch_all_states(...) == ls_enum_variantes_copie_statiquement_dans_mon_code
```
Dans mon cas, je suis plus souple, car ayant la garantie que les variantes publiées
de mon `enum` ne seront pas dépréciées par de nouvelles versions de l'API, je vérifie
simplement cette assertion :
```python
# idem
ls_enum_variantes_copie = [
"VALIDATION",
"SEND",
"COMMIT",
"REVIEW",
... # et une centaine d'états supplémentaires
]
# si la ligne suivante lance une AssertionError, l'API n'a pas honoré son
# contrat de ne pas déprécier les variantes de l'enum.
assert set(fetch_all_states(...)).issuperset(set(ls_enum_variantes_copie))
```
Notez que si je souhaite être strict et n'autoriser aucun changement des
variantes de l'API, alors il est préférable de copier coller les variantes
directement dans le code et de comparer ces valeurs à l'exécution.
Le code en est rendu plus lisible. Mais ce n'est pas le cas de figure dans
lequel je me trouve.
# Conclusion
Seule la solution utilisant un copier coller permettait de voir la liste des valeurs de l'`enum` dans le code.
Je combine les avantages de cette solution et de la troisième que j'ai implémentée en copiant les valeurs prises par l'`enum` dans la docstring de sa classe et en mentionnant la version du service distant associé.
Cela me permet de combiner les avantages de toutes les solutions à l'exception, bien sûr, d'un peu de temps passé.
L'inconvénient inattendu de ce code, c'est celui de devoir créer une classe d'aide dont le seul but soit de forcer un comportement afin d'obtenir le résultat escompté.
Si vous avez des suggestions pour améliorer ce bazar, je prends.
Joyeux code !
Motius

View File

@@ -0,0 +1,60 @@
---
title: Démonter un disque distant après une erreur réseau
date: 2020-05-30
author: motius
template: post
tags: sshfs,réseau,umount,fusermount
---
Bonjour !
Aujourd'hui, un micro tutoriel d'administration système pour gérer des problèmes de déconnexion du réseau.
J'utilise `sshfs` afin de monter un disque distant. Ça me permet d'avoir de la synchronisation de données sur un seul disque dur de référence, qui reste solidement attaché à un serveur. Il m'est donc inutile de brancher et débrancher le disque, et de le déplacer.
Ce programme s'utilise ainsi :
```bash
sshfs user@remote:/chemin/du/répertoire/distant /mnt/chemin/du/répertoire/local
```
Note : il vous faudra avoir les droits d'écriture sur le répertoire local sur lequel vous montez le disque distant.
Et pour le démonter, j'utilise :
```bash
fusermount -u /mnt/chemin/du/répertoire/local
```
Cette commande peut échouer pour plusieurs raisons, notamment s'il y a toujours un programme qui utilise le disque distant. Ça peut être aussi bête que d'essayer de démonter le répertoire depuis un shell (`bash`, `zsh`) dont le répertoire courant est dans l'arborescence sous le disque distant.
Le message d'erreur ressemble à ceci :
```
fusermount: failed to unmount /mnt/chemin/du/répertoire/local: Device or resource busy
```
Il suffit d'utiliser la commande `lsof` (list open files) pour savoir quels programmes utilisent encore le disque distant de cette manière :
```bash
lsof | grep /mnt/chemin/du/répertoire/local
```
Mais ! mais mais, il peut arriver que la connexion réseau se coupe (ou que votre serviteur déplace son ordinateur hors de portée du Wi-Fi, il lui arrive d'être distrait...)
Dans ce cas, on peut essayer de recourir à `umount` en tant qu'utilisateur root, _si on lui passe les bonnes options_, que voici :
```bash
# Lazy unmount. Detach the filesystem from the file hierarchy now, and clean up all
# references to this filesystem as soon as it is not busy anymore.
umount -l /mnt/chemin/du/répertoire/local
# StackOverflow suggère aussi :
# https://stackoverflow.com/questions/7878707/how-to-unmount-a-busy-device
umount -f /mnt/chemin/du/répertoire/local
# mais la page de manuel indique que cette option est utile pour les disques NFS.
```
Joyeux code !
Motius

View File

@@ -0,0 +1,176 @@
---
title: Explorer des logs ELK avec JsonPath
date: 2020-07-04
author: motius
template: post
tags: python,JSON,log,ELK,développement
---
Bonjour !
Aujourd'hui, je veux vous parler d'une bibliothèque Python3 :
[`jsonpath2`](https://pypi.org/project/jsonpath2/).
# Un peu de contexte
Il s'agit d'une petite bibliothèque de code qui permet de filtrer des données au
format JSON. Vous me direz, on peut déjà faire ça avec des petites fonctions
utilitaires, quelques coups de liste en compréhension. Il y a bien sûr un grand
nombre de choses que la bibliothèque ne permet pas de faire, on y reviendra,
mais concentrons-nous d'abord sur ce qu'elle permet, et les avantages qu'elle
procure.
Pour cela, je vous propose tout simplement de vous présenter l'exemple que j'ai
eu à traiter.
# Un exemple
Supposez que vous ayez comme moi une application qui écrive un journal
d'exécution au format JSON, dont les entrées sont relevées périodiquement par
un [ELK](https://fr.wikipedia.org/wiki/Elk#Sigle), et que vous ayez à analyser
une journée complète, ce qui vous donne environ 50 Mo de logs compressés en
gzip, et 700 Mo une fois décompressés. Vous rentrez ça dans un interpréteur
`ipython` et bam, 3 Go de RAM supplémentaires utilisés. (Dans les cas où vous
utilisez beaucoup de mémoire dans `ipython`, rappelez-vous que celui-ci stocke
tout ce que vous taper dans des variables nommées `_i1`, `_i2`... et les
résultats de ces opérations dans les variables correspondantes `_1`, `_2`, ce
qui peut faire que votre interpréteur consomme une très grande quantité de
mémoire, pensez à la gérer en créant vous-même des variables et en les
supprimant avec `del` si nécessaire. Mais je m'égare.)
Il peut y avoir plusieurs raisons qui font que ces logs ne seront pas
complètement homogènes :
- vous avez plusieurs applications qui fontionnent en microservices ;
- les messages comportant des exceptions ont des champs que les autres messages
plus informatifs n'ont pas ;
- _etc_.
Toujours est-il que pour analyser ce JSON, vous pouvez être dans un beau pétrin
au moment où vous vous rendez compte que chacune des petites fonctions
utilitaires que vous écrivez doit :
- gérer un grand nombre de cas ;
- gérer des cas d'erreur ;
- être facilement composable, même pour les cas d'erreur.
Je ne dis pas que ce soit infaisable, et il m'est arriver de le faire ainsi pour
certaines actions plutôt qu'en utilisant JsonPath.
Pour l'installation, c'est comme d'habitude dans le nouveau monde Python :
```bash
# dans un virtualenv Python3
pip install jsonpath2
```
# Cas pratiques
## Exemple de code n°1
Par exemple, si je souhaite obtenir toutes les valeurs du champ `message`
qu'il se trouve dans mon JSON, je peux le faire ainsi :
```python
import json
from jsonpath2.path import Path as JsonPath
with open("/path/to/json_file/file.json", mode='r') as fr:
json_data = json.loads(fr.read())
pattern = "$..message"
ls_msg = [
match.curent_value
for match in JsonPath.parse_str(pattern).match(json_data)
]
```
La variable qui nous intéresse ici, c'est `pattern`. Elle se lit ainsi :
1. `$` : racine de l'arbre analysé
2. `..` : récursion sur tous les niveaux
3. `message` : la chaîne de caractères recherchés
## Avantage
Le premier avantage que l'on voit ici, c'est la possibilité de rechercher la
valeur d'un champ quelle que soit la profondeur de ce champ dans des logs.
## Exemple de code n°2
On peut aussi raffiner la recherche. Dans mon cas, j'avais une quantité de
champs `"message"`, mais tous ne m'intéressaient pas. J'ai donc précisé que je
souhaitais obtenir les champs `"message"` seulement si le champ parent est, dans
mon cas, `"_source"` de la manière suivante :
```python
pattern = "$.._source.message"
```
Par rapport au motif précédent, le seul nouveau caractère spécial est :
4. `.` : permet d'accéder au descendant direct d'un champ.
## Avantage
L'autre avantage qu'on vient de voir, c'est la possibilité de facilement
rajouter des contraintes sur la structure de l'arbre, afin de mieux choisir les
champs que l'on souhaite filtrer.
## Exemple de code n°3
Dans mon cas, j'avais besoin de ne récupérer le contenu des champ `"message"`
que si le log sélectionné était celui associé à une exception, ce qui
correspondait à environ 1% des cas sur à peu près 600000 entrées.
Le code suivant me permet de sélectionner les `"message"` des entrées pour
lesquelles il y a un champ `"exception"` présent :
```python
pattern = "$..[?(@._source.exception)]._source.message"
```
Il y a pas mal de nouveautés par rapport aux exemples précédents :
5. `@` : il s'agit de l'élément couramment sélectionné
6. `[]` : permet de définir un prédicat ou d'itérer sur une collection
7. `?()` : permet d'appliquer un filtre
## Avantage
On peut facilement créer un prédicat simple pour le filtrage d'éléments, même
lorsque l'élément sur lequel on effectue le prédicat n'est pas le champ
recherché _in fine_.
# Au sujet de jsonpath2
Si vous êtes intéressé par le projet, je vous mets à disposition les liens
suivants (ils sont faciles à trouver en cherchant un peu sur le sujet) :
- [un article](https://goessner.net/articles/JsonPath/) présentant le filtrage
JSON d'après l'équivalent XPath pour XML ;
- [le lien vers PyPI de la bibliothèque](https://pypi.org/project/jsonpath2/)
- [le lien GitHub de la bibliothèque](https://github.com/pacifica/python-jsonpath2/)
- [le lien vers une implémentation JavaScript populaire](https://www.npmjs.com/package/jsonpath)
de JsonPath.
`jsonpath2` utilise le générateur de parseur [ANTLR](https://www.antlr.org/),
qui est un projet réputé du Pr. Terence Parr.
# Inconvénients
Parmi les prédicats qu'on peut faire, on peut tester si une chaîne de caractères
est égale à une chaîne recherchée, mais les caractères qu'on peut mettre dans
la chaîne recherchée sont assez limités : je n'ai pas essayé de faire compliqué,
seulement de rechercher des stacktraces Python ou Java, qui ont peu de
caractères spéciaux.
Il paraît qu'on peut effectuer des filtrages plus puissants avec une
fonctionnalité supplémentaire que je n'ai pas présentés parce que je n'ai pas
pris le temps de l'utiliser :
8. `()` : s'utilise afin d'exécuter des expressions personnalisées.
J'espère que tout ceci pourra vous être utile. Je vous recommande notamment de
tester vos motifs sur un petit jeu de données, on peut facilement faire des
bêtises et consommer beaucoup de mémoire et pas mal de temps sans cela.
Joyeux code !
Motius

View File

@@ -0,0 +1,55 @@
---
title: La guerre des protocoles
date: 2020-11-30
author: raspbeguy
template: post
tags: tribune,protocole,web,xmpp,réseaux sociaux,xmpp,matrix
---
J'ai hésité à appeler cet article "_Oh putain c'qu'il est blèèèème, mon HTML_" mais j'ai eu peur que Renaud vienne m'apprendre un jeu rigolo à grand coup de chaîne de vélo.
Voici un recueil de mes propres opinions qui ont lentement muri face à la mode de la réinvention de la roue. Attention, ce n'est pas un phénomène nouveau. Cependant je pense que la dernière décennie, qui a vu l'âge de la puberté d'Internet (car à mon avis Internet n'est toujours pas à l'âge adulte) a été l'occasion pour beaucoup de personnes de remettre en question des éléments essentiels de cet outil.
# La diversité c'est bien au niveau microscopique...
Qu'on se le dise : la diversité des technologies, c'est plutôt une bonne chose, en général. Cela permet de découper les piles technologiques en segments atomiques (une techno par tâche simple) et ainsi, si on n'est pas satisfait du résultat d'une techno, on la change, ou mieux, on la subdivise encore.
Prenons un exemple pratique : Hastagueule, comme la plupart des sites, est composé de 3 "briques" : un frontal, une application, un dépôt de données.
Il y a très longtemps, Apache2 s'occupait à la fois du frontal HTTP et de l'interpréteur pour l'application (Wordpress, en PHP). La pile était donc la suivante :
- Frontal (+ interpréteur application) : Apache2
- Application : Wordpress
- Dépôt de données : MySQL
Plus tard, j'ai scindé cette tâche : j'ai utilisé nginx pour faire frontal, et php-fpm pour l'interpréteur. Ensuite, j'ai changé de base de donnée en passant de MySQL à MariaDB. Plus récemment, j'ai décidé de me passer de Wordpress et de paccer à [PicoCMS](edito-11-refonte-en-profondeur) et du coup de me débarasser de la base de donnée en passant par des données en fichier texte brut, versionnées par git.
La nouvelle pile est devenue la suivante :
- Frontal : Nginx
- Interpréteur application : php-fpm
- Application : PicoCMS
- Dépôt de données : dossier versionné par git
Les briques ont pu la plupart du temps être changées sans altérer le résultat produit. Bien entendu, le changement d'application, lui, a été disruptif et a impliqué de changer le système de données également, mais cela était justement désiré.
# ... mais peut poser problème au niveau macroscopique !
On parle maintenant au niveau plus global qui est l'usage que l'on fait des piles de technologies.
Internet est un outil de communication. Des gens diffusent des contenus et se parlent entre eux. Comme dans la vie normale donc, il y a des codes, des conventions, des règles, des habitudes.
Pour que les gens gardent le fil de leur vie personnelle, ils ont tendance à adopter un nombre très limité d'outils pour s'informer et communiquer. La diversité sauvage et non coordonnée, à ce niveau, crée donc immanquablement un cloisonnement de groupes d'utilisateurs. À moins que des passerelles simples et attrayantes soient disponibles, les utilisateurs sont donc contraints à multiplier les outils pour leur survie numérique.
Chacun est libre d'utiliser ce qu'il veut, en particulier lorsqu'il est le seul impacté par ses choix. Mais avant de recréer un outil à destination d'utilisateurs finaux, il faut vraiment bien estimer la légitimité et les implications de cette action. En ce qui me concerne, une raison qui rend cette action légitime à coup sûr, c'est l'absence d'autres projets libres remplissant cette fonction.
## Gemini
[Gemini](https://gemini.circumlunar.space/) est l'élément déclencheur qui m'a amené à écrire cet article.
Au début des années 90, alors qu'Internet n'est utilisé que par une minorité d'universitaires boutonneux pour faire des truc d'intello, on voit naître à peu près simultanément au moins deux technologies de récupérer des informations navigables, sous formes d'index et de pages : HTTP en Europe, et Gopher en Amérique. Ces deux technologies vont se développer en parallèle et donner deux facettes de l'Internet navigable, celle d'HTTP sera appelée le Web. Après seulement quelques années, Gopher finit par se faire manger par HTTP pour être réduit à peau de chagrin.
Je ne vais pas taper sur Gopher. Ce protocole a au moins la légitimité d'être né en même temps que le web. Cependant, son déclin est en partie lié à son austérité.
# Navigation

View File

@@ -0,0 +1,31 @@
---
title: Microsoft, la France et données de santé
date: 2020-10-19
author: raspbeguy
template: post
tags: politique,france,sénat,pétition,gafam,souveraineté
---
Vous vous souvenez quand le gouvernement maitrisait le sujet du numérique ? Moi non plus.
On pourrait débattre longtemps sur la décision du déploiement de la 5G. On le fera probablement, mais pas tout de suite. Là maintenant c'est à propos de santé que je vais vous parler. Et non, pas de rapport avec la COVID-19.
Le gouvernement travaille depuis un certain temps sur un projet qu'on appelle [la Platerforme de Données de Santé](https://drees.solidarites-sante.gouv.fr/etudes-et-statistiques/acces-aux-donnees-de-sante/article/plateforme-des-donnees-de-sante) (en anglais _Health Data Hub_). L'idée derrière tout ça est de centraliser et rationaliser les informations de santé de toute la France, ce qui passe entre autres par y mettre un petit coup d'intelligence artificielle.
Dans l'idée ça pourrait être une bonne idée, cela permettrai d'encadrer les décisions médicales, de faciliter la recherche et de suivre la santé de la population au niveau national. C'est d'ailleurs sur cette infrastructure que s'appuie l'outil StopCovid, dans les condition et l'absence de succès que nous connaissons.
C'est un projet ambitieux sans aucun doute. C'est à ce stade que la route devient glissante. En effet il était prévu, jusque tout récemment, que cette platerforme repose sur Microsoft Azure, donc des données américaines et une entreprise suhette à nombre controverses.
Plusieurs remarques doivent venir à l'esprit :
* **Lobbying** : L'État français ne respecte pas le procédé règlementaire pour ce projet, qui impose de passer par un cahier des charges et un appel d'offre publique.
* **Communication** : Par le choix d'une plateforme étrangère et non-européenne, l'État statue que la France et l'Europe n'ont pas la compétence pour assurer techniquement un tel projet.
* **Souveraineté numérique** : Les données seraient donc sous la juridiction américaine, donc non protégées par le RGPD et soumises à toutes sortes d'atrocités juridiques comme le Cloud Act et le Patriot Act, qui permettent en gros à l'État américain de faire ce qu'il veut avec ces données.
* **Vulnérabilité des données** : Rien ne permettrait de vérifier que Microsoft ne fait pas d'usage commercial de ces données.
* **Économie** : Encore une fuite d'argent publique vers un pays étranger qui n'en a pas besoin et sans véritable raison.
La décision avait provoqué un soulèvement tant au niveau des associations et organismes de protection de la vie numérique que de l'industrie hexagonale. Octave est [fou de rage](https://twitter.com/olesovhcom/status/1267510178108375040). [Une pétition au Sénat](https://petitions.senat.fr/initiatives/i-455) a été lancée. Des sourcils ont été froncés.
Il semblerait que la polémique ait porté quelques fruits et que l'État envisage [un rétropédalage](https://www.conseil-etat.fr/actualites/actualites/health-data-hub-et-protection-de-donnees-personnelles-des-precautions-doivent-etre-prises-dans-l-attente-d-une-solution-perenne).
On a donc l'air de s'éloigner du ravin, mais on a bel et bien senti le vent du boulet. Vous pouvez toujours [signer la pétition](https://petitions.senat.fr/initiatives/i-455), juste au cas où.

View File

@@ -47,7 +47,7 @@ Contrairement à OpenVPN qui par défaut a une vision client/serveur, c'est à d
- Alice
- Carol
![](%assets_dir%/wireguard-le-vpn-sauce-kiss/wg-p2p.svg)
![](%assets_url%/wireguard-le-vpn-sauce-kiss/wg-p2p.svg)
L'ennui, c'est que si on veut ajouter une nouvelle machine Dave en respectant cette topologie, il faut récupérer les infos de toutes les autres machines (Alice, Bob et Carol) pour dresser la liste de pairs de Dave, et ensuite on doit ajouter Dave à la liste de pairs d'Alice, Bob et Carol.
@@ -61,7 +61,7 @@ De plus, pour établir une liaison entre deux pairs, il faut un point d'accès c
- Alice
- Bob
![](%assets_dir%/wireguard-le-vpn-sauce-kiss/wg-p2p.svg)
![](%assets_url%/wireguard-le-vpn-sauce-kiss/wg-p2p.svg)
Ainsi, quand on voudra ajouter Dave dans la topologie, il suffira que Dave ait connaissance de Carol dans sa liste de pairs, et il faudra dire à Carol d'ajouter Dave à ses pairs.
@@ -158,7 +158,7 @@ On est pas obligé de ne mettre que l'adresse VPN du pair. D'ailleurs, notament
Reprenons notre scénario *roadwarrior* avec Alice et Bob en pair mobile et Carol en passerelle d'accès. On définit le réseau VPN 10.0.0.0/16. D'autre part, disons que le réseau interne auquel Carol doit servir de passerelle est en 192.168.0.0/16 et contient une machine Dave.
![](%assets_dir%/wireguard-le-vpn-sauce-kiss/wg-rw.svg)
![](%assets_url%/wireguard-le-vpn-sauce-kiss/wg-rw.svg)
Carol a donc une paire de clef :