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)
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))
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.
Doing so is also considered semantically invalid - the spec forbids parameterizing Literal with dynamically computed parameters. The runtime implementation simply doesn't have the information it would need to validate this. Building VALID_ARGUMENTS from Argument is the way to go.
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)
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))
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.
Doing so is also considered semantically invalid - the spec forbids parameterizing Literal with dynamically computed parameters. The runtime implementation simply doesn't have the information it would need to validate this. Building VALID_ARGUMENTS from Argument is the way to go.
If anyone's still looking for a workaround for this:
typing.Literal[tuple(VALID_ARGUMENTS)]
Inference on Literal Types - Typing - Discussions on Python.org
Using typing.Literal for choosing from a set of strings
Unpacking container values to Literal type
Getting the literal out of a python Literal type, at runtime? - Stack Overflow
I want to create a type alias for a Literal type from the values of a container (list, tuple, members of an Enum, whatever). This would be nice and DRY as the type could inherit the values of the container as my developing API evolves.
My only issue is that MyPy doesn't appreciate it (Invalid type alias: expression is not a valid type [valid-type]). But I'm inclined to say yeet and go with it. Thoughts?
from typing import Literal
modes = ("r", "w", "a")
# The WET (write everything twice) way
type ModeVals1 = Literal["r", "w", "a"]
# The DRY (don't repeat yourself) way
type ModeVals2 = Literal[*modes]
# functionally equivalent right?
# In use
def my_func(mode: ModeVals2, ...):
...
The typing module provides a function get_args which retrieves the arguments with which your Literal was initialized.
>>> from typing import Literal, get_args
>>> l = Literal['add', 'mul']
>>> get_args(l)
('add', 'mul')
However, I don't think you gain anything by using a Literal for what you propose.
I'm working with lots of sets of different characters and character sequences and ended up here.
If you're using type aliases defined using the type statement, get_args doesn't work directly.
And a Union of Literals is messy, so the below is what I ended up with:
from collections.abc import Sequence, Iterator
from typing import Literal, get_args, TypeAliasType, cast
def get_literal_vals(alias: TypeAliasType) -> frozenset:
def val(alias: TypeAliasType):
return alias.__value__
def args(alias: TypeAliasType):
return get_args(val(alias))
def resolveT -> Iterator[T]:
if isinstance(alias, TypeAliasType):
for val in resolve(args(alias)):
yield from resolve(val)
return
if isinstance(alias, tuple):
t_seq = cast(Sequence[T], alias)
for element in t_seq:
yield from resolve(element)
return
yield alias
return frozenset(resolve(alias))
type Doubles = Literal["ab", "de", "gh"]
type Triples = Literal["abc", "def", "ghi"]
type DT = Doubles | Triples
dt_set: frozenset[DT] = get_literal_vals(DT)
This has some type checking issues depending on how strict your type checker is but works in practice:
>>> type Triples = Literal["abc", "def", "ghi"]
>>> get_literal_vals(Triples)
frozenset({'def', 'abc', 'ghi'})
>>> type Doubles = Literal["ab", "de", "gh"]
>>> get_literal_vals(Doubles)
frozenset({'gh', 'de', 'ab'})
>>> type DT = Doubles | Triples
>>> get_literal_vals(DT)
frozenset({'ghi', 'de', 'ab', 'abc', 'gh', 'def'})
An interesting side effect of this is that dynamic user input string values can be constrained to a set of specific literal values within conditional branches.
def foo(bar: str):
if bar in dt_set:
# bar is considered a literal value in DT
# by some type checkers.
pass
# bar is an unconstrained string value
# outside of the conditional branch.