Skip to content

Commit 9a159aa

Browse files
committed
Typer Dropped: Dropped typer for full click adoption and customization
1 parent fcc9181 commit 9a159aa

28 files changed

Lines changed: 1086 additions & 486 deletions

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ install-full: ## Install dependencies
1717
make install
1818
pre-commit install -f
1919

20-
lint: ## Run code linters
21-
black --check ellar_cli tests
20+
lint:fmt ## Run code linters
2221
ruff check ellar_cli tests
2322
mypy ellar_cli
2423

ellar_cli/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if __name__ == "__main__":
1+
if __name__ == "__main__": # pragma: no cover
22
from ellar_cli.cli import main
33

44
main()

ellar_cli/cli.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
11
import os
22
import sys
3-
import typing as t
43

54
from typer import echo
65

7-
from .main import _typer, build_typers
8-
9-
__all__ = ["main"]
6+
import ellar_cli.click as click
107

8+
from .main import app_cli
119

12-
def register_commands(*typer_commands: t.Any) -> None:
13-
for typer_command in typer_commands:
14-
_typer.add_typer(typer_command)
10+
__all__ = ["main"]
1511

1612

17-
@_typer.command()
13+
@app_cli.command()
14+
@click.argument("name")
1815
def say_hi(name: str):
19-
echo(f"Welcome {name}, to Ellar CLI, python web framework")
20-
21-
22-
# register all EllarTyper(s) to root typer
23-
# register_commands(*other_commands)
16+
echo(f"Welcome {name}, to Ellar CLI, ASGI Python Web framework\n")
2417

2518

2619
def main():
2720
sys.path.append(os.getcwd())
28-
build_typers()
29-
_typer(prog_name="Ellar, Python Web framework")
21+
app_cli()
3022

3123

32-
if __name__ == "__main__":
24+
if __name__ == "__main__": # pragma: no cover
3325
main()

ellar_cli/click/__init__.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import typing as t
2+
3+
import click
4+
from click.core import Context as Context
5+
from click.core import Group as Group
6+
from click.core import Option as Option
7+
from click.core import Parameter as Parameter
8+
from click.decorators import confirmation_option as confirmation_option
9+
from click.decorators import help_option as help_option
10+
from click.decorators import make_pass_decorator as make_pass_decorator
11+
from click.decorators import pass_context as pass_context
12+
from click.decorators import pass_obj as pass_obj
13+
from click.decorators import password_option as password_option
14+
from click.decorators import version_option as version_option
15+
from click.exceptions import Abort as Abort
16+
from click.exceptions import BadArgumentUsage as BadArgumentUsage
17+
from click.exceptions import BadOptionUsage as BadOptionUsage
18+
from click.exceptions import BadParameter as BadParameter
19+
from click.exceptions import ClickException as ClickException
20+
from click.exceptions import Exit as Exit
21+
from click.exceptions import FileError as FileError
22+
from click.exceptions import MissingParameter as MissingParameter
23+
from click.exceptions import NoSuchOption as NoSuchOption
24+
from click.exceptions import UsageError as UsageError
25+
from click.types import BOOL as BOOL
26+
from click.types import FLOAT as FLOAT
27+
from click.types import INT as INT
28+
from click.types import STRING as STRING
29+
from click.types import UNPROCESSED as UNPROCESSED
30+
from click.types import UUID as UUID
31+
from click.types import Choice as Choice
32+
from click.types import DateTime as DateTime
33+
from click.types import File as File
34+
from click.types import FloatRange as FloatRange
35+
from click.types import IntRange as IntRange
36+
from click.types import ParamType as ParamType
37+
from click.types import Path as Path
38+
from click.types import Tuple as Tuple
39+
from click.utils import echo as echo
40+
from click.utils import format_filename as format_filename
41+
from click.utils import get_app_dir as get_app_dir
42+
from click.utils import get_binary_stream as get_binary_stream
43+
from click.utils import get_text_stream as get_text_stream
44+
from click.utils import open_file as open_file
45+
46+
from .argument import Argument
47+
from .command import Command
48+
from .group import AppContextGroup, EllarCommandGroup
49+
from .util import with_app_context
50+
51+
52+
def argument(
53+
*param_decls: str,
54+
cls: t.Optional[t.Type[click.Argument]] = None,
55+
required: bool = True,
56+
help: t.Optional[str] = None,
57+
hidden: t.Optional[bool] = None,
58+
**attrs: t.Any,
59+
) -> t.Callable:
60+
return click.argument(
61+
*param_decls,
62+
cls=cls or Argument,
63+
required=required,
64+
hidden=hidden,
65+
help=help,
66+
**attrs,
67+
)
68+
69+
70+
def option(
71+
*param_decls: str, cls: t.Optional[t.Type[Option]] = None, **attrs: t.Any
72+
) -> t.Callable:
73+
return click.option(*param_decls, cls=cls, **attrs)
74+
75+
76+
def command(
77+
name: t.Optional[str] = None, cls: t.Optional[click.Command] = None, **attrs: t.Any
78+
) -> t.Union[click.Command, t.Any]:
79+
return click.command(name=name, cls=cls or Command, **attrs) # type:ignore[arg-type]
80+
81+
82+
def group(
83+
name: t.Union[str, t.Callable, None] = None,
84+
cls: t.Optional[t.Type[click.Group]] = None,
85+
**attrs: t.Any,
86+
) -> t.Callable:
87+
return click.group(name=name, cls=cls or AppContextGroup, **attrs) # type:ignore[arg-type]
88+
89+
90+
__all__ = [
91+
"argument",
92+
"Argument",
93+
"Option",
94+
"option",
95+
"AppContextGroup",
96+
"EllarCommandGroup",
97+
"with_app_context",
98+
"Context",
99+
"Group",
100+
"Parameter",
101+
"make_pass_decorator",
102+
"confirmation_option",
103+
"help_option",
104+
"group",
105+
"pass_context",
106+
"password_option",
107+
"version_option",
108+
"Abort",
109+
"BadArgumentUsage",
110+
"BadOptionUsage",
111+
"BadParameter",
112+
"ClickException",
113+
"FileError",
114+
"MissingParameter",
115+
"NoSuchOption",
116+
"UsageError",
117+
"pass_obj",
118+
"BOOL",
119+
"Choice",
120+
"DateTime",
121+
"File",
122+
"FLOAT",
123+
"FloatRange",
124+
"INT",
125+
"IntRange",
126+
"ParamType",
127+
"Path",
128+
"STRING",
129+
"Tuple",
130+
"UNPROCESSED",
131+
"UUID",
132+
"echo",
133+
"format_filename",
134+
"get_app_dir",
135+
"get_binary_stream",
136+
"get_text_stream",
137+
"open_file",
138+
"Exit",
139+
]
140+
141+
142+
def __dir__() -> t.List[str]:
143+
return sorted(__all__) # pragma: no cover

