Skip to content

Commit 414bc5c

Browse files
authored
Merge pull request #63 from python-ellar/drop_typer
Drop Typer for Click
2 parents 5b94c98 + 3db43ab commit 414bc5c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1425
-627
lines changed

.github/workflows/publish.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ jobs:
1818
run: pip install flit
1919
- name: Install Dependencies
2020
run: flit install --symlink
21+
- name: Install build dependencies
22+
run: pip install build
23+
- name: Build distribution
24+
run: python -m build
2125
- name: Publish
22-
env:
23-
FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }}
24-
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
25-
run: flit publish
26+
uses: pypa/[email protected]
27+
with:
28+
password: ${{ secrets.PYPI_API_TOKEN }}

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/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Ellar CLI Tool for Scaffolding Ellar Projects, Modules and also running Ellar Commands"""
22

3-
__version__ = "0.3.0"
3+
__version__ = "0.3.2"

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: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
11
import os
22
import sys
3-
import typing as t
43

5-
from typer import echo
4+
import ellar_cli.click as click
65

7-
from .main import _typer, build_typers
6+
from .main import app_cli
87

98
__all__ = ["main"]
109

1110

12-
def register_commands(*typer_commands: t.Any) -> None:
13-
for typer_command in typer_commands:
14-
_typer.add_typer(typer_command)
15-
16-
17-
@_typer.command()
11+
@app_cli.command()
12+
@click.argument("name")
1813
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)
14+
click.echo(f"Welcome {name}, to Ellar CLI, ASGI Python Web framework\n")
2415

2516

2617
def main():
2718
sys.path.append(os.getcwd())
28-
build_typers()
29-
_typer(prog_name="Ellar, Python Web framework")
19+
app_cli()
3020

3121

32-
if __name__ == "__main__":
22+
if __name__ == "__main__": # pragma: no cover
3323
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)