questionpy-org / questionpy-sdk

Library and toolset for the development of QuestionPy packages
https://questionpy.org
MIT License
0 stars 2 forks source link

OptionEnum im JSON #44

Closed MHajoha closed 1 year ago

MHajoha commented 1 year ago

Damit OptionEnum-Varianten ohne extra JSON encoder als ihre Value serialisiert werden, muss OptionEnum ein str sein. Um noch label und selected reinzukriegen, gibt option() aber ein _OptionInfo-Objekt zurück. Das sollte eigentlich nur bis zu OptionEnum.__init__ leben, es stellt sich aber heraus, dass EnumMeta.__new__ sofort str(_OptionInfo(...)) aufruft, sodass der String-Wert "_OptionInfo(...)" war und das dann im JSON landete.

Um das zu lösen war es letztenendes nötig, die Metaklasse anzufassen. Da wird der _OptionInfo der Name hinzugefügt, sodass __new__ ihn verwenden kann.

TL;DR: Vorher "_OptionInfo(label=..., selected=...)", jetzt korrekt "OPT_1" im JSON.

MHajoha commented 1 year ago

Kannst du bitte irgendwo als Kommentar noch etwas genauer beschreiben, wie das funktioniert? Wer sagt dem JSON Encoder, dass der string, den er ausgeben soll, unter _OptionInfo.value steht? Oder macht Enum/EnumMeta letztendlich aus den _OptionInfo members Instanzen der eigenen Klasse OptionEnum?

Beim Schreiben des Kommentars wurde mir, glaube ich, klar, dass

    def __new__(cls, option: _OptionInfo) -> "OptionEnum":
        return super().__new__(cls, option.value)

der eigentliche Trick ist, damit der JSON Encoder die Objekte als String betrachtet und den value bekommt, oder?

Genau. Also ein class X(str, Enum) ist nichts besonderes. Wenn EnumMeta.__new__ dann y = "y" sieht, ruft es X("y") (also str("y")) auf. Bei uns steht rechts aber nunmal nicht der Name nochmal als str, sondern ein _OptionInfo-Objekt. Das schmeißt Python auch in X(_OptionInfo(...)), aber _OptionInfo enthielt halt nicht den Member-Namen, der eigentlich der String-Wert werden soll. Deshalb fügt _OptionEnumMeta den Member-Namen jetzt hinzu, sodass OptionEnum.__new__ ihn an str.__new__ geben kann.

Vielleicht kannst du das Verhalten aber im Code doch noch etwas beschreiben für den Fall, dass wir irgendwann die Klassen wieder anfassen müssen.

Ich habe OptionEnum's docstring eine Implementation Note-Sektion hinzugefügt, der das relativ detailliert beschreibt.