Skip to content

Commit 2f6969c

Browse files
Niloth-ptimabbott
authored andcommitted
google-calendar: Support loading options from the zuliprc.
By loading from the new "google-calendar" section of the zuliprc. Moved the default values out of the argparse arguments, and into a dataclass, to implement the below hierarchy of loading options: - initialize options to default values - overwrite with the options present in the config file - overwrite with the options passed-in via the command line
1 parent 3869f0e commit 2f6969c

File tree

1 file changed

+80
-16
lines changed

1 file changed

+80
-16
lines changed

zulip/integrations/google/google-calendar

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import os
77
import runpy
88
import sys
99
import time
10+
from configparser import ConfigParser
11+
from dataclasses import dataclass
1012
from typing import List, Optional, Set, Tuple
1113

1214
import dateutil.parser
@@ -29,6 +31,18 @@ TOKENS_PATH = os.path.join(HOME_DIR, TOKENS_FILE)
2931
CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105
3032
CLIENT_SECRET_PATH = os.path.join(HOME_DIR, CLIENT_SECRET_FILE)
3133

34+
35+
@dataclass
36+
class GoogleCalendarOptions:
37+
calendar_id: str = "primary"
38+
interval: int = 30
39+
channel: Optional[str] = None
40+
topic: str = "calendar-reminders"
41+
noauth_local_webserver: bool = False
42+
tokens_path: str = TOKENS_PATH
43+
client_secret_path: str = CLIENT_SECRET_PATH
44+
45+
3246
# Our cached view of the calendar, updated periodically.
3347
events: List[Tuple[int, datetime.datetime, str]] = []
3448

