Coverage for compiler_admin / commands / time / download.py: 100%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 05:48 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 05:48 +0000
1from datetime import datetime, timedelta
2from typing import List
3from zoneinfo import ZoneInfo
5import click
7from compiler_admin.services.toggl import TogglTime
9TZINFO = ZoneInfo("America/Los_Angeles")
12def local_now():
13 return datetime.now(tz=TZINFO)
16def prior_month_end():
17 now = local_now()
18 first = now.replace(day=1)
19 return first - timedelta(days=1)
22def prior_month_start():
23 end = prior_month_end()
24 return end.replace(day=1)
27@click.command()
28@click.option(
29 "--start",
30 metavar="YYYY-MM-DD",
31 default=prior_month_start().strftime("%Y-%m-%d"),
32 callback=lambda ctx, param, val: datetime.strptime(val, "%Y-%m-%d").replace(tzinfo=TZINFO),
33 help="The start date of the reporting period. Defaults to the beginning of the prior month.",
34)
35@click.option(
36 "--end",
37 metavar="YYYY-MM-DD",
38 default=prior_month_end().strftime("%Y-%m-%d"),
39 callback=lambda ctx, param, val: datetime.strptime(val, "%Y-%m-%d").replace(tzinfo=TZINFO),
40 help="The end date of the reporting period. Defaults to the end of the prior month.",
41)
42@click.option(
43 "--output",
44 help="The path to the file where downloaded data should be written. Defaults to a path calculated from the date range.",
45)
46@click.option(
47 "--all",
48 "include_all",
49 is_flag=True,
50 default=False,
51 help="Download all time entries. The default is to download only billable time entries.",
52)
53@click.option(
54 "-c",
55 "--client",
56 "client_ids",
57 envvar="TOGGL_CLIENT_ID",
58 help="An ID for a Toggl Client to filter for in reports. Can be supplied more than once.",
59 metavar="CLIENT_ID",
60 multiple=True,
61 type=int,
62)
63@click.option(
64 "-p",
65 "--project",
66 "project_ids",
67 help="An ID for a Toggl Project to filter for in reports. Can be supplied more than once.",
68 metavar="PROJECT_ID",
69 multiple=True,
70 type=int,
71)
72@click.option(
73 "-t",
74 "--task",
75 "task_ids",
76 help="An ID for a Toggl Project Task to filter for in reports. Can be supplied more than once.",
77 metavar="TASK_ID",
78 multiple=True,
79 type=int,
80)
81@click.option(
82 "-u",
83 "--user",
84 "user_ids",
85 help="An ID for a Toggl User to filter for in reports. Can be supplied more than once.",
86 metavar="USER_ID",
87 multiple=True,
88 type=int,
89)
90def download(
91 start: datetime,
92 end: datetime,
93 output: str = "",
94 include_all: bool = False,
95 client_ids: List[int] = [],
96 project_ids: List[int] = [],
97 task_ids: List[int] = [],
98 user_ids: List[int] = [],
99):
100 """Download a Toggl time report in CSV format."""
101 time = TogglTime()
103 if not output:
104 output = f"Toggl_time_entries_{start.strftime('%Y-%m-%d')}_{end.strftime('%Y-%m-%d')}.csv"
106 params = dict(start_date=start, end_date=end, output_path=output, output_cols=time.TOGGL_COLUMNS)
108 # By default include billable=True in params.
109 # If --all was passed, do not add billable so callers can treat absence as "all".
110 if not include_all:
111 params.update(dict(billable=True))
112 if client_ids:
113 params.update(dict(client_ids=client_ids))
114 if project_ids:
115 params.update(dict(project_ids=project_ids))
116 if task_ids:
117 params.update(dict(task_ids=task_ids))
118 if user_ids:
119 params.update(dict(user_ids=user_ids))
121 click.echo("Downloading Toggl time entries with parameters:")
122 for k, v in params.items():
123 click.echo(f" {k}: {v}")
125 time.download(**params)
127 click.echo()
128 click.echo(f"Download complete: ./{output}")