Skip to content

Commit 06420c5

Browse files
committed
refactor(cli): describe as @click.group of commands
1 parent a42a9fb commit 06420c5

File tree

3 files changed

+15
-906
lines changed

3 files changed

+15
-906
lines changed

compiler_admin/main.py

+11-218
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,24 @@
1-
from argparse import ArgumentParser, _SubParsersAction
2-
from datetime import datetime, timedelta
3-
import os
4-
import sys
1+
import click
52

6-
from pytz import timezone
3+
from compiler_admin import __version__
74

8-
from compiler_admin import __version__ as version
95
from compiler_admin.commands.info import info
106
from compiler_admin.commands.init import init
117
from compiler_admin.commands.time import time
12-
from compiler_admin.commands.time.convert import CONVERTERS
138
from compiler_admin.commands.user import user
14-
from compiler_admin.commands.user.convert import ACCOUNT_TYPE_OU
159

1610

17-
TZINFO = timezone(os.environ.get("TZ_NAME", "America/Los_Angeles"))
11+
@click.group
12+
@click.version_option(__version__, prog_name="compiler-admin")
13+
def main():
14+
"""Compiler's command line interface."""
15+
pass
1816

1917

20-
def local_now():
21-
return datetime.now(tz=TZINFO)
22-
23-
24-
def prior_month_end():
25-
now = local_now()
26-
first = now.replace(day=1)
27-
return first - timedelta(days=1)
28-
29-
30-
def prior_month_start():
31-
end = prior_month_end()
32-
return end.replace(day=1)
33-
34-
35-
def add_sub_cmd_parser(parser: ArgumentParser, dest="subcommand", help=None):
36-
"""Helper adds a subparser for the given dest."""
37-
return parser.add_subparsers(dest=dest, help=help)
38-
39-
40-
def add_sub_cmd(cmd: _SubParsersAction, subcmd, help) -> ArgumentParser:
41-
"""Helper creates a new subcommand parser."""
42-
return cmd.add_parser(subcmd, help=help)
43-
44-
45-
def add_sub_cmd_with_username_arg(cmd: _SubParsersAction, subcmd, help) -> ArgumentParser:
46-
"""Helper creates a new subcommand parser with a required username arg."""
47-
sub_cmd = add_sub_cmd(cmd, subcmd, help=help)
48-
sub_cmd.add_argument("username", help="A Compiler user account name, sans domain.")
49-
return sub_cmd
50-
51-
52-
def setup_info_command(cmd_parsers: _SubParsersAction):
53-
info_cmd = add_sub_cmd(cmd_parsers, "info", help="Print configuration and debugging information.")
54-
info_cmd.set_defaults(func=info)
55-
56-
57-
def setup_init_command(cmd_parsers: _SubParsersAction):
58-
init_cmd = add_sub_cmd_with_username_arg(
59-
cmd_parsers, "init", help="Initialize a new admin project. This command should be run once before any others."
60-
)
61-
init_cmd.add_argument("--gam", action="store_true", help="If provided, initialize a new GAM project.")
62-
init_cmd.add_argument("--gyb", action="store_true", help="If provided, initialize a new GYB project.")
63-
init_cmd.set_defaults(func=init)
64-
65-
66-
def setup_time_command(cmd_parsers: _SubParsersAction):
67-
time_cmd = add_sub_cmd(cmd_parsers, "time", help="Work with Compiler time entries.")
68-
time_cmd.set_defaults(func=time)
69-
time_subcmds = add_sub_cmd_parser(time_cmd, help="The time command to run.")
70-
71-
time_convert = add_sub_cmd(time_subcmds, "convert", help="Convert a time report from one format into another.")
72-
time_convert.add_argument(
73-
"--input",
74-
default=os.environ.get("TOGGL_DATA", sys.stdin),
75-
help="The path to the source data for conversion. Defaults to $TOGGL_DATA or stdin.",
76-
)
77-
time_convert.add_argument(
78-
"--output",
79-
default=os.environ.get("HARVEST_DATA", sys.stdout),
80-
help="The path to the file where converted data should be written. Defaults to $HARVEST_DATA or stdout.",
81-
)
82-
time_convert.add_argument(
83-
"--from",
84-
default="toggl",
85-
choices=sorted(CONVERTERS.keys()),
86-
dest="from_fmt",
87-
help="The format of the source data. Defaults to 'toggl'.",
88-
)
89-
time_convert.add_argument(
90-
"--to",
91-
default="harvest",
92-
choices=sorted([to_fmt for sub in CONVERTERS.values() for to_fmt in sub.keys()]),
93-
dest="to_fmt",
94-
help="The format of the converted data. Defaults to 'harvest'.",
95-
)
96-
time_convert.add_argument("--client", default=None, help="The name of the client to use in converted data.")
97-
98-
time_download = add_sub_cmd(time_subcmds, "download", help="Download a Toggl report in CSV format.")
99-
time_download.add_argument(
100-
"--start",
101-
metavar="YYYY-MM-DD",
102-
default=prior_month_start(),
103-
type=lambda s: TZINFO.localize(datetime.strptime(s, "%Y-%m-%d")),
104-
help="The start date of the reporting period. Defaults to the beginning of the prior month.",
105-
)
106-
time_download.add_argument(
107-
"--end",
108-
metavar="YYYY-MM-DD",
109-
default=prior_month_end(),
110-
type=lambda s: TZINFO.localize(datetime.strptime(s, "%Y-%m-%d")),
111-
help="The end date of the reporting period. Defaults to the end of the prior month.",
112-
)
113-
time_download.add_argument(
114-
"--output",
115-
default=os.environ.get("TOGGL_DATA", sys.stdout),
116-
help="The path to the file where downloaded data should be written. Defaults to $TOGGL_DATA or stdout.",
117-
)
118-
time_download.add_argument(
119-
"--all",
120-
default=True,
121-
action="store_false",
122-
dest="billable",
123-
help="Download all time entries. The default is to download only billable time entries.",
124-
)
125-
time_download.add_argument(
126-
"--client",
127-
dest="client_ids",
128-
metavar="CLIENT_ID",
129-
action="append",
130-
type=int,
131-
help="An ID for a Toggl Client to filter for in reports. Can be supplied more than once.",
132-
)
133-
time_download.add_argument(
134-
"--project",
135-
dest="project_ids",
136-
metavar="PROJECT_ID",
137-
action="append",
138-
type=int,
139-
help="An ID for a Toggl Project to filter for in reports. Can be supplied more than once.",
140-
)
141-
time_download.add_argument(
142-
"--task",
143-
dest="task_ids",
144-
metavar="TASK_ID",
145-
action="append",
146-
type=int,
147-
help="An ID for a Toggl Project Task to filter for in reports. Can be supplied more than once.",
148-
)
149-
time_download.add_argument(
150-
"--user",
151-
dest="user_ids",
152-
metavar="USER_ID",
153-
action="append",
154-
type=int,
155-
help="An ID for a Toggl User to filter for in reports. Can be supplied more than once.",
156-
)
157-
158-
159-
def setup_user_command(cmd_parsers: _SubParsersAction):
160-
user_cmd = add_sub_cmd(cmd_parsers, "user", help="Work with users in the Compiler org.")
161-
user_cmd.set_defaults(func=user)
162-
user_subcmds = add_sub_cmd_parser(user_cmd, help="The user command to run.")
163-
164-
user_alumni = add_sub_cmd_with_username_arg(user_subcmds, "alumni", help="Convert a user account to a Compiler alumni.")
165-
user_alumni.add_argument("--notify", help="An email address to send the alumni's new password.")
166-
user_alumni.add_argument(
167-
"--force", action="store_true", default=False, help="Don't ask for confirmation before conversion."
168-
)
169-
170-
user_create = add_sub_cmd_with_username_arg(user_subcmds, "create", help="Create a new user in the Compiler domain.")
171-
user_create.add_argument("--notify", help="An email address to send the newly created account info.")
172-
173-
user_convert = add_sub_cmd_with_username_arg(user_subcmds, "convert", help="Convert a user account to a new type.")
174-
user_convert.add_argument("account_type", choices=ACCOUNT_TYPE_OU.keys(), help="Target account type for this conversion.")
175-
user_convert.add_argument(
176-
"--force", action="store_true", default=False, help="Don't ask for confirmation before conversion."
177-
)
178-
user_convert.add_argument("--notify", help="An email address to send the alumni's new password.")
179-
180-
user_delete = add_sub_cmd_with_username_arg(user_subcmds, "delete", help="Delete a user account.")
181-
user_delete.add_argument("--force", action="store_true", default=False, help="Don't ask for confirmation before deletion.")
182-
183-
user_offboard = add_sub_cmd_with_username_arg(user_subcmds, "offboard", help="Offboard a user account.")
184-
user_offboard.add_argument("--alias", help="Account to assign username as an alias.")
185-
user_offboard.add_argument(
186-
"--force", action="store_true", default=False, help="Don't ask for confirmation before offboarding."
187-
)
188-
189-
user_reset = add_sub_cmd_with_username_arg(
190-
user_subcmds, "reset", help="Reset a user's password to a randomly generated string."
191-
)
192-
user_reset.add_argument("--force", action="store_true", default=False, help="Don't ask for confirmation before reset.")
193-
user_reset.add_argument("--notify", help="An email address to send the newly generated password.")
194-
195-
add_sub_cmd_with_username_arg(user_subcmds, "restore", help="Restore an email backup from a prior offboarding.")
196-
197-
user_signout = add_sub_cmd_with_username_arg(user_subcmds, "signout", help="Signs a user out from all active sessions.")
198-
user_signout.add_argument("--force", action="store_true", default=False, help="Don't ask for confirmation before signout.")
199-
200-
201-
def main(argv=None):
202-
argv = argv if argv is not None else sys.argv[1:]
203-
parser = ArgumentParser(prog="compiler-admin")
204-
205-
# https://stackoverflow.com/a/8521644/812183
206-
parser.add_argument(
207-
"-v",
208-
"--version",
209-
action="version",
210-
version=f"%(prog)s {version}",
211-
)
212-
213-
cmd_parsers = add_sub_cmd_parser(parser, dest="command", help="The command to run")
214-
setup_info_command(cmd_parsers)
215-
setup_init_command(cmd_parsers)
216-
setup_time_command(cmd_parsers)
217-
setup_user_command(cmd_parsers)
218-
219-
if len(argv) == 0:
220-
argv = ["info"]
221-
222-
args, extra = parser.parse_known_args(argv)
223-
224-
if args.func:
225-
return args.func(args, *extra)
226-
else:
227-
raise ValueError("Unrecognized command")
228-
18+
main.add_command(init)
19+
main.add_command(info)
20+
main.add_command(time)
21+
main.add_command(user)
22922