@@ -54,14 +68,12 @@ usage = r"""google-calendar [--config-file PATH_TO_ZULIPRC_OF_BOT]
5468
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage=usage), allow_provisioning=True)
5569
parser.add_argument(
5670
"--interval",
57-
default=30,
5871
type=int,
5972
help="Minutes before event for reminder [default: 30]",
6073
)
6174
parser.add_argument(
6275
"--calendar",
6376
dest="calendar_id",
64-
default="primary",
6577
help="The ID of the calendar you want to receive reminders from. By default, the primary calendar is used.",
6678
)
6779
parser.add_argument(
@@ -71,30 +83,77 @@ parser.add_argument(
7183
parser.add_argument(
7284
"--topic",
7385
help="The topic to which to send the reminders to. Ignored if --channel is unspecified. 'calendar-reminders' is used as the default topic name.",
74-
default="calendar-reminders",
7586
)
7687
parser.add_argument(
7788
"--client-secret-file",
7889
help="The path to the file containing the client secret for the Google Calendar API. By default, the client secret file is assumed to be at {CLIENT_SECRET_PATH}.",
79-
default=CLIENT_SECRET_PATH,
8090
dest="client_secret_path",
8191
)
8292
parser.add_argument(
8393
"--tokens-file",
8494
help=f"The path to the file containing the tokens for the Google Calendar API. By default, the tokens file is generated at {TOKENS_PATH} after the first run.",
85-
default=TOKENS_PATH,
8695
dest="tokens_path",
8796
)
8897
parser.add_argument(
8998
"-n",
9099
"--noauth_local_webserver",
91100
action="store_true",
92-
default=False,
93101
help="The default authorization process runs a local web server, which requires a browser on the same machine. For non-interactive environments and machines without browser access, e.g., remote servers, this option allows manual authorization. The authorization URL is printed, which the user can copy into a browser, copy the resulting authorization code, and paste back into the command line.",
94102
)
95-
options = parser.parse_args()
103+
commandline_options = parser.parse_args()
104+
if commandline_options.verbose:
105+
logging.getLogger().setLevel(logging.INFO)
96106

97-
zulip_client = zulip.init_from_options(options)
107+
108+
valid_keys = list(GoogleCalendarOptions.__dataclass_fields__.keys())
109+
110+
111+
def load_config_options(config_path: Optional[str]) -> GoogleCalendarOptions:
112+
if config_path is None:
113+
config_path = zulip.get_default_config_filename()
114+
assert config_path is not None
115+
if not os.path.exists(config_path):
116+
logging.info("No config file found at %s", config_path)
117+
return GoogleCalendarOptions()
118+
119+
logging.info("Loading Google Calendar configuration from %s", config_path)
120+
config = ConfigParser()
121+
try:
122+
config.read(config_path)
123+
except Exception:
124+
logging.exception("Error reading config file %s", config_path)
125+
126+
section = "google-calendar"
127+
config_values = {}
128+
if section in config:
129+
for key, value in config[section].items():
130+
if key in valid_keys:
131+
expected_type = GoogleCalendarOptions.__annotations__[key]
132+
config_values[key] = True if expected_type == bool else expected_type(value)
133+
logging.info("Setting key: %s to %s", key, config_values[key])
134+
else:
135+
logging.warning(
136+
"Unknown key %s in section %s of config file %s", key, section, config_path
137+
)
138+
return GoogleCalendarOptions(**config_values)
139+
140+
141+
def update_calendar_options_from_commandline_args(
142+
calendar_options: GoogleCalendarOptions, commandline_options: argparse.Namespace
143+
) -> None:
144+
for key, value in commandline_options.__dict__.items():
145+
# Boolean arguments (store-true) have a default value of False when not passed in.
146+
# So, we ignore them, to prevent overwriting the config file option that is set.
147+
if key in valid_keys and value is not None and value is not False:
148+
setattr(calendar_options, key, value)
149+
150+
151+
# Calendar options can be passed in from the command line or via zuliprc.
152+
# The command line options override the zuliprc options.
153+
calendar_options = load_config_options(commandline_options.zulip_config_file)
154+
update_calendar_options_from_commandline_args(calendar_options, commandline_options)
155+
156+
zulip_client = zulip.init_from_options(commandline_options)
98157

99158
# Import dependencies only after parsing command-line args,
100159
# as the --provision flag can be used to install the dependencies.
@@ -108,9 +167,6 @@ except ImportError:
108167
)
109168
sys.exit(1)
110169

111-
if options.verbose:
112-
logging.getLogger().setLevel(logging.INFO)
113-
114170

115171
def get_credentials() -> Credentials:
116172
"""Fetches credentials using the get-google-credentials script.
@@ -121,7 +177,10 @@ def get_credentials() -> Credentials:
121177
try:
122178
fetch_creds = runpy.run_path("./get-google-credentials")["get_credentials"]
123179
return fetch_creds(
124-
options.tokens_path, options.client_secret_path, SCOPES, options.noauth_local_webserver
180+
calendar_options.tokens_path,
181+
calendar_options.client_secret_path,
182+
SCOPES,
183+
calendar_options.noauth_local_webserver,
125184
)
126185
except Exception:
127186
logging.exception("Error getting google credentials")
@@ -136,7 +195,7 @@ def populate_events() -> Optional[None]:
136195
feed = (
137196
service.events()
138197
.list(
139-
calendarId=options.calendar_id,
198+
calendarId=calendar_options.calendar_id,
140199
timeMin=now,
141200
maxResults=5,
142201
singleEvents=True,
@@ -181,7 +240,7 @@ def send_reminders() -> Optional[None]:
181240

182241
for id, start, summary in events:
183242
dt = start - now
184-
if dt.days == 0 and dt.seconds < 60 * options.interval:
243+
if dt.days == 0 and dt.seconds < 60 * calendar_options.interval:
185244
# The unique key includes the start time, because of
186245
# repeating events.
187246
key = (id, start)
@@ -203,9 +262,14 @@ def send_reminders() -> Optional[None]:
203262
message = "Reminder:\n\n" + "\n".join("* " + m for m in messages)
204263

205264
user_profile = zulip_client.get_profile()
206-
if options.channel is not None:
265+
if calendar_options.channel is not None:
207266
result = zulip_client.send_message(
208-
{"type": "stream", "to": options.channel, "topic": options.topic, "content": message}
267+
{
268+
"type": "stream",
269+
"to": calendar_options.channel,
270+
"topic": calendar_options.topic,
271+
"content": message,
272+
}
209273
)
210274
else:
211275
result = zulip_client.send_message(

0 commit comments

Comments
 (0)