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

1from datetime import datetime, timedelta 

2from typing import List 

3from zoneinfo import ZoneInfo 

4 

5import click 

6 

7from compiler_admin.services.toggl import TogglTime 

8 

9TZINFO = ZoneInfo("America/Los_Angeles") 

10 

11 

12def local_now(): 

13 return datetime.now(tz=TZINFO) 

14 

15 

16def prior_month_end(): 

17 now = local_now() 

18 first = now.replace(day=1) 

19 return first - timedelta(days=1) 

20 

21 

22def prior_month_start(): 

23 end = prior_month_end() 

24 return end.replace(day=1) 

25 

26 

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() 

102 

103 if not output: 

104 output = f"Toggl_time_entries_{start.strftime('%Y-%m-%d')}_{end.strftime('%Y-%m-%d')}.csv" 

105 

106 params = dict(start_date=start, end_date=end, output_path=output, output_cols=time.TOGGL_COLUMNS) 

107 

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)) 

120 

121 click.echo("Downloading Toggl time entries with parameters:") 

122 for k, v in params.items(): 

123 click.echo(f" {k}: {v}") 

124 

125 time.download(**params) 

126 

127 click.echo() 

128 click.echo(f"Download complete: ./{output}")