File: //opt/alt/python311/lib/python3.11/site-packages/typer/main.py
import inspect
import os
import sys
import traceback
from datetime import datetime
from enum import Enum
from functools import update_wrapper
from pathlib import Path
from traceback import FrameSummary, StackSummary
from types import TracebackType
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
from uuid import UUID
import click
from .completion import get_completion_inspect_parameters
from .core import MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption
from .models import (
AnyType,
ArgumentInfo,
CommandFunctionType,
CommandInfo,
Default,
DefaultPlaceholder,
DeveloperExceptionConfig,
FileBinaryRead,
FileBinaryWrite,
FileText,
FileTextWrite,
NoneType,
OptionInfo,
ParameterInfo,
ParamMeta,
Required,
TyperInfo,
)
from .utils import get_params_from_function
try:
import rich
from rich.console import Console
from rich.traceback import Traceback
console_stderr = Console(stderr=True)
except ImportError: # pragma: no cover
rich = None # type: ignore
_original_except_hook = sys.excepthook
_typer_developer_exception_attr_name = "__typer_developer_exception__"
def except_hook(
exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType]
) -> None:
exception_config: Union[DeveloperExceptionConfig, None] = getattr(
exc_value, _typer_developer_exception_attr_name, None
)
standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK")
if (
standard_traceback
or not exception_config
or not exception_config.pretty_exceptions_enable
):
_original_except_hook(exc_type, exc_value, tb)
return
typer_path = os.path.dirname(__file__)
click_path = os.path.dirname(click.__file__)
supress_internal_dir_names = [typer_path, click_path]
exc = exc_value
if rich:
rich_tb = Traceback.from_exception(
type(exc),
exc,
exc.__traceback__,
show_locals=exception_config.pretty_exceptions_show_locals,
suppress=supress_internal_dir_names,
)
console_stderr.print(rich_tb)
return
tb_exc = traceback.TracebackException.from_exception(exc)
stack: List[FrameSummary] = []
for frame in tb_exc.stack:
if any(frame.filename.startswith(path) for path in supress_internal_dir_names):
if not exception_config.pretty_exceptions_short:
# Hide the line for internal libraries, Typer and Click
stack.append(
traceback.FrameSummary(
filename=frame.filename,
lineno=frame.lineno,
name=frame.name,
line="",
)
)
else:
stack.append(frame)
# Type ignore ref: https://github.com/python/typeshed/pull/8244
final_stack_summary = StackSummary.from_list(stack)
tb_exc.stack = final_stack_summary
for line in tb_exc.format():
print(line, file=sys.stderr)
return
def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]:
install_param, show_param = get_completion_inspect_parameters()
click_install_param, _ = get_click_param(install_param)
click_show_param, _ = get_click_param(show_param)
return click_install_param, click_show_param
class Typer:
def __init__(
self,
*,
name: Optional[str] = Default(None),
cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
chain: bool = Default(False),
result_callback: Optional[Callable[..., Any]] = Default(None),
# Command
context_settings: Optional[Dict[Any, Any]] = Default(None),
callback: Optional[Callable[..., Any]] = Default(None),
help: Optional[str] = Default(None),
epilog: Optional[str] = Default(None),
short_help: Optional[str] = Default(None),
options_metavar: str = Default("[OPTIONS]"),
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
add_completion: bool = True,
# Rich settings
rich_markup_mode: MarkupMode = None,
rich_help_panel: Union[str, None] = Default(None),
pretty_exceptions_enable: bool = True,
pretty_exceptions_show_locals: bool = True,
pretty_exceptions_short: bool = True,
):
self._add_completion = add_completion
self.rich_markup_mode: MarkupMode = rich_markup_mode
self.rich_help_panel = rich_help_panel
self.pretty_exceptions_enable = pretty_exceptions_enable
self.pretty_exceptions_show_locals = pretty_exceptions_show_locals
self.pretty_exceptions_short = pretty_exceptions_short
self.info = TyperInfo(
name=name,
cls=cls,
invoke_without_command=invoke_without_command,
no_args_is_help=no_args_is_help,
subcommand_metavar=subcommand_metavar,
chain=chain,
result_callback=result_callback,
context_settings=context_settings,
callback=callback,
help=help,
epilog=epilog,
short_help=short_help,
options_metavar=options_metavar,
add_help_option=add_help_option,
hidden=hidden,
deprecated=deprecated,
)
self.registered_groups: List[TyperInfo] = []
self.registered_commands: List[CommandInfo] = []
self.registered_callback: Optional[TyperInfo] = None
def callback(
self,
name: Optional[str] = Default(None),
*,
cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
chain: bool = Default(False),
result_callback: Optional[Callable[..., Any]] = Default(None),
# Command
context_settings: Optional[Dict[Any, Any]] = Default(None),
help: Optional[str] = Default(None),
epilog: Optional[str] = Default(None),
short_help: Optional[str] = Default(None),
options_metavar: str = Default("[OPTIONS]"),
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
# Rich settings
rich_help_panel: Union[str, None] = Default(None),
) -> Callable[[CommandFunctionType], CommandFunctionType]:
def decorator(f: CommandFunctionType) -> CommandFunctionType:
self.registered_callback = TyperInfo(
name=name,
cls=cls,
invoke_without_command=invoke_without_command,
no_args_is_help=no_args_is_help,
subcommand_metavar=subcommand_metavar,
chain=chain,
result_callback=result_callback,
context_settings=context_settings,
callback=f,
help=help,
epilog=epilog,
short_help=short_help,
options_metavar=options_metavar,
add_help_option=add_help_option,
hidden=hidden,
deprecated=deprecated,
rich_help_panel=rich_help_panel,
)
return f
return decorator
def command(
self,
name: Optional[str] = None,
*,
cls: Optional[Type[TyperCommand]] = None,
context_settings: Optional[Dict[Any, Any]] = None,
help: Optional[str] = None,
epilog: Optional[str] = None,
short_help: Optional[str] = None,
options_metavar: str = "[OPTIONS]",
add_help_option: bool = True,
no_args_is_help: bool = False,
hidden: bool = False,
deprecated: bool = False,
# Rich settings
rich_help_panel: Union[str, None] = Default(None),
) -> Callable[[CommandFunctionType], CommandFunctionType]:
if cls is None:
cls = TyperCommand
def decorator(f: CommandFunctionType) -> CommandFunctionType:
self.registered_commands.append(
CommandInfo(
name=name,
cls=cls,
context_settings=context_settings,
callback=f,
help=help,
epilog=epilog,
short_help=short_help,
options_metavar=options_metavar,
add_help_option=add_help_option,
no_args_is_help=no_args_is_help,
hidden=hidden,
deprecated=deprecated,
# Rich settings
rich_help_panel=rich_help_panel,
)
)
return f
return decorator
def add_typer(
self,
typer_instance: "Typer",
*,
name: Optional[str] = Default(None),
cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
chain: bool = Default(False),
result_callback: Optional[Callable[..., Any]] = Default(None),
# Command
context_settings: Optional[Dict[Any, Any]] = Default(None),
callback: Optional[Callable[..., Any]] = Default(None),
help: Optional[str] = Default(None),
epilog: Optional[str] = Default(None),
short_help: Optional[str] = Default(None),
options_metavar: str = Default("[OPTIONS]"),
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
# Rich settings
rich_help_panel: Union[str, None] = Default(None),
) -> None:
self.registered_groups.append(
TyperInfo(
typer_instance,
name=name,
cls=cls,
invoke_without_command=invoke_without_command,
no_args_is_help=no_args_is_help,
subcommand_metavar=subcommand_metavar,
chain=chain,
result_callback=result_callback,
context_settings=context_settings,
callback=callback,
help=help,
epilog=epilog,
short_help=short_help,
options_metavar=options_metavar,
add_help_option=add_help_option,
hidden=hidden,
deprecated=deprecated,
rich_help_panel=rich_help_panel,
)
)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
if sys.excepthook != except_hook:
sys.excepthook = except_hook
try:
return get_command(self)(*args, **kwargs)
except Exception as e:
# Set a custom attribute to tell the hook to show nice exceptions for user
# code. An alternative/first implementation was a custom exception with
# raise custom_exc from e
# but that means the last error shown is the custom exception, not the
# actual error. This trick improves developer experience by showing the
# actual error last.
setattr(
e,
_typer_developer_exception_attr_name,
DeveloperExceptionConfig(
pretty_exceptions_enable=self.pretty_exceptions_enable,
pretty_exceptions_show_locals=self.pretty_exceptions_show_locals,
pretty_exceptions_short=self.pretty_exceptions_short,
),
)
raise e
def get_group(typer_instance: Typer) -> TyperGroup:
group = get_group_from_info(
TyperInfo(typer_instance),
pretty_exceptions_short=typer_instance.pretty_exceptions_short,
rich_markup_mode=typer_instance.rich_markup_mode,
)
return group
def get_command(typer_instance: Typer) -> click.Command:
if typer_instance._add_completion:
click_install_param, click_show_param = get_install_completion_arguments()
if (
typer_instance.registered_callback
or typer_instance.info.callback
or typer_instance.registered_groups
or len(typer_instance.registered_commands) > 1
):
# Create a Group
click_command: click.Command = get_group(typer_instance)
if typer_instance._add_completion:
click_command.params.append(click_install_param)
click_command.params.append(click_show_param)
return click_command
elif len(typer_instance.registered_commands) == 1:
# Create a single Command
single_command = typer_instance.registered_commands[0]
if not single_command.context_settings and not isinstance(
typer_instance.info.context_settings, DefaultPlaceholder
):
single_command.context_settings = typer_instance.info.context_settings
click_command = get_command_from_info(
single_command,
pretty_exceptions_short=typer_instance.pretty_exceptions_short,
rich_markup_mode=typer_instance.rich_markup_mode,
)
if typer_instance._add_completion:
click_command.params.append(click_install_param)
click_command.params.append(click_show_param)
return click_command
raise RuntimeError(
"Could not get a command for this Typer instance"
) # pragma: no cover
def get_group_name(typer_info: TyperInfo) -> Optional[str]:
if typer_info.callback:
# Priority 1: Callback passed in app.add_typer()
return get_command_name(typer_info.callback.__name__)
if typer_info.typer_instance:
registered_callback = typer_info.typer_instance.registered_callback
if registered_callback:
if registered_callback.callback:
# Priority 2: Callback passed in @subapp.callback()
return get_command_name(registered_callback.callback.__name__)
if typer_info.typer_instance.info.callback:
return get_command_name(typer_info.typer_instance.info.callback.__name__)
return None
def solve_typer_info_help(typer_info: TyperInfo) -> str:
# Priority 1: Explicit value was set in app.add_typer()
if not isinstance(typer_info.help, DefaultPlaceholder):
return inspect.cleandoc(typer_info.help or "")
# Priority 2: Explicit value was set in sub_app.callback()
try:
callback_help = typer_info.typer_instance.registered_callback.help
if not isinstance(callback_help, DefaultPlaceholder):
return inspect.cleandoc(callback_help or "")
except AttributeError:
pass
# Priority 3: Explicit value was set in sub_app = typer.Typer()
try:
instance_help = typer_info.typer_instance.info.help
if not isinstance(instance_help, DefaultPlaceholder):
return inspect.cleandoc(instance_help or "")
except AttributeError:
pass
# Priority 4: Implicit inference from callback docstring in app.add_typer()
if typer_info.callback:
doc = inspect.getdoc(typer_info.callback)
if doc:
return doc
# Priority 5: Implicit inference from callback docstring in @app.callback()
try:
callback = typer_info.typer_instance.registered_callback.callback
if not isinstance(callback, DefaultPlaceholder):
doc = inspect.getdoc(callback or "")
if doc:
return doc
except AttributeError:
pass
# Priority 6: Implicit inference from callback docstring in typer.Typer()
try:
instance_callback = typer_info.typer_instance.info.callback
if not isinstance(instance_callback, DefaultPlaceholder):
doc = inspect.getdoc(instance_callback)
if doc:
return doc
except AttributeError:
pass
# Value not set, use the default
return typer_info.help.value
def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo:
values: Dict[str, Any] = {}
name = None
for name, value in typer_info.__dict__.items():
# Priority 1: Value was set in app.add_typer()
if not isinstance(value, DefaultPlaceholder):
values[name] = value
continue
# Priority 2: Value was set in @subapp.callback()
try:
callback_value = getattr(
typer_info.typer_instance.registered_callback, # type: ignore
name,
)
if not isinstance(callback_value, DefaultPlaceholder):
values[name] = callback_value
continue
except AttributeError:
pass
# Priority 3: Value set in subapp = typer.Typer()
try:
instance_value = getattr(
typer_info.typer_instance.info, # type: ignore
name,
)
if not isinstance(instance_value, DefaultPlaceholder):
values[name] = instance_value
continue
except AttributeError:
pass
# Value not set, use the default
values[name] = value.value
if values["name"] is None:
values["name"] = get_group_name(typer_info)
values["help"] = solve_typer_info_help(typer_info)
return TyperInfo(**values)
def get_group_from_info(
group_info: TyperInfo,
*,
pretty_exceptions_short: bool,
rich_markup_mode: MarkupMode,
) -> TyperGroup:
assert (
group_info.typer_instance
), "A Typer instance is needed to generate a Click Group"
commands: Dict[str, click.Command] = {}
for command_info in group_info.typer_instance.registered_commands:
command = get_command_from_info(
command_info=command_info,
pretty_exceptions_short=pretty_exceptions_short,
rich_markup_mode=rich_markup_mode,
)
if command.name:
commands[command.name] = command
for sub_group_info in group_info.typer_instance.registered_groups:
sub_group = get_group_from_info(
sub_group_info,
pretty_exceptions_short=pretty_exceptions_short,
rich_markup_mode=rich_markup_mode,
)
if sub_group.name:
commands[sub_group.name] = sub_group
solved_info = solve_typer_info_defaults(group_info)
(
params,
convertors,
context_param_name,
) = get_params_convertors_ctx_param_name_from_function(solved_info.callback)
cls = solved_info.cls or TyperGroup
assert issubclass(cls, TyperGroup)
group = cls(
name=solved_info.name or "",
commands=commands,
invoke_without_command=solved_info.invoke_without_command,
no_args_is_help=solved_info.no_args_is_help,
subcommand_metavar=solved_info.subcommand_metavar,
chain=solved_info.chain,
result_callback=solved_info.result_callback,
context_settings=solved_info.context_settings,
callback=get_callback(
callback=solved_info.callback,
params=params,
convertors=convertors,
context_param_name=context_param_name,
pretty_exceptions_short=pretty_exceptions_short,
),
params=params,
help=solved_info.help,
epilog=solved_info.epilog,
short_help=solved_info.short_help,
options_metavar=solved_info.options_metavar,
add_help_option=solved_info.add_help_option,
hidden=solved_info.hidden,
deprecated=solved_info.deprecated,
rich_markup_mode=rich_markup_mode,
# Rich settings
rich_help_panel=solved_info.rich_help_panel,
)
return group
def get_command_name(name: str) -> str:
return name.lower().replace("_", "-")
def get_params_convertors_ctx_param_name_from_function(
callback: Optional[Callable[..., Any]],
) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]:
params = []
convertors = {}
context_param_name = None
if callback:
parameters = get_params_from_function(callback)
for param_name, param in parameters.items():
if lenient_issubclass(param.annotation, click.Context):
context_param_name = param_name
continue
click_param, convertor = get_click_param(param)
if convertor:
convertors[param_name] = convertor
params.append(click_param)
return params, convertors, context_param_name
def get_command_from_info(
command_info: CommandInfo,
*,
pretty_exceptions_short: bool,
rich_markup_mode: MarkupMode,
) -> click.Command:
assert command_info.callback, "A command must have a callback function"
name = command_info.name or get_command_name(command_info.callback.__name__)
use_help = command_info.help
if use_help is None:
use_help = inspect.getdoc(command_info.callback)
else:
use_help = inspect.cleandoc(use_help)
(
params,
convertors,
context_param_name,
) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
cls = command_info.cls or TyperCommand
command = cls(
name=name,
context_settings=command_info.context_settings,
callback=get_callback(
callback=command_info.callback,
params=params,
convertors=convertors,
context_param_name=context_param_name,
pretty_exceptions_short=pretty_exceptions_short,
),
params=params, # type: ignore
help=use_help,
epilog=command_info.epilog,
short_help=command_info.short_help,
options_metavar=command_info.options_metavar,
add_help_option=command_info.add_help_option,
no_args_is_help=command_info.no_args_is_help,
hidden=command_info.hidden,
deprecated=command_info.deprecated,
rich_markup_mode=rich_markup_mode,
# Rich settings
rich_help_panel=command_info.rich_help_panel,
)
return command
def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]:
convertor: Optional[Callable[[Any], Any]] = None
if lenient_issubclass(type_, Path):
convertor = param_path_convertor
if lenient_issubclass(type_, Enum):
convertor = generate_enum_convertor(type_)
return convertor
def param_path_convertor(value: Optional[str] = None) -> Optional[Path]:
if value is not None:
return Path(value)
return None
def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]:
val_map = {str(val.value): val for val in enum}
def convertor(value: Any) -> Any:
if value is not None:
val = str(value)
if val in val_map:
key = val_map[val]
return enum(key)
return convertor
def generate_list_convertor(
convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any]
) -> Callable[[Sequence[Any]], Optional[List[Any]]]:
def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]:
if default_value is None and len(value) == 0:
return None
return [convertor(v) if convertor else v for v in value]
return internal_convertor
def generate_tuple_convertor(
types: Sequence[Any],
) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]:
convertors = [determine_type_convertor(type_) for type_ in types]
def internal_convertor(
param_args: Optional[Tuple[Any, ...]],
) -> Optional[Tuple[Any, ...]]:
if param_args is None:
return None
return tuple(
convertor(arg) if convertor else arg
for (convertor, arg) in zip(convertors, param_args)
)
return internal_convertor
def get_callback(
*,
callback: Optional[Callable[..., Any]] = None,
params: Sequence[click.Parameter] = [],
convertors: Optional[Dict[str, Callable[[str], Any]]] = None,
context_param_name: Optional[str] = None,
pretty_exceptions_short: bool,
) -> Optional[Callable[..., Any]]:
use_convertors = convertors or {}
if not callback:
return None
parameters = get_params_from_function(callback)
use_params: Dict[str, Any] = {}
for param_name in parameters:
use_params[param_name] = None
for param in params:
if param.name:
use_params[param.name] = param.default
def wrapper(**kwargs: Any) -> Any:
_rich_traceback_guard = pretty_exceptions_short # noqa: F841
for k, v in kwargs.items():
if k in use_convertors:
use_params[k] = use_convertors[k](v)
else:
use_params[k] = v
if context_param_name:
use_params[context_param_name] = click.get_current_context()
return callback(**use_params)
update_wrapper(wrapper, callback)
return wrapper
def get_click_type(
*, annotation: Any, parameter_info: ParameterInfo
) -> click.ParamType:
if parameter_info.click_type is not None:
return parameter_info.click_type
elif parameter_info.parser is not None:
return click.types.FuncParamType(parameter_info.parser)
elif annotation == str:
return click.STRING
elif annotation == int:
if parameter_info.min is not None or parameter_info.max is not None:
min_ = None
max_ = None
if parameter_info.min is not None:
min_ = int(parameter_info.min)
if parameter_info.max is not None:
max_ = int(parameter_info.max)
return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp)
else:
return click.INT
elif annotation == float:
if parameter_info.min is not None or parameter_info.max is not None:
return click.FloatRange(
min=parameter_info.min,
max=parameter_info.max,
clamp=parameter_info.clamp,
)
else:
return click.FLOAT
elif annotation == bool:
return click.BOOL
elif annotation == UUID:
return click.UUID
elif annotation == datetime:
return click.DateTime(formats=parameter_info.formats)
elif (
annotation == Path
or parameter_info.allow_dash
or parameter_info.path_type
or parameter_info.resolve_path
):
return click.Path(
exists=parameter_info.exists,
file_okay=parameter_info.file_okay,
dir_okay=parameter_info.dir_okay,
writable=parameter_info.writable,
readable=parameter_info.readable,
resolve_path=parameter_info.resolve_path,
allow_dash=parameter_info.allow_dash,
path_type=parameter_info.path_type,
)
elif lenient_issubclass(annotation, FileTextWrite):
return click.File(
mode=parameter_info.mode or "w",
encoding=parameter_info.encoding,
errors=parameter_info.errors,
lazy=parameter_info.lazy,
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, FileText):
return click.File(
mode=parameter_info.mode or "r",
encoding=parameter_info.encoding,
errors=parameter_info.errors,
lazy=parameter_info.lazy,
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, FileBinaryRead):
return click.File(
mode=parameter_info.mode or "rb",
encoding=parameter_info.encoding,
errors=parameter_info.errors,
lazy=parameter_info.lazy,
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, FileBinaryWrite):
return click.File(
mode=parameter_info.mode or "wb",
encoding=parameter_info.encoding,
errors=parameter_info.errors,
lazy=parameter_info.lazy,
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, Enum):
return click.Choice(
[item.value for item in annotation],
case_sensitive=parameter_info.case_sensitive,
)
raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover
def lenient_issubclass(
cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]]
) -> bool:
return isinstance(cls, type) and issubclass(cls, class_or_tuple)
def get_click_param(
param: ParamMeta,
) -> Tuple[Union[click.Argument, click.Option], Any]:
# First, find out what will be:
# * ParamInfo (ArgumentInfo or OptionInfo)
# * default_value
# * required
default_value = None
required = False
if isinstance(param.default, ParameterInfo):
parameter_info = param.default
if parameter_info.default == Required:
required = True
else:
default_value = parameter_info.default
elif param.default == Required or param.default == param.empty:
required = True
parameter_info = ArgumentInfo()
else:
default_value = param.default
parameter_info = OptionInfo()
annotation: Any = Any
if not param.annotation == param.empty:
annotation = param.annotation
else:
annotation = str
main_type = annotation
is_list = False
is_tuple = False
parameter_type: Any = None
is_flag = None
origin = getattr(main_type, "__origin__", None)
if origin is not None:
# Handle Optional[SomeType]
if origin is Union:
types = []
for type_ in main_type.__args__:
if type_ is NoneType:
continue
types.append(type_)
assert len(types) == 1, "Typer Currently doesn't support Union types"
main_type = types[0]
origin = getattr(main_type, "__origin__", None)
# Handle Tuples and Lists
if lenient_issubclass(origin, List):
main_type = main_type.__args__[0]
assert not getattr(
main_type, "__origin__", None
), "List types with complex sub-types are not currently supported"
is_list = True
elif lenient_issubclass(origin, Tuple): # type: ignore
types = []
for type_ in main_type.__args__:
assert not getattr(
type_, "__origin__", None
), "Tuple types with complex sub-types are not currently supported"
types.append(
get_click_type(annotation=type_, parameter_info=parameter_info)
)
parameter_type = tuple(types)
is_tuple = True
if parameter_type is None:
parameter_type = get_click_type(
annotation=main_type, parameter_info=parameter_info
)
convertor = determine_type_convertor(main_type)
if is_list:
convertor = generate_list_convertor(
convertor=convertor, default_value=default_value
)
if is_tuple:
convertor = generate_tuple_convertor(main_type.__args__)
if isinstance(parameter_info, OptionInfo):
if main_type is bool and parameter_info.is_flag is not False:
is_flag = True
# Click doesn't accept a flag of type bool, only None, and then it sets it
# to bool internally
parameter_type = None
default_option_name = get_command_name(param.name)
if is_flag:
default_option_declaration = (
f"--{default_option_name}/--no-{default_option_name}"
)
else:
default_option_declaration = f"--{default_option_name}"
param_decls = [param.name]
if parameter_info.param_decls:
param_decls.extend(parameter_info.param_decls)
else:
param_decls.append(default_option_declaration)
return (
TyperOption(
# Option
param_decls=param_decls,
show_default=parameter_info.show_default,
prompt=parameter_info.prompt,
confirmation_prompt=parameter_info.confirmation_prompt,
prompt_required=parameter_info.prompt_required,
hide_input=parameter_info.hide_input,
is_flag=is_flag,
flag_value=parameter_info.flag_value,
multiple=is_list,
count=parameter_info.count,
allow_from_autoenv=parameter_info.allow_from_autoenv,
type=parameter_type,
help=parameter_info.help,
hidden=parameter_info.hidden,
show_choices=parameter_info.show_choices,
show_envvar=parameter_info.show_envvar,
# Parameter
required=required,
default=default_value,
callback=get_param_callback(
callback=parameter_info.callback, convertor=convertor
),
metavar=parameter_info.metavar,
expose_value=parameter_info.expose_value,
is_eager=parameter_info.is_eager,
envvar=parameter_info.envvar,
shell_complete=parameter_info.shell_complete,
autocompletion=get_param_completion(parameter_info.autocompletion),
# Rich settings
rich_help_panel=parameter_info.rich_help_panel,
),
convertor,
)
elif isinstance(parameter_info, ArgumentInfo):
param_decls = [param.name]
nargs = None
if is_list:
nargs = -1
return (
TyperArgument(
# Argument
param_decls=param_decls,
type=parameter_type,
required=required,
nargs=nargs,
# TyperArgument
show_default=parameter_info.show_default,
show_choices=parameter_info.show_choices,
show_envvar=parameter_info.show_envvar,
help=parameter_info.help,
hidden=parameter_info.hidden,
# Parameter
default=default_value,
callback=get_param_callback(
callback=parameter_info.callback, convertor=convertor
),
metavar=parameter_info.metavar,
expose_value=parameter_info.expose_value,
is_eager=parameter_info.is_eager,
envvar=parameter_info.envvar,
autocompletion=get_param_completion(parameter_info.autocompletion),
# Rich settings
rich_help_panel=parameter_info.rich_help_panel,
),
convertor,
)
raise AssertionError("A click.Parameter should be returned") # pragma: no cover
def get_param_callback(
*,
callback: Optional[Callable[..., Any]] = None,
convertor: Optional[Callable[..., Any]] = None,
) -> Optional[Callable[..., Any]]:
if not callback:
return None
parameters = get_params_from_function(callback)
ctx_name = None
click_param_name = None
value_name = None
untyped_names: List[str] = []
for param_name, param_sig in parameters.items():
if lenient_issubclass(param_sig.annotation, click.Context):
ctx_name = param_name
elif lenient_issubclass(param_sig.annotation, click.Parameter):
click_param_name = param_name
else:
untyped_names.append(param_name)
# Extract value param name first
if untyped_names:
value_name = untyped_names.pop()
# If context and Click param were not typed (old/Click callback style) extract them
if untyped_names:
if ctx_name is None:
ctx_name = untyped_names.pop(0)
if click_param_name is None:
if untyped_names:
click_param_name = untyped_names.pop(0)
if untyped_names:
raise click.ClickException(
"Too many CLI parameter callback function parameters"
)
def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any:
use_params: Dict[str, Any] = {}
if ctx_name:
use_params[ctx_name] = ctx
if click_param_name:
use_params[click_param_name] = param
if value_name:
if convertor:
use_value = convertor(value)
else:
use_value = value
use_params[value_name] = use_value
return callback(**use_params)
update_wrapper(wrapper, callback)
return wrapper
def get_param_completion(
callback: Optional[Callable[..., Any]] = None,
) -> Optional[Callable[..., Any]]:
if not callback:
return None
parameters = get_params_from_function(callback)
ctx_name = None
args_name = None
incomplete_name = None
unassigned_params = list(parameters.values())
for param_sig in unassigned_params[:]:
origin = getattr(param_sig.annotation, "__origin__", None)
if lenient_issubclass(param_sig.annotation, click.Context):
ctx_name = param_sig.name
unassigned_params.remove(param_sig)
elif lenient_issubclass(origin, List):
args_name = param_sig.name
unassigned_params.remove(param_sig)
elif lenient_issubclass(param_sig.annotation, str):
incomplete_name = param_sig.name
unassigned_params.remove(param_sig)
# If there are still unassigned parameters (not typed), extract by name
for param_sig in unassigned_params[:]:
if ctx_name is None and param_sig.name == "ctx":
ctx_name = param_sig.name
unassigned_params.remove(param_sig)
elif args_name is None and param_sig.name == "args":
args_name = param_sig.name
unassigned_params.remove(param_sig)
elif incomplete_name is None and param_sig.name == "incomplete":
incomplete_name = param_sig.name
unassigned_params.remove(param_sig)
# Extract value param name first
if unassigned_params:
show_params = " ".join([param.name for param in unassigned_params])
raise click.ClickException(
f"Invalid autocompletion callback parameters: {show_params}"
)
def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any:
use_params: Dict[str, Any] = {}
if ctx_name:
use_params[ctx_name] = ctx
if args_name:
use_params[args_name] = args
if incomplete_name:
use_params[incomplete_name] = incomplete
return callback(**use_params)
update_wrapper(wrapper, callback)
return wrapper
def run(function: Callable[..., Any]) -> None:
app = Typer(add_completion=False)
app.command()(function)
app()