ellar_cli/click/argument.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import inspect as _inspect
2+
import typing as t
3+
4+
import click
5+
from click.formatting import join_options as _join_options
6+
7+
8+
class Argument(click.Argument):
9+
"""
10+
Regular click.Argument with extra formatting for printing command arguments
11+
"""
12+
13+
def __init__(
14+
self,
15+
param_decls: t.Sequence[str],
16+
required: t.Optional[bool] = None,
17+
help: t.Optional[str] = None,
18+
hidden: t.Optional[bool] = None,
19+
**attrs: t.Any,
20+
) -> None:
21+
super().__init__(param_decls, required=required, **attrs)
22+
self.help = help
23+
self.hidden = hidden
24+
25+
# overridden to customize the automatic formatting of metavars
26+
def make_metavar(self) -> str:
27+
if self.metavar is not None:
28+
return self.metavar
29+
var = "" if self.required else "["
30+
var += "<" + self.name + ">" # type:ignore[operator]
31+
if self.nargs != 1:
32+
var += ", ..."
33+
if not self.required:
34+
var += "]"
35+
return var
36+
37+
# this code is 90% copied from click.Option.get_help_record
38+
def get_help_record(self, ctx: click.Context) -> t.Optional[t.Tuple[str, str]]:
39+
any_prefix_is_slash: t.List[bool] = []
40+
41+
if self.hidden:
42+
# TODO: test
43+
return None
44+
45+
def _write_opts(opts: t.Any) -> str:
46+
rv, any_slashes = _join_options(opts)
47+
if any_slashes:
48+
# TODO: test
49+
any_prefix_is_slash[:] = [True]
50+
rv += ": " + self.make_metavar()
51+
return rv
52+
53+
rv = [_write_opts(self.opts)]
54+
if self.secondary_opts:
55+
# TODO: test
56+
rv.append(_write_opts(self.secondary_opts))
57+
58+
help_ = self.help or ""
59+
extra = []
60+
61+
if self.default is not None:
62+
if isinstance(self.default, (list, tuple)):
63+
# TODO: test
64+
default_string = ", ".join("%s" % d for d in self.default)
65+
elif _inspect.isfunction(self.default):
66+
# TODO: test
67+
default_string = "(dynamic)"
68+
else:
69+
default_string = self.default # type:ignore[assignment]
70+
extra.append("default: {}".format(default_string))
71+
72+
if self.required:
73+
extra.append("required")
74+
if extra:
75+
help_ = "%s[%s]" % (help_ and help_ + " " or "", "; ".join(extra))
76+
77+
return (any_prefix_is_slash and "; " or " / ").join(rv), help_

ellar_cli/click/command.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import typing as t
2+
3+
import click
4+
5+
6+
class Command(click.Command):
7+
"""
8+
The same as click.Command with extra behavior to print command args and options
9+
"""
10+
11+
# overridden to support displaying args before the options metavar
12+
def collect_usage_pieces(self, ctx: click.Context) -> t.List[str]:
13+
rv: t.List[str] = []
14+
for param in self.get_params(ctx):
15+
rv.extend(param.get_usage_pieces(ctx))
16+
17+
rv.append(self.options_metavar) # type:ignore[arg-type]
18+
return rv
19+
20+
# overridden to group arguments separately from options
21+
def format_options(
22+
self, ctx: click.Context, formatter: click.HelpFormatter
23+
) -> None:
24+
args, opts = [], []
25+
for param in self.get_params(ctx):
26+
rv = param.get_help_record(ctx)
27+
if rv is not None:
28+
if isinstance(param, click.Argument):
29+
args.append(rv)
30+
else:
31+
opts.append(rv)
32+
33+
def print_args() -> None:
34+
if args:
35+
with formatter.section("Arguments".upper()):
36+
formatter.write_dl(args)
37+
38+
def print_opts() -> None:
39+
if opts:
40+
with formatter.section(self.options_metavar): # type:ignore[arg-type]
41+
formatter.write_dl(opts)
42+
43+
print_args()
44+
print_opts()

0 commit comments

Comments
 (0)