[article] add examples to dynamic enum
This commit is contained in:
parent
feee74665a
commit
cc664b5918
|
@ -11,6 +11,59 @@ 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.
|
||||
|
@ -105,8 +158,10 @@ elif state is MonEnumCustom.COMMIT:
|
|||
print("Validé")
|
||||
elif state is MonEnumCustom.SEND:
|
||||
print("Aucune modification possible")
|
||||
else:
|
||||
elif ...:
|
||||
...
|
||||
else:
|
||||
raise UnknownEnumStateValue("Code is not in sync with API")
|
||||
```
|
||||
|
||||
# Un petit plus... avec un mixin
|
||||
|
@ -140,12 +195,53 @@ class EnumMixin:
|
|||
|
||||
Avec le mixin de conversion, le code de génération d'`enum` devient :
|
||||
```python
|
||||
# ceci n'a pas changé
|
||||
# inchangé
|
||||
enum_mapping = EnumMappingHelper(d_enum_values)
|
||||
# cela si
|
||||
# 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.
|
||||
|
@ -155,6 +251,8 @@ Cela me permet de combiner les avantages de toutes les solutions à l'exception,
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue