From 47b83535b850deaf866b4a94dbd10940737450d7 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Fri, 7 Aug 2026 22:27:43 -0500 Subject: [PATCH] Add Annotate Return Type On Enum Function as a Python TIL --- README.md | 3 +- .../annotate-return-type-on-enum-function.md | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 python/annotate-return-type-on-enum-function.md diff --git a/README.md b/README.md index 366687e..37aa748 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1854 TILs and counting..._ +_1855 TILs and counting..._ See some of the other learning resources I work on: @@ -1076,6 +1076,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Access Most Recent Return Value In REPL](python/access-most-recent-return-value-in-repl.md) - [Access SQLite Result Values By Name With Row Factory](python/access-sqlite-result-values-by-name-with-row-factory.md) - [Access Variables Outside Loop Scope](python/access-variables-outside-loop-scope.md) +- [Annotate Return Type On Enum Function](python/annotate-return-type-on-enum-function.md) - [Another Way To Mark Keyword-Only Dataclass Fields](python/another-way-to-mark-keyword-only-dataclass-fields.md) - [Argument Defaults Are Evaluated When Function Is Defined](python/argument-defaults-are-evaluated-when-function-is-defined.md) - [Assert Is Only A Development Check](python/assert-is-only-a-development-check.md) diff --git a/python/annotate-return-type-on-enum-function.md b/python/annotate-return-type-on-enum-function.md new file mode 100644 index 0000000..39073c4 --- /dev/null +++ b/python/annotate-return-type-on-enum-function.md @@ -0,0 +1,61 @@ +# Annotate Return Type On Enum Function + +I defined the following `StrEnum` class to capture the canonical set of valid +values for a _storage format_ in +[`py-vmt`](https://github.com/jbranchaud/py-vmt). It includes a `default` +function that can be called to get what the system considers the default storage +format. + +```python +class StorageFormat(StrEnum): + SQLITE = "sqlite" + JSON = "json" + + @staticmethod + def default(): + return StorageFormat.SQLITE +``` + +This works, but what is missing is a type annotation for the return value of +`default`. + +I'd like to use `StorageFormat` as the return type annotation. However, the +annotations are eagerly evaluated at the time the function definition is being +processed. The `StorageFormat` class is not fully evaluated at that point and so +isn't available. + +Instead, what I can do is quote `StorageFormat` as a string to make it a +_forward reference_. + +```python +class StorageFormat(StrEnum): + SQLITE = "sqlite" + JSON = "json" + + @staticmethod + def default() -> "StorageFormat": + return StorageFormat.SQLITE +``` + +The string type annotation will be lazily evaluated at a later time when +`StorageFormat` fully exists. This is behavior that is slated to be deprecated. +And it is no longer needed as of [Python 3.14 which lazily evaluates all +annotations by default +now](https://docs.python.org/3/reference/compound_stmts.html#annotations). + +If I want to be more specific with the above annotation, I can import [`Literal` +from `typing`](https://docs.python.org/3/library/typing.html#typing.Literal) and +then wrap a specific enum value. + +```python +class StorageFormat(StrEnum): + SQLITE = "sqlite" + JSON = "json" + + @staticmethod + def default() -> "Literal[StorageFormat.SQLITE]": + return StorageFormat.SQLITE +``` + +Now it will specifically enforce `SQLITE` rather than accepting `SQLITE` or +`JSON`.