16

I would like to inizialize a pydantic Literal from an array of strings

from typing import Literal
from pydantic import BaseModel

CLASS_NAME_VALUES = ["primary", "secondary", "success", "danger", "warning", "info", "dark"]
ICON_NAME_VALUES = ["electricity.svg", "water.svg",
                    "internet.svg", "naturalGas.svg", "noCategory.svg"]

class CategoryRequest(BaseModel):
    class_name: Literal[CLASS_NAME_VALUES]
    icon_name: Literal[ICON_NAME_VALUES]

But I am getting the following error

  File "pydantic/main.py", line 252, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/fields.py", line 309, in pydantic.fields.ModelField.infer
  File "pydantic/fields.py", line 271, in pydantic.fields.ModelField.__init__
  File "pydantic/fields.py", line 351, in pydantic.fields.ModelField.prepare
  File "pydantic/fields.py", line 529, in pydantic.fields.ModelField.populate_validators
  File "pydantic/validators.py", line 566, in find_validators
  File "pydantic/validators.py", line 410, in pydantic.validators.make_literal_validator
TypeError: unhashable type: 'list'

Any suggestion?

1

2 Answers 2

21

At runtime, an arbitrary value is allowed as type argument to Literal[...], but type checkers may impose restrictions. For example, mypy permits only one or more literal bool, int, str, bytes, enum values, None and aliases to other Literal types. For example, Literal[3 + 4] or List[(3, 4)] are disallowed.

As for pydantic, it permits uses values of hashable types in Literal, like tuple. For example, your sample could be rewritten using tuple as:

from typing import Literal
from pydantic import BaseModel

CLASS_NAME_VALUES = ("primary", "secondary", "success", "danger", "warning", "info", "dark")
ICON_NAME_VALUES = ("electricity.svg", "water.svg",
                    "internet.svg", "naturalGas.svg", "noCategory.svg")


class CategoryRequest(BaseModel):
    class_name: Literal[CLASS_NAME_VALUES]
    icon_name: Literal[ICON_NAME_VALUES]

But I would strongly advise against using this syntax, but sticking to the canonical one, which is also allowed by the type checkers:

CLASS_NAME_VALUES = Literal["primary", "secondary", "success", "danger", "warning", "info", "dark"]
ICON_NAME_VALUES = Literal["electricity.svg", "water.svg",
                    "internet.svg", "naturalGas.svg", "noCategory.svg"]


class CategoryRequest(BaseModel):
    class_name: CLASS_NAME_VALUES
    icon_name: ICON_NAME_VALUES
Sign up to request clarification or add additional context in comments.

2 Comments

So if I need my ICON_NAME_VALUES as an iterable later in my code, I have to define two constants? Once the Literal["electricity.svg", "water.svg", ...] for type checking and once a list/tuple ["electricity.svg", "water.svg", ...] to use in the code? How cumbersome! Apparently, I cannot go vice versa and get a list from the Literal either?
@stefanbschneider it's possible other way around, and that's the canonical solution: stackoverflow.com/questions/64522040/…
2

Starting with python 3.11 (see PEP-0646) you can just unpack tuple/array with a star operator:

class CategoryRequest(BaseModel):
    class_name: Literal[*CLASS_NAME_VALUES]
    icon_name: Literal[*ICON_NAME_VALUES]

Additional links:

2 Comments

Can you provide a citation for this?
From a type-checker perspective, this is invalid, and you'll get a type-checking error. From a runtime perspective, this just does the same thing Literal[tuple(CLASS_NAME_VALUES)] already did in earlier versions.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.