Skip to content

Instantly share code, notes, and snippets.

@TomFaulkner
Last active May 4, 2022 01:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TomFaulkner/5d0cf328fc79300453c806b6b6e52c8f to your computer and use it in GitHub Desktop.
Save TomFaulkner/5d0cf328fc79300453c806b6b6e52c8f to your computer and use it in GitHub Desktop.
Dynamic StrEnums (Python's Enum inheriting from a str)

Why?

There may be a better way to do this, but this worked for me, and it is compatible with FastAPI's use of Enums.

Why not use the Enum functional API? (Animal = Enum('Animal', 'ANT BEE CAT DOG'))

This works if the fields are to be autogenerated using numbers, however to be useful with FastAPI Enum has to have values as str.

Are you sure?

Probably not, I would be glad to get an example of the above working, and compatible with FastAPI's Query Enums.

My use is:

post_orders: Ordering = {
    "created_time": "Oldest First",
    "comment_count": "Most Comments",
    "inverse_comment_count": "Fewest Comments",
}

PostOrders = create_strenum("PostOrders", svc_post.post_orders)

@router.get("/posts/", response_model=Posts)
async def get_all(
    order: PostOrders,
) -> Posts:
    pass

With the Enum functional example the FastAPI choices would be integers rather than the strings.

from enum import Enum
from typing import Sequence
class StrEnum(str, Enum):
pass
template = """class {name}(StrEnum):
{fields}
"""
def create_strenum(name: str, keys: Sequence):
"""Dynamically create a StrEnum from a Sequence.
>>> my_enum = create_strenum('Flowers', ('rose', 'violet'))
>>> my_enum
<enum 'Flowers'>
>>> my_enum.__members__
mappingproxy({'rose': <Flowers.rose: 'rose'>, 'violet': <Flowers.violet: 'violet'>})
>>> my_enum.rose
<Flowers.rose: 'rose'>
"""
# implementation borrowed from Python's NamedTuple
fields = "; ".join([f'{k}="{k}"' for k in keys])
code = template.format(name=name, fields=fields)
namespace = dict(__name__=f"dynstrenum_{name}", StrEnum=StrEnum)
exec(code, namespace)
return namespace[name]
if __name__ == "__main__":
import doctest
doctest.testmod()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment