-
-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Converting sisl_toolbox
to typer.
#608
Open
pfebrer
wants to merge
2
commits into
zerothi:main
Choose a base branch
from
pfebrer:606_typer_cli
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Classes that hold information regarding how a given parameter should behave in a CLI | ||
# They are meant to be used as metadata for the type annotations. That is, passing them | ||
# to Annotated. E.g.: Annotated[int, CLIArgument(option="some_option")]. Even if they | ||
# are empty, they indicate whether to treat the parameter as an argument or an option. | ||
class CLIArgument: | ||
def __init__(self, **kwargs): | ||
self.kwargs = kwargs | ||
|
||
class CLIOption: | ||
def __init__(self, *param_decls: str, **kwargs): | ||
if len(param_decls) > 0: | ||
kwargs["param_decls"] = param_decls | ||
self.kwargs = kwargs | ||
|
||
def get_params_help(func) -> dict: | ||
"""Gets the text help of parameters from the docstring""" | ||
params_help = {} | ||
|
||
in_parameters = False | ||
read_key = None | ||
arg_content = "" | ||
|
||
for line in func.__doc__.split("\n"): | ||
if "Parameters" in line: | ||
in_parameters = True | ||
space = line.find("Parameters") | ||
continue | ||
|
||
if in_parameters: | ||
if len(line) < space + 1: | ||
continue | ||
if len(line) > 1 and line[0] != " ": | ||
break | ||
|
||
if line[space] not in (" ", "-"): | ||
if read_key is not None: | ||
params_help[read_key] = arg_content | ||
|
||
read_key = line.split(":")[0].strip() | ||
arg_content = "" | ||
else: | ||
if arg_content == "": | ||
arg_content = line.strip() | ||
arg_content = arg_content[0].upper() + arg_content[1:] | ||
else: | ||
arg_content += " " + line.strip() | ||
|
||
if line.startswith("------"): | ||
break | ||
|
||
if read_key is not None: | ||
params_help[read_key] = arg_content | ||
|
||
return params_help |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import typing | ||
from typing_extensions import Annotated | ||
|
||
from enum import Enum | ||
|
||
import inspect | ||
from copy import copy | ||
import yaml | ||
|
||
import typer | ||
|
||
from ._cli_arguments import CLIArgument, CLIOption, get_params_help | ||
|
||
def get_dict_param_kwargs(dict_annotation_args): | ||
|
||
def yaml_dict(d: str): | ||
|
||
if isinstance(d, dict): | ||
return d | ||
|
||
return yaml.safe_load(d) | ||
|
||
argument_kwargs = {"parser": yaml_dict} | ||
|
||
if len(dict_annotation_args) == 2: | ||
try: | ||
argument_kwargs["metavar"] = f"YAML_DICT[{dict_annotation_args[0].__name__}: {dict_annotation_args[1].__name__}]" | ||
except: | ||
argument_kwargs["metavar"] = f"YAML_DICT[{dict_annotation_args[0]}: {dict_annotation_args[1]}]" | ||
|
||
return argument_kwargs | ||
|
||
# This dictionary keeps the kwargs that should be passed to typer arguments/options | ||
# for a given type. This is for example to be used for types that typer does not | ||
# have built in support for. | ||
_CUSTOM_TYPE_KWARGS = { | ||
dict: get_dict_param_kwargs, | ||
} | ||
|
||
def _get_custom_type_kwargs(type_): | ||
|
||
if hasattr(type_, "__metadata__"): | ||
type_ = type_.__origin__ | ||
|
||
if typing.get_origin(type_) is not None: | ||
args = typing.get_args(type_) | ||
type_ = typing.get_origin(type_) | ||
else: | ||
args = () | ||
|
||
try: | ||
argument_kwargs = _CUSTOM_TYPE_KWARGS.get(type_, {}) | ||
if callable(argument_kwargs): | ||
argument_kwargs = argument_kwargs(args) | ||
except: | ||
Check notice Code scanning / CodeQL Except block handles 'BaseException' Note
Except block directly handles BaseException.
|
||
argument_kwargs = {} | ||
|
||
return argument_kwargs | ||
|
||
|
||
def annotate_typer(func): | ||
"""Annotates a function for a typer app. | ||
|
||
It returns a new function, the original function is not modified. | ||
""" | ||
# Get the help message for all parameters found at the docstring | ||
params_help = get_params_help(func) | ||
|
||
# Get the original signature of the function | ||
sig = inspect.signature(func) | ||
|
||
# Loop over parameters in the signature, modifying them to include the | ||
# typer info. | ||
new_parameters = [] | ||
for param in sig.parameters.values(): | ||
|
||
argument_kwargs = _get_custom_type_kwargs(param.annotation) | ||
|
||
default = param.default | ||
if isinstance(param.default, Enum): | ||
default = default.value | ||
|
||
typer_arg_cls = typer.Argument if param.default == inspect.Parameter.empty else typer.Option | ||
if hasattr(param.annotation, "__metadata__"): | ||
for meta in param.annotation.__metadata__: | ||
if isinstance(meta, CLIArgument): | ||
typer_arg_cls = typer.Argument | ||
argument_kwargs.update(meta.kwargs) | ||
elif isinstance(meta, CLIOption): | ||
typer_arg_cls = typer.Option | ||
argument_kwargs.update(meta.kwargs) | ||
|
||
if "param_decls" in argument_kwargs: | ||
argument_args = argument_kwargs.pop("param_decls") | ||
else: | ||
argument_args = [] | ||
|
||
new_parameters.append( | ||
param.replace( | ||
default=default, | ||
annotation=Annotated[param.annotation, typer_arg_cls(*argument_args, help=params_help.get(param.name), **argument_kwargs)] | ||
) | ||
) | ||
|
||
# Create a copy of the function and update it with the modified signature. | ||
# Also remove parameters documentation from the docstring. | ||
annotated_func = copy(func) | ||
|
||
annotated_func.__signature__ = sig.replace(parameters=new_parameters) | ||
annotated_func.__doc__ = func.__doc__[:func.__doc__.find("Parameters\n")] | ||
|
||
return annotated_func |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check notice
Code scanning / CodeQL
Except block handles 'BaseException' Note