I have a function which validates its argument to accept only values from a given list of valid options. Typing-wise, I reflect this behavior using a Literal type alias, like so:
from typing import Literal
VALID_ARGUMENTS = ['foo', 'bar']
Argument = Literal['foo', 'bar']
def func(argument: 'Argument') -> None:
if argument not in VALID_ARGUMENTS:
raise ValueError(
f'argument must be one of {VALID_ARGUMENTS}'
)
# ...
This is a violation of the DRY principle, because I have to rewrite the list of valid arguments in the definition of my Literal type, even if it is already stored in the variable VALID_ARGUMENTS. How can I create the Argument Literal type dynamically, given the VALID_ARGUMENTS variable?
The following things do not work:
from typing import Literal, Union, NewType
Argument = Literal[*VALID_ARGUMENTS] # SyntaxError: invalid syntax
Argument = Literal[VALID_ARGUMENTS] # Parameters to generic types must be types
Argument = Literal[Union[VALID_ARGUMENTS]] # TypeError: Union[arg, ...]: each arg must be a type. Got ['foo', 'bar'].
Argument = NewType(
'Argument',
Union[
Literal[valid_argument]
for valid_argument in VALID_ARGUMENTS
]
) # Expected type 'Type[_T]', got 'list' instead
So, how can it be done? Or can’t it be done at all?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Go the other way around, and build VALID_ARGUMENTS from Argument:
Argument = typing.Literal['foo', 'bar'] VALID_ARGUMENTS: typing.Tuple[Argument, ...] = typing.get_args(Argument)
It’s possible at runtime to build Argument from VALID_ARGUMENTS, but doing so is incompatible with static analysis, which is the primary use case of type annotations. Building VALID_ARGUMENTS from Argument is the way to go.
I’ve used a tuple for VALID_ARGUMENTS here, but if for some reason you really prefer a list, you can get one:
VALID_ARGUMENTS: typing.List[Argument] = list(typing.get_args(Argument))
Method 2
If anyone’s still looking for a workaround for this:
typing.Literal[tuple(VALID_ARGUMENTS)]
Method 3
Here is the workaround for this. But don’t know if it is a good solution.
VALID_ARGUMENTS = ['foo', 'bar'] Argument = Literal['1'] Argument.__args__ = tuple(VALID_ARGUMENTS) print(Argument) # typing.Literal['foo', 'bar']
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0