<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Julien Enselme personal blog - Programmation</title><link href="https://www.jujens.eu/" rel="alternate"></link><link href="https://www.jujens.eu/feeds/programmation.atom.xml" rel="self"></link><id>https://www.jujens.eu/</id><updated>2025-09-14T00:00:00+02:00</updated><entry><title>My shell config</title><link href="https://www.jujens.eu/posts/en/2025/Sep/14/my-shell-config/" rel="alternate"></link><published>2025-09-14T00:00:00+02:00</published><updated>2025-09-14T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-09-14:/posts/en/2025/Sep/14/my-shell-config/</id><summary type="html">&lt;p&gt;I discovered a lot of tools this year, most of them I now rely on daily.
So I thought it would be a nice time to share them with various custom configs I wrote.&lt;/p&gt;
&lt;p&gt;TL;DR: install &lt;tt class="docutils literal"&gt;zoxide&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;direnv&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;starship&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;atuin&lt;/tt&gt; and have this in your &lt;tt class="docutils literal"&gt;.zshrc&lt;/tt&gt; (or &lt;tt class="docutils literal"&gt;.bashrc …&lt;/tt&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I discovered a lot of tools this year, most of them I now rely on daily.
So I thought it would be a nice time to share them with various custom configs I wrote.&lt;/p&gt;
&lt;p&gt;TL;DR: install &lt;tt class="docutils literal"&gt;zoxide&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;direnv&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;starship&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;atuin&lt;/tt&gt; and have this in your &lt;tt class="docutils literal"&gt;.zshrc&lt;/tt&gt; (or &lt;tt class="docutils literal"&gt;.bashrc&lt;/tt&gt;) and enjoy a better Shell experience:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;direnv&lt;span class="w"&gt; &lt;/span&gt;hook&lt;span class="w"&gt; &lt;/span&gt;zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;starship&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Beware, this doesn’t work with Bash by default.
# See https://docs.atuin.sh/guide/installation/#installing-the-shell-plugin
&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;atuin&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;zsh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;All my configs are tracked with &lt;tt class="docutils literal"&gt;git&lt;/tt&gt; to ease sharing (mostly) and track changes.&lt;/p&gt;
&lt;p&gt;First, let’s talk about the shell.
&lt;a class="reference external" href="https://www.jujens.eu/posts/en/2021/Nov/07/from-zsh-to-fish/"&gt;I switch to Fish in 2021&lt;/a&gt; only to come back to ZSH (with &lt;a class="reference external" href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;) in 2024 because of work (long story short using Bash or ZSH like all my colleges makes things way easier for me).
Having ZSH at home too is easier since Fish and ZSH differ in some important ways in how to write functions, conditions or loops.
The gain of Fish aren’t big enough for me to use Fish only at home or to maintain work things in Fish.
The fact that I don’t do advanced things in shell often and that most of my scripts remain basic and must be Bash compatible also has a play here: most of the time I don’t need the niceties of Fish.
Furthermore, with the &lt;a class="reference external" href="https://github.com/zsh-users/zsh-autosuggestions"&gt;zsh-autosuggestions&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;zsh-syntax-highlighting&lt;/a&gt; plugins, I can have the two most useful features of Fish in my ZSH shell.
So, definitely no pressure to use Fish.&lt;/p&gt;
&lt;p&gt;This brings me to the most useful of the tools I want to present: &lt;a class="reference external" href="https://starship.rs"&gt;starship&lt;/a&gt;.
It allows you you configure your prompt (to display relevant data about git repo, Pythhon, Node, Rust…) directly in your shell.
It’s compatible with Bash, ZSH, Fish, Elvish and more!
So no matter what you are using, you can have a shell that looks the same with a very nice prompt.
It has lots of configuration options, I personally only changed the colors to make it behave better on light themes.&lt;/p&gt;
&lt;p&gt;Another very important tool to me I used for years is &lt;a class="reference external" href="https://direnv.net"&gt;direnv&lt;/a&gt;.
It can load/unload environment variables based on the directory you are in.
It can also run command once the step into the directory of a project.
For instance, it can automatically enable a Python virtual environment.
This way, you are sure to always use the proper version of Python no matter where you are!
It’s specially useful when you work on different projects.&lt;/p&gt;
&lt;p&gt;A most recent find is &lt;a class="reference external" href="https://atuin.sh"&gt;atuin&lt;/a&gt;.
It makes accessing and search your command history way easier.
I find it hard to revert to &amp;quot;normal&amp;quot; history now.
It even allows you to &lt;a class="reference external" href="https://docs.atuin.sh/guide/sync/"&gt;sync history between machines&lt;/a&gt; either by using their service or by self hosting the software.
I personally don’t use this feature: commands between my work and personal projects don’t overlap enough besides simple commands to make it useful.
And you have to trust the service with commands that potentially contains secrets (they say everything is encrypted, but you still &lt;em&gt;need&lt;/em&gt; to trust them on that).&lt;/p&gt;
&lt;p&gt;Other tools I like and use a lot (I note that most of the new ones are written in Rust and some are in Go):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;git&lt;/tt&gt;: you surely know and use it too, but &lt;a class="reference external" href="https://www.jujens.eu/static/shell/gitconfig"&gt;I have lots of aliases you might find useful&lt;/a&gt;: nice shortcuts, to easily amend a commit, to easily do a fixup commit… And many more!&lt;ul&gt;
&lt;li&gt;I also use &lt;a class="reference external" href="https://github.com/dandavison/delta"&gt;git-delta&lt;/a&gt; to have nicer output with syntax highlighting.
A must have if you ask me.
&lt;a class="reference external" href="https://www.jujens.eu/static/shell/delta/themes.gitconfig"&gt;I have light themes&lt;/a&gt; as part of my configs too.
&lt;a class="reference external" href="https://difftastic.wilfred.me.uk"&gt;difftastic&lt;/a&gt; seems like a nice alternative, but will change the output to make it more meaningful and I prefer to have the raw one.&lt;/li&gt;
&lt;li&gt;I heard of &lt;a class="reference external" href="https://mergiraf.org/introduction.html"&gt;mergiraf&lt;/a&gt; to help solve merge conflicts, but haven’t tested it.
Looks cool though!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; as a replacement of &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt;. It ignores files in &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt; by default and has overall better default behaviors.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.jedsoft.org/most/"&gt;most&lt;/a&gt; as a replacement of &lt;tt class="docutils literal"&gt;less&lt;/tt&gt;: it has better scrolling and supports binary files.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/eza-community/eza"&gt;eza&lt;/a&gt; as a modern replacement of &lt;tt class="docutils literal"&gt;ls&lt;/tt&gt;. I now even have an alias to use &lt;tt class="docutils literal"&gt;eza&lt;/tt&gt; instead of &lt;tt class="docutils literal"&gt;ls&lt;/tt&gt; if &lt;tt class="docutils literal"&gt;eza&lt;/tt&gt; is installed!&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/casey/just"&gt;just&lt;/a&gt; as a replacement of &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; without its weirdness, better Windows support and more reasonable defaults.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/sharkdp/fd"&gt;fd&lt;/a&gt; as a replacement of &lt;tt class="docutils literal"&gt;find&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/ajeetdsouza/zoxide"&gt;zoxide&lt;/a&gt; to navigate more easily in directories.
Its main feature is to enable easy nested navigation.
Let’s say you have &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/Projects/org/my-project&lt;/span&gt;&lt;/tt&gt;, do &lt;tt class="docutils literal"&gt;z &lt;span class="pre"&gt;~/Projects/org/my-project&lt;/span&gt;&lt;/tt&gt; once and then you can do &lt;tt class="docutils literal"&gt;z &lt;span class="pre"&gt;my-project&lt;/span&gt;&lt;/tt&gt; where ever you are and move to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;Projects/org/my-project&lt;/span&gt;&lt;/tt&gt; automatically.
No need to remember the full path of start navigation from home!
Note: I still use &lt;tt class="docutils literal"&gt;cd&lt;/tt&gt;, maybe just because of old habits.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/bootandy/dust"&gt;dust&lt;/a&gt; as a nice replacement of good old &lt;tt class="docutils literal"&gt;du&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://micro-editor.github.io"&gt;micro&lt;/a&gt; as a nicer CLI editor than &lt;tt class="docutils literal"&gt;nano&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;vim&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://httpie.io"&gt;httpie&lt;/a&gt; to have output coloring and formatting when I do HTTP requests in CLI.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/XAMPPRocky/tokei"&gt;tokei&lt;/a&gt; to have nice stats about code (number of files, lines…).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other articles about shell configurations that you may find useful:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://frankwiles.com/posts/my-cli-world/?featured_on=pythonbytes"&gt;My CLI World by Frank Willes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://jvns.ca/blog/2024/09/12/reasons-i--still--love-fish/"&gt;Reasons I still love the fish shell by Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://jvns.ca/blog/2025/01/11/getting-a-modern-terminal-setup/"&gt;What's involved in getting a &amp;quot;modern&amp;quot; terminal setup? by Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://treyhunner.com/2024/10/switching-from-virtualenvwrapper-to-direnv-starship-and-uv/"&gt;Switching from virtualenvwrapper to direnv, Starship, and uv by Trey Hunner&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please share your tools and configs in the comments!&lt;/p&gt;
</content><category term="Programmation"></category><category term="Linux"></category><category term="Shell"></category><category term="Bash"></category><category term="Zsh"></category></entry><entry><title>My opinion on Django Ninja</title><link href="https://www.jujens.eu/posts/en/2025/Jul/06/django-ninja/" rel="alternate"></link><published>2025-07-06T00:00:00+02:00</published><updated>2025-07-06T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-07-06:/posts/en/2025/Jul/06/django-ninja/</id><summary type="html">&lt;p&gt;If you’re using &lt;a class="reference external" href="https://www.djangoproject.com"&gt;the Django web framework (DRF)&lt;/a&gt; and are building APIs, you probably know about &lt;a class="reference external" href="https://www.django-rest-framework.org"&gt;the Django Rest Framework&lt;/a&gt; a toolkit to build APIs in Django since more than a decade (the first 3.x version was release in 2014).
It’s great and I’ve used it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you’re using &lt;a class="reference external" href="https://www.djangoproject.com"&gt;the Django web framework (DRF)&lt;/a&gt; and are building APIs, you probably know about &lt;a class="reference external" href="https://www.django-rest-framework.org"&gt;the Django Rest Framework&lt;/a&gt; a toolkit to build APIs in Django since more than a decade (the first 3.x version was release in 2014).
It’s great and I’ve used it professionally to build several APIs.
It’s also complex and showing its age when compared to what can be done in &lt;a class="reference external" href="https://flask.palletsprojects.com/"&gt;Flask&lt;/a&gt; or &lt;a class="reference external" href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily, there’s a new possibility in Django’s space: &lt;a class="reference external" href="https://github.com/vitalik/django-ninja"&gt;django-ninja&lt;/a&gt;.
It reached 1.0 in 2023 and was first release in 2020.
It’s heavily inspired by FastAPI, relies on &lt;a class="reference external" href="https://pydantic.dev"&gt;Pydantic&lt;/a&gt; for payload validation and supports async out of the box.
It’s also easier to setup.
That’s why I decided to use it on one of my personal projects which needed an API.
After about a year, I’d like to give my opinion on it.&lt;/p&gt;
&lt;p&gt;Please keep in mind that I only built a small API with it.
I haven’t used it yet on bigger projects, so I can’t say much about this use case.
I think it’s ready and mature enough and would definitely try it over good old DRF, but you may reach for someone else’s opinion if you plan to use it on a big project.&lt;/p&gt;
&lt;p&gt;Overall, I’m pleased and very impressed by &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-ninja&lt;/span&gt;&lt;/tt&gt;.
The biggest selling point for me is Pydantic.
This library is awesome and used more and more.
I already use it extensively at work.
I also use it in my project to validate the input of JSON data stored in the database and to parse some JSON files.
Being able to also use it for my API is a big plus for me: use cases are very similar and having only one library to learn is a breeze.&lt;/p&gt;
&lt;p&gt;Regarding the creation of the API itself, it’s a bunch of decorated and type annotated functions.
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-ninja&lt;/span&gt;&lt;/tt&gt; takes care of the rest like validating POST payload or mapping GET parameters to function parameters.
It even generate the documentation automatically.
On that regard, it’s indeed very close to how FastAPI works.
Simple and yet efficient.
It seems to lack the ability to use class based views, which could be detrimental to grouping some functions.&lt;/p&gt;
&lt;p&gt;I also like the fact that to change the HTTP response code, all you have to do is return a tuple of &lt;tt class="docutils literal"&gt;status_code, response&lt;/tt&gt; like in Flask.&lt;/p&gt;
&lt;p&gt;I’m a bit surprised it doesn’t handle more things &lt;a class="reference external" href="https://django-ninja.dev/guides/authentication/"&gt;regarding authentication&lt;/a&gt;.
It supports Django’s auth out of the box, but it’s based on cookies.
So it’s only useful to debug the API in your browser.
For an API key, HTTP Bearer or HTTP Basic auth, it gives you a base class you can subclass to implement your custom method.
I choose to use a JWT token passed in a header.
I guess it’s not more integrated because there are so many options available and possible links with the user model that what you’d need would be there anyway.&lt;/p&gt;
&lt;p&gt;While I’m very pleased with what &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-ninja&lt;/span&gt;&lt;/tt&gt; has to offer, I encountered a few issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;I have &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"&gt;Content Security Policy (CSP)&lt;/a&gt; with nonce enabled on my site thanks to &lt;a class="reference external" href="https://django-csp-test.readthedocs.io/en/latest/"&gt;django-csp&lt;/a&gt;.
Because of this, the JS and CSS loaded by the docs couldn’t be loaded because no nonce was applied in the doc templates.
I solved it by copying the &lt;tt class="docutils literal"&gt;swagger.html&lt;/tt&gt; template and adding &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nonce=&amp;quot;{{&lt;/span&gt; request.csp_nonce }}&amp;quot;&lt;/tt&gt; were the static files are loaded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;When not authenticated and targeting the API with &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt;, I got some CSRF error.
Making the &lt;tt class="docutils literal"&gt;HttpBearer&lt;/tt&gt; auth the default (ie before &lt;tt class="docutils literal"&gt;django_auth&lt;/tt&gt; in the list of auth providers) solved the problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;On some endpoints, I allow the &lt;tt class="docutils literal"&gt;PATCH&lt;/tt&gt; HTTP method to be used to update only the supplied fields.
Initially, I used &lt;a class="reference external" href="https://django-ninja.dev/guides/response/django-pydantic/?h=patchdi#patchdict"&gt;PatchDict&lt;/a&gt; provided by &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-ninja&lt;/span&gt;&lt;/tt&gt;.
Sadly, given how it’s implemented, it allowed some fields to be set to None in the payload without validation error when these fields are not nullable.
This caused errors at the database level when the API tried to save these models.
What I ended up doing to solve this is:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create a custom type named &lt;tt class="docutils literal"&gt;NotSet&lt;/tt&gt; integrated in Pydantic thanks to the &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; class method.
It takes the underlying type as an argument to display beautiful default values in the documentation.
You can &lt;a class="reference external" href="https://github.com/Jenselme/legadilo/blob/6746516ed9632a864b7f3b1c5ac04839f6abb4af/legadilo/utils/api.py#L18"&gt;view its implementation here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a custom update function that skips values set to &lt;tt class="docutils literal"&gt;NotSet&lt;/tt&gt; when updating the Django model.
You can &lt;a class="reference external" href="https://github.com/Jenselme/legadilo/blob/6746516ed9632a864b7f3b1c5ac04839f6abb4af/legadilo/utils/api.py#L33"&gt;view its implementation here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use the type like this &lt;tt class="docutils literal"&gt;read_at: datetime | SkipJsonSchema[NotSet] | None = NotSet(datetime.now)&lt;/tt&gt; in my Ninja schemas.
The &lt;tt class="docutils literal"&gt;SkipJsonSchema&lt;/tt&gt; prevents the type to be mentioned in the documentation since it’s a technical type users can’t use directly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It’s not as easy as I’d hoped, but it works and I think it’s an acceptable work around, at least for my needs.
There’s probably a way to make this more generic and reusable on existing schemas, but I don’t need that right now.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To conclude, I’m glad this solution exists and I think I have a cleaner, more modern and more simple way to create APIs than DRF.
I had some issues and spent time in the documentation at first, but nothing weird since it’s a new library for me.
From what I’ve seen and after my usage, it seems like a powerful, feature complete solution to build API with Django.
I hope I’ll be able to use on a bigger project!&lt;/p&gt;
&lt;p&gt;As usual, I’d be delighted to hear your opinion in the comments below.&lt;/p&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Django"></category><category term="Web"></category></entry><entry><title>My opinions on HTMX</title><link href="https://www.jujens.eu/posts/en/2025/Jun/24/htmx/" rel="alternate"></link><published>2025-06-24T00:00:00+02:00</published><updated>2025-06-24T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-06-24:/posts/en/2025/Jun/24/htmx/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://htmx.org/"&gt;HTMX&lt;/a&gt; is a JavaScript library designed to add interactivity to your web pages.
With it, you don’t build a single page application, you use standard templates (written in Django, Jinja) rendered in your backend, add attributes to your HTML elements and let HTMX add the appropriate interactivity.
From what …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://htmx.org/"&gt;HTMX&lt;/a&gt; is a JavaScript library designed to add interactivity to your web pages.
With it, you don’t build a single page application, you use standard templates (written in Django, Jinja) rendered in your backend, add attributes to your HTML elements and let HTMX add the appropriate interactivity.
From what I read, it’s getting lots of popularity in the Django world.
That’s why I heard of it in the first place and one of the reason I choose it.&lt;/p&gt;
&lt;p&gt;There are alternatives like &lt;a class="reference external" href="https://alpinejs.dev"&gt;Alpine.js&lt;/a&gt; or &lt;a class="reference external" href="https://turbo.hotwired.dev"&gt;Turbo&lt;/a&gt;.
After looking at their documentation and examples, I had the feeling HTMX is better suited for what I was building.
HTMX also seems more popular and integration in Django is made easy thanks to &lt;a class="reference external" href="https://django-htmx.readthedocs.io/en/latest/"&gt;django-htmx&lt;/a&gt;.
It’s also quite small (at least compared to the cost of a SPA): only 17.5kb once minified and gzipped.
It should be almost all the JS you need when you build something with it: if you need way more, switching to a SPA could make more sense.
Likewise, if you need to build an app or handle complex state, a true SPA might be easier to maintain in the long run.&lt;/p&gt;
&lt;p&gt;I think the library is powerful and relatively easy to use: all you have to do is include it and add the proper attributes.
It also has &lt;a class="reference external" href="https://htmx.org/examples/"&gt;lots of examples&lt;/a&gt; to help you get started.
Pretty much everything I needed to use already had an example associated with it.&lt;/p&gt;
&lt;p&gt;In my case, I use it in an article reading application: the backend fetches RSS feeds and displays the fetched articles on a page.
The articles are grouped into various reading lists.
To improve UX, when a user clicks on the &amp;quot;Mark as read&amp;quot; button, I don’t want a full page reload.
That’s where HTMX comes in!&lt;/p&gt;
&lt;p&gt;With HTMX, I can create a form and submit it to mark the article as read without this page reload.
To do this, adding &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hx-boost&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hx-swap&lt;/span&gt;&lt;/tt&gt; to the form is enough to make HTMX handle the submission and handle the HTML from the response.
A nice feature here is that you can update multiple part of the page in one go: the element associated with the update and something else thanks to a process named Out of Band Content swapping (oob for short): instead of just returning the element you need to swap, you can return multiple and HTMX will swap them all.
In my case: the article card and the unread count are updated at the same time thanks to this process.&lt;/p&gt;
&lt;p&gt;Two features required a bit more work to make them work correctly.&lt;/p&gt;
&lt;p&gt;The first one is a feature, which when enabled, allows to read articles on scroll to easily scan big chunks of articles and only open those that interest you.
This required some custom JS to correctly detect scroll end, wait a few seconds without interactions to trigger the action (to make sure the user won’t scroll up), find the articles that are not visible anymore and mark them all as read.
It required some digging into the documentation to be able to hook on htmx and use its JavaScript API correctly.
In the end, it worked really well!&lt;/p&gt;
&lt;p&gt;The second one, is when users try to delete an article: I wanted to display a pretty modal and not a basic JS alert.
Part of the problem comes from the fact that I have multiple possible action buttons and only for some I want the confirm modal to be triggered.
This isn’t supported out of the box.&lt;/p&gt;
&lt;p&gt;What I ended up doing was have multiple forms and use the &lt;tt class="docutils literal"&gt;form&lt;/tt&gt; attribute of buttons to select which form the buttons must submit.
This way, I can have a delete form with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hx-confirm&lt;/span&gt;&lt;/tt&gt; (use to display an alert or run a custom action on a click) and only open the modal in that case.
As far as I can remember, putting &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hx-confirm&lt;/span&gt;&lt;/tt&gt; on the button directly didn’t work (I don’t remember the exact behavior in that case).
To have this work in all cases, I had to setup a custom event listeners on &lt;tt class="docutils literal"&gt;htmx:confirm&lt;/tt&gt; to capture the event and run some custom processing code.
I also had to make sure the name of the button was correctly transmitted in the request which wasn’t done automatically by HTMX in that case for some reason.
The full code is available &lt;a class="reference external" href="https://github.com/Jenselme/legadilo/blob/f76cde94a26fe8191d7ea7a1439ac130a2773b05/legadilo/static/js/base.js"&gt;here&lt;/a&gt; if you need more details.&lt;/p&gt;
&lt;p&gt;To write the proper solution, I had to debug HTMX.
It’s a pain as in any library you don’t know but not a very big deal.
In Django debug mode, I load the unminified version of the library and from there, I was able to add breakpoints from the browser tools and see what was happening.
I had to do it another time when I tried an HTML minification library which broke HTMX completely.
It turns out, it removed &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;type=&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;/tt&gt; attributes since it’s the default, but HTMX requires them to boost forms.&lt;/p&gt;
&lt;p&gt;The library is great and I’m very pleased by it.
There are however, as with any tech, some downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;HTMX behaviors can only be tested with end to end tests.
Not much of an issue for me: they don’t change much and can be tested manually.
It can be a problem on a bigger app, but I think it’s also hard to mess up something the smallest amount of manual testing would catch: you are only adding HTML attributes after all, the rest is handled by HTMX itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;I think you &lt;em&gt;must&lt;/em&gt; enable CSP (Content Security Policy) to make sure no HTML coming from an untrusted source is used by HTMX to swap elements and thus execute JavaScript.
You should to it anyway, HTMX or not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;If you only enhance links and forms with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hx-boost&lt;/span&gt;&lt;/tt&gt;, your application will work without JS since browser have a fallback.
If you do other things, JS becomes required since without it some behaviors will break.
Not a very big deal in my opinion since all your users should have JS enabled.
In my case, some menus managed by &lt;a class="reference external" href="getbootstrap.com"&gt;Bootstrap&lt;/a&gt; won’t open without JS, making the navigation in the app a pain anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Since these attributes are specific to HTMX, I guess there is a form of vendor lock-in.
But the attributes are easy to spot and since the alternatives also rely on attributes, I guess a search/replace would do most of the migration.
It’s still way easier than changing SPA frameworks since it does way less!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;The documentation promotes behaviors that are not accessible by default.
To quote it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why should only &amp;lt;a&amp;gt; &amp;amp; &amp;lt;form&amp;gt; be able to make HTTP requests?&lt;/p&gt;
&lt;p&gt;Why should only click &amp;amp; submit events trigger them?&lt;/p&gt;
&lt;p&gt;Why should only GET &amp;amp; POST methods be available?&lt;/p&gt;
&lt;p&gt;Why should you only be able to replace the entire screen?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I guess it’s up to you to make sure that what you do is accessible, just like with a SPA and use the proper attributes for accessibility.
The examples could also promote accessibility more.
&lt;a class="reference external" href="https://htmx.org/examples/bulk-update/"&gt;Some already do&lt;/a&gt;, without explaining it enough in my taste, but most don’t.
I think that accessibility is in such a bad place, that having all the official examples promote best practices would be a huge help.&lt;/p&gt;
&lt;p&gt;According to &lt;a class="reference external" href="https://news.ycombinator.com/item?id=42616692"&gt;this hacker news thread&lt;/a&gt;, the maintainers are aware of this and may improve the situation.
There are also &lt;a class="reference external" href="https://news.ycombinator.com/item?id=42616692"&gt;one issue about the accessibility implication of HTMX&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/bigskysoftware/htmx/issues/1431"&gt;one about improving the examples&lt;/a&gt; and there seems &lt;a class="reference external" href="https://htmx.org/examples/move-before/"&gt;to have plans&lt;/a&gt; to use the new &lt;tt class="docutils literal"&gt;moveBefore&lt;/tt&gt; API to improve the situation (this API is &lt;a class="reference external" href="https://caniuse.com/?search=moveBefore"&gt;only available in Chrome&lt;/a&gt; and only since the beginning of the year).&lt;/p&gt;
&lt;p&gt;To be fully honest, I don’t think my project is fully accessible.
As a backend developer, I don’t know enough to see the issue (and I’m probably far from the only one if I trust my experience and the reports regarding the poor state of web accessibility).
That’s also why, I rely as much as possible on forms with &lt;a class="reference external" href="https://htmx.org/docs/#progressive_enhancement"&gt;progressive enhancement&lt;/a&gt; and use Bootstrap which have an accessibility team.
No matter the lib, most examples I see don’t even mention it.
That’s not only an HTMX problem.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In conclusion, HTMX adds some complexity compared to basic pages, but it remains manageable.
I’m happy with the lib and the fact it allows me to write most of my logic in Python and Django with standard HTML and almost no custom JavaScript.
Now that the heavy lifting is done, it’s been months since I even had to touch HTMX features.
Almost all my logic lives in the backend which feels easier and make me more productive: no language or context juggling.
Plus, all the behaviors are defined in the HTML with the rest of the attributes.
Depending on what you are building, it can be a good and more simple alternative to a full fledged SPA.
It also gives me some memories of when I was a student and was building websites without a JS framework: use of plain JS files to extend your site’s features.
It was a mess back then, even with the help of jQuery.
Now, it feels clean and easy!&lt;/p&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Django"></category><category term="Web"></category><category term="JavaScript"></category></entry><entry><title>Simply how licenses are applied to your project with SPDX and reuse</title><link href="https://www.jujens.eu/posts/en/2025/Jun/22/reuse-licenses/" rel="alternate"></link><published>2025-06-22T00:00:00+02:00</published><updated>2025-06-22T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-06-22:/posts/en/2025/Jun/22/reuse-licenses/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://spdx.dev"&gt;SPDX (Software Package Data Exchange)&lt;/a&gt; is a project managed by the Linux foundation created to standardize how licenses are identified in a human and machine readable way.
In a nutshell, instead of adding a big header to your files to identify the applicable license, you apply a copyright text and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://spdx.dev"&gt;SPDX (Software Package Data Exchange)&lt;/a&gt; is a project managed by the Linux foundation created to standardize how licenses are identified in a human and machine readable way.
In a nutshell, instead of adding a big header to your files to identify the applicable license, you apply a copyright text and a license identifier.
For a Python file under the GPL, instead of this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{{ project }}
Copyright (C) {{ year }}  {{ organization }}

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
&lt;/pre&gt;
&lt;p&gt;It would give this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# SPDX-FileCopyrightText: {{ year }} {{ organization }}
#
# SPDX-License-Identifier: GPL-3.0-or-later
&lt;/pre&gt;
&lt;p&gt;It’s shorter and more readable (at least in my opinion).
And a script can easily read this header and compile a list of all used licenses and to each file they apply.&lt;/p&gt;
&lt;p&gt;You can go &lt;a class="reference external" href="https://spdx.org/licenses/"&gt;to the list of licenses&lt;/a&gt; to find the identifier and text of the licenses you want to use.
Apparently, these identifiers have been around for several years (at least 2021) and are already used by big projects like &lt;a class="reference external" href="https://community.kde.org/Guidelines_and_HOWTOs/Licensing"&gt;KDE&lt;/a&gt;, &lt;a class="reference external" href="https://www.qt.io/blog/switching-to-spdx"&gt;Qt&lt;/a&gt; or &lt;a class="reference external" href="https://fedoraproject.org/wiki/Changes/SPDX_Licenses_Phase_1"&gt;Fedora&lt;/a&gt;.
I personally only learned about them earlier this year and dug into the subject this month.&lt;/p&gt;
&lt;p&gt;So far, I thought is was interesting, but wouldn’t impact my projects too much: I’d only have to switch one header for another.
That’s until I learned about &lt;a class="reference external" href="https://reuse.software/"&gt;the REUSE tool&lt;/a&gt; by the FSFE in &lt;a class="reference external" href="https://fedoramagazine.org/beginners-guide-for-open-source-developers-for-software-licensing-with-fsfe-reuse/"&gt;Fedora magazine&lt;/a&gt;.
The goal of this tool is to help you choose licenses, download their full text and add the proper header to your files.
For files that cannot have a license header, it can create a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;FILE_NAME&amp;gt;.license&lt;/span&gt;&lt;/tt&gt; file or define the license in a &lt;tt class="docutils literal"&gt;REUSE.toml&lt;/tt&gt; file.
Once this is done, it can enforce that all your files have a license.&lt;/p&gt;
&lt;p&gt;It’s easy to setup (including in pre-commit hooks) and comes with &lt;a class="reference external" href="https://reuse.software/tutorial/"&gt;a nice tutorial&lt;/a&gt; and &lt;a class="reference external" href="https://reuse.software/faq/"&gt;FAQ&lt;/a&gt; to help you get started.
The catch is that it can be a bit time consuming to enforce at first to find the proper licenses to apply to each file.
Luckily, it can add headers to multiple files at the same time.
This tool is used by KDE, Linux, Nextcloud and Curl!&lt;/p&gt;
&lt;p&gt;In my case, I used it in one of my GNU AGLP licensed project.
It contained many files of different types, some with a proper AGPL header, some without.
And others were copy/pasted from the internet and thus the AGPL with my name in the copyright didn’t apply.
So I had to review for each file what to do and apply the header with the proper copyright text in bulk of files.
At least, now it’s done and files I don’t have the copyright on but I can use in my project (because they are published on a free software license like MIT) are clearly identified.&lt;/p&gt;
&lt;p&gt;I wandered whether I should use only the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;SPDX-FileCopyrightText&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;SPDX-License-Identifier&lt;/span&gt;&lt;/tt&gt; in the header or keep the full GNU AGPL copyright statement.
According to the GNU AGLP full text, this header is only a strong suggestion to help readers identify the license of the file.
So nothing mandatory on that side and I think the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;SPDX-License-Identifier&lt;/span&gt;&lt;/tt&gt; fills that role too even if it’s less clear since it doesn’t give any explanation nor explicitly state the absence of warranty.
After looking a bit in the &lt;a class="reference external" href="https://code.qt.io/cgit/qt/"&gt;Qt&lt;/a&gt;, &lt;a class="reference external" href="https://invent.kde.org/explore/groups?sort=name_asc"&gt;KDE&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/fsfe/reuse-tool/"&gt;REUSE&lt;/a&gt; repositories, which all use some variants of the GNU GLP licenses, I saw only the SPDX identifier and not the classical headers, I guess it’s fine.
It’s also much better than most programs under non GLP licenses which tend to have no header at all!
I also searched the internet for this but couldn’t find anything useful.
If you have more information and this, please let me know in the comments below.&lt;/p&gt;
&lt;p&gt;Some interesting articles I found on this subject:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/david-a-wheeler/spdx-tutorial/blob/master/README.md"&gt;SPDX Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://fedoramagazine.org/beginners-guide-for-open-source-developers-for-software-licensing-with-fsfe-reuse/"&gt;Making sense of software licensing with FSFE REUSE: A beginner’s guide for open source developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://fossa.com/blog/understanding-using-spdx-license-identifiers-license-expressions/"&gt;Understanding and Using SPDX License Identifiers and License Expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.fedoraproject.org/en-US/legal/spdx/#%5C_types_of_spdx_license_expressions"&gt;SPDX License Expressions&lt;/a&gt; in my opinion it has the best explanation of the &lt;tt class="docutils literal"&gt;AND&lt;/tt&gt; operator.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Programmation"></category></entry><entry><title>Promise returned by async functions</title><link href="https://www.jujens.eu/posts/en/2025/May/24/promise-async-functions/" rel="alternate"></link><published>2025-05-24T00:00:00+02:00</published><updated>2025-05-24T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-05-24:/posts/en/2025/May/24/promise-async-functions/</id><summary type="html">&lt;p&gt;Recently at work, I encountered a problem with a function that should have returned a custom promise subclass.
The goal of this subclass is to be able to call a &lt;tt class="docutils literal"&gt;cancel&lt;/tt&gt; method to cancel some pending action done within the promise and then reject it.
It looks like this:&lt;/p&gt;
&lt;pre class="code JavaScript literal-block"&gt;
&lt;span class="kd"&gt;class …&lt;/span&gt;&lt;/pre&gt;</summary><content type="html">&lt;p&gt;Recently at work, I encountered a problem with a function that should have returned a custom promise subclass.
The goal of this subclass is to be able to call a &lt;tt class="docutils literal"&gt;cancel&lt;/tt&gt; method to cancel some pending action done within the promise and then reject it.
It looks like this:&lt;/p&gt;
&lt;pre class="code JavaScript literal-block"&gt;
&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CancellablePromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Canceling&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// Actual operation&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Unlike what I expected, I didn’t get a &lt;tt class="docutils literal"&gt;CancelablePromise&lt;/tt&gt; but a standard &lt;tt class="docutils literal"&gt;Promise&lt;/tt&gt; object.
I initially thought it came from the chain: I didn’t stored the return promise directly but the output of a &lt;tt class="docutils literal"&gt;.catch&lt;/tt&gt; method like this:&lt;/p&gt;
&lt;pre class="code JavaScript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;myPromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buildCancellable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Oops&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;After running some tests, it turns out that chaining from a custom promise class will return an instance of the custom promise class.
So the culprit wasn’t there.
I continued debugging and acquired the certainty that &lt;tt class="docutils literal"&gt;buildCancellable&lt;/tt&gt; didn’t return the custom class at all in the first place.
I’m also certain it used to.&lt;/p&gt;
&lt;p&gt;It turns out the &lt;tt class="docutils literal"&gt;buildCancellable&lt;/tt&gt; function was made &lt;tt class="docutils literal"&gt;async&lt;/tt&gt; a few months ago.
Instead of being defined like this:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buildCancellable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CancellablePromise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It’s now defined like this:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buildCancellable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Stuff using await&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CancellablePromise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It turns out that returned values of &lt;tt class="docutils literal"&gt;async&lt;/tt&gt; function are always wrapped in a proper &lt;tt class="docutils literal"&gt;Promise&lt;/tt&gt; even if they are themselves already a &lt;tt class="docutils literal"&gt;Promise&lt;/tt&gt;.
It has a very little impact on most usage since nested promise are automatically unwrapped, so we have this behavior:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;innerPromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Resolving inner promise&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Inner value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outerPromise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;resolving outer promise&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerPromise&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;outerPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// Will log &amp;quot;Inner value&amp;quot; as expected.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The value:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;But it made the &lt;tt class="docutils literal"&gt;cancel&lt;/tt&gt; method non reachable since the actual &lt;tt class="docutils literal"&gt;CancellablePromise&lt;/tt&gt; cannot be reached now.
The only solution is to remove the &lt;tt class="docutils literal"&gt;async&lt;/tt&gt; keywords and remove &lt;tt class="docutils literal"&gt;await&lt;/tt&gt; from the body.&lt;/p&gt;
&lt;p&gt;I hope you found this reminder useful.
I did have an &lt;em&gt;of course&lt;/em&gt; moment once I figured it out, but was a bit lost at the start.
I really expected to get the proper object back (and I still think it’s reasonable to expect &lt;tt class="docutils literal"&gt;Promise&lt;/tt&gt; to not be wrapped again) since it’s already a &lt;tt class="docutils literal"&gt;Promise&lt;/tt&gt;.
I guess it’s best for consistency to have JS behave this way.
And it’s transparent in almost all cases anyway.&lt;/p&gt;
</content><category term="Programmation"></category><category term="JavaScript"></category><category term="TypeScript"></category></entry><entry><title>Using Pydantic models within enums</title><link href="https://www.jujens.eu/posts/en/2025/Apr/26/pydantic-enums/" rel="alternate"></link><published>2025-04-26T00:00:00+02:00</published><updated>2025-04-26T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-04-26:/posts/en/2025/Apr/26/pydantic-enums/</id><summary type="html">&lt;p&gt;In previous articles, I provided &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/12/python-enums/"&gt;some tips on enums&lt;/a&gt; and explained
&lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/19/using-custom-classes-pydantic/"&gt;how to use a custom class with Pydantic&lt;/a&gt;. Now, I’ll dig into all
kinds of enum usages with Pydantic, including enums whose members are
Pydantic models themselves! Let’s dive right in!&lt;/p&gt;
&lt;p&gt;Just like before, you can access …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In previous articles, I provided &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/12/python-enums/"&gt;some tips on enums&lt;/a&gt; and explained
&lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/19/using-custom-classes-pydantic/"&gt;how to use a custom class with Pydantic&lt;/a&gt;. Now, I’ll dig into all
kinds of enum usages with Pydantic, including enums whose members are
Pydantic models themselves! Let’s dive right in!&lt;/p&gt;
&lt;p&gt;Just like before, you can access the code of this article &lt;a class="reference external" href="https://www.jujens.eu/static/pydantic-enums/pydantic_enums.py"&gt;as a Python
script&lt;/a&gt; and &lt;a class="reference external" href="https://www.jujens.eu/static/pydantic-enums/pydantic_enums.ipynb"&gt;as a notebook&lt;/a&gt;. I won’t include the imports in
the code examples for brevity, refer to the notebook or the script if
needed.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#using-normal-enums" id="toc-entry-1"&gt;Using normal enums&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#with-different-types-in-the-enum" id="toc-entry-2"&gt;With different types in the enum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#using-enums-with-pydantic-models" id="toc-entry-3"&gt;Using enums with Pydantic models&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#how-does-this-behave-by-default" id="toc-entry-4"&gt;How does this behave by default?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#creating-a-custom-enum-base-class" id="toc-entry-5"&gt;Creating a custom Enum base class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#alternative-with-annotations" id="toc-entry-6"&gt;Alternative with annotations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#logic-into-the-model-thats-the-member-of-the-enum" id="toc-entry-7"&gt;Logic into the model that’s the member of the enum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#putting-the-logic-in-the-leaf-model" id="toc-entry-8"&gt;Putting the logic in the leaf model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#conclusion" id="toc-entry-9"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#bonus" id="toc-entry-10"&gt;Bonus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="using-normal-enums"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Using normal enums&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s start small with how Pydantic behaves with normal enums.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IntValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StrValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;valid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;invalid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NormalEnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# False is the default. If True, will use 1 or &amp;quot;valid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# in the model instead of the enum members.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_enum_values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;int_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IntValues&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;str_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StrValues&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NormalEnumModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;int_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;str_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;valid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The model:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dumped in Python mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dumped in JSON mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
The model: int_value=&amp;lt;IntValues.first: 1&amp;gt; str_value=&amp;lt;StrValues.valid: 'valid'&amp;gt;
Dumped in Python mode: {'int_value': &amp;lt;IntValues.first: 1&amp;gt;, 'str_value': &amp;lt;StrValues.valid: 'valid'&amp;gt;}
Dumped in JSON mode: {'int_value': 1, 'str_value': 'valid'}
&lt;/pre&gt;
&lt;p&gt;Each field is correctly parsed as an enum member. When dumping in JSON
mode, the value is used and the member remains in Python mode. I say
this behaves as expected.&lt;/p&gt;
&lt;p&gt;Once again, as expected, values outside the enum will raise a
&lt;tt class="docutils literal"&gt;ValidationError&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Values outside the enum, will fail!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;NormalEnumModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;int_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;str_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nope&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failing as expected with values outside the enum with:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Failing as expected with values outside the enum with:
2 validation errors for NormalEnumModel
int_value
  Input should be 1 or 2 [type=enum, input_value=10, input_type=int]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/enum"&gt;https://errors.pydantic.dev/2.11/v/enum&lt;/a&gt;
str_value
  Input should be 'valid' or 'invalid' [type=enum, input_value='nope', input_type=str]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/enum"&gt;https://errors.pydantic.dev/2.11/v/enum&lt;/a&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s illustrate the behavior when forcing Pydantic to use enum values
instead of enum members with &lt;tt class="docutils literal"&gt;use_enum_values=True&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModelWithValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_enum_values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;int_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IntValues&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;str_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StrValues&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModelWithValues&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;int_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;str_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;valid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The model:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dumped in Python mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dumped in JSON mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
The model: int_value=1 str_value='valid'
Dumped in Python mode: {'int_value': 1, 'str_value': 'valid'}
Dumped in JSON mode: {'int_value': 1, 'str_value': 'valid'}
&lt;/pre&gt;
&lt;p&gt;This time, the values is used in the model and no matter how it’s
dumped.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="with-different-types-in-the-enum"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;With different types in the enum&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the basics are covered, let’s use more complex enums. Let’s
start to see what happens if we try to mix types in &lt;tt class="docutils literal"&gt;IntEnum&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;StrEnum&lt;/tt&gt;. If Python can parse a type automatically in &lt;tt class="docutils literal"&gt;IntEnum&lt;/tt&gt; it
will do so:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WithIntAndIntAsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;member1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# This will be parse automatically by Python.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;member2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;10&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;member1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WithIntAndIntAsString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;WithIntAndIntAsString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;member2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WithIntAndIntAsString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;WithIntAndIntAsString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
member1 &amp;lt;class 'int'&amp;gt; 1
member2 &amp;lt;class 'int'&amp;gt; 10
&lt;/pre&gt;
&lt;p&gt;Otherwise, it will raise a &lt;tt class="docutils literal"&gt;TypeError&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MoreStringValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;member1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;member1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Python won’t cast anything to int.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failing with error:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Failing with error: 10 is not a string
&lt;/pre&gt;
&lt;p&gt;If we need to mix types, we can use a base &lt;tt class="docutils literal"&gt;Enum&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultipleBasicTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;member1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;member1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;member2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MultipleBasicTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MultipleBasicTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;class 'str'&amp;gt; &amp;lt;class 'int'&amp;gt;
&lt;/pre&gt;
&lt;p&gt;If you need to enforce a consistent type within your enums, please refer
to of &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/12/python-enums/"&gt;my previous article&lt;/a&gt; and the &lt;tt class="docutils literal"&gt;enforce_member_type&lt;/tt&gt; decorator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-enums-with-pydantic-models"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Using enums with Pydantic models&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we know how enums behave with Pydantic, let’s dive the heart of
this article: enums with Pydantic models as members and their usage in
other Pydantic models! I’ll detail several possible strategies here and
discuss the pros and cons of each one. You’ll probably have questions or
remarks on these (and maybe even new methods), so don’t hesitate to use
the comment section.&lt;/p&gt;
&lt;p&gt;I’ll use an example to make things less abstract. I’ll leave your
imagination work to find useful use-cases. You can also just see it as a
way to have fun with Python and Pydantic.&lt;/p&gt;
&lt;p&gt;Let’s imagine that you are running a blog platform. Each article on the
platform is associated with a category. A category could be represented
with this Pydantic model:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# The id of the category in the database&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# A unique human-readable code identifying the category.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# The title to display to the users.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s say that for editorial reasons the list of categories is fixed. So
this list can fit into an enum. The ids and the codes are set within the
enum to ease referencing the categories in the codebase without needing to
query the database. This will help to create new categories in the
database when you add some. All you’ll have to do is loop over the
members of the enum.&lt;/p&gt;
&lt;p&gt;We’ll also assume it allows great simplification in
you code to be able to hard code the id of the category directly. The
enum makes this clean and readable. It will also make your JSON API more
simple to use for your users. To create an article, they can use the
human readable code of the category instead of the id. They can send
this:&lt;/p&gt;
&lt;pre class="code json literal-block"&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;to get this back:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;No need to bother them with ids!&lt;/p&gt;
&lt;p&gt;Let’s start this journey by creating the &lt;tt class="docutils literal"&gt;ArticleCategory&lt;/tt&gt; enum:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cooking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;programming&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;You’ll then be able to mention this in you article model:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# id of the article in the database.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Allow None in the model to enable user to create an article in the API.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Since ids don’t bring anything for this article, I’ll omit them from now on to make the examples a bit more simple.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-does-this-behave-by-default"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;How does this behave by default?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s see:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
id=None category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
&lt;/pre&gt;
&lt;p&gt;Our &lt;tt class="docutils literal"&gt;category&lt;/tt&gt; field is a member of the enum as expected.&lt;/p&gt;
&lt;p&gt;What about a call to &lt;tt class="docutils literal"&gt;model_dump&lt;/tt&gt;?&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dump in Python mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dump in JSON mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Dump in Python mode: {'id': None, 'category': &amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;, 'title': 'Fun with Pydantic'}
Dump in JSON mode: {'id': None, 'category': {'id': 2, 'code': 'programming', 'title': 'Programming'}, 'title': 'Fun with Pydantic'}
&lt;/pre&gt;
&lt;p&gt;If we dump for Python, the member of the enum is preserved and as JSON
our model is serialized. So far, it’s only standard Pydantic stuff.&lt;/p&gt;
&lt;p&gt;What if we pass a new &lt;tt class="docutils literal"&gt;Category&lt;/tt&gt; instance? Let’s start with one equal
to a member of the enum:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
id=None category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
&lt;/pre&gt;
&lt;p&gt;Once again it works and &lt;tt class="docutils literal"&gt;category&lt;/tt&gt; is a member of the enum.&lt;/p&gt;
&lt;p&gt;What if we use a new category not part of the enum?&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Traveling&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It fails:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
It fails:
1 validation error for Article
category
  Input should be Category(id=1, code='cooking', title='Cooking') or Category(id=2, code='programming', title='Programming') [type=enum, input_value=Category(id=3, code='travel', title='Travel'), input_type=Category]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/enum"&gt;https://errors.pydantic.dev/2.11/v/enum&lt;/a&gt;
&lt;/pre&gt;
&lt;p&gt;As expected, we get an error because this category is not part of the
enum.&lt;/p&gt;
&lt;p&gt;And with a dict representing a category?&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It fails:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
It fails:
1 validation error for Article
category
  Input should be Category(id=1, code='cooking', title='Cooking') or Category(id=2, code='programming', title='Programming') [type=enum, input_value={'id': 2, 'code': 'progra... 'title': 'Programming'}, input_type=dict]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/enum"&gt;https://errors.pydantic.dev/2.11/v/enum&lt;/a&gt;
&lt;/pre&gt;
&lt;p&gt;That’s a bit sad. Likewise, we cannot reference our category by id or
code when creating an article by default. Luckily, it’s a problem that
can be solved. So, let’s do it!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-custom-enum-base-class"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Creating a custom Enum base class&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you read &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/19/using-custom-classes-pydantic/"&gt;my previous article on custom classes and Pydantic&lt;/a&gt;, this idea should come to mind.
It sure was the first solution that came to mine. All we need to to is
create a &lt;tt class="docutils literal"&gt;BasePydanticEnum&lt;/tt&gt; class inheriting from &lt;tt class="docutils literal"&gt;Enum&lt;/tt&gt; and hook
this enum to Pydantic with the &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; class
method. It will allow enum members to be loaded from their &lt;tt class="docutils literal"&gt;code&lt;/tt&gt; field
and serialized to their &lt;tt class="docutils literal"&gt;code&lt;/tt&gt; field. I won’t detail
&lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; too much here.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;To simplify code samples, I’ll assume all models used this way have a field named &lt;tt class="docutils literal"&gt;code&lt;/tt&gt; for the serialization. I’ll provide at the end, as a bonus, a set of classes that allow the field to be specified for more flexibility. This way, you’ll be able to have multiple enums with Pydantic model as member and use different identifier fields.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePydanticEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__get_pydantic_core_schema__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GetCoreSchemaHandler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Get the member from the enum no matter what we have as input.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# If we fail to find a matching member, it will fail.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# It accepts: code, enum member and enum member value as input.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;get_member_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_info_plain_validator_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_member&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json_or_python_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;json_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_member_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;python_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_member_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Get the code from the value of the enum member.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plain_serializer_function_ser_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# If the input is already a member or is a member value, let’s use it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# If not, search for the member with input_value as code.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# Try to validate the input as a model, in case the user supplied a dict&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# representing a member. Validating during each loop is suboptimal,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# improve this if you care about this feature.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Not easy since you can’t know easily the type of you member values by&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# default. Forcing the child to implement a _get_value_type class method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# would solve this.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="c1"&gt;# Validated successfully and matches the current member.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Raise a ValueError if our search fails for Pydantic to create its proper&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# ValidationError.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to convert &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to a member of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePydanticEnumArticleCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BasePydanticEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cooking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;programming&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BasePydanticEnumArticleCategory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s check it works as expected. I’ll just provide the full code
examples with their output, since it should be straightforward at this
point.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BasePydanticEnumArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creation from member:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dump in Python mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dump in JSON mode:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;From a category object that’s equal to a member:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;Creating from a valid code:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;Creating from a valid dict:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fun with Pydantic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Travel&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Traveling&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating with a non member category fails as expected.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BasePydanticEnumArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Traveling&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating from an invalid code fails as expected.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Creation from member: category=&amp;lt;BasePydanticEnumArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
Dump in Python mode: {'category': 'programming', 'title': 'Fun with Pydantic'}
Dump in JSON mode: {'category': 'programming', 'title': 'Fun with Pydantic'}
From a category object that’s equal to a member: category=&amp;lt;BasePydanticEnumArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
Creating from a valid code: category=&amp;lt;BasePydanticEnumArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
Creating from a valid dict: category=&amp;lt;BasePydanticEnumArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; title='Fun with Pydantic'
Creating with a non member category fails as expected.
Creating from an invalid code fails as expected.
&lt;/pre&gt;
&lt;p&gt;So far so good. And problem solved for all the use cases! &lt;em&gt;But,&lt;/em&gt; this
solution also has a set of drawbacks:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;All your enums must inherit from a custom enum class.&lt;/li&gt;
&lt;li&gt;All the logic is in this custom class while the logic for the serialization and deserialization is about the model.&lt;/li&gt;
&lt;li&gt;It requires advanced concepts.&lt;/li&gt;
&lt;li&gt;It’s complex. And to make it generic it’s even more complex since part of the logic must be spread: the serialization/deserialization logic stays in the base enum class and the name of the identifier field must go into the model or the child class. It’s easier to enforce in the child enum since we can create the method in the base class with a &lt;tt class="docutils literal"&gt;raise NotImplemented&lt;/tt&gt; body.&lt;ul&gt;
&lt;li&gt;Advanced note: since enum already has a metaclass, the custom class cannot inherit from &lt;tt class="docutils literal"&gt;ABCMeta&lt;/tt&gt; and use the &lt;tt class="docutils literal"&gt;&amp;#64;abstract_method&lt;/tt&gt; decorator.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This serialization/deserialization is forced on all users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Can we find a better way to do this? Please read-on!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="alternative-with-annotations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Alternative with annotations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you’ve used Pydantic, you may wander if we can use the &lt;tt class="docutils literal"&gt;Annotate&lt;/tt&gt;
pattern to do this. Using &lt;tt class="docutils literal"&gt;BeforeValidator&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;PlainSerializer&lt;/tt&gt; we
can. It’s verbose and error prone (we must specify both each time we
define a field). It also relies on less advanced Pydantic concepts.&lt;/p&gt;
&lt;p&gt;The serializer is easy and reusable:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;EnumMemberToCodeSerializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PlainSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The validator a bit less:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Same logic that in the class. I left the loading a dict into a model&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# out for simplicity.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to find a member in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="c1"&gt;# The actual validator must be built with the enum as input so it can access&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# the proper members.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_enum_from_code_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BeforeValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now let’s glue it together in a model and test how this behaves:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnnotatedArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;create_enum_from_code_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;EnumMemberToCodeSerializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AnnotatedArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking behavior:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnnotatedArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooking&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnnotatedArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;AnnotatedArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Should not go there&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed with invalid code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Checking behavior:
category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;
{'category': 'programming'}
{'category': 'programming'}
category=&amp;lt;ArticleCategory.cooking: Category(id=1, code='cooking', title='Cooking')&amp;gt;
category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;
Failed with invalid code
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;You may wander why I didn’t use something like &lt;tt class="docutils literal"&gt;category: build_pydantic_enum_type_annotation(ArticleCategoryForAnnotated)&lt;/tt&gt; to build everything with one function.
&lt;tt class="docutils literal"&gt;build_pydantic_enum_type_annotation&lt;/tt&gt; could be defined like so:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_pydantic_enum_type_annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;BeforeValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;EnumMemberToCodeSerializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p class="last"&gt;The reason is: it won’t work. It requires to use a call expression with a type expression and that’s not allowed.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;It works, but is also quite weird and complex. I think I prefer the
first solution. The field must be annotated with both a custom
serializer and a custom validator or it won’t work. At least, it only
concerns the leaf model, the rest can remain standard. I’m still sure we can
do better.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="logic-into-the-model-thats-the-member-of-the-enum"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Logic into the model that’s the member of the enum&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s try another approach in which the enum stays standard and it’s its
Pydantic model members which know how to serialize themselves. In this
solution:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Serialization is made easy with a &lt;tt class="docutils literal"&gt;model_serializer&lt;/tt&gt; placed on the
enum that is a member of the class.&lt;/li&gt;
&lt;li&gt;Derialization is harder:&lt;ul&gt;
&lt;li&gt;We cannot hook into the model because it won’t be used: we will
receive a string instead of an enum value and this string won’t be
equal to any member of the enum. So Pydantic will stop there and
won’t use any &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;model_validator(mode=&amp;quot;before&amp;quot;)&lt;/span&gt;&lt;/tt&gt; we may have to load
the data. We don’t have an easy access to the enum there anyway to
identify the member to use.&lt;/li&gt;
&lt;li&gt;We cannot use a &lt;tt class="docutils literal"&gt;model_validator&lt;/tt&gt; on the enum since it’s not a
Pydantic model. It would also mean we have part of the logic in the
model and part on the enum which breaks locality. It would go
against what we are trying to achieve here.&lt;/li&gt;
&lt;li&gt;We could put a &lt;tt class="docutils literal"&gt;model_validator&lt;/tt&gt; on the leaf model but we break
locality even further.&lt;/li&gt;
&lt;li&gt;What works is to hook into the &lt;tt class="docutils literal"&gt;__eq__&lt;/tt&gt; method of the model: if
the value we receive is the identifier of the model, we can let
Pydantic know it found the right member of the enum. When it loads
the data, Pydantic will loop over the enum member and check to see
whether its input is equal to one of their value. If so, it will use
it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseComplexEnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;model_serializer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_serialize_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__eq__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Both are Pydantic models, let's see whether Pydantic thinks they are&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# equal by using the BaseModel.__eq__&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__eq__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# We have an identifier, let's see whether it matches this model's one.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ComplexCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseComplexEnumModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ComplexArticleCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;programming&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ComplexCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cooking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ComplexCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s run some basic tests:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ComplexArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ComplexArticleCategory&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ComplexArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking behavior:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;From a member value:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ComplexArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ComplexArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;From a member:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ComplexArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ComplexArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;ComplexArticle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Should not go there&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed with invalid code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# weird but it works since we overloaded __eq__ to load that.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ComplexCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;some_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Some title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;some_code&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Checking behavior: category=&amp;lt;ComplexArticleCategory.programming: ComplexCategory(code='programming', title='Programming')&amp;gt;
From a member value: category=&amp;lt;ComplexArticleCategory.programming: ComplexCategory(code='programming', title='Programming')&amp;gt;
From a member: category=&amp;lt;ComplexArticleCategory.cooking: ComplexCategory(code='cooking', title='Cooking')&amp;gt;
Failed with invalid code
&lt;/pre&gt;
&lt;p&gt;I find this solution very weird and magical. And it leaks outside the
Pydantic context by changing deeply how equality behaves. It works, it
illustrates a “creative” use of overloading &lt;tt class="docutils literal"&gt;__eq__&lt;/tt&gt; for advanced
needs, but I can’t remand it. Once again, I’m sure we can find something
better.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-the-logic-in-the-leaf-model"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Putting the logic in the leaf model&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After trying to put all the logic in the model that’s used in the enum,
let’s try to put it at the other end: in the model that uses the enum.
Just like with the annotated example, this solution uses a custom
serializer and a custom validator.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseLeafModelWithCodeSupport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;model_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;before&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_load_pydantic_enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Do nothing for fields whose are not defined as enums&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# Loop over the enum members now that we know that field_def.annotation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# is an enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="c1"&gt;# Find the allowed values as input: the member itself, the member&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="c1"&gt;# value and the code.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="c1"&gt;# If one of them matches, member is the member we are looking for.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_allowed_input_values_for_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                    &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                    &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_allowed_input_values_for_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# It’s an enum member without a code. Can’t do anything with it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Let Pydantic load it its usual way.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;model_serializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;when_used&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wrap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_serialize_pydantic_enums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SerializerFunctionWrapHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SerializationInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Let Pydantic serialize the values automatically.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_fields__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Find the fields defined as enums.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# A field may not have a serialization alias, use its name in&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# this case instead.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Use the serialization alias, only when dumping by alias.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;by_alias&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="n"&gt;field_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# Not an enum member with a code. We don’t care about it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# Some fields may be excluded from serialization (because they are&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="c1"&gt;# unset for instance). Let's ignore those.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;serialization_field_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;serialization_field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s test:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleWithCodeSupport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseLeafModelWithCodeSupport&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category_with_alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serialization_alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArticleWithCodeSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;category_with_alias&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking behaviors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ArticleWithCodeSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooking&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ArticleWithCodeSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArticleCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;programming&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;ArticleWithCodeSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;travel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Should not go there&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed with invalid code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Checking behaviors
category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; category_with_alias=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;
{'category': &amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;, 'category_with_alias': &amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;}
{'category': {'id': 2, 'code': 'programming', 'title': 'Programming'}, 'cat': {'id': 2, 'code': 'programming', 'title': 'Programming'}}
{'category': {'id': 2, 'code': 'programming', 'title': 'Programming'}, 'category_with_alias': {'id': 2, 'code': 'programming', 'title': 'Programming'}}
category=&amp;lt;ArticleCategory.cooking: Category(id=1, code='cooking', title='Cooking')&amp;gt; category_with_alias=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;
category=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt; category_with_alias=&amp;lt;ArticleCategory.programming: Category(id=2, code='programming', title='Programming')&amp;gt;
Failed with invalid code
&lt;/pre&gt;
&lt;p&gt;Works very well! It’s based on &lt;tt class="docutils literal"&gt;model_validator&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;model_serializer&lt;/tt&gt; which are more common than
&lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt;. It requires care in the implementation
to work correctly and to prevent issues with “normal” enum fields. Most
notably, it must loop over the field definitions and access advanced
attributes of the model.&lt;/p&gt;
&lt;p&gt;On the bright side, all the logic is in the model that actually cares
about it (well, in its base class to be exact). I think it’s the best
solution since everything is done locally in one place &lt;em&gt;and&lt;/em&gt; it only
impacts the model that requires the behavior regarding the code
attribute. The implementation is not too complex if properly documented
(at least in my opinion).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Which solution would I choose? The “logic in the leaf model with model
serialize and model validator” since I think it’s the cleanest and one
of the more simple. It’s also the one with the least impact on other
part of the code: only the model that need the weird “code for member
behavior” is impacted. I may also consider the first solution that
relies on &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; since I think it’s quite
clean even if logic is in a weird place. I’d probably do this if I have
many custom classes and thus, &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; has
become very familiar to me.&lt;/p&gt;
&lt;p&gt;I’d just discard the other possibilities as too weird or complex.&lt;/p&gt;
&lt;p&gt;I hope you enjoyed this article about a weird use case of Pydantic with
enums as much as I did and learned things on the way. I myself really
liked the exploration and the different ideas that required each case to
work. Don’t hesitate to leave a comment below if needed!&lt;/p&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;div class="section" id="bonus"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Bonus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In all the examples above, I relied on a field named &lt;tt class="docutils literal"&gt;code&lt;/tt&gt; to do the
serialization/deserialization. I didn’t try to support any other field.
Here, I’ll give ways for the 1st and 4th method to select which field
must be used instead. I’ll assume you’ve read the article and are at
ease with it so I won’t comment the code as much. If you have a problem,
let me know in the comments!&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I haven’t tested these solutions as much so they may be a bit buggy.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePydanticEnumWithIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Allow enum member to be loaded from their identifier (a field of the model) and then serialized to their identifier.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__get_pydantic_core_schema__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GetCoreSchemaHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Get the member from the enum no matter what we have as input. If we fail to find a matching member, it will fail.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# It accepts: code, enum member and enum member value as input.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;get_member_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_info_plain_validator_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json_or_python_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;json_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_member_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;python_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_member_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plain_serializer_function_ser_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_serialize_to_identifier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_identifier&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to convert &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to a member of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_serialize_to_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_identifier&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Used to override in each enum what field of the member to use to load/serialize the data.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Note: using &amp;#64;abstractmethod is NOT straightforward: both Enum and ABC uses metaclass and they are not compatible.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# We should be able to create our own to adapt it. Probably not worth it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BasePydanticEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;first_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;first_model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;second_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second_model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ModelValues&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;model_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;first_model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
model_value=&amp;lt;ModelValues.first_model: EnumModel(code='first_model')&amp;gt;
{'model_value': 'first_model'}
&lt;/pre&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;property&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="c1"&gt;# Sad that it must be on this model and on no the leaf.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelDecoratorModelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;first_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;first_choice&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;second_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EnumModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second_choice&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseModelDecoratorWithComplexEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;model_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;before&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_load_pydantic_enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# Loop over the enum members now that we know that field_def.annotation is an enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_allowed_input_values_for_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                    &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_allowed_input_values_for_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;identifier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="n"&gt;member_identifier_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;member_identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member_identifier_field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member_identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;model_serializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;when_used&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wrap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_serialize_pydantic_enums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SerializerFunctionWrapHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SerializationInfo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# A field may not have a serialization alias, use its name in this case instead.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;serialization_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serialization_alias&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;by_alias&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="c1"&gt;# Some fields may be excluded from serialization (because they are unset for instance). Let's ignore those.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;serialization_field_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;serialization_field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_allowed_input_values_for_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelDecoratorWithComplexEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModelDecoratorWithComplexEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ModelValues&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;enum_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ModelDecoratorModelValues&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;model_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;first_model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
model_value=&amp;lt;ModelValues.first_model: EnumModel(code='first_model')&amp;gt;
{'model_value': 'first_model'}
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Pydantic"></category></entry><entry><title>Using Pydantic with custom classes</title><link href="https://www.jujens.eu/posts/en/2025/Apr/19/using-custom-classes-pydantic/" rel="alternate"></link><published>2025-04-19T00:00:00+02:00</published><updated>2025-04-19T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-04-19:/posts/en/2025/Apr/19/using-custom-classes-pydantic/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://docs.pydantic.dev/latest/"&gt;Pydantic&lt;/a&gt; is an awesome data
validation library for Python. It’s getting very popular these days
because it’s fast, has lots of features and is pleasant to use. I’m
using it at work and it personal projects. It’s one of the strong points
of &lt;a class="reference external" href="https://fastapi.tiangolo.com"&gt;FastAPI&lt;/a&gt;, the new …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://docs.pydantic.dev/latest/"&gt;Pydantic&lt;/a&gt; is an awesome data
validation library for Python. It’s getting very popular these days
because it’s fast, has lots of features and is pleasant to use. I’m
using it at work and it personal projects. It’s one of the strong points
of &lt;a class="reference external" href="https://fastapi.tiangolo.com"&gt;FastAPI&lt;/a&gt;, the new popular Python
framework. It can even integrate with Django thanks to
&lt;a class="reference external" href="https://django-ninja.dev/"&gt;django-ninja&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While it integrates with standard Python types out of the box, using it
with custom classes requires a bit more work. It’s
documented &lt;a class="reference external" href="https://docs.pydantic.dev/latest/concepts/types/#custom-types"&gt;here for generalities about custom classes&lt;/a&gt;
and &lt;a class="reference external" href="https://docs.pydantic.dev/latest/concepts/types/#generic-containers"&gt;here for specificities about container types&lt;/a&gt;,
but I don’t think this part of the documentation is very clear. That’s
why, in this article, I’ll share my experience with how I integrated a
custom class with Pydantic. I hope you can use it to understand better
how it works and thus facilitate using your custom classes with
Pydantic. &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Apr/26/pydantic-enums/"&gt;In a second article&lt;/a&gt;, I’ll give another example
of custom classes with Pydantic by using enums whose members are
Pydantic models.&lt;/p&gt;
&lt;p&gt;You can access the code of this article
&lt;a class="reference external" href="https://www.jujens.eu/static/using-custom-classes-pydantic/pydantic_with_custom_classes.ipynb"&gt;as a notebook&lt;/a&gt;
and &lt;a class="reference external" href="https://www.jujens.eu/static/using-custom-classes-pydantic/pydantic_with_custom_classes.py"&gt;as a Python script&lt;/a&gt;.
I’ll strip imports from inner examples for brevity.
Feel free to download these files and experiment! The code is tested on
Python 3.11 but should work with any recent enough version of Python.&lt;/p&gt;
&lt;p&gt;For the purpose of this article, I’ll suppose you know Python and have a basic understanding of Pydantic, generic types in Python and know the common dunder methods.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-i-needed-to-integrate-a-custom-class-in-the-first-place" id="toc-entry-1"&gt;Why I needed to integrate a custom class in the first place&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-class-without-pydantic" id="toc-entry-2"&gt;The class without Pydantic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#adding-pydantic" id="toc-entry-3"&gt;Adding Pydantic&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#testing" id="toc-entry-4"&gt;Testing&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#loading-from-a-dict" id="toc-entry-5"&gt;Loading from a &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#loading-from-json" id="toc-entry-6"&gt;Loading from json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#key-access" id="toc-entry-7"&gt;Key access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#access-invalid-key" id="toc-entry-8"&gt;Access invalid key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#direct-creation" id="toc-entry-9"&gt;Direct creation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#validating-with-a-readonlymapping-instance" id="toc-entry-10"&gt;Validating with a ReadonlyMapping instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#setting-a-key" id="toc-entry-11"&gt;Setting a key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#key-parsing" id="toc-entry-12"&gt;Key parsing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#validation-failure" id="toc-entry-13"&gt;Validation failure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#without-any-generic-annotation" id="toc-entry-14"&gt;Without any generic annotation:&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#conclusion" id="toc-entry-15"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="why-i-needed-to-integrate-a-custom-class-in-the-first-place"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Why I needed to integrate a custom class in the first place&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My goal was to create a read only mapping compatible with Pydantic.
Here’s how it must work:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Usage with Pydantic must be transparent:&lt;ul&gt;
&lt;li&gt;When the model is serialized, the mapping must be serialized as a
dict.&lt;/li&gt;
&lt;li&gt;It must be created from a dict containing values. Both the keys and
the values from this input dict must be parsed and validated. To
increase reusability, the model must be made generic to allow
validation of different types: for instance a
&lt;tt class="docutils literal"&gt;ReadonlyMapping[str, int]&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;ReadonlyMapping[int, float]&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Just like any other Pydantic models, if I pass an instance to the
model, the model must use the class directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once set in the model, the field must not be editable any more. The
goal being that the model must be fully immutable so that neither its
fields nor the content of the mapping can be edited by mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So my goal is to arrive at something like this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImmutableModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Make the model immutable: cannot add nor change attributes.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;my_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;my_dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my_field&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;my_dict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}})&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fails as expected with:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# The underlying structure is still mutable and can be changed.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# despite the model being frozen. That’s what we want to prevent.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="the-class-without-pydantic"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;The class without Pydantic&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s start by building a class that will be our generic readonly
mapping. At its core, this class will just be a wrapper around a good
old dict. It will rely on &lt;tt class="docutils literal"&gt;typing.Generic&lt;/tt&gt; to be generic and will
implement &lt;tt class="docutils literal"&gt;__getitem__&lt;/tt&gt; to provide item access. It’ll also implement
&lt;tt class="docutils literal"&gt;__str__&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;__repr__&lt;/tt&gt; to ease debugging. &lt;tt class="docutils literal"&gt;__setitem__&lt;/tt&gt; won’t be
implemented to prevent setting an item and get a &lt;tt class="docutils literal"&gt;TypeError&lt;/tt&gt; if we try
to.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I didn’t use &lt;tt class="docutils literal"&gt;MappingProxyType&lt;/tt&gt; because I wanted a class that’s easier to extend to fit my needs here.
In another context, that’s the solution I’d have chosen to have a readonly mapping.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# Generic type for the key.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Generic type for the value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__getitem__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_mapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_mapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This class can then be used this way:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;readonly_mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]({&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readonly_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Test
&lt;/pre&gt;
&lt;p&gt;And it fails as expected on key assignment:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;readonly_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Testing&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fails as expected with:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Fails as expected with: 'ReadonlyMapping' object does not support item assignment
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-pydantic"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Adding Pydantic&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the base is built, let’s iterate on that. To hook Pydantic with
this custom class, a class method named &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt;
must be added. It’ll use low level Pydantic functions to generate a
proper Pydantic schema based on your generic arguments and let Pydantic
handle the parsing and the validation. These methods come from
&lt;tt class="docutils literal"&gt;pydantic_core&lt;/tt&gt; and are used internally by Pydantic. They are
documented
&lt;a class="reference external" href="https://docs.pydantic.dev/latest/api/pydantic_core_schema/"&gt;here&lt;/a&gt;.
Here’s how it looks like (with inline comments to explain its tricky
part):&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;    &lt;span class="nd"&gt;&amp;#64;classmethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__get_pydantic_core_schema__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GetCoreSchemaHandler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CoreSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Retrieve the generic arguments. args is a tuple of the type used for&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Key and the type used for Value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_typing_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# If no generics are passed, args will be an empty tuple.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Let’s fallback to a raw dict in this case.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# Make Pydantic generate a schema for dict[Key, Value] that it can&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;15 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# directly use for validation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;16 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# This allows for Pydantic to parse the keys and the values and to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;17 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# validate them without further work.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;18 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="n"&gt;validate_dict_data_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;19 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;                &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;20 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;21 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;22 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="n"&gt;validate_dict_data_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;23 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;24 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Create a schema that will build our instance from cls&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;25 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# (ie ReadonlyMapping) from the validated dict.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;26 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="n"&gt;from_dict_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_info_after_validator_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;27 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_dict_data_schema&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;28 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;29 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;30 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Validator to hook an already built instance of ReadonlyMapping&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;31 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# into the model.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;32 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="n"&gt;from_instance_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_instance_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;33 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;34 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# Combine from_dict_schema with from_instance_schema to be able&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;35 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# to use both.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;36 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# They will be tried in order: 1st from_instance_schema and if&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;37 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# it fails from_dict_schema.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;38 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="c1"&gt;# If the last one fails, validation will fail.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;39 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;union_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;40 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;from_instance_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_dict_schema&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;41 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;42 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;43 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json_or_python_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;44 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# Use our schema when raw JSON data is used directly.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;45 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="n"&gt;json_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;46 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# With Python data.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;47 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="n"&gt;python_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;48 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# When the model is serialized to Python or JSON, return the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;49 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="c1"&gt;# underlying dict that Pydantic can handle directly.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;50 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;core_schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plain_serializer_function_ser_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;51 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;                &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_mapping&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;52 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;53 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;div class="section" id="testing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s add a test model for testing:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And on to the tests we go!&lt;/p&gt;
&lt;div class="section" id="loading-from-a-dict"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Loading from a &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my_int&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;my_mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;77&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The model:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;m_python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;As Python:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_python&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m_python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;m_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_dump_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;As JSON:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_json&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
The model: &amp;lt;class '__main__.MyModel'&amp;gt; my_mapping={'key': 77}
As Python: &amp;lt;class 'dict'&amp;gt; {'my_mapping': {'key': 77}}
As JSON: &amp;lt;class 'str'&amp;gt; {&amp;quot;my_mapping&amp;quot;:{&amp;quot;key&amp;quot;:77}}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="loading-from-json"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Loading from json&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;{&amp;quot;my_mapping&amp;quot;: {&amp;quot;key&amp;quot;: &amp;quot;77&amp;quot;}}&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The model from JSON:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
The model from JSON: my_mapping={'key': 77}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="key-access"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Key access&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
{'key': 77} 77
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="access-invalid-key"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Access invalid key&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nope&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Accessing an invalid key fails as expected with error:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Accessing an invalid key fails as expected with error: 'nope'
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="direct-creation"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Direct creation&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Direct creation:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Direct creation: my_mapping={'key': 77}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="validating-with-a-readonlymapping-instance"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Validating with a ReadonlyMapping instance&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my_mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;})})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Validating with a ReadonlyMapping instance:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Validating with a ReadonlyMapping instance: my_mapping={'key': 77}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-a-key"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Setting a key&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Setting a key fails as expected with error:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Setting a key fails as expected with error: 'ReadonlyMapping' object does not support item assignment
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="key-parsing"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Key parsing&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyOtherModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyOtherModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Keys and values are correctly parsed and validated:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Keys and values are correctly parsed and validated: mapping={1: 1, 2: False}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="validation-failure"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Validation failure&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;MyOtherModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Some value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Must fail&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This fails as expected with error e:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
This fails as expected with error e: 4 validation errors for MyOtherModel
mapping.is-instance[ReadonlyMapping]
  Input should be an instance of ReadonlyMapping [type=is_instance_of, input_value={'key': '1', '2': 'Some value'}, input_type=dict]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/is_instance_of"&gt;https://errors.pydantic.dev/2.11/v/is_instance_of&lt;/a&gt;
mapping.function-after[ReadonlyMapping(), dict[int,union[int,bool]]].key.[key]
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='key', input_type=str]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/int_parsing"&gt;https://errors.pydantic.dev/2.11/v/int_parsing&lt;/a&gt;
mapping.function-after[ReadonlyMapping(), dict[int,union[int,bool]]].2.int
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='Some value', input_type=str]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/int_parsing"&gt;https://errors.pydantic.dev/2.11/v/int_parsing&lt;/a&gt;
mapping.function-after[ReadonlyMapping(), dict[int,union[int,bool]]].2.bool
  Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='Some value', input_type=str]
    For further information visit &lt;a class="reference external" href="https://errors.pydantic.dev/2.11/v/bool_parsing"&gt;https://errors.pydantic.dev/2.11/v/bool_parsing&lt;/a&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="without-any-generic-annotation"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Without any generic annotation:&lt;/a&gt;&lt;/h4&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoTypeAnnotationModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReadonlyMapping&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoTypeAnnotationModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Read and parsed, no validation:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
Read and parsed, no validation: mapping={'key': 'value', 1: 1}
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope I demystified how to use a custom class with Pydantic. Once you know a
bit how &lt;tt class="docutils literal"&gt;__get_pydantic_core_schema__&lt;/tt&gt; can be used and how to use the
core validation schemas, it’s not that hard. Just like me, you’ll
probably have to experiment a bit to fully grasp what is going on. And
if something is unclear, don’t hesitate to ask a question below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Pydantic"></category></entry><entry><title>Some tips on Python’s enums</title><link href="https://www.jujens.eu/posts/en/2025/Apr/12/python-enums/" rel="alternate"></link><published>2025-04-12T00:00:00+02:00</published><updated>2025-04-12T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-04-12:/posts/en/2025/Apr/12/python-enums/</id><summary type="html">&lt;p&gt;I’d like to share a few tips I recently (re)discovered about Python’s enums.
While interesting on its own, this article is also paving the way on a more advanced article I hope to write soon.
Let’s dive right in!&lt;/p&gt;
&lt;div class="section" id="auto"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;auto&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;By using the function &lt;tt class="docutils literal"&gt;enum.auto …&lt;/tt&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I’d like to share a few tips I recently (re)discovered about Python’s enums.
While interesting on its own, this article is also paving the way on a more advanced article I hope to write soon.
Let’s dive right in!&lt;/p&gt;
&lt;div class="section" id="auto"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;auto&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;By using the function &lt;tt class="docutils literal"&gt;enum.auto&lt;/tt&gt;, you can set integer values automatically in you enum.
So this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;SECOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;is equivalent to this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;SECOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.auto"&gt;https://docs.python.org/3/library/enum.html#enum.auto&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unique"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;unique&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;Utility decorators to make sure all values are unique in the enum.
So this will raise &lt;tt class="docutils literal"&gt;ValueError: duplicate values found in &amp;lt;enum &lt;span class="pre"&gt;'MyEnum'&amp;gt;:&lt;/span&gt; THIRD &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; SECOND&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="nd"&gt;&amp;#64;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;SECOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;THIRD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.unique"&gt;https://docs.python.org/3/library/enum.html#enum.unique&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="nonmember"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;nonmember&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;This function will prevent a field to become an enum member.
It will be a standard attribute instead.
So this code:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;SECOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;my_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonmember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyEnum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyEnum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_field&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;will print:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;MyEnum.FIRST: 1&amp;gt;
5
&lt;/pre&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.nonmember"&gt;https://docs.python.org/3/library/enum.html#enum.nonmember&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="enum-flag"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;enum.Flag&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;A utility class to create an enum that supports bitwise operations.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;READ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;WRITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;read_or_write&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;READ&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WRITE&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.Flag"&gt;https://docs.python.org/3/library/enum.html#enum.Flag&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="enum-verify"&gt;
&lt;h2&gt;&lt;tt class="docutils literal"&gt;enum.verify&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;A decorator to apply various predefined verifications on your enums.
You can check that your values are unique (like with &lt;tt class="docutils literal"&gt;enum.unique&lt;/tt&gt;), continuous or valid flags.
For instance, this code will raise &lt;tt class="docutils literal"&gt;ValueError: invalid enum 'MyEnum': missing values 2&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="nd"&gt;&amp;#64;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CONTINUOUS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;THIRD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.verify"&gt;https://docs.python.org/3/library/enum.html#enum.verify&lt;/a&gt; and &lt;a class="reference external" href="https://docs.python.org/3/library/enum.html#enum.EnumCheck"&gt;https://docs.python.org/3/library/enum.html#enum.EnumCheck&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="extra"&gt;
&lt;h3&gt;Extra&lt;/h3&gt;
&lt;p&gt;Sadly, the provided &lt;tt class="docutils literal"&gt;verify&lt;/tt&gt; decorator can only run pre-defined checks.
Luckily, you can get inspiration from it to create your own.
For instance, to check that all members are of the same type and raise a &lt;tt class="docutils literal"&gt;ValueError&lt;/tt&gt; if not, you can use:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enforce_member_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type_to_enforce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type_to_enforce&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not of type &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;type_to_enforce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="nd"&gt;&amp;#64;enforce_member_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WrongTypeInEnforcedEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;FIRST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;SECOND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;second&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Python"></category></entry><entry><title>Writing a browser extension</title><link href="https://www.jujens.eu/posts/en/2025/Feb/16/writting-browser-extension/" rel="alternate"></link><published>2025-02-16T00:00:00+01:00</published><updated>2025-02-16T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-02-16:/posts/en/2025/Feb/16/writting-browser-extension/</id><summary type="html">&lt;p&gt;I recently wrote a browser extension for Firefox and Chromium based browsers for my &lt;a class="reference external" href="httsp://www.legadilo.eu"&gt;Legadilo (RSS feeds aggregator and articles saver)&lt;/a&gt; project.
The goal is to make it easier and faster to save an article or to subscribe to a feed directly on a web page without forcing you to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently wrote a browser extension for Firefox and Chromium based browsers for my &lt;a class="reference external" href="httsp://www.legadilo.eu"&gt;Legadilo (RSS feeds aggregator and articles saver)&lt;/a&gt; project.
The goal is to make it easier and faster to save an article or to subscribe to a feed directly on a web page without forcing you to open the app.
I never wrote a browser extension before and it didn’t go as I thought it would.
So I’d like to share a bit of my experience.&lt;/p&gt;
&lt;p&gt;At high level, an extension is just a bunch of HTML, JS and CSS files zipped together with a &lt;tt class="docutils literal"&gt;manifest.json&lt;/tt&gt; file and some icons.
I rely on Mozilla’s &lt;a class="reference external" href="https://www.npmjs.com/package/web-ext"&gt;web-ext tool&lt;/a&gt; to do the packaging for me.
The &lt;tt class="docutils literal"&gt;manifest.json&lt;/tt&gt; is there to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Define where the icons are.&lt;/li&gt;
&lt;li&gt;Define what permissions the extension will need&lt;/li&gt;
&lt;li&gt;The version (v2 or v3) of the extension.&lt;/li&gt;
&lt;li&gt;Link your files to what the extension needs:&lt;ul&gt;
&lt;li&gt;Point to the HTML file for options.&lt;/li&gt;
&lt;li&gt;Point to the HTML file for each action you will define.&lt;/li&gt;
&lt;li&gt;Point to the background script or service worker to use to handle network requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other JS files are loaded directly in their respective HTML file.
I load them with &lt;tt class="docutils literal"&gt;&amp;lt;script &lt;span class="pre"&gt;src=&amp;quot;action.js&amp;quot;&lt;/span&gt; &lt;span class="pre"&gt;type=&amp;quot;module&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/tt&gt; to be able to use new style imports in them without transpilling.&lt;/p&gt;
&lt;p&gt;To keep things simple, I decided not to use a framework and to code interactions myself.
So I have all &amp;quot;screens&amp;quot; directly in the same HTML hidden by default with &lt;tt class="docutils literal"&gt;display: none&lt;/tt&gt;.
My JS file then hides/displays what is needed and fills the required content by updating and inserting relevant DOM nodes.
Like in the old days.
It works fine for me since my extension remains very simple, but I think that for more complex use cases, a lightweight framework like React, Vue or Svelte would come in handy.
I also wander if you could use web components (eventually with a library like &lt;a class="reference external" href="https://lit.dev"&gt;LitElement&lt;/a&gt;) to keeps things small and simple and rely on the new features of the browser instead.&lt;/p&gt;
&lt;p&gt;I took me some time to figure out how to wire everything together.
I didn’t find the doc that great and ended up mostly relying on existing extension to understand how to do what I wanted to do.
With hours of tries and errors.&lt;/p&gt;
&lt;p&gt;I developed my extension of Firefox because I prefer their dev tools (just like for normal web development).
The most frustrating part was understanding how the dev tools work to have proper debugging and logging.
On Firefox, it happens on a dedicated page &lt;tt class="docutils literal"&gt;debug:&lt;/tt&gt; and then opening the proper dev tools.
Sometimes, I had to manually refresh the extension in there, and sometimes it was automatic.
With more experience, it may become more obvious when a manual action is needed.&lt;/p&gt;
&lt;p&gt;Once I got everything working in Firefox, I tried the extension on Chromium.
And it didn’t work.
I decided to use manifest v3 right away: for this extension the new restrictions don’t apply and I thought both browsers support it correctly in the same way.
It turns out, for the background script part (both how it runs and how it communicates with other JS scripts), Firefox still uses the manifest v2 way while Chromium is on the v3 way.
I think it’s to still allow network based ad blockers to work with Firefox.
This introduced lots of small differences even for my tiny extension.&lt;/p&gt;
&lt;p&gt;What I ended up doing: instead of trying to solve them at once, I copied the FF extension and ported it to &amp;quot;full&amp;quot; v3 so it would work correctly on Chrome.
I then diffed the two implementations and worked on merging them.
It took some time, but I managed to do that by introducing glue functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;In the &lt;tt class="docutils literal"&gt;background.js&lt;/tt&gt; script, both browsers don’t subscribe to events the same way.
Symmetrically, they don’t send responses the same way.&lt;/p&gt;
&lt;pre class="code js literal-block"&gt;
&lt;span class="c1"&gt;// Check whether we are on FF.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isFirefox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFirefox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onConnect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="c1"&gt;// This is required to correctly get the response on the other side!&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// I lost lots of time because I forgot it because it seems so useless!&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// request is on the format you need. Same for response.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// I choose a very simple { name: &amp;quot;&amp;quot;, payload {} } structure.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;save-article&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;processSaveArticleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Unknown action &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;On the &lt;tt class="docutils literal"&gt;action.js&lt;/tt&gt; end, I must connect to the port on FF but not on Chromium.
This gives a little wrapper like this:&lt;/p&gt;
&lt;pre class="code js literal-block"&gt;
&lt;span class="c1"&gt;// Only used on FF.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isFirefox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOMContentLoaded&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;connectToPort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Do extension related stuff&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connectToPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Chromium based browsers will receive the response directly&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// from the function call sending the message.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isFirefox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;legadilo-popup&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;And then to send messages to the background script (still in &lt;tt class="docutils literal"&gt;action.js&lt;/tt&gt;):&lt;/p&gt;
&lt;pre class="code js literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;doStuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;update-feed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;feedId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFirefox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// On response will be called as part of the port behavior.&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c1"&gt;// Manually call onMessage to process the response in the same way&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// on all browsers.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;displayErrorMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;hideErrorMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;hideLoader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;saved-article&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;savedArticleSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Unknown action &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once I had everything working on both browsers, the time to publish this extension on the web stores came:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;On Firefox it was really easy: a small form to fill with the extension attached to it.
An automated review later, the extension was online.&lt;/li&gt;
&lt;li&gt;For Chrome, it took much longer:&lt;ul&gt;
&lt;li&gt;First, you must pay a small fee (to protect against spam I suppose).&lt;/li&gt;
&lt;li&gt;Then I had to fill a form, which took much longer than for Firefox: I needed extra icons, screenshots and I had to give much more details mostly about data collection and a privacy.
I couldn’t help but ask myself: &amp;quot;like Google cared about privacy and data collection&amp;quot;.
That’s the only reason I have &lt;a class="reference external" href="https://www.legadilo.eu/privacy/"&gt;an almost empty privacy page on the app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Only then and after one rejection because I was missing some infos, I managed to publish the extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now my extension is online and I use it.
I still have works to do to improve it, but the base is here and useful.
I’m glad of the UX it gives.
I didn’t expect cross browser compatibility to be that hard.
I guess it comes from the fact we are still in the transition to manifest v3.
I also wasn’t ready for how much infos the Chrome webstore asks to publish even this basic extension.
On the bright side, it forced me to write a simple privacy policy that states I don’t collect nor sell anything (so you know that as well).&lt;/p&gt;
</content><category term="Programmation"></category><category term="Web"></category></entry><entry><title>Using podman for containers</title><link href="https://www.jujens.eu/posts/en/2025/Feb/09/using-podman/" rel="alternate"></link><published>2025-02-09T00:00:00+01:00</published><updated>2025-02-09T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-02-09:/posts/en/2025/Feb/09/using-podman/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://podman.io/"&gt;Podman&lt;/a&gt; is an alternative to Docker and Docker compose.
It uses the same CLI interface than Docker and uses the same standardized image format.
So you can use an image built with Docker with it or build an image and then use it with Docker.
Its &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;podman-compose&lt;/span&gt;&lt;/tt&gt; command is compatible …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://podman.io/"&gt;Podman&lt;/a&gt; is an alternative to Docker and Docker compose.
It uses the same CLI interface than Docker and uses the same standardized image format.
So you can use an image built with Docker with it or build an image and then use it with Docker.
Its &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;podman-compose&lt;/span&gt;&lt;/tt&gt; command is compatible with Docker compose files, so again nothing to adapt there.&lt;/p&gt;
&lt;p&gt;If both tools are so close, you are probably wandering why not use Docker since it’s more famous?
While close in its usage, Podman is very different in its implementation.
It doesn’t rely on a daemon nor requires root privileges to run containers.
So you don’t have to enable anything to get started and you have better isolation by default:
within the container root will be mapped to the current user and other users to dedicated UID and GIU on the host.
For more on this subject and Docker, see &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/"&gt;my &amp;quot;use linux user namespaces in docker&amp;quot; article&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This relies on &lt;tt class="docutils literal"&gt;/etc/subuid&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;/etc/subguid&lt;/tt&gt; files on linux.
Make sure they are filled with something like this (this should be the case on modern distributions):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# username:1st sub id:number of sub id
# Typically this will be:
jujens:100000:65536
&lt;/pre&gt;
&lt;p class="last"&gt;See my article linked above for more on this.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Since there is no daemon started by default on boot, it means containers won’t be restarted after a reboot in the default configuration.
I think it can be quite good for development containers you may not need after each reboot.
But if you want to use it to run your production containers, that’s a problem.
Luckily, Podman let you manage your containers with &lt;tt class="docutils literal"&gt;systemctl&lt;/tt&gt; thanks to dedicated unit files.
This means you have all the power of systemd to run your containers and manage their dependencies.&lt;/p&gt;
&lt;p&gt;To do this (full example below):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create a file ending with &lt;tt class="docutils literal"&gt;.container&lt;/tt&gt; in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/containers/systemd/&lt;/span&gt;&lt;/tt&gt;. For instance, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/containers/systemd/legadilo.container&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Refresh the daemon with &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; &lt;span class="pre"&gt;daemon-reload&lt;/span&gt;&lt;/tt&gt; (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--user&lt;/span&gt;&lt;/tt&gt; is paramount since we are using a non root user to run the unit).&lt;/li&gt;
&lt;li&gt;Start the unit with &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; start &amp;lt;UNIT&amp;gt;&lt;/tt&gt; In this case: &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; start legadilo&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Check that the container is running with &lt;tt class="docutils literal"&gt;podman ps&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Reboot and check that the container is still running. By default, your containers will be named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-&amp;lt;UNIT&amp;gt;&lt;/span&gt;&lt;/tt&gt; In this case, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-legadilo&lt;/span&gt;&lt;/tt&gt;. Any volumes or networks will be created automatically from their associated definition files.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What’s very nice is that you can also rely on this system to auto-update containers.
Let’s say you are using the &lt;tt class="docutils literal"&gt;legadilo:latest&lt;/tt&gt; image.
If it changes, running &lt;tt class="docutils literal"&gt;podman &lt;span class="pre"&gt;auto-update&lt;/span&gt;&lt;/tt&gt; will pull and restart the service with the new image automatically.
This can also be run automatically thanks to the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;podman-auto-update.timer&lt;/span&gt;&lt;/tt&gt; provided systemd timer!
For more on systemd timers, see &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2025/Feb/01/systemd-timers/"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a sample &lt;tt class="docutils literal"&gt;legadilo.container&lt;/tt&gt; file to help you get started:&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Legadilo container&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Dependencies can be any service as usual.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Or any other containers referenced by their implicit service file.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres.service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres.service&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;rg.fr-par.scw.cloud/legadilo/legadilo-django:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Force a name to prevent the systemd- prefix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;legadilo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# To rely on journald to view the logs (optional).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;LogDriver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;journald&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Configure directly the environment variables here.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ENV_VAR=value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Or use an env file.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;~/.private/legadilo.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8080:8000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;legadilo.network&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Required to make the container start on boot.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the related &lt;tt class="docutils literal"&gt;legadilo.network&lt;/tt&gt; (put it next to the &lt;tt class="docutils literal"&gt;.container&lt;/tt&gt; file):&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Legadilo Network&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Network]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.30.0/24&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.30.1&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;legadilo-postgres.volume&lt;/span&gt;&lt;/tt&gt; for the PostgreSQL database (still next to the &lt;tt class="docutils literal"&gt;.container&lt;/tt&gt; file):&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PG Volume&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Volume]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;legadilo-postgres.container&lt;/span&gt;&lt;/tt&gt; file for completeness:&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Podman PostgreSQL&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/postgres:17&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;legadilo-postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;LogDriver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;journald&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres.volume:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;POSTGRES_HOST=postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;POSTGRES_PORT=5432&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;POSTGRES_DB=legadilo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;POSTGRES_USER=legadilo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;POSTGRES_PASSWORD=&amp;lt;RETRACTED&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;legadilo.network&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;You can then inspect the logs with &lt;tt class="docutils literal"&gt;journalctl CONTAINER_NAME=legadilo&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;journalctl &lt;span class="pre"&gt;CONTAINER_NAME=legadilo-postgres&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I gave a name the &lt;tt class="docutils literal"&gt;legadilo&lt;/tt&gt; container since it will never clash with something system related.
I think it’s easier that way.
But, do it the way you want.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;If needed, you can access the host with the &lt;tt class="docutils literal"&gt;host.containers.internal&lt;/tt&gt; name.
I think it requires Podman 5.3 or more if you use 5.x versions of Podman to due a limitation in earlier releases 5.x releases.&lt;/p&gt;
&lt;p class="last"&gt;If you do, please make sure the service listens to the proper interface.
Otherwise, it won’t accept the connection coming from the container.
It can be &lt;tt class="docutils literal"&gt;0.0.0.0.&lt;/tt&gt; to listen to all interfaces or the interface associated with the docker container to limit incoming traffic.
In the default configuration, it appears to be &lt;tt class="docutils literal"&gt;192.168.100.133&lt;/tt&gt;.
By running &lt;tt class="docutils literal"&gt;ip a&lt;/tt&gt; on the host, you should be able to find the right address.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To conclude, I think Podman is a great replacement for Docker.
It solves nicely and by default the long standing issue I had with isolation (ie not running the containers as root while still having files created by the root user in the container owned by my user outside).
Being able to manage containers with systemd is awesome and allows me to run all services the same way: with &lt;tt class="docutils literal"&gt;systemctl&lt;/tt&gt;!
I’m now using &lt;tt class="docutils literal"&gt;podman&lt;/tt&gt; locally on personal work and intent to use it on my servers.&lt;/p&gt;
&lt;p&gt;That’s it for today.
If you have any questions or remarks, please leave them below!&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.redhat.com/en/blog/quadlet-podman"&gt;Make systemd better for podman with Quadlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://linuxconfig.org/how-to-run-podman-containers-under-systemd-with-quadlet"&gt;How to run Podman contains under systemd with Quadlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html"&gt;The documentation page about Podman systemd units&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Programmation"></category><category term="Docker"></category><category term="podman"></category><category term="containers"></category><category term="systemctl"></category><category term="Linux"></category></entry><entry><title>Systemd Timers</title><link href="https://www.jujens.eu/posts/en/2025/Feb/01/systemd-timers/" rel="alternate"></link><published>2025-02-01T00:00:00+01:00</published><updated>2025-02-01T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-02-01:/posts/en/2025/Feb/01/systemd-timers/</id><summary type="html">&lt;p&gt;After using &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Anacron"&gt;anacron&lt;/a&gt; for years to run a backup script regularly, I decided to have a look at &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; timers.
Overall, &lt;tt class="docutils literal"&gt;anacron&lt;/tt&gt; worked fine: I could run tasks as my user and it would start tasks if they missed a run.
But, I was still frustrated with how it worked …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After using &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Anacron"&gt;anacron&lt;/a&gt; for years to run a backup script regularly, I decided to have a look at &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; timers.
Overall, &lt;tt class="docutils literal"&gt;anacron&lt;/tt&gt; worked fine: I could run tasks as my user and it would start tasks if they missed a run.
But, I was still frustrated with how it worked: I had to configure it to be able to run cron tasks with a non root user and never remembered where the config file was.
I also wanted better notifications in case of failure: CRON programs send logs by email by default which is of no use to me on my local machine.
And I am in the process of using &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; more on my servers.
So it only makes sense to use it more locally too and to rely on its features to replace CRON jobs.&lt;/p&gt;
&lt;p&gt;After digging a bit, the feature that convinced me to use &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; timers is the &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; &lt;span class="pre"&gt;list-timers&lt;/span&gt;&lt;/tt&gt; command (omit the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--user&lt;/span&gt;&lt;/tt&gt; option to list system timers):
it lists all the timers with their next and last execution dates as well as how much time is remaining before the next run.
I can see everything in a human readable way with one command!
Of course, everything ends logged in &lt;tt class="docutils literal"&gt;journald&lt;/tt&gt;!
As with any &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; service, you can also specify dependencies of the service to run.
I can also use &lt;tt class="docutils literal"&gt;Weekly&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Monthly&lt;/tt&gt; keywords to more easily define when to run a task without bothering with CRON syntax!
I couldn’t ask for more.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;With &lt;tt class="docutils literal"&gt;Weekly&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Monthly&lt;/tt&gt;, scripts will run of the 1st day of the week or of the month at midnight which is fine for the small scripts I have, but may run lots of big scripts at the same time on your setup.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The only pain point I see is when writing a new timer.
Timers are divided in two parts: the timer itself which define when something must run and an associated service file to run something.
It’s a bit tiresome to write, but since I don’t do it often, it’s not a very big deal.
On the bright side, you can run the service without the timer and it will behave the same.&lt;/p&gt;
&lt;p&gt;Now that the introduction is over, let’s look at an example.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I’ll focus on user run services since that’s what I need, but if you save the files under &lt;tt class="docutils literal"&gt;/etc/systemd/system&lt;/tt&gt; and omit &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--user&lt;/span&gt;&lt;/tt&gt; from the commands, you can create custom and system wide timers and services.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;All the config files must be under &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/systemd/user/&lt;/span&gt;&lt;/tt&gt;.
Here is a sample &lt;tt class="docutils literal"&gt;.timer&lt;/tt&gt; file to help you get started (for instance &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;backup-stuff.timer&lt;/span&gt;&lt;/tt&gt;):&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Weekly backup&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Timer]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Supports keywords like weekly and monthly and CRON syntax.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;OnCalendar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;weekly&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Run now if the scheduled run was missed.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Persistent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Which service to run with this timer.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;backup-stuff.service&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the accompanying &lt;tt class="docutils literal"&gt;.service&lt;/tt&gt; file (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;backup-stuff.service&lt;/span&gt;&lt;/tt&gt;):&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Sync server backup to the local machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Specify dependencies as usual in systemd units.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# %n is the name of the current unit.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# This service will be called in case of failure with the name of the current unit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# so it can mention it in its error message.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;OnFailure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;notify-errors&amp;#64;%n.service&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Should be a oneshot service: we start with the timer, the script runs and then stops.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# %h will be replaced by the home directory of the user.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%h/bin/backup-servers.sh&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If you want to run it outside the timer, you can do &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; start &lt;span class="pre"&gt;backup-stuff.service&lt;/span&gt;&lt;/tt&gt;.
Even if you do that, the service won’t skip its next timer run.
That’s very useful for debugging.
And just like a normal run, the logs will end up in &lt;tt class="docutils literal"&gt;journald&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;And now, the service to notify failures, saved in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;.config/systemd/user/notify-errors&amp;#64;.service&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code systemd literal-block"&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Notify errors to the user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# %i will be replaced by the parameter (ie what comes after &amp;#64; when the service was called).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%h/bin/systemd-notify-errors.sh %i&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the accompanying script:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Get the last execution timestamp to filter the logs for this execution.
&lt;/span&gt;&lt;span class="nv"&gt;last_execution_timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;--property&lt;span class="w"&gt; &lt;/span&gt;ExecMainStartTimestamp&lt;span class="w"&gt; &lt;/span&gt;--value&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Extract the logs of this unit.
&lt;/span&gt;&lt;span class="nv"&gt;logs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;journalctl&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;--unit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--since&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$last_execution_timestamp&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Display the error to the user.
# Notification is displayed during 5 minutes (in ms).
# This requires the libnotify-tools package.
&lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;--urgency&lt;span class="w"&gt; &lt;/span&gt;normal&lt;span class="w"&gt; &lt;/span&gt;--expire-time&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;300000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="s2"&gt; failed&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$logs&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Don’t forget to refresh the &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; daemon with &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; &lt;span class="pre"&gt;daemon-reload&lt;/span&gt;&lt;/tt&gt; or you won’t be able to see your new services and timers.&lt;/p&gt;
&lt;p&gt;All we have left to do is to enable the timer with &lt;tt class="docutils literal"&gt;systemctl &lt;span class="pre"&gt;--user&lt;/span&gt; enable &lt;span class="pre"&gt;backup-stuff.timer&lt;/span&gt;&lt;/tt&gt;
And we are done!&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;User timers will only be executed for users that logs in by default.
To always execute them when the machine is up even if the user don’t log in, enable lingering with &lt;tt class="docutils literal"&gt;sudo loginctl &lt;span class="pre"&gt;enable-linger&lt;/span&gt; USER&lt;/tt&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://wiki.archlinux.org/title/Systemd/Timers"&gt;systemd/Timers&lt;/a&gt; on the Archlinux wiki.&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;Working with ``systemd`&lt;/cite&gt; Timers &amp;lt;&lt;a class="reference external" href="https://documentation.suse.com/smart/systems-management/html/systemd-working-with-timers/index.html"&gt;https://documentation.suse.com/smart/systems-management/html/systemd-working-with-timers/index.html&lt;/a&gt;&amp;gt;`__ on the openSUSE documentation.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Programmation"></category><category term="systemctl"></category><category term="Linux"></category></entry><entry><title>My take on UV and Ruff</title><link href="https://www.jujens.eu/posts/en/2025/Jan/25/uv-and-ruff/" rel="alternate"></link><published>2025-01-25T00:00:00+01:00</published><updated>2025-01-25T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2025-01-25:/posts/en/2025/Jan/25/uv-and-ruff/</id><summary type="html">&lt;p&gt;I recently tried the new and shiny tools made by &lt;a class="reference external" href="https://astral.sh/"&gt;astral&lt;/a&gt;.
I only used them on my personal projects yet, but I’m still very impressed!
You may already have heard of them.
I’ll try to keep the article concise and won’t dig too deep into the tools …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently tried the new and shiny tools made by &lt;a class="reference external" href="https://astral.sh/"&gt;astral&lt;/a&gt;.
I only used them on my personal projects yet, but I’m still very impressed!
You may already have heard of them.
I’ll try to keep the article concise and won’t dig too deep into the tools to prevent this article to be outdated fast: these tools are under very active development and thus change quickly.
The drawbacks listed here may not be a think any more when you’ll read it.&lt;/p&gt;
&lt;div class="section" id="ruff"&gt;
&lt;h2&gt;Ruff&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;ruff&lt;/tt&gt; the linting tool to unite them all.
It replaces &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt; and most of its plugins, &lt;tt class="docutils literal"&gt;pylint&lt;/tt&gt; and your formatter (most likely &lt;tt class="docutils literal"&gt;black&lt;/tt&gt; nowadays).
It’s very feature complete with over 800 rules.
And growing!
It’s main selling point is: it’s blazing fast.&lt;/p&gt;
&lt;p&gt;I was personally impressed by its speed both to validate a file and to reformat it.
The difference of speed with &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt; is noticeable and huge from &lt;tt class="docutils literal"&gt;pylint&lt;/tt&gt; (which was always slow).&lt;/p&gt;
&lt;p&gt;Integration with &lt;a class="reference external" href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt; comes out of the box, so it should be easy to run it both locally and in CI.
&lt;a class="reference external" href="https://docs.astral.sh/ruff/integrations/"&gt;The documentation&lt;/a&gt; also notably mentions GitHub actions, Gitlab CI and Docker.&lt;/p&gt;
&lt;p&gt;Formatting works great in Pycharm on save by configuring a save action.
Linting doesn’t work out of the box (it might change, see &lt;a class="reference external" href="https://github.com/astral-sh/ruff/issues/10102"&gt;this GitHub issue&lt;/a&gt;).
But since PyCharm has good linting built in, I only encountered a few linting errors on pre-commit.
So not a big deal for me.&lt;/p&gt;
&lt;p&gt;It also has &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff"&gt;an official VSCode extension&lt;/a&gt;.
I never tested it, so no opinions on that.
Given its rating and usage, I guess it’s very good.&lt;/p&gt;
&lt;p&gt;With any luck, it will also &lt;a class="reference external" href="https://github.com/astral-sh/ruff/issues/3893"&gt;replace type checkers&lt;/a&gt; one day with a much faster alternative.&lt;/p&gt;
&lt;p&gt;The only issue I see is if you have custom &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt; plugins: &lt;tt class="docutils literal"&gt;ruff&lt;/tt&gt; &lt;a class="reference external" href="https://github.com/astral-sh/ruff/issues/283"&gt;doesn’t yet support plugins&lt;/a&gt;.
But you could only rely on &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt; for that and use &lt;tt class="docutils literal"&gt;ruff&lt;/tt&gt; for everything else.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="uv"&gt;
&lt;h2&gt;uv&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;uv&lt;/tt&gt; is a new package manager. It can replace &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt; and tools like &lt;tt class="docutils literal"&gt;poetry&lt;/tt&gt; to manage your dependencies.
It supports dependencies groups and lock files.
Everything will be into your &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; file following the latest PEP.
Sadly, &lt;a class="reference external" href="https://github.com/dependabot/dependabot-core/issues/10478"&gt;dependabot is still not compatible with it&lt;/a&gt;, so no update notifications yet.
I hope this comes soon.&lt;/p&gt;
&lt;p&gt;It can also replace &lt;tt class="docutils literal"&gt;pipx&lt;/tt&gt; to install and run Python commands.
All you need to do is to install the tool with &lt;tt class="docutils literal"&gt;uv tool install flake8&lt;/tt&gt;.
You can then use the &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt; command as usual (provided &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.local/bin&lt;/span&gt;&lt;/tt&gt; is in your &lt;tt class="docutils literal"&gt;PATH&lt;/tt&gt;). You can also download and run a tool once with &lt;tt class="docutils literal"&gt;uvx flake8&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Even better, you can add dependencies to your script and let &lt;tt class="docutils literal"&gt;uv&lt;/tt&gt; create a venv and run the script in it with all its dependencies automatically!
To do so:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create your script.&lt;/li&gt;
&lt;li&gt;Add the dependencies with &lt;tt class="docutils literal"&gt;uv add &lt;span class="pre"&gt;--script&lt;/span&gt; example.py DEPS&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Run with &lt;tt class="docutils literal"&gt;uv run example.py&lt;/tt&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It can also download a Python interpretor if you don’t have it already installed on your machine.
So if a script is only compatible with a version of Python you don’t have, &lt;tt class="docutils literal"&gt;uv&lt;/tt&gt; will still be able to run it for you automatically.&lt;/p&gt;
&lt;p&gt;Once again, with &lt;tt class="docutils literal"&gt;uv&lt;/tt&gt; you don’t need anything else to run Python scripts and projects!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I’m very impressed by what they built and how fast they made it.
I strongly suggest you test these tools.
Given the vide of the community, you’ll probably use them too!
And you’ll probably be able to replace all your flake8 and &lt;tt class="docutils literal"&gt;pylint&lt;/tt&gt; workflows with them and make them run faster!
And to run your script without a sweat and built in dependency management!&lt;/p&gt;
&lt;p&gt;I also think the future of these tools will be great: they are open source after all, so the community should be able to take them back if needed.
Given how used and liked they are, I don’t see them disappear.
The only thing that could be an issue: astral is a VC backed company, so they will have to make money somehow at some point.
No idea what form it will take though.&lt;/p&gt;
&lt;p&gt;And you, do you have any thoughts on these tools? Did you use and like them?&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Python"></category></entry><entry><title>Weird test behavior in my Django project test suite after an IntegrityError</title><link href="https://www.jujens.eu/posts/en/2024/Dec/01/django-integrityerror-tests/" rel="alternate"></link><published>2024-12-01T00:00:00+01:00</published><updated>2024-12-01T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2024-12-01:/posts/en/2024/Dec/01/django-integrityerror-tests/</id><summary type="html">&lt;p&gt;Recently I encountered a very weird behavior in my Django project test suite.
I created a view that caught an &lt;tt class="docutils literal"&gt;IntegrityError&lt;/tt&gt; from the database (caused by duplicates in a unique index).
When this error occurs, I want to respond with a &lt;tt class="docutils literal"&gt;409 - CONFLICT&lt;/tt&gt; status code and an error message.&lt;/p&gt;
&lt;p&gt;TL …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I encountered a very weird behavior in my Django project test suite.
I created a view that caught an &lt;tt class="docutils literal"&gt;IntegrityError&lt;/tt&gt; from the database (caused by duplicates in a unique index).
When this error occurs, I want to respond with a &lt;tt class="docutils literal"&gt;409 - CONFLICT&lt;/tt&gt; status code and an error message.&lt;/p&gt;
&lt;p&gt;TL;DR: it’s caused by how transactions works in the test suite.
Using a &lt;tt class="docutils literal"&gt;transaction.atomic()&lt;/tt&gt; on the view solved it.
Read on to learn more.&lt;/p&gt;
&lt;p&gt;Everything worked fine in the browser.
But in the test suite, I always got a &lt;tt class="docutils literal"&gt;400 - BAD REQUEST&lt;/tt&gt; error with the default HTML error page as content.
Not my custom status nor custom JSON.
Worst, if I tried to log the queries, &lt;tt class="docutils literal"&gt;django_assert_num_queries&lt;/tt&gt; listed none.
Yet, I was sure they were executed.
And if I launched the debugger, my view correctly caught the &lt;tt class="docutils literal"&gt;IntegrityError&lt;/tt&gt; and responded with the proper response and the proper status.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I’m using &lt;tt class="docutils literal"&gt;pytest&lt;/tt&gt;, but if I’m right, it doesn’t have an impact on the problem nor on how to solve it.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I had no clue what was going on.
Luckily, I had to do the same modification (catch some &lt;tt class="docutils literal"&gt;IntegrityError&lt;/tt&gt;) in two other views as well.
I wandered if I’d always get the same behavior.
So I updated the second view and got the same issue.
I moved on the the third view and it worked as expected!&lt;/p&gt;
&lt;p&gt;At least I now had a comparison point to start investigating.
The main difference was that my third view was decorated with a &lt;tt class="docutils literal"&gt;transaction.atomic()&lt;/tt&gt;.
The other weren’t.&lt;/p&gt;
&lt;p&gt;So it hit me: each test functions is run in a transaction.
This way, we can easily and quickly rollback all the changes made by test test when it ends (successfully or not).
So, when my &lt;tt class="docutils literal"&gt;IntegrityError&lt;/tt&gt; hit, it caused a rollback to happen at test level before the test completed.
That’s why &lt;tt class="docutils literal"&gt;django_assert_num_queries&lt;/tt&gt; couldn’t list the queries and what caused me not to have access to the proper response.
I don’t know why in this case the response is always a generic 400 and not a more specific error message though.&lt;/p&gt;
&lt;p&gt;After testing with my existing views by adding/removing &lt;tt class="docutils literal"&gt;transaction.atomic()&lt;/tt&gt;,
I was able to confirm that I only got the correct behavior when &lt;tt class="docutils literal"&gt;transaction.atomic()&lt;/tt&gt; was present.
So I updated the view, made my test suite pass and went on to write this blog post!&lt;/p&gt;
&lt;p&gt;In the end, I think I was lucky to have the need to update a view wrapped in &lt;tt class="docutils literal"&gt;transaction.atomic()&lt;/tt&gt;.
Without this basis to compare, I think I’d have search way longer what was going on and why the problem was even occurring!&lt;/p&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Web"></category><category term="Django"></category></entry><entry><title>Offline support almost without Javascript</title><link href="https://www.jujens.eu/posts/en/2024/Feb/27/offline-support-almost-no-js/" rel="alternate"></link><published>2024-02-27T00:00:00+01:00</published><updated>2024-02-27T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2024-02-27:/posts/en/2024/Feb/27/offline-support-almost-no-js/</id><summary type="html">&lt;p&gt;Recently I wandered wether I could build a website with offline support without building a full SPA.
The answer is yes it’s doable: you only need Javascript for the service worker.
Just for the fun, I also tried it with navigation done with &lt;a class="reference external" href="https://htmx.org/"&gt;HTMX&lt;/a&gt; and without much surprise it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I wandered wether I could build a website with offline support without building a full SPA.
The answer is yes it’s doable: you only need Javascript for the service worker.
Just for the fun, I also tried it with navigation done with &lt;a class="reference external" href="https://htmx.org/"&gt;HTMX&lt;/a&gt; and without much surprise it also works.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;To do these tests, you must use a small webserver to serve your content.
I used &lt;tt class="docutils literal"&gt;python3 &lt;span class="pre"&gt;-m&lt;/span&gt; http.server&lt;/tt&gt;.&lt;/p&gt;
&lt;p class="last"&gt;If you use Firefox, you must shutdown the server to be offline.
If you go offline in the dev tools (for some reason I don’t know), your navigation won’t be handled by the service worker and you will appear truly offline.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let’s start by building a simple &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt; page:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt; 3 &lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt; 4 &lt;/span&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;base&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt; 5 &lt;/span&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'serviceWorker'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'load'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/service-worker.js'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Service Worker registered with scope:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Service Worker registration failed:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;15 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;16 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;17 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;18 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/page2.html'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;19 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;20 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;21 &lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;22 &lt;/span&gt;          &lt;span class="cm"&gt;&amp;lt;!-- &amp;lt;script src=&amp;quot;/htmx.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; --&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;23 &lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;24 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;25 &lt;/span&gt;
&lt;span class="ln"&gt;26 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;27 &lt;/span&gt;
&lt;span class="ln"&gt;28 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My first paragraph.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;29 &lt;/span&gt;
&lt;span class="ln"&gt;30 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;31 &lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Index&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;32 &lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page1.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;33 &lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page2.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;34 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;35 &lt;/span&gt;
&lt;span class="ln"&gt;36 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ln"&gt;37 &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The HTML part should be straightforward.
In the &lt;tt class="docutils literal"&gt;script&lt;/tt&gt; tag, I register my service worker (lines 7 to 15) and prefetch a page once it is ready so it’s in the cache (line 17 to 19).
The rest is just code to navigate between the different pages and a title to identify the page.&lt;/p&gt;
&lt;p&gt;I build two other page by copy/pasting this into &lt;tt class="docutils literal"&gt;page1.html&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;page2.html&lt;/tt&gt; and changing the &lt;tt class="docutils literal"&gt;h1&lt;/tt&gt;.
I then created my &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;service-worker.js&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code js literal-block"&gt;
&lt;span class="ln"&gt; 1 &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'my-cache-v1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 2 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;urlsToCache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 3 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 4 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'/page1.html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 5 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 6 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 7 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'install'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 8 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt; 9 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;10 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;11 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlsToCache&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;12 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;13 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;14 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;15 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;16 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fetch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;17 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Captured fetching'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;18 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;19 &lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;20 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;21 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;22 &lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Serving from cache'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;23 &lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;24 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;25 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Fetching from server'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;26 &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;27 &lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;28 &lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;29 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;30 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Add the fetched response to the cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;31 &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetchResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;32 &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;33 &lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;34 &lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;35 &lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="ln"&gt;36 &lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;In it, I add some content to the cache by default (lines 7 to 14).&lt;/p&gt;
&lt;p&gt;I then register a listener to the &lt;tt class="docutils literal"&gt;fetch&lt;/tt&gt; event so I can choose how to respond to HTTP requests (lines 16 to 36).
I choose to serve the response from the cache if I have it.
If not, I fetch it then put it in the cache for later usage.&lt;/p&gt;
&lt;p&gt;It’s very basic, but it’s enough to make it work.
You can also test it by navigating &lt;a class="reference external" href="https://www.jujens.eu/static/basic-offline-pwa/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;&lt;tt class="docutils literal"&gt;page2.html&lt;/tt&gt; (fetched from the main page) won’t be put into the cache until you refresh the page.
From what I understood, it’s for consistency: never serve from the cache until you are sure the service worker is active and working.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can now test the app and see how it behaves.&lt;/p&gt;
&lt;p&gt;To test with HTMX, I replaced the &lt;tt class="docutils literal"&gt;ul&lt;/tt&gt; (lines 30 to 34) with either:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="cm"&gt;&amp;lt;!-- Test with HTMX boost to use standard links --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;hx-boost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Index&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page1.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page2.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="cm"&gt;&amp;lt;!-- Test with HTMX links to use standard links --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;hx-get&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;hx-push-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Index&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;hx-get&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page1.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;hx-push-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;hx-get&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/page2.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;hx-push-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page 2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;That’s it.
The prefetching is very basic, but I think you get the idea: generate a list of URL and then call them.&lt;/p&gt;
&lt;p&gt;If you want to learn more about a related topic, I wrote an article about &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2020/Feb/29/django-pwa/"&gt;PWA and Django&lt;/a&gt; a while back.&lt;/p&gt;
</content><category term="Programmation"></category><category term="PWA"></category><category term="HTML"></category><category term="Javascript"></category></entry><entry><title>Development containers</title><link href="https://www.jujens.eu/posts/en/2024/Jan/02/decontainers/" rel="alternate"></link><published>2024-01-02T00:00:00+01:00</published><updated>2024-01-02T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2024-01-02:/posts/en/2024/Jan/02/decontainers/</id><summary type="html">&lt;p&gt;I recently discovered the &lt;a class="reference external" href="https://containers.dev/"&gt;dev containers standard&lt;/a&gt; (or development containers for long) recently after trying to contribute to a project which had them enabled by default.
It seems to be a new standard way to work with containers in development.&lt;/p&gt;
&lt;p&gt;The goal is to provide a container you can use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently discovered the &lt;a class="reference external" href="https://containers.dev/"&gt;dev containers standard&lt;/a&gt; (or development containers for long) recently after trying to contribute to a project which had them enabled by default.
It seems to be a new standard way to work with containers in development.&lt;/p&gt;
&lt;p&gt;The goal is to provide a container you can use as a full featured environment for development.
Your editor and its terminal are connected to the container and you run everything in it.
So instead of running some things locally or wrapping everything in &lt;tt class="docutils literal"&gt;docker compose exec&lt;/tt&gt; to run them on the container, you run everything directly in the container thanks to your editor.
With VSCode, you can even &lt;a class="reference external" href="https://code.visualstudio.com/docs/devcontainers/containers#_personalizing-with-dotfile-repositories"&gt;personalize the container&lt;/a&gt; to install your favorite shell and theme (in my case, I use ZSH or Fish with &lt;a class="reference external" href="https://starship.rs/"&gt;starship&lt;/a&gt;), use your customized git configuration as well as any extra tools you might need.&lt;/p&gt;
&lt;p&gt;It is compatible with plain docker as well as docker compose and &lt;a class="reference external" href="https://containers.dev/supporting"&gt;supports&lt;/a&gt; VSCode and Pycharm (currently in beta in Pycharm).
According to my tests, it works very well with VSCode and will be picked up automatically if you have the proper extensions installed (see the getting started section of &lt;a class="reference external" href="https://code.visualstudio.com/docs/devcontainers/containers#_getting-started"&gt;the doc&lt;/a&gt; which is really good) and is still rocky in Pycharm (it even broke for a while after a Pycharm update).
Once it supported correctly, it means no matter what editor you use (provided it supports the standard) you will have the same experience and behaviors.
And for Pycharm, it means you can use it inside a container without the usual slowness regarding container creation for every command you launch.&lt;/p&gt;
&lt;p&gt;I think it’s a very good idea and that we should use it more.
It does come with a few issues if you use user namespaces with Docker like me.
Let’s dig deeper for that use case.&lt;/p&gt;
&lt;div class="section" id="user-namespaces"&gt;
&lt;h2&gt;User namespaces&lt;/h2&gt;
&lt;p&gt;The goal of user namespaces is to map a user inside the container to another user outside.
I’ve explored this extensively in &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/"&gt;this article&lt;/a&gt;.
For instance, I map the root user inside the container to my user outside.
This way, all files created by root in the container will belong to me outside.
It makes sharing files on volumes much easier on Linux where there is no abstraction layer to do UID conversions automatically.
It also brings extra security: root inside the container is a mere user on the host.
If you don’t run the container with the root user, files will belong to a weird UID outside the container.&lt;/p&gt;
&lt;p&gt;Let’s illustrate this with an example. Let’s take this Dockerfile:&lt;/p&gt;
&lt;pre class="code Dockerfile literal-block"&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:latest&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;passwd&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;groupadd&lt;span class="w"&gt; &lt;/span&gt;--gid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dev-user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;--uid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--gid&lt;span class="w"&gt; &lt;/span&gt;dev-user&lt;span class="w"&gt; &lt;/span&gt;--shell&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;--create-home&lt;span class="w"&gt; &lt;/span&gt;dev-user
&lt;/pre&gt;
&lt;p&gt;With user namespacing enabled in the Docker daemon, build the image with &lt;tt class="docutils literal"&gt;docker build &lt;span class="pre"&gt;-f&lt;/span&gt; Dockerfile &lt;span class="pre"&gt;--tag&lt;/span&gt; test:latest .&lt;/tt&gt; then run it with &lt;tt class="docutils literal"&gt;docker run &lt;span class="pre"&gt;-it&lt;/span&gt; &lt;span class="pre"&gt;--rm&lt;/span&gt; test:latest&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;If you look at permissions in the home folder or &lt;tt class="docutils literal"&gt;/usr/bin/bash&lt;/tt&gt; you should see something like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
root&amp;#64;435ec3552812:/# ls -alhn /home/
total 0
drwxr-xr-x 1    0    0  16 Dec 24 13:24 .
drwxr-xr-x 1    0    0 174 Dec 24 13:24 ..
drwxr-xr-x 1 1000 1000  54 Dec 24 13:24 dev-user
root&amp;#64;435ec3552812:/# ls -alh /home/
total 0
drwxr-xr-x 1 root     root      16 Dec 24 13:24 .
drwxr-xr-x 1 root     root     174 Dec 24 13:24 ..
drwxr-xr-x 1 dev-user dev-user  54 Dec 24 13:24 dev-user
&lt;/pre&gt;
&lt;p&gt;Now, rerun with: &lt;tt class="docutils literal"&gt;docker run &lt;span class="pre"&gt;-it&lt;/span&gt; &lt;span class="pre"&gt;--rm&lt;/span&gt; &lt;span class="pre"&gt;--userns&lt;/span&gt; host test:latest&lt;/tt&gt;  to disable user namespacing for this run. You should see something like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
root&amp;#64;565d6ae60c50:/# ls -alh /home/
total 0
drwxr-xr-x 1 dev-user  dev-user   16 Dec 24 13:24 .
drwxr-xr-x 1 dev-user  dev-user  174 Dec 24 13:26 ..
drwxr-xr-x 1 100000999 100000999  54 Dec 24 13:24 dev-user
root&amp;#64;565d6ae60c50:/# ls -alhn /home/
total 0
drwxr-xr-x 1      1000      1000  16 Dec 24 13:24 .
drwxr-xr-x 1      1000      1000 174 Dec 24 13:26 ..
drwxr-xr-x 1 100000999 100000999  54 Dec 24 13:24 dev-user
&lt;/pre&gt;
&lt;p&gt;Root became user with UID 1000 (including for already present files like &lt;tt class="docutils literal"&gt;/usr/bin/bash&lt;/tt&gt;) while user became something non existent (the remapped UID used during the build).
Before, inside the container everything was correct including for files like &lt;tt class="docutils literal"&gt;/usr/bin/bash&lt;/tt&gt; which was correctly owned by root with UID 0 inside the container.&lt;/p&gt;
&lt;div class="section" id="disabling-user-namespacing"&gt;
&lt;h3&gt;Disabling user namespacing&lt;/h3&gt;
&lt;p&gt;Let’s now disable user namespacing in the Docker daemon and retry: &lt;tt class="docutils literal"&gt;docker run &lt;span class="pre"&gt;-it&lt;/span&gt; &lt;span class="pre"&gt;--rm&lt;/span&gt; test:latest&lt;/tt&gt;. You should get something like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
root&amp;#64;ed2f0d20f197:/# ls -aln /usr/bin/bash
-rwxr-xr-x 1 0 0 1265648 Apr 23  2023 /usr/bin/bash
root&amp;#64;ed2f0d20f197:/# ls -alh /usr/bin/bash
-rwxr-xr-x 1 root root 1.3M Apr 23  2023 /usr/bin/bash
root&amp;#64;ed2f0d20f197:/# ls -alh /home/
total 0
drwxr-xr-x 1 root     root     16 Dec 24 13:30 .
drwxr-xr-x 1 root     root      0 Dec 24 13:31 ..
drwxr-xr-x 1 dev-user dev-user 54 Dec 24 13:30 dev-user
root&amp;#64;ed2f0d20f197:/# ls -alhn /home/
total 0
drwxr-xr-x 1    0    0 16 Dec 24 13:30 .
drwxr-xr-x 1    0    0  0 Dec 24 13:31 ..
drwxr-xr-x 1 1000 1000 54 Dec 24 13:30 dev-user
&lt;/pre&gt;
&lt;p&gt;So everything is fine.
But, files created with root in the container will be owned by root outside.
Files created in the container will be owned by whatever user has UID 1000 on your system (most likely you).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="problems-with-vscode-and-devcontainers"&gt;
&lt;h3&gt;Problems with VSCode and devcontainers&lt;/h3&gt;
&lt;!-- Doesn’t seem to affect PyCharm. --&gt;
&lt;p&gt;By default, VSCode uses the root user inside the container: you will perform all your operations as root whether user namespacing is enabled or not.
With user namespacing on, it’s not a big deal since everything will be mapped to the correct user.
Without it, everything belongs to root which is a pain.
You are also doing all your actions as root which is still weird and probably a bad idea since it may not be realistic to enforce user namespacing to all members of your team (without even thinking about open source project).&lt;/p&gt;
&lt;p&gt;You can setup VSCode to use a different user.
That will work fine without user namespacing.
But with it, you UID outside the container will be whatever UID user namespacing attributed to you.
So you won’t have read access to the files (unless you open read access for everybody but that’s just another bad idea).
So you will be stuck and won’t be able to develop.&lt;/p&gt;
&lt;p&gt;You could ask VScode to run the containers with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--userns&lt;/span&gt; host&lt;/tt&gt; option (it’s also doable with docker compose by adding &lt;tt class="docutils literal"&gt;userns_mode: host&lt;/tt&gt; to your service definition).
But, since the user will be added during the build step and you can’t build your image with user ns disabled (or if it’s possible, I haven’t found how), the files of the newly added users won’t belong to it as we’ve seen earlier and VSCode won’t be able to install its server.&lt;/p&gt;
&lt;p&gt;It turns out, VSCode has a nice feature: &lt;tt class="docutils literal"&gt;updateRemoteUserUID&lt;/tt&gt;.
It’s on by default and will change the UID of the user of the container (unless it’s root which must stay with UID 0) to the UID of your user.
So, you can transparently create file inside or outside the container they will have the correct UID!
Sadly (and despite what the doc is saying) it doesn’t seem to work for GID which will still be whatever it is in the container.
But it’s still a huge step forward.&lt;/p&gt;
&lt;p&gt;Here is the docker file I used (inside a &lt;tt class="docutils literal"&gt;.devcontainer&lt;/tt&gt; folder):&lt;/p&gt;
&lt;pre class="code Dockerfile literal-block"&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:latest&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;passwd&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Let’s use something that proably doesn’t exit on the system.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;groupadd&lt;span class="w"&gt; &lt;/span&gt;--gid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dev-user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;--uid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--gid&lt;span class="w"&gt; &lt;/span&gt;dev-user&lt;span class="w"&gt; &lt;/span&gt;--shell&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;--create-home&lt;span class="w"&gt; &lt;/span&gt;dev-user&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/app&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dev-user&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And the &lt;tt class="docutils literal"&gt;devcontainer.json&lt;/tt&gt; (also inside the &lt;tt class="docutils literal"&gt;.devcontainer&lt;/tt&gt; folder):&lt;/p&gt;
&lt;pre class="code json literal-block"&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dockerfile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dockerfile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workspaceMount&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source=${localWorkspaceFolder},target=/app,type=bind&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workspaceFolder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;updateRemoteUserUID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Let’s recap:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Enabling user namespacing: besides using root as a user for all your actions, I don’t think it’s usable. And it’s probably a bad idea (you wouldn’t do it on your host system after all) and for your team mates that don’t have user namespacing enabled it will create a security issue since they are also running everything as root on the host.&lt;/li&gt;
&lt;li&gt;Disabling user namespacing and using a dedicated user: thanks to VSCode and its &lt;tt class="docutils literal"&gt;updateRemoteUserUID&lt;/tt&gt; feature everything will work correctly out of the box. I still think it’s better to explicitly set the UID and GID to 1000 when building the container: it’s probably the right value. Since the GID isn’t updated, it’s a very good default and will hide the internals away for most users.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up"&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I’ve been a huge proponent of user namespacing for years since it makes everything go more smoothly on Linux while increasing security.
It doesn’t seem to work well with dev containers.
Since the spec allows you to remap the UID of a standard user inside the container to your UID outside, it’s not a very big deal.&lt;/p&gt;
&lt;p&gt;I think from now on, I’ll only enable user namespacing if I really need it and launch all my dev works in containers as a standard user letting my IDE doing the UID remapping if needed.&lt;/p&gt;
&lt;p&gt;I also think I’d leave user namespacing on in my production environment so that if an attacker gains root access in a container, they don’t have root access on the host.&lt;/p&gt;
&lt;p&gt;And you, what do you think?
Do you know more about devcontainers?
If so, please leave a comment below.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Docker"></category></entry><entry><title>Django async</title><link href="https://www.jujens.eu/posts/en/2023/Dec/10/django-async/" rel="alternate"></link><published>2023-12-10T00:00:00+01:00</published><updated>2023-12-10T00:00:00+01:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2023-12-10:/posts/en/2023/Dec/10/django-async/</id><summary type="html">&lt;p&gt;Now that Django is fully async (views, middleware and ORM), I though it was a good time to test how it behaves when run asynchronously.
I’ll try to keep this article concise with only relevant data and resources.
Code can be seen in &lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async"&gt;a sample project&lt;/a&gt; so you can …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Now that Django is fully async (views, middleware and ORM), I though it was a good time to test how it behaves when run asynchronously.
I’ll try to keep this article concise with only relevant data and resources.
Code can be seen in &lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async"&gt;a sample project&lt;/a&gt; so you can check the code and go further if you want.
I won’t explain it, but I think it’s simple enough to be easy to understand if you already know Python and Django.
I also provide a synthesis and conclusion at the end of the article.
I checked the most common solutions to serve a project: the classic (and included in Django) &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; (dev only), &lt;a class="reference external" href="https://gunicorn.org/"&gt;gunicorn&lt;/a&gt;, &lt;a class="reference external" href="https://www.uvicorn.org/"&gt;uvicorn&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/django/daphne"&gt;daphne&lt;/a&gt; and &lt;a class="reference external" href="https://hypercorn.readthedocs.io/en/latest/index.html"&gt;hypercorn&lt;/a&gt;.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#running-a-basic-view" id="toc-entry-1"&gt;Running a basic view&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sync-vs-async-behavior" id="toc-entry-2"&gt;Sync vs async behavior&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#getting-more-serious-with-async-using-the-streaminghttpresponse" id="toc-entry-3"&gt;Getting more serious with async: using the StreamingHttpResponse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#going-further-with-async-server-sent-events-sse" id="toc-entry-4"&gt;Going further with async: Server Sent Events (SSE)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#how-about-websockets" id="toc-entry-5"&gt;How about Websockets?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#http2-support" id="toc-entry-6"&gt;HTTP2 support&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#how-about-http2-push-feature" id="toc-entry-7"&gt;How about HTTP2 PUSH feature?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#load-testing" id="toc-entry-8"&gt;Load testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#wrapping-up" id="toc-entry-9"&gt;Wrapping up!&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#my-recommendations" id="toc-entry-10"&gt;My recommendations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="running-a-basic-view"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Running a basic view&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started by checking the behavior of a basic view: it renders a template and loads a CSS file.
I wanted to check how each solution behaves when the template and the static file are updated and whether everything is served correctly.
This is mostly to check the behavior of each solution during development: in production, you won’t update your templates on the fly and will rely on something else to serve your static assets.
I think it is still interesting and useful if you want to be as close as possible from your production environment in development.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt;: as expected, everything went smoothly, the static file is served and when the template is updated a simple page reload allowed me to see the newest version.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt;: when launched directly, the template modification is not available until I do a restart and the static file is not found. It’s not surprising since allowing this is a feature of &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; for ease of development.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;: same as daphne.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;uvicorn&lt;/tt&gt;: same as daphne.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt;: same as daphne.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s interesting is that you can change this default behavior with command line options for development:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt;: you can install it as an app in your Django project. It will then be picked up by &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt;. So &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; will behave like an ASGI server while still being able to serve static files and to see template modifications immediately. You can make sure &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; is used if you see &lt;tt class="docutils literal"&gt;Starting ASGI/Daphne&lt;/tt&gt; in the startup logs.&lt;/li&gt;
&lt;li&gt;For all other servers, you will need to rely on &lt;a class="reference external" href="https://whitenoise.readthedocs.io/en/latest/django.html"&gt;Whitenoise&lt;/a&gt; to serve static assets and use their reloading option to update when source code change and their watch extra files one to restart when HTML files are updated. Please note that during my test, reloading in &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; seemed broken.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regarding template reloading, you could also choose to disable template caching to always use the latest one.
See &lt;a class="reference external" href="https://nickjanetakis.com/blog/django-4-1-html-templates-are-cached-by-default-with-debug-true"&gt;this article&lt;/a&gt; for more.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;If you are in an async view, you must use only async operation and wrap sync operations (like a call to the ORM) in &lt;tt class="docutils literal"&gt;sync_to_async&lt;/tt&gt;. You will get errors if you don’t. Same goes in reverse with &lt;tt class="docutils literal"&gt;async_to_sync&lt;/tt&gt;. You can make sync and async views cohabit without any issues whether you serve your project on WSGI or ASGI. You won’t really benefit from async on a WSGI serve app though.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;You can hook into the signal &lt;tt class="docutils literal"&gt;autoreload_started&lt;/tt&gt; to make &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; restart on any file you want. But it’s not documented and thus may break at any time. See &lt;a class="reference external" href="https://stackoverflow.com/a/43593959"&gt;here&lt;/a&gt; for more.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/6d286d7a9dba2a30c80372a739a768a78c3a2237"&gt;Do basic tests with API &amp;amp; template views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/960ce5a5402076da40f29becfbdb0e918194bd55"&gt;Setup daphne&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/d8c50ea0971ec1dee519e5d52b98b14dd5d8f902"&gt;Test with model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/5eef873d01574dd9e185bfda0477cecd3bbd3d5b"&gt;Test app servers&lt;/a&gt; (includes script to launch each servers in a dev like and prod like fashion).&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/1f8fb40288110eba9f2d2ff89d61e6657e6dd8bf"&gt;Use whitenoise for static file serving&lt;/a&gt; (I basically followed &lt;a class="reference external" href="https://whitenoise.readthedocs.io/en/latest/django.html#using-whitenoise-with-django"&gt;the tutorial&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="sync-vs-async-behavior"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Sync vs async behavior&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To spot any differences between sync and async behaviors, I created a very simple view that returns JSON.
I then sleep 10s with &lt;tt class="docutils literal"&gt;time.sleep&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;asyncio.sleep&lt;/tt&gt;.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt;: I passed the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--nothreading&lt;/span&gt;&lt;/tt&gt; option to avoid having multiple threads that could handle the requests simultaneously. By default, if I launch two requests in parallel, the first completes in 10s and the second one in 20s. So they are handled one after the other. The fact that no, one or both requests are made to an async view doesn’t change a thing. That was what I was expecting. I’ll call this the fully sync behavior.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt;: both requests always ends after 10s. Even if I target two times the sync view. I suppose it is using &lt;tt class="docutils literal"&gt;sync_to_async&lt;/tt&gt; to run the non async views, which, as far as I know, will make it run in a thread. Using it directly or through &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; doesn’t change the behavior. I’ll call this the fully async behavior.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;: as expected, I get the fully sync behavior.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;uvicorn&lt;/tt&gt;: as expected, I get the fully async behavior, whether I launch it directly or as a &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; worker as suggested &lt;a class="reference external" href="https://www.uvicorn.org/deployment/#gunicorn"&gt;in the documentation&lt;/a&gt; for production environment.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt;: as expected, I get the fully async behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/6d286d7a9dba2a30c80372a739a768a78c3a2237"&gt;Do basic tests with API &amp;amp; template views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/d8c50ea0971ec1dee519e5d52b98b14dd5d8f902"&gt;Test with model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="getting-more-serious-with-async-using-the-streaminghttpresponse"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Getting more serious with async: using the StreamingHttpResponse&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;StreamingHttpResponse&lt;/tt&gt; is not new and allows us to stream a response, ie instead of sending it in one go you send it chunk by chunk.
The use case for this in &lt;a class="reference external" href="https://docs.djangoproject.com/en/4.2/ref/request-response/#streaminghttpresponse-objects"&gt;the documentation&lt;/a&gt; is to send a big CSV file.
As the documentation points out, in WSGI, you will need a worker for the whole duration of the response.
This worker won’t be able to serve any other clients.
That’s where ASGI really comes handy: your worker can still server clients while it is waiting on IO.
Let’s test this.&lt;/p&gt;
&lt;p&gt;I created two new views to test this: one sync and one async.
I used &lt;a class="reference external" href="https://httpie.io/"&gt;HTTPIE&lt;/a&gt; like this to view the streaming: &lt;tt class="docutils literal"&gt;http &lt;span class="pre"&gt;'http://localhost:8000/stream'&lt;/span&gt; &lt;span class="pre"&gt;--stream&lt;/span&gt;&lt;/tt&gt; (sync stream) and &lt;tt class="docutils literal"&gt;http &lt;span class="pre"&gt;'http://localhost:8000/astream'&lt;/span&gt; &lt;span class="pre"&gt;--stream&lt;/span&gt;&lt;/tt&gt; (async stream).&lt;/p&gt;
&lt;p&gt;When using &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; I can serve both views.
The sync one is correctly streamed.
The async one responds but all in one go (ie without any streaming).
And I got this warning: &lt;em&gt;StreamingHttpResponse must consume asynchronous iterators in order to serve them synchronously. Use a synchronous iterator instead&lt;/em&gt;.
It’s consistent with what the doc says:&lt;/p&gt;
&lt;blockquote&gt;
When serving under WSGI, this should be a sync iterator. When serving under ASGI, then it should be an async iterator. […] Under WSGI the response will be iterated synchronously. Under ASGI the response will be iterated asynchronously. (This is why the iterator type must match the protocol you’re using.)&lt;/blockquote&gt;
&lt;p&gt;When using an ASGI server, I got the reversed behavior: the async view streamed its content while the sync one didn’t.
And I got this warning: &lt;em&gt;StreamingHttpResponse must consume synchronous iterators in order to serve them asynchronously. Use an asynchronous iterator instead&lt;/em&gt; (except for &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; which for some reason didn’t print anything).&lt;/p&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/50a2629000a51e89d5502756f595b585800a895e"&gt;Test StreamingHttpResponse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="going-further-with-async-server-sent-events-sse"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Going further with async: Server Sent Events (SSE)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Instead of just stream data, how about streaming it to a browser and allowing the browser to react?
This could be handy to notify the browser of some changes (like new data being inserted).
Like websockets, the client could see the update immediately.
Unlike Websockets, communication is unidirectional: from the server to the client.
But it should be enough for many use cases and doesn’t require any extra lib.&lt;/p&gt;
&lt;p&gt;This is done with &lt;tt class="docutils literal"&gt;StreamingHttpResponse&lt;/tt&gt; and an async iterator.&lt;/p&gt;
&lt;p&gt;How to get notified of events?
You could use &lt;tt class="docutils literal"&gt;PostgreSQL&lt;/tt&gt; directly thanks to its listen/notify feature or rely on the pub/sub feature of Redis.&lt;/p&gt;
&lt;p&gt;There are several things you must pay attention to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Each message must be ended with &lt;em&gt;two line breaks&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The data must start with &lt;tt class="docutils literal"&gt;data:&lt;/tt&gt; or you won’t be able to access the data in JS. So your payload must be like &lt;tt class="docutils literal"&gt;f&amp;quot;data: {data}&amp;quot;&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;The content type of your response must be &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;quot;text/event-stream&amp;quot;&lt;/span&gt;&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;You can then create an &lt;tt class="docutils literal"&gt;EventSource&lt;/tt&gt; in JS and parse each event &lt;tt class="docutils literal"&gt;data&lt;/tt&gt; property.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details on this, please read &lt;a class="reference external" href="https://valberg.dk/django-sse-postgresql-listen-notify.html"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/23b081411831eb82dc94d7da7ebd06cce3736a73"&gt;Test server sent events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="how-about-websockets"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;How about Websockets?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To use Websockets, you still need to use &lt;a class="reference external" href="https://channels.readthedocs.io/en/stable/index.html"&gt;Django Channels&lt;/a&gt;.
It works on all servers except &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; (you &lt;em&gt;need&lt;/em&gt; ASGI for this, no work around or compatibility with &lt;tt class="docutils literal"&gt;async_to_sync&lt;/tt&gt; this time!).&lt;/p&gt;
&lt;p&gt;It’s very easy to set up.
To test it you don’t even need Redis (the only supported channel used to dispatch messages to all consumers) and can rely on a channel that works in memory.
I only followed &lt;a class="reference external" href="https://channels.readthedocs.io/en/stable/tutorial/index.html"&gt;the official tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/0a1e1a4eb325091c58a7434542cafb3e58330c97"&gt;Test Websocket with channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="http2-support"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;HTTP2 support&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTP2 is only supported by &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; out of the box.
For &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; you need to install two new packages: one for HTTP2 and one TLS.
That’s because daphne only does HTTP2 through TLS.
Since your browser won’t open a HTTP2 connexion unless it’s under TLS it’s not a big deal.
&lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; doesn’t support HTTP2 and &lt;a class="reference external" href="https://github.com/encode/uvicorn/issues/47#issuecomment-1029020324"&gt;uvicorn decided&lt;/a&gt; not to add it because they are alternatives (&lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; as well as using a good old reverse proxy like &lt;tt class="docutils literal"&gt;nginx&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;Apache&lt;/tt&gt; in from of the ASGI server).&lt;/p&gt;
&lt;p&gt;It worked fine with both &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;To test an HTTP2 connection, you can use &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; with its &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--http2&lt;/span&gt;&lt;/tt&gt; option or Firefox (as far as I know Chrome doesn’t display the HTTP version of the connection).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;To test this in Firefox, I had to generate self signed certificates. I used the method described &lt;a class="reference external" href="https://devopscube.com/create-self-signed-certificates-openssl/"&gt;here&lt;/a&gt;. See the script for how to launch &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; with HTTP2 and certificates.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/895038e9a1eeba826cac5c272b033be394b72fa1"&gt;Setup HTTP2 for daphne&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="how-about-http2-push-feature"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;How about HTTP2 PUSH feature?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When HTTP2 was launched I clearly remember that push was &lt;em&gt;the&lt;/em&gt; feature everybody was exited about.
I never bothered to dig and enable it, but it sounded compelling: you could push assets to the browser before it even asked for it to load them faster!
This test sounded like the perfect time to give it a try.&lt;/p&gt;
&lt;p&gt;And… it turns out &lt;a class="reference external" href="https://developer.chrome.com/blog/removing-push?hl=e"&gt;Chrome removed it&lt;/a&gt; a couple of years ago and &lt;a class="reference external" href="http://nginx.org/en/CHANGES"&gt;nginx deprecated it&lt;/a&gt; in June 2023 in version 1.25.1 (the directives have no effect now, but don’t yet trigger an error). Ouch!&lt;/p&gt;
&lt;p&gt;It turns out that it’s not easy to use, makes caching a lot harder and can lead to needless resources being pushed or the same resources being pushed more than once.
For more, please read &lt;a class="reference external" href="https://evertpot.com/http-2-push-is-dead/"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I still decided to test it for the sake of it.
I made it run easily &lt;a class="reference external" href="https://httpd.apache.org/docs/2.4/fr/howto/http2.html"&gt;with Apache&lt;/a&gt;.
I’ll extend a bit since it not obvious:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;I used the default &lt;tt class="docutils literal"&gt;httpd.conf&lt;/tt&gt; file from the container as a basis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Then I added this at the top of the file:&lt;/p&gt;
&lt;pre class="code apache literal-block"&gt;
&lt;span class="nb"&gt;Listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;LoadModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http2_module&lt;span class="w"&gt; &lt;/span&gt;modules/mod_http2.so&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;Protocols&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;h2&lt;span class="w"&gt; &lt;/span&gt;http/1.1&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;LoadModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ssl_module&lt;span class="w"&gt; &lt;/span&gt;modules/mod_ssl.so&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;SSLEngine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;Listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;SSLCertificateFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/certs/server.crt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;SSLCertificateKeyFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/certs/server.key&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;LoadModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;proxy_module&lt;span class="w"&gt; &lt;/span&gt;modules/mod_proxy.so&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;LoadModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;proxy_http_module&lt;span class="w"&gt; &lt;/span&gt;modules/mod_proxy_http.so&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;ProxyPass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;/static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;ProxyPass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://172.17.0.1:80/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;ProxyPassReverse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://172.17.0.1:80/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;VirtualHost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;DocumentRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;/var/www/html&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/var/www/html&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;Require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;granted&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;ServerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;ServerAlias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;I then updated my view so to would add a &lt;tt class="docutils literal"&gt;Link&lt;/tt&gt; header listing the resources to push:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;Link&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;/static/home.css&amp;gt;; as=style; rel=preload, &amp;lt;/static/list.css&amp;gt;; as=style; rel=preload&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;You can see in Firefox in the dev tools: the CSS files are loaded but not associated request is displayed in the explorer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;You can test it more easily with &lt;a class="reference external" href="https://nghttp2.org/"&gt;nghttp&lt;/a&gt; a CLI tool dedicated to testing HTTP2 connections. To do so, once you’ve installed it, you can launch it like this: &lt;tt class="docutils literal"&gt;nghttp &lt;span class="pre"&gt;-ans&lt;/span&gt; &lt;span class="pre"&gt;https://localhost:8080/async&lt;/span&gt;&lt;/tt&gt;. It will list the resources loaded by the page with an asterix if they were pushed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I never managed to make it work with &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; though with the same settings.
Since it is being deprecated, I didn’t dig any further.&lt;/p&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;cite&gt;Test with reverse proxy for benchmark &amp;amp; HTTP push &amp;lt;https://gitlab.com/Jenselme/dj-test-async/-/commit/42fa4713047291716637f3eb99709faa10b392d9&amp;gt;&lt;/cite&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="load-testing"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Load testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After testing all that, I decided to do some load testing to see how everything behaved.
I did all my testing with &lt;a class="reference external" href="https://nghttp2.org/documentation/h2load-howto.html"&gt;h2load&lt;/a&gt; which comes with &lt;tt class="docutils literal"&gt;nghttp&lt;/tt&gt; since it supports load testing with HTTP1 and 2.
I won’t post the detail results and, as any benchmark made by an amateur in non real conditions, you should take it with a big grain of salt.&lt;/p&gt;
&lt;p&gt;I tested on HTTP2 (always with TLS) and with HTTP1 (with and without TLS).
When I used a reserve proxy, &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; in HTTP1 was always the application server.
I tested with 1 concurrent connection, 200 and 500.&lt;/p&gt;
&lt;p&gt;Here is a summary of my results:&lt;/p&gt;
&lt;!-- server protocol: min-max 1 client, 200 clients, 500 clients.
daphne http2: 42ms- -373ms  671ms- -15s  172ms- -24s(23% errors) (no diff sync vs async views)
daphne http1 no https: 10ms- -109ms  1s- -19s  4s- -33s (on errors)
daphne http1 + https: 24ms- -104ms  280ms- -17s   147ms- -45s (no errors)
hypercorn http2: 43ms- -425ms  3s- -28s  7s- -75s
hypercorn http: go weird error with the script. Works in the browser, no digging.
gunicorn (base): 1,15ms
uvicorn: 23ms- -119ms  175ms- -19s (2% errors)  4s- -16s
Apache HTTP2: 18ms- -282ms  15ms- -293ms  (&lt;1% errors)  19ms- -42s  (44% errors)
Apache HTTP1 + https: 4ms- -135ms  963ms-19s  3s- -36s
Nginx http2, lots of errors, maybe because of https://www.nginx.com/blog/http-2-rapid-reset-attack-impacting-f5-nginx-products/
Nginx HTTP1 close to Apache. --&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Adding HTTPS slows the app. This is mostly seen when comparing HTTP1 with HTTP1 + TLS. No surprises there: TLS has a cost. Letting a reverse proxy handle TLS removes this slowness.&lt;/li&gt;
&lt;li&gt;In this test, the app was slower when server in HTTP2, whether directly on in front of a reverse proxy. I guess HTTP2 has an extra cost (in addition to TLS). Relying on a reverse proxy, just like with HTTP1, greatly improved performance (note that the proxy and daphne communicated in HTTP1).&lt;/li&gt;
&lt;li&gt;I got some errors (between 25% and 44%) when using HTTP2 with 500 concurrent clients. I got more error with Apache than with raw daphne. I think this case is very theoretic anyway since you wouldn’t have this in on a real production environment: your app would be much more complex and you would probably have more workers to process the load thus preventing this issue.&lt;/li&gt;
&lt;li&gt;I found no performance difference between all servers. Nor between sync and async views.&lt;/li&gt;
&lt;li&gt;I got errors with &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; in HTTP2. I don’t know why and it seemed to work fine in a browser. I didn’t try to dig any further.&lt;/li&gt;
&lt;li&gt;I also got lots of errors with &lt;tt class="docutils literal"&gt;nginx&lt;/tt&gt; in HTTP2. A quick search yielded &lt;a class="reference external" href="https://www.nginx.com/blog/http-2-rapid-reset-attack-impacting-f5-nginx-products/"&gt;this result&lt;/a&gt; so it may be a protection against attack. I tried some configuration to prevent this without success.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Relevant commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/7079bc305b0f606d683d2865d22289fa91d2e10d"&gt;Do some load testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/dj-test-async/-/commit/42fa4713047291716637f3eb99709faa10b392d9"&gt;Test with reverse proxy for benchmark &amp;amp; HTTP push&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Wrapping up!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s summarize what I’ve learned so far:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;All solutions can serve both sync and async views quite efficiently. But you can only really benefit from async on ASGI and most notably from websockets and SSE.&lt;/li&gt;
&lt;li&gt;All solutions recommended in &lt;a class="reference external" href="https://docs.djangoproject.com/fr/4.2/howto/deployment/asgi/"&gt;the Django documentation&lt;/a&gt; seem mature and performant.&lt;/li&gt;
&lt;li&gt;All solutions can be used in development.&lt;/li&gt;
&lt;li&gt;You probably still want a reverse proxy to handle HTTPS and HTTP2 connections. Letting something else will degrade performance quite a bit. But if you can’t or don’t want to, it’s not an obligation.&lt;/li&gt;
&lt;/ul&gt;
&lt;table border="1" class="docutils"&gt;
&lt;caption&gt;Synthesis&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head stub"&gt;&amp;nbsp;&lt;/th&gt;
&lt;th class="head"&gt;HTTP2&lt;/th&gt;
&lt;th class="head"&gt;SSE&lt;/th&gt;
&lt;th class="head"&gt;Websockets&lt;/th&gt;
&lt;th class="head"&gt;Usage in dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;th class="stub"&gt;Daphne&lt;/th&gt;
&lt;td&gt;✅ (with extra deps, TLS only)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⚠️ (the easiest to integrate with Django but harder to watch for extra files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;Gunicorn&lt;/th&gt;
&lt;td&gt;❌ (not a very big deal if you have a reverse proxy anyway)&lt;/td&gt;
&lt;td&gt;❌ (requires ASGI)&lt;/td&gt;
&lt;td&gt;❌ (requires ASGI)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;Uvicorn (with Gunicorn in prod)&lt;/th&gt;
&lt;td&gt;❌ (not a very big deal if you have a reverse proxy anyway)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;Hypercorn&lt;/th&gt;
&lt;td&gt;✅ (couln’t make server push work)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (had an issue with reloading, but should work)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="section" id="my-recommendations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;My recommendations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After all that, what would I recommend?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If you don’t need async and thus ASGI, you can probably stick with your current stack. It’s solid and won’t go away.&lt;/li&gt;
&lt;li&gt;I’d still put a reverse proxy in front of my app, even for ASGI.&lt;/li&gt;
&lt;li&gt;For a pure ASGI project, I think I’d use &lt;tt class="docutils literal"&gt;daphne&lt;/tt&gt; and install it as an app. I’d to it because it’s the easiest to integrate with Django, including the &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; command I’m used to having during development. It also makes static files serving and template changes easier out of the box.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;uvicorn&lt;/tt&gt; under &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; looks like a very good alternative. And you benefit from all the options of &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; in production.&lt;/li&gt;
&lt;li&gt;I’d reach for &lt;tt class="docutils literal"&gt;hypercorn&lt;/tt&gt; only if I needed all of its features (like the ability to use &lt;tt class="docutils literal"&gt;uvloop&lt;/tt&gt; or its experimental HTTP3 feature).&lt;/li&gt;
&lt;li&gt;If you are in the process of migration to async, I think you should start by running &amp;quot;old&amp;quot; views under WSGi and let a reverse proxy route traffic to an ASGI server for async views. Once most of your app is migrated, I think you can switch to ASGI and let is serve your sync views until you change them (or for the end of time because you’ll never have time to migrate something that works).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having said all that, I’ll gladly hear what you think in the comments!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Web"></category><category term="Django"></category><category term="Python"></category></entry><entry><title>Writing RSS reading app with various frontend frameworks</title><link href="https://www.jujens.eu/posts/en/2023/Sep/09/js-frameworks-rss/" rel="alternate"></link><published>2023-09-09T00:00:00+02:00</published><updated>2023-09-09T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2023-09-09:/posts/en/2023/Sep/09/js-frameworks-rss/</id><summary type="html">&lt;!-- my notes: https://trello.com/c/swnG6rWA/2604-%C3%A9crire-un-article-sur-mes-lecteurs-rss --&gt;
&lt;p&gt;During the summer, I decided to test a few frontend framework to see what’s going on in this space and form a better opinions over alternatives to React.
I tested Svelte because after hearing from it I felt attracted to it, Vue because it is popular, React to have …&lt;/p&gt;</summary><content type="html">&lt;!-- my notes: https://trello.com/c/swnG6rWA/2604-%C3%A9crire-un-article-sur-mes-lecteurs-rss --&gt;
&lt;p&gt;During the summer, I decided to test a few frontend framework to see what’s going on in this space and form a better opinions over alternatives to React.
I tested Svelte because after hearing from it I felt attracted to it, Vue because it is popular, React to have a good comparison point and Angular because I was about to take a new job that required Angular.
I also included Aurelia, the framework I first used to create the app I used to test all the others.&lt;/p&gt;
&lt;p&gt;To do these tests, I decided to code a RSS reading app: you connect on the first page and you are then redirected to the list of articles and categories.
You see the articles of the current category (the unread ones by default), you can switch categories, mark articles as (un)read, read articles on scroll and on swipe.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#my-tests" id="toc-entry-1"&gt;My tests&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#aurelia" id="toc-entry-2"&gt;Aurelia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#svelte" id="toc-entry-3"&gt;Svelte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#vuejs" id="toc-entry-4"&gt;VueJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#reactjs" id="toc-entry-5"&gt;ReactJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#angular" id="toc-entry-6"&gt;Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#summary" id="toc-entry-7"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#conclusion" id="toc-entry-8"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="my-tests"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;My tests&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="aurelia"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Aurelia&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aurelia is a complete frontend framework I used on some of my personal projects.
The framework is really good although a lot less known than the alternatives.
From my experience, the community is small but very active.
The main drawback I experimented was the lack of good tooling (things may have changed, I haven’t used Aurelia in a while).&lt;/p&gt;
&lt;p&gt;The framework has pretty much everything you may need: a way to define components, dependency injection, a HTTP service, a logger…
You can also use libraries that manipulate the DOM directly with it.&lt;/p&gt;
&lt;p&gt;When I created the first app years ago, I had a good experience using the framework.
I can’t really say more today.
Please note that, just like with angular, the framework is big and designed to create apps, not enhance a page and that the resulting bundles can be big.&lt;/p&gt;
&lt;p&gt;Last but not least, the app is rather big: 1,764 lines of code (1387 of TypeScript, 231 HTML).
Part of this is explained by the fact it has features no other apps have (like offline support with a Service Worker).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="svelte"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Svelte&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Svelte is a relatively new framework.
You can use it as a library to enhance your pages or as a framework (with SSR, routing, stores) thanks to svelte kit.
It has lots of features and a good community.&lt;/p&gt;
&lt;p&gt;It’s the first framework I used during my tests.
I found it easy to handle.
What I specially liked is the way you define components: everything goes into the same file, you use a good templating language for your HTML that reminds me of the Django one and you define your CSS for just your component.
Overall, it feels like you are using standard tech.
Same goes for the stores: it feels like I was using JS and you can easily use them outside Svelte if needed (although reading values isn’t easy)!&lt;/p&gt;
&lt;p&gt;I decided to use &lt;a class="reference external" href="https://www.skeleton.dev/"&gt;Skeleton&lt;/a&gt; to help me style the app.
I think it was either a wrong choice or that I needed to configure it better to have something more pleasing to the eye.&lt;/p&gt;
&lt;p&gt;What I found interesting is that the framework also help you define animations on elements.
It help me a great deal to do the read on swipe feature correctly.
Beware though, by default, it also tried to animate all the articles when I switched categories which created slowness and bugs (we didn’t really change the page).
Understanding and fixing this took me a long time (but it occurred again with other frameworks, so it’s not a Svelte thing).&lt;/p&gt;
&lt;p&gt;I didn’t like how reactive statements work: dependencies are implicit and I encountered a problem where a statement was run each time an object changed while I was only concerned with one of its properties.&lt;/p&gt;
&lt;p&gt;I also found strange that I had to run two commands to compile the app and have all the errors.&lt;/p&gt;
&lt;p&gt;Last but not least, the resulting app size is small: 1,317 lines of code (450 lines of Svelte, 978 lines of TypeScript).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="vuejs"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;VueJS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vue is the main competitor to React.
It’s a rendering lib you can extend to have all you need (router, store…).
Most of these &amp;quot;extensions&amp;quot; are maintained by vue.&lt;/p&gt;
&lt;p&gt;In terms of structure of a component, it’s very similar to Svelte.
The way it handles state is very similar to React while being less verbose.&lt;/p&gt;
&lt;p&gt;When I tried to add translations, I choose &lt;a class="reference external" href="https://www.npmjs.com/package/vue-i18n"&gt;vue-i18n&lt;/a&gt; which seemed like a popular choice.
But I couldn’t extract strings automatically.
Using &lt;a class="reference external" href="https://formatjs.io/docs/getting-started/installation/"&gt;FormatJS&lt;/a&gt; which is compatible with both React and Vue might have been a better choice (although I never succeed to make string extraction work with it).&lt;/p&gt;
&lt;p&gt;I think that &lt;tt class="docutils literal"&gt;watchEffect&lt;/tt&gt; works a bit better than in Svelte, even if they also rely on automatic dependency discovery.&lt;/p&gt;
&lt;p&gt;I also found strange that I had to run two commands to compile the app and have all the errors.&lt;/p&gt;
&lt;p&gt;I didn’t do read on scroll nor read on swipe: I found no easy helpers to help me and wanted to spend time on other tests.&lt;/p&gt;
&lt;p&gt;Last but not least, the resulting app size is small: 1,165 lines of code (444 lines of Vue, 717 lines of TypeScript).
I’d say on par with Svelte given it’s a bit smaller but I have less features here.&lt;/p&gt;
&lt;p&gt;A tiny thing I found strange: with the default rules, semi-colons are not used.
I don’t think I encountered any other default with this rules.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="reactjs"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;ReactJS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;React is probably the most popular one.
It’s a rendering lib you can extend with lib maintained by third party to meet all your needs.&lt;/p&gt;
&lt;p&gt;Here you are forced to use JSX to define your components.
I have always disliked it since I think it forces you to do React things to define your view and your behaviors.
On the bright side, you can define multiple components in the same file and create many tiny reusable components.&lt;/p&gt;
&lt;p&gt;The hooks system to perform side effects or have cached values is very verbose but also very explicit about dependencies.
It has pros (it’s obvious and you can manage them by hand if needed) as well as cons (it’s verbose, easy to forget without a linter, if some deps are missing you must document why).&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://mui.com/"&gt;Material UI&lt;/a&gt; is an astonishing component library: complete, well documented and easy to use.
It’s the best component library I used.&lt;/p&gt;
&lt;p&gt;One the good side, you can view all errors in one command.&lt;/p&gt;
&lt;p&gt;I found a helper lib to help me implement the read on swipe.&lt;/p&gt;
&lt;p&gt;I also tested for the first time the new features of Redux: async thunks.
I think that having this included out of the gate is a good thing as we won’t need to rely on external libs to perform something that we almost always need to do.
Redux is still very decoupled in different concepts: it makes it very clean and also very verbose.&lt;/p&gt;
&lt;p&gt;At last but not least, the app is rather big: 1,684 lines of code (630 TSX, 1219 TypeScript).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="angular"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Angular&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Angular is a big batteries included framework.
It’s the one that has the big enterprise touch and the one where you don’t really need to install anything but Angular libs to achieve your goals.&lt;/p&gt;
&lt;p&gt;It contains everything you need from the HTTP router, Angular Material to help you design your UI, Service worker service, form validation and building… to dependency injection.
It also comes with more concepts: components, directives, services.
Like Aurelia, it is centered around classes whereas other framework are more centered around functions.
By default, a component is split into 3 files: the TypeScript code, the HTML and the CSS.&lt;/p&gt;
&lt;p&gt;Its binding system is also more complex: while in all other frameworks you can bind anything, here you must bind data and event (to provide output to your component) in a different manner.
If you just bind a function, call it won’t refresh the state of your app.&lt;/p&gt;
&lt;p&gt;You will also need to learn and use &lt;a class="reference external" href="https://rxjs.dev/"&gt;RxJS&lt;/a&gt; since it is heavily used by the framework, including its HTTP service.
It’s a world of its own that will take you time to get used to (let alone master), will increase the size of your bundles but is very powerful and even pleasing to use once you get the gist of it.&lt;/p&gt;
&lt;p&gt;During development, I found that it required more full page reloads than any other solution.
It’s a pain since it can make you loose context and may force you to reenter data/reopen modals.&lt;/p&gt;
&lt;p&gt;While it includes a translation system, it was way too complicated for my needs.
I felt it was tailor for big enterprise usage.
Luckily, I could use another library that was more aligned with my needs.&lt;/p&gt;
&lt;p&gt;At last but not least, the app is quite big.
I had to do some improvements to limit the size.
It’s better that the raw one, but still big.
I have 1,859 lines of code (1,590 TypeScript, 286 HTML, 132 CSS).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Summary&lt;/a&gt;&lt;/h3&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;To run the size tests, I always used a full reload on each page.&lt;/p&gt;
&lt;/div&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="12%" /&gt;
&lt;col width="19%" /&gt;
&lt;col width="38%" /&gt;
&lt;col width="13%" /&gt;
&lt;col width="9%" /&gt;
&lt;col width="9%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Framework&lt;/th&gt;
&lt;th class="head"&gt;Link to project&lt;/th&gt;
&lt;th class="head"&gt;My opinion&lt;/th&gt;
&lt;th class="head"&gt;Lines of code&lt;/th&gt;
&lt;th class="head"&gt;Connection page size&lt;/th&gt;
&lt;th class="head"&gt;Articles page size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://aurelia.io/"&gt;Aurelia&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/aurss"&gt;aurss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Good framework, not really known/used. Tooling could be improved.&lt;/td&gt;
&lt;td&gt;1,764&lt;/td&gt;
&lt;td&gt;37 requests to load 1.57MB&lt;/td&gt;
&lt;td&gt;39 requests to load 1.66MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://svelte.dev/"&gt;Svelte&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/svelte-rss"&gt;svelte-rss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Very good framework, pleasant to use, modern and lightweight.&lt;/td&gt;
&lt;td&gt;1,317&lt;/td&gt;
&lt;td&gt;18 requests to load 235KB&lt;/td&gt;
&lt;td&gt;18 requests to load 240KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://vuejs.org/"&gt;Vue&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/vue-rss"&gt;vue-rss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;More or less the same as Svelte. After these small tests, I’d favour Svelte over Vue.&lt;/td&gt;
&lt;td&gt;1,165 (but some features are lacking)&lt;/td&gt;
&lt;td&gt;7 requests to load 220KB&lt;/td&gt;
&lt;td&gt;9 requests to load 231BK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://react.dev/"&gt;React&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/react-rss"&gt;react-rss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Solid framework, good community, possibilities to make native app, very good components libraries. Shows its age.&lt;/td&gt;
&lt;td&gt;1,684&lt;/td&gt;
&lt;td&gt;7 requests to load 528KB&lt;/td&gt;
&lt;td&gt;8 requests to load 530KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://angular.io/"&gt;Angular&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://gitlab.com/Jenselme/ng-rss"&gt;ng-rss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Complete framework, heavy to use and load. Overall good dev experience even if it’s verbose.&lt;/td&gt;
&lt;td&gt;1,859&lt;/td&gt;
&lt;td&gt;8 requests to load 1.04MB&lt;/td&gt;
&lt;td&gt;7 requests to load 1.04MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I enjoyed testing these frameworks and dive into their differences.
I loved Svelte the most: code is pleasing to write, I like its architecture and conventions, the community is big enough so you shouldn’t have any problems (or if you do, someone should be able to help).
I’d probably pick it to start my next project if I were starting from scratch.&lt;/p&gt;
&lt;p&gt;Vue seemed nice too.
But a bit stuck between Svelte and React, like I could have part of the joy of Svelte while still having to endure a bit of pain from React.&lt;/p&gt;
&lt;p&gt;React is my least favorite by far.
I’ve had a long and complex relationship with React, always failing to understand the hype around it (and more recently the hype around the hooks).
I only used it because my job required me to.
I was glad to see I could get exited by a frontend tech.
For more on this, I invite you to read two articles by Josh Collinsworth who expressed quite eloquently this feeling I have: &lt;a class="reference external" href="https://joshcollinsworth.com/blog/self-fulfilling-prophecy-of-react"&gt;The self-fulfilling prophecy of React&lt;/a&gt; and &lt;a class="reference external" href="https://joshcollinsworth.com/blog/antiquated-react"&gt;Things you forgot (or never knew) because of React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Angular is its own beast.
If you work in a big enterprise, it’s probably the best choice for you.
I think it can be pleasing to use, but it’s also complex and verbose.&lt;/p&gt;
&lt;p&gt;In a nutshell&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;There is a world outside React.&lt;/li&gt;
&lt;li&gt;You should probably checkout Svelte or Vue if you haven’t already.&lt;/li&gt;
&lt;li&gt;Keep a very close eye on web components: they are very likely to be the future. In fact, whatever you are using and whatever your opinion is (or just because you have little time and cannot checkout many things), just keep an eye on that. You can read &lt;a class="reference external" href="https://eisenbergeffect.medium.com/2023-state-of-web-components-c8feb21d4f16"&gt;this article&lt;/a&gt; to get a good start.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Web"></category><category term="Javascript"></category><category term="Typescript"></category><category term="React"></category><category term="Angular"></category><category term="Svelte"></category><category term="Vue"></category></entry><entry><title>My opinion after testing some AI code assistant</title><link href="https://www.jujens.eu/posts/en/2023/Aug/21/ai-tests/" rel="alternate"></link><published>2023-08-21T00:00:00+02:00</published><updated>2023-08-21T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2023-08-21:/posts/en/2023/Aug/21/ai-tests/</id><summary type="html">&lt;!-- my notes: https://trello.com/c/SDmffwaT/2580-tester-les-ia --&gt;
&lt;p&gt;With all the hype around AI and since I had time to spare, I decided to test some AI coding assistants to make my own opinion about them.
I'll start by giving my opinion on each assistant I tried.
I will be a bit fuzzy since I didn't intend to …&lt;/p&gt;</summary><content type="html">&lt;!-- my notes: https://trello.com/c/SDmffwaT/2580-tester-les-ia --&gt;
&lt;p&gt;With all the hype around AI and since I had time to spare, I decided to test some AI coding assistants to make my own opinion about them.
I'll start by giving my opinion on each assistant I tried.
I will be a bit fuzzy since I didn't intend to write this blog post and didn't take as many notes as I should have.
Then I'll wrap up with my general opinion and some advices.&lt;/p&gt;
&lt;p&gt;I ran most of my tests writing a small task manager in React, VueJS, Svelte and Elm.
I tried to switch between all of them between each to view the differences.
I tried CodeWhisperer last on a dedicated test in TypeScript and in Python, not on the task manager project.
I ran the same tests with Codeium to have a comparison point.
I mostly used VSCode for my tests and use Codeium a bit with PyCharm.&lt;/p&gt;
&lt;div class="section" id="tested-code-assistants"&gt;
&lt;h2&gt;Tested code assistants&lt;/h2&gt;
&lt;div class="section" id="aws-codewhisperer"&gt;
&lt;h3&gt;AWS CodeWhisperer&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://aws.amazon.com/fr/codewhisperer/resources/"&gt;AWS CodeWhisperer&lt;/a&gt; is AWS take on the subject.
It's tied to your AWS account and AWS extension for your editor.
It's free for individuals.&lt;/p&gt;
&lt;p&gt;According to my tests, it's the worst overall.
On the bright side, it did generate a few test cases that were more relevant to my use case than other tools and can handle imports automatically.
But it suggested &lt;tt class="docutils literal"&gt;any&lt;/tt&gt; way too often in its TS completions, proposed less variations between suggestions, proposed more irrelevant suggestions, proposed more complex suggestions and relied too often on line to line suggestion (versus block/function suggestion for other assistants).
It's also way slower and felt unresponsive at times.&lt;/p&gt;
&lt;p&gt;It's also the only tool that proposes a security scanner.
I tried in on a piece of Python code and its suggestions were good: I was lacking a context manager and doing a SQL injection which the tool spotted.
It was &lt;em&gt;very&lt;/em&gt; slow, even on my small file (~10 lines of code).
I don't know how it behaves nor what it reports on larger and more complex pieces of code.&lt;/p&gt;
&lt;p&gt;To conclude: It's definitely a pass for me.
In this current state, it's also a pass if you are tied to AWS product.
Just code without, you should have a better time.
The security scanner can be a good thing if it works properly on large code bases though.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="github-copilot"&gt;
&lt;h3&gt;GitHub Copilot&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.github.com/en/copilot/quickstart"&gt;GitHub Copilot&lt;/a&gt; is you guessed it made by GitHub.
It's linked to your GitHub account and requires a dedicated extension in your editor.
It's not free even for individuals, but comes with a 1 month trial period (that's what I used).&lt;/p&gt;
&lt;p&gt;According to my tests, it proposed the best suggestions of all tested tools.
It's even good at not messing up too much parentheses and curly braces completions in existing code (where all other tools had some/more issues).
It's also fast at responding.
It does the job and does it well (for an IA assistant).&lt;/p&gt;
&lt;p&gt;There is also &lt;a class="reference external" href="https://docs.github.com/en/copilot/github-copilot-chat/about-github-copilot-chat#limitations-of-github-copilot-chat"&gt;a chat in beta&lt;/a&gt;.
I couldn't try it: it's only opened for business accounts right now.
But, from my experience with &lt;a class="reference internal" href="#codeium"&gt;codeium&lt;/a&gt; it should be a valuable additions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="codeium"&gt;
&lt;h3&gt;Codeium&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeium.com/"&gt;Codeium&lt;/a&gt; is a product made by a startup.
It requires an account (you can use your Google account) and a dedicated extension in your editor.
The VSCode extension is the most complete since it supports all features (completion and chat), but I tested it in PyCharm and code completion work fine (I lacked the chat though).
You can test it in &lt;a class="reference external" href="https://codeium.com/playground"&gt;their playground&lt;/a&gt; before creating an account nor installing anything.
That's a good thing in my opinion.
It's free for individual use.
It's also the tool I used beyond my small IA assistants tests (because it's good, the chat is quite handy and it's free).&lt;/p&gt;
&lt;p&gt;It's suggestions aren't always the most relevant and it tends to fail for parentheses/curly braces completions.
When generating test cases, I had to guide it to have something good.
It invented a method named &lt;tt class="docutils literal"&gt;toBeUpperCase&lt;/tt&gt; and in a &lt;tt class="docutils literal"&gt;forEach&lt;/tt&gt; loop instead of using the item it used the array and the proper index.
Weird (but working).
Inventions struck me more with this tool than with the others (but it's also the one I used most, so I may be biased).
Its different proposals are good and different.
By default, in TypeScript, it create fat arrow functions on multiple lines even when the result could be a one liner.
In Python, it tends to propose &lt;tt class="docutils literal"&gt;pass&lt;/tt&gt; as the body of the function by default (you can then make it write it).&lt;/p&gt;
&lt;p&gt;It's also the only assistant I tested that propose more features than mere code completions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;It comes with a chat that is a must use. Instead of searching Stack Overflow for something, most of the time I could just ask it how to do something (like a form in Angular) and it would respond. Depending on the answers, I could get a code snippet I could use, a detailed explanation or I had to ask it again (and sometimes go to Stack Overflow).&lt;/li&gt;
&lt;li&gt;It proposes tools to refactor code: you select a piece of code, run a default refactoring action or explain what refactoring you want. Then it proposes a code snippet you can apply to take the refactoring into account. I only used it on simple tasks, but it worked well. This sounds really promising since refactoring can be really boring.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="my-opinion"&gt;
&lt;h2&gt;My opinion&lt;/h2&gt;
&lt;p&gt;With the notable exception of CodeWhisperer I enjoyed working with these tools.
I mostly wrote the name of the function I wanted and let the tool complete it.
Test cases aside, I don't think I had to explain in natural language what I wanted to get a good result (please note that I used very explicit function names).
I tried to disable it to see if I would miss it.
And I did.
Mostly for writing boring code in my place!&lt;/p&gt;
&lt;p&gt;There are a few things to keep in mind while using these tools:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;They are not always up to date.&lt;/li&gt;
&lt;li&gt;They can propose really stupid things or &amp;quot;hallucinate&amp;quot;. What's more surprising (even if you know a bit how they work), is that they propose both really relevant suggestions and really stupid ones in the same session. It also help put into perspective our potential replacement as a developer (and you still have to explain to it what to do!). So for now, developers are safe.&lt;/li&gt;
&lt;li&gt;It's a bit frustrating that they get parentheses/curly braces wrong so often.&lt;/li&gt;
&lt;li&gt;Depending on the context you use it in (existing file, opened files), results can vary. Try to open relevant files to help it do more relevant completions.&lt;/li&gt;
&lt;li&gt;It can be tempting to not refactor the supplied code because it arrived fast and was &amp;quot;good enough&amp;quot;. Since the tool allowed me to be more productive, sometimes I just wanted to be a bit too &amp;quot;productive&amp;quot; and move on to other part of the project and leave the code as is.&lt;/li&gt;
&lt;li&gt;Beware not to get stuck on the proposed solution for too long: if it doesn't work and you fail to get it working, starting from scratch by yourself may save you time in the end (or chatting with the bot, or search Stack Overflow).&lt;/li&gt;
&lt;li&gt;Solutions (both autocomplete suggestions and answers from the chat) are without context. So you can't know easily (besides your own experience) whether they are good. At least on Stack Overflow you have ratings, comments and other answers to help you (I always read comments and other answers when I'm not sure about the top one, I hope you do too!).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="how-about-learning"&gt;
&lt;h3&gt;How about learning?&lt;/h3&gt;
&lt;p&gt;You can use these assistants to help you write code in a language you don't really know.
I tried that with &lt;a class="reference external" href="https://elm-lang.org/"&gt;Elm&lt;/a&gt;.
It was more pleasing that doing it only with the documentation and web searches: you can rely on completions to get to your goal more rapidly and with less frustration.
Especially with a language as different from the ones I know as Elm.&lt;/p&gt;
&lt;p&gt;Did I really learn or practice Elm?
No!
That's not really a problem in this context, because I didn't really want to learn it.
I think it can be if I really tried: how could I get into the right state of mind, learn by practising if a tool gives me the answers?
I could understand the code and edit it a bit thanks to my experience, but overall, I don't really know if the code is actually good.&lt;/p&gt;
&lt;p&gt;More generally, I don't think you can truly learn anything with a tool like this: you need to think, fail and write code by your own to learn.
I don't think there is an alternative to this.
But these tools will make it harder to do it correctly.
For instance, I tried to do some type challenges in TS.
With the autocomplete on, I just got the answers.
Answers I could not have found on my own without trying a bunch of things (and some I just couldn't get without the solutions since they relied on things I didn't know).&lt;/p&gt;
&lt;p&gt;And you also need to know whether the code is good, secure and maintainable.&lt;/p&gt;
&lt;p&gt;The chat can be useful to get relevant information right from your editor though.
But I think you'll still need to write code by yourself, experience developers to review your code and blog/courses/books to move forward in your programming journey.
So I don't advise using completions when trying to learn: you could feel more productive at the start, but I think it will come and bite you in the long run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-about-using-it-on-the-job"&gt;
&lt;h3&gt;How about using it on the job?&lt;/h3&gt;
&lt;p&gt;With all that said, these assistants sound like the prefect tool to help to you in your job.
There are some points to consider before that though:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Technically, you must pay for them to use them in a professional context. They are not expensive, but depending on your company size, it can become quite expensive.&lt;/li&gt;
&lt;li&gt;They are many legal considerations to take into account:&lt;ul&gt;
&lt;li&gt;You could violate copyrights: the assistants learned from OSS projects and may generate license protected code that will end up in your project. It's mostly dangerous for GPL protected code. While technically you could do it before, you had to conscientiously do it, search for it and use it. Now, it's at the finger tip of millions of developers that will have no idea they are infringing any copyright.&lt;/li&gt;
&lt;li&gt;Who owns the output of an IA tool is not clear.&lt;/li&gt;
&lt;li&gt;This requires to send code to a distant server you don't control. This could result in code/data/secret leakage. You need to make sure your provider won't learn from your code. You need to trust it and read its data policy: to be more accurate, it's too important to just accept without understanding it in a professional context and you need to get legal involved.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So don't do this on your own, involve your co-workers and management.
Even to use the tool from time to time at your job with a personal account.
To avoid having problems over this, you must get management support &lt;em&gt;before&lt;/em&gt; using the tool.
Otherwise, this too can come and bite you!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up"&gt;
&lt;h3&gt;Wrapping up&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;AI assistant can be very powerful tools to help you refactor code, get answers faster and help you write code (either tricky or boring).&lt;/li&gt;
&lt;li&gt;Probably not the best tool to learn and practice programming. I think it applies to all other activities.&lt;/li&gt;
&lt;li&gt;You still need to rely on your experience to maintain code quality and security (and to make the whole thing work).&lt;/li&gt;
&lt;li&gt;Make sure to have management support if you want to use them at work: they bring a lot of non technical issues you cannot solve on your own.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other takes on this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://martinfowler.com/articles/exploring-gen-ai.html"&gt;Exploring Generative AI&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="AI"></category></entry><entry><title>My opinion on enums in TypeScript</title><link href="https://www.jujens.eu/posts/en/2023/Aug/18/typescript-emuns/" rel="alternate"></link><published>2023-08-18T00:00:00+02:00</published><updated>2023-08-18T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2023-08-18:/posts/en/2023/Aug/18/typescript-emuns/</id><summary type="html">&lt;p&gt;Today I'd like to dig a bit on enums in TypeScript and their potential alternatives.
For this, I'll be using the &lt;a class="reference external" href="https://www.typescriptlang.org/play"&gt;playground&lt;/a&gt; and TypeScript 5.1.6 (latest released version when I am writing this).
I expect you to know what enums are and to be comfortable in TypeScript.&lt;/p&gt;
&lt;p&gt;Let's …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I'd like to dig a bit on enums in TypeScript and their potential alternatives.
For this, I'll be using the &lt;a class="reference external" href="https://www.typescriptlang.org/play"&gt;playground&lt;/a&gt; and TypeScript 5.1.6 (latest released version when I am writing this).
I expect you to know what enums are and to be comfortable in TypeScript.&lt;/p&gt;
&lt;p&gt;Let's start with a very basic definition and usage of an enum:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// We can use the members.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// We can use member values directly (by default enums maps to numbers, the first member is 0 and then each member increment by 1).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// We cannot use a value that is not part of the enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;On previous versions of TypeScript (that is until version 5), &lt;tt class="docutils literal"&gt;simpleLog(10)&lt;/tt&gt; was allowed and would not be reported as an error.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We can also use specify numbers explicitly:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This yields an error since 0 isn't valid any more.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This works.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;We can also use strings:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// Using members directly still works.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This fails since Ruby is not an allowed value of the enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This also fails, even if Python is an allowed value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now that we've reviewed the basics, let's see why we would use enums:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;It's obvious when reading the code that the value belongs to a collection of values. We are not seeing an random &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;'Python'&lt;/tt&gt; but &lt;tt class="docutils literal"&gt;ProgrammingLanguages.Python&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Even though we can use member values directly with &amp;quot;number&amp;quot; enums, I think it's a bad idea. I'd even say, the compiler should behave like with string enums and report an error.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;We can check that all cases are handled. In the code samples below, if a branch is not covered (or if we add values to the enum), we will get an error:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertNever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;never&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="sb"&gt;`Unhandled discriminated union member: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logProgrammingLanguageStringEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;console.log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;console.log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;assertNever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kt"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kt"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;To map enum values to other values. Just like with the previous example, if a value is missing or when we add values to the enum, we will get an error:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesToEmoji&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;We can get all the members of the enum like this (it works because once transpiled to JavaScript, enums are just objects, more on that later):&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="c1"&gt;// We can of course also use Object.values and Object.entries.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If enums have nice qualities, then why would we want to use something else?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;We can have those same properties in other ways. So it makes sense to compare the different possibilities before making a choice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;TypeScript will generate extra code that must be shipped and executed. So this enum:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Will become:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TypeScript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TypeScript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}));&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;So when we have many enums, their impact won't be negligible both in term of size and execution time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What are the alternatives?&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Plain constants. We define &lt;tt class="docutils literal"&gt;const PYTHON = 'PYTHON'&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;const TYPESCRIPT = 'TYPESCRIPT'&lt;/tt&gt;. It works but we loose the fact that these variables are grouped and the compiler cannot notify us about invalid cases in usage. So, not acceptable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Union types. We can define &lt;tt class="docutils literal"&gt;type Languages = 'Python' | 'TypeScript'&lt;/tt&gt;. This will still work as expected:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesToMessage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesUnionToEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;However, when reading, it's not obvious that the value comes from a union type and we cannot get all the members programmatically in code (types are compiled away).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Using a const objects. It's probably the alternative that is closer to using enums. But we need an intermediate type to ease its usage:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// This is not allowed on a const object. So no risk of adding members by mistake.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// We must extract the exact keys of our object.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keyof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguagesToMessage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguagesToEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Using const enum. Instead of declaring our enum like we did, we declare it like this:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This way, it will be compiled away. It's an enum behaving like an enum with a notable exception &lt;tt class="docutils literal"&gt;const languages = Object.keys(ProgrammingLanguages)&lt;/tt&gt; is triggering an error since it doesn't transpile into an object.&lt;/p&gt;
&lt;p&gt;However, do note that they come with their own set of &lt;a class="reference external" href="https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls"&gt;pitfalls&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we wrap all this up, what should we use?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;I think we should use string union types as much as possible to replace enums: they fit nicely into the language and are easy to define and use. On reading, it's a bit harder to see they come from a collection of values, but I think that with habit you can guess when a union type is used (at least that was the case for me). And you editor should be able to help you out. On writing, our editor will complete the strings, so it's not a problem.&lt;/p&gt;
&lt;p&gt;I don't think we should use other union types (like number union types). Reading a plain number won't give enough information to understand what is going on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;If you need to access all the members in you code, const objects are a good alternative. They are even recommended in &lt;a class="reference external" href="https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums"&gt;TypeScript documentation&lt;/a&gt;!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Given the pitfall coming with const enum, I think they should be avoided (and if you don't use them often, you'll probably forget about those pitfalls).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TL;DR: Enums can still be used, but we have better alternatives now.
So I think we should use them only sparingly and always start with either a union type or a const object.&lt;/p&gt;
&lt;p&gt;Last question: should you migrate away from enums?
I think you may but don't need to.
They add code to your bundles and may slow code executions, but it this the first thing you must do to improve this?
I don't think so.
Checking duplicated or big libs will probably contribute more to code bloats than enums.
But I guess it depends on your code base and on how much you use enums.
It's also easy to do (even if it can be time consuming).&lt;/p&gt;
&lt;p&gt;For reference and to help you test what I explained here, here's the full code:&lt;/p&gt;
&lt;pre class="code typescript literal-block"&gt;
&lt;span class="c1"&gt;// Basic enums&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// We can use the members.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesNumberEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// We can use member values directly (by default enums maps to numbers, the first member is 0 and then each member increment by 1).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// We cannot use a value that is not part of the enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesExplicitNumberEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This yields an error since 0 isn't valid any more.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This works.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogBis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;



&lt;/span&gt;&lt;span class="c1"&gt;// String enums&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// Using members directly still works.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This fails since Ruby is not an allowed value of the enum.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This also fails, even if Python is an allowed value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;simpleLogTer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertNever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;never&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="sb"&gt;`Unhandled discriminated union member: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logProgrammingLanguageStringEnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;console.log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;console.log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kt"&gt;assertNever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kt"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages.TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kt"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;logProgrammingLanguageStringEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;toEmoji&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesToEmoji&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// We can of course also use Object.values and Object.entries.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;



&lt;/span&gt;&lt;span class="c1"&gt;// Exploring alternavites.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesToMessage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;languagesUnionToEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Languages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keyof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;// This is not allowed on a const object.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;allowedLanguages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguagesToMessage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'💎'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allowedLanguagesToEmoji&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;AllowedLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'🐍'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProgrammingLanguagesConst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Python'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TypeScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
</content><category term="Programmation"></category><category term="TypeScript"></category></entry><entry><title>Exploring a weird HTTP issues</title><link href="https://www.jujens.eu/posts/en/2022/May/04/exploring-weird-http-issue/" rel="alternate"></link><published>2022-05-04T00:00:00+02:00</published><updated>2022-05-04T00:00:00+02:00</updated><author><name>Julien Enselme</name></author><id>tag:www.jujens.eu,2022-05-04:/posts/en/2022/May/04/exploring-weird-http-issue/</id><summary type="html">&lt;p&gt;Today I'd like to explain how I tried to solve a weird HTTP issues that I found at work.
I hope you will find the method and the trials I used the useful/interesting if/when you will encounter something similar.&lt;/p&gt;
&lt;p&gt;The issue was this: I needed to dynamically generate …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I'd like to explain how I tried to solve a weird HTTP issues that I found at work.
I hope you will find the method and the trials I used the useful/interesting if/when you will encounter something similar.&lt;/p&gt;
&lt;p&gt;The issue was this: I needed to dynamically generate a ZIP file with a list of files that couldn't be known before generation.
For small ZIP files, everything went fine.
For bigger ones, the download would stop before the end and I would only get a corrupted ZIP file.
This was of course only happening in production: locally, the download could be long but it would always complete.
The stop would always happen when a request was taking at least 30s to complete.
Since the project is configured have timeouts at 30s at multiple level to avoid too long requests, that wasn't specific enough to avoid some digging.&lt;/p&gt;
&lt;p&gt;The website is written in Python using the &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django web framework&lt;/a&gt;.
In production it's deployed in kubernetes and it's run with &lt;a class="reference external" href="https://gunicorn.org/"&gt;gunicorn&lt;/a&gt; a well known and used Python application server.
In front of &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;, there is a nginx reverse proxy, mostly to buffer file upload requests (see &lt;a class="reference external" href="https://www.jujens.eu/posts/en/2021/Mar/29/deploy-django-kubernetes/"&gt;this article&lt;/a&gt; for more information on that).&lt;/p&gt;
&lt;p&gt;Now that you have this context, let's dive in.&lt;/p&gt;
&lt;div class="section" id="gunicorn"&gt;
&lt;h2&gt;gunicorn?&lt;/h2&gt;
&lt;p&gt;I didn't start my exploration methodically.
I had an intuition it could be linked to &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;: the Django application would take too much time to generate the ZIP and &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; would time out.
By default &lt;a class="footnote-reference" href="#gunicorn-default" id="footnote-reference-1"&gt;[1]&lt;/a&gt;, a request times out after 30s.
This couldn't happen locally since I use Django's built in server which doesn't have a timeout.
And in production, I could see in the logs that &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; was restarting its worker process.&lt;/p&gt;
&lt;p&gt;So, I timed the ZIP creation process.
In some cases, it could get close to 30s and even be longer than this.
This was mostly due to the fact we were trying to compress the files.
Since they were PDF and images, we didn't really need to compress them (and it was causing issues anyway).
So, I changed how we create the ZIP file to skip the compression entirely and create an uncompressed ZIP file.
In worst cases, the ZIP file creation now only took around 5s.&lt;/p&gt;
&lt;p&gt;At this stage, I thought I solved the problem.
However, after some more testing in pre-production, I found out that sometimes, the request would still abort and result in a corrupted ZIP file.
I had to dig some more!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="more-gunicorn-issues"&gt;
&lt;h2&gt;More gunicorn issues?&lt;/h2&gt;
&lt;p&gt;Since &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; is serving the file, I was thinking that maybe it was blocked until the file was completely downloaded.
It's an issue I already encountered with file uploads and that I solved with a nginx reverse proxy.
Maybe something similar was happening, although I was expecting nginx to buffer the response in both directions.&lt;/p&gt;
&lt;p&gt;To test this, I configured a nginx instance locally and added the &lt;tt class="docutils literal"&gt;limit_rate 1M;&lt;/tt&gt; &lt;a class="footnote-reference" href="#why-nginx-not-browser" id="footnote-reference-2"&gt;[2]&lt;/a&gt; to the &lt;tt class="docutils literal"&gt;server&lt;/tt&gt; block to slow down downloads so I could witness the problem.
I then launched the site with &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; with only one worker and triggered a download.
I tried to make requests to &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; both directly and through nginx.
&lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; responded as expected while the download continued.
I then switched &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; off, just to be sure.
The download continued as expected.
So, I could rule out &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;.
nginx was correctly buffering the response.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="let-s-go-beyond-30s"&gt;
&lt;h2&gt;Let's go beyond 30s&lt;/h2&gt;
&lt;p&gt;So far, so good.
However, locally, the download took less than 30s.
So, I tweaked the &lt;tt class="docutils literal"&gt;limit_rate&lt;/tt&gt; parameter again to be sure to hit this 30s mark.
The download failed and stopped at 30s.&lt;/p&gt;
&lt;p&gt;For reasons that are beyond the scope of this article, when the user triggers the download through the interface as I was, we actually download the file in JS and then on something like this &lt;tt class="docutils literal"&gt;URL.createObjectURL(new &lt;span class="pre"&gt;Blob([response.body]))&lt;/span&gt;&lt;/tt&gt; to actually save the file.
It's not something I'd recommend generally since it adds complexity and the browser can handle it just fine on its own.
But, it's how it is.&lt;/p&gt;
&lt;p&gt;So, I tried to query the API directly without relying on any JS layer.
It worked.
It looks like a timeout issue in the frontend code.&lt;/p&gt;
&lt;p&gt;Once again, I had similar issues with file uploads a while back: the frontend also had timeouts configured.
As it turned out, these timeouts were also an issue here.
The project uses &lt;a class="reference external" href="https://visionmedia.github.io/superagent/"&gt;SuperAgent&lt;/a&gt; for its HTTP request, so I just had to configure it so this request could have a longer timeout.&lt;/p&gt;
&lt;p&gt;I tested again, and it worked!
I thought I solved it.
It turned out there was another issue I couldn't spot locally.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="to-the-server"&gt;
&lt;h2&gt;To the server!&lt;/h2&gt;
&lt;p&gt;At this stage, I once again though the problem was solved.
So I deployed my solution in pre-production and tested.
And it failed.
Again.&lt;/p&gt;
&lt;p&gt;Our pre-production environnement is a bit peculiar, and we have two nginx reverse proxies in front of the app (we have only one in production).
I though it could be an interaction between these two.&lt;/p&gt;
&lt;p&gt;So I tweaked the configuration of both nginx.
I increased all relevant timeout values: &lt;tt class="docutils literal"&gt;proxy_connect_timeout 600;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;proxy_send_timeout 600;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;proxy_read_timeout 600;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;send_timeout 600;&lt;/tt&gt;.
I even disabled proxy buffering just in case with &lt;tt class="docutils literal"&gt;proxy_buffering off;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;It had no effect.
At this stage, I resorted to try each step in the test environnement separately.
To do this, I disabled all HTTPS redirections and disabled permissions on my backend view so I could access the URL directly with &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; &lt;a class="footnote-reference" href="#disable-checks" id="footnote-reference-3"&gt;[3]&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;Our test environnement is in kubernetes, but the same method applies to any environnement.
I connected to the pod where &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; and its nginx side car were running and tried this:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Talking directly to &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt;: &lt;tt class="docutils literal"&gt;curl &lt;span class="pre"&gt;-v&lt;/span&gt; &lt;span class="pre"&gt;--output&lt;/span&gt; gunicorn.zip &lt;span class="pre"&gt;localhost:8000/api/download-url&lt;/span&gt;&lt;/tt&gt; &lt;a class="footnote-reference" href="#not-true-url" id="footnote-reference-4"&gt;[4]&lt;/a&gt;. It worked perfectly and the ZIP file was valid (I unzipped it).&lt;/li&gt;
&lt;li&gt;Talking to the nginx sidecar: &lt;tt class="docutils literal"&gt;curl &lt;span class="pre"&gt;-v&lt;/span&gt; &lt;span class="pre"&gt;--output&lt;/span&gt; proxy.zip &lt;span class="pre"&gt;localhost:80/api/download-url/&lt;/span&gt;&lt;/tt&gt;. Again, it worked.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I then connected to the pod of the second reverse proxy and ran:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
curl -v --output proxy.zip https://test.example.com/api/download-url/
&lt;/pre&gt;
&lt;p&gt;And it failed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-i-am-missing"&gt;
&lt;h2&gt;What I am missing?&lt;/h2&gt;
&lt;p&gt;To dig deeper, still thinking the issue was in the second proxy, I increased its log level with &lt;tt class="docutils literal"&gt;error_log stderr debug;&lt;/tt&gt;.
I got this error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
epoll_wait() reported that client prematurely closed connection, so upstream connection is closed too while reading upstream, client: &amp;lt;IP&amp;gt;, server: &amp;lt;SERVER_NAME&amp;gt;, request: &amp;quot;GET /api/download-url/ HTTP/1.1&amp;quot;, upstream: &amp;quot;http://&amp;lt;IP&amp;gt;:80/api/download-all/&amp;quot;, host: &amp;quot;&amp;lt;HOST&amp;gt;&amp;quot;
&lt;/pre&gt;
&lt;p&gt;That's a weird error.
&lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; (what I thought as the client) didn't closed the connection.
What could be happening?&lt;/p&gt;
&lt;p&gt;I googled a bit but didn't find anything useful.
I tried again thinking at each step what the request actually did and traversed.
And then, I got it: by using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://test.example.com&lt;/span&gt;&lt;/tt&gt; I was &lt;em&gt;not&lt;/em&gt; talking directly to the second proxy.
I was going through the load balancer first.
It's obvious once you know it, but I had to think about it.&lt;/p&gt;
&lt;p&gt;So I tried again with &lt;tt class="docutils literal"&gt;curl &lt;span class="pre"&gt;-v&lt;/span&gt; &lt;span class="pre"&gt;--output&lt;/span&gt; proxy.zip &lt;span class="pre"&gt;-X&lt;/span&gt; 'Host: test.example.com' &lt;span class="pre"&gt;localhost/api/download-all/&lt;/span&gt;&lt;/tt&gt; to skip the load balancer and &lt;em&gt;really&lt;/em&gt; talk to the proxy directly.
And it worked!&lt;/p&gt;
&lt;p&gt;So the problem came from the load balancer after all!
And the message reported by nginx is coherent with this: the load balancer is its client and it's closing the connection.
If it were not, I'd be out of ideas this time and would need to find an alternative solution to my problem.&lt;/p&gt;
&lt;p&gt;I searched the documentation and found out that there is a timeout for these requests (it's not hit for file uploads for some reasons or I'd have know about it sooner) and that we can configure it.
The documentation for GKE is &lt;a class="reference external" href="https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features#timeout"&gt;here&lt;/a&gt;.
I applied the suggested configuration and… TADA!
Problem solved for good.
At least until the download isn't longer than the supplied timeout, which is unlikely for our use cases.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;It took more time that I originally anticipated.
The fact that many problems were tied together made the resolution harder:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The ZIP creation was too slow. So &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; could time out on some occasions &lt;a class="footnote-reference" href="#k8s-probes" id="footnote-reference-5"&gt;[5]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The frontend had a timeout, so the request couldn't also complete because of that.&lt;/li&gt;
&lt;li&gt;The request was so long that we also hit a timeout at infrastructure level. This one could only be found in a test environnement close enough of production. To make things worse, I forgot about the load balancer and thought I was talking to a component directly when I actually wasn't.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Luckily for me, I encountered a similar problem with file upload.
This time, a rapid Google search indicated me to use a reverse proxy to solve the issue.
So, as always, the more experience who have, the more likely you are to have an idea or a lead to where the problem is.&lt;/p&gt;
&lt;p&gt;In the end, I had to use to the good old method of testing each component after another to find out what is actually causing the issue.
It's long a a bit tedious, but it works.&lt;/p&gt;
&lt;p&gt;While I was doing this, I couldn't help but think of other solutions.
It's a good thing I didn't stop, because most of them wouldn't have worked and all of them would have needlessly increased the complexity of the project.
The solutions I thought of were:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create the ZIP file with &lt;tt class="docutils literal"&gt;gunicorn&lt;/tt&gt; and use a nginx feature named &lt;a class="reference external" href="https://blog.horejsek.com/nginx-x-accel-explained/"&gt;X-Accel-Redirect&lt;/a&gt; to transfer quickly the request to nginx. It would have been useless since nginx was already buffering the request correctly.&lt;/li&gt;
&lt;li&gt;Uploading the file to bucket storage and redirect the user to it. It would have worked but I would also have to clean the ZIP files after a while.&lt;/li&gt;
&lt;li&gt;Switch to an extra service altogether if the ZIP creation had to be long. It could have helped, but I would still have the long download issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope you will find this useful.
If you have a question or a remark, please leave a comment below.&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="gunicorn-default" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;And we use this default value.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="why-nginx-not-browser" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;You may wander why I used this instead of the browser tools. I started with the throttle tool of the browser, but by default it was way too slow. I managed to configure it, but on some occasions the limit wasn't enforced correctly. And later, I needed to test with &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; anyway. So in the end, it saved me quite a lot of time.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="disable-checks" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;It goes without saying than this environnement doesn't contain any sensitive information. I used random public test files to test my ZIP download.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="not-true-url" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;It's a placeholder URL as you probably guessed.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="k8s-probes" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;It could also seems unresponsive to kubernetes probes so kubernetes would restart it. I didn't mention this sooner to make reading easier. Since our requests are now fast enough, tweaking the probes isn't necessary, so I didn't do it.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="Programmation"></category><category term="Python"></category><category term="Django"></category><category term="nginx"></category><category term="GCP"></category><category term="devops"></category></entry></feed>