23023
if __name__ == "__main__":
23124
raise SystemExit(main())

tests/conftest.py

-48
Original file line numberDiff line numberDiff line change
@@ -58,72 +58,24 @@ def mock_commands_alumni(mock_command):
5858
return mock_command("alumni")
5959

6060

61-
@pytest.fixture
62-
def mock_commands_create(mock_command):
63-
"""Fixture returns a function that patches the create function in a given module."""
64-
return mock_command("create")
65-
66-
67-
@pytest.fixture
68-
def mock_commands_convert(mock_command):
69-
"""Fixture returns a function that patches the convert command function in a given module."""
70-
return mock_command("convert")
71-
72-
7361
@pytest.fixture
7462
def mock_commands_delete(mock_command):
7563
"""Fixture returns a function that patches the delete command function in a given module."""
7664
return mock_command("delete")
7765

7866

79-
@pytest.fixture
80-
def mock_commands_info(mock_command):
81-
"""Fixture returns a function that patches the info command function in a given module."""
82-
return mock_command("info")
83-
84-
85-
@pytest.fixture
86-
def mock_commands_init(mock_command):
87-
"""Fixture returns a function that patches the init command function in a given module."""
88-
return mock_command("init")
89-
90-
91-
@pytest.fixture
92-
def mock_commands_offboard(mock_command):
93-
"""Fixture returns a function that patches the offboard command function in a given module."""
94-
return mock_command("offboard")
95-
96-
9767
@pytest.fixture
9868
def mock_commands_reset(mock_command):
9969
"""Fixture returns a function that patches the reset command function in a given module."""
10070
return mock_command("reset")
10171

10272

103-
@pytest.fixture
104-
def mock_commands_restore(mock_command):
105-
"""Fixture returns a function that patches the restore command function in a given module."""
106-
return mock_command("restore")
107-
108-
10973
@pytest.fixture
11074
def mock_commands_signout(mock_command):
11175
"""Fixture returns a function that patches the signout command function in a given module."""
11276
return mock_command("signout")
11377

11478

115-
@pytest.fixture
116-
def mock_commands_time(mock_command):
117-
"""Fixture returns a function that patches the time command function in a given module."""
118-
return mock_command("time")
119-
120-
121-
@pytest.fixture
122-
def mock_commands_user(mock_command):
123-
"""Fixture returns a function that patches the user command function in a given module."""
124-
return mock_command("user")
125-
126-
12779
@pytest.fixture
12880
def mock_google_CallGAMCommand(mock_module_name):
12981
"""Fixture returns a function that patches the CallGAMCommand function from a given module."""

0 commit comments

Comments
 (0)