I recommend built-in typing.Literal
which was implemented in Python 3.8
Simple type hinting
This first example only does "type" hinting in the IDE
from typing import Literal
_TYPES = Literal["solar", "view", "both"]
def func(a, b, c, type_: _TYPES = "solar"):
pass
Here's how it looks in PyCharm, notice how "solra" is highlighted and how it nicely generates the docs for us

Simple assertion
If you want to raise an exception when the parameter check fails we can make use of get_args
to stay dry
from typing import Literal, get_args
_TYPES = Literal["solar", "view", "both"]
def func(a, b, c, type_: _TYPES = "solar"):
options = get_args(_TYPES)
assert type_ in options, f"'{type_}' is not in {options}"
Dynamic assertion
Taking it one step further we can create a function to dynamically check any supplied Literals
We can get the previous frame from sys
to extract the provided arguments in the given function.
We then use the function's __annotations__
attribute to see what values the arguments should be
from typing import Literal, get_args, get_origin
from sys import _getframe
def enforce_literals(function):
kwargs = _getframe(1).f_locals
for name, type_ in function.__annotations__.items():
value = kwargs.get(name)
options = get_args(type_)
if get_origin(type_) is Literal and name in kwargs and value not in options:
raise AssertionError(f"'{value}' is not in {options} for '{name}'")
_TYPES = Literal["solar", "view", "both"]
_NUMS = Literal[1, 2, 3, 4, 5]
def func(a, b, c, type_: _TYPES = "solar", num: _NUMS = 1):
enforce_literals(func)
func(1, 2, 3, "solar", 6)
AssertionError: '6' is not in (1, 2, 3, 4, 5) for 'num'
Oct 2022 Edit: Changed from inspect.stack()[1]
(963 usec) to sys._getframe(1)
(1.2 usec). Should have used inspect.currentframe().f_back
(2.7 usec) from the start but sys
is even better!
Feb 2023 Edit: Fixed bug for when return type is defined, pointed out by @jung rhew. Also published generaltool to make it maintainable.