Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix update_events for nonexistent IDs and support creation of new events #134

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions postics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python

import argparse
import asyncio

import ics

from config import password, username
from spond import spond

DESCRIPTION = """
Read in iCal events from .ics file[s] and post them to Spond.
""".strip()


def ics2spond(event):
"""Create Spond event dictionary from ics.Event"""

return {
"heading": event.name,
"description": event.description,
"startTimestamp": event.begin.isoformat(),
"endTimestamp": event.end.isoformat(),
"location": {"feature": event.location},
}


async def post_events(args, gid=None, owners=[]):
"""
Read Calendar from .ics file[s] and post all events to Spond.

Parameters
----------
args : argparse.Namespace
Command line arguments and options returned by ArgumentParser.parse_args(),
containing options and file name[s] (wildcards supported).
gid : str
'id' of Spond group to post to (default: first group from `get_groups()` for user).
owners : list
list of user's {'id': uid} (default: [user] from `config.username`).
"""

s = spond.Spond(username=username, password=password)

if len(owners) == 0:
user = await s.get_person(username)
owners = [user["profile"]]

if gid is None:
groups = await s.get_groups()
for mygroup in groups:
if mygroup["contactPerson"]["id"] == owners[0]["id"]:
break
else:
raise ValueError(f"No group with contact person {owners[0]['id']} found")
recipients = {"group": mygroup}
else:
recipients = {"group": {"id": gid}}

if not args.quiet:
print(f"Posting as {username} ({owners[0]['id']}): {recipients['group']['id']}")

for filename in args.filename: # Support wildcards
if not args.quiet:
print(f"Reading {filename}:")
calendar = ics.Calendar(open(filename).read())
for event in calendar.events:
updates = {"owners": owners, "recipients": recipients}
updates.update(ics2spond(event))
uid = getattr(event, "uid", "")
if args.verbose:
print(event.serialize())
elif not args.quiet:
print(event.name)
events = await s.update_event(uid, updates)
await s.clientsession.close()


def main(args=None):
"""The main function called by the `postics` script."""
parser = argparse.ArgumentParser(
description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter
)
# parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument(
"-v",
"--verbose",
default=False,
action="store_true",
help="verbose mode; echo full iCal events parsed",
)
parser.add_argument(
"-q",
"--quiet",
default=False,
action="store_true",
help="quiet mode; do not echo iCal event names",
)
parser.add_argument(
"-g", "--gid", default=None, help="specify Spond group ID of recipients group"
)
parser.add_argument(
"filename",
nargs="+",
help="Path to one or more ics files; wildcards are supported",
)

args = parser.parse_args(args)

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.run(post_events(args, gid=args.gid))


if __name__ == "__main__":
main()
70 changes: 39 additions & 31 deletions spond/spond.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,28 +313,26 @@ async def get_event(self, uid: str) -> dict:
@_SpondBase.require_authentication
async def update_event(self, uid: str, updates: dict):
"""
Updates an existing event.
Updates an existing event or creates a new one.

Parameters
----------
uid : str
UID of the event.
UID of the event to be updated. If no event of that UID exists,
a new one will be created with default settings.
updates : dict
The changes. e.g. if you want to change the description -> {'description': "New Description with changes"}
The changes to existing event or default template for new events.
e.g. if you want to change the description ->
{'description': "New Description with changes"}
For a new event this should at a minimum include entries for
(list of) 'owners', 'recipients' (a dict of {"group": {"id": GID}}),
'heading', 'startTimestamp', 'endTimestamp' (in datetime.isoformat).

Returns
-------
json results of post command

"""
if not self.events:
await self.get_events()
for event in self.events:
if event["id"] == uid:
break

url = f"{self.api_url}sponds/{uid}"

base_event: dict = {
"heading": None,
"description": None,
Expand All @@ -358,29 +356,39 @@ async def update_event(self, uid: str, updates: dict):
"autoAccept": False,
"payment": {},
"attachments": [],
"id": None,
"tasks": {
"openTasks": [],
"assignedTasks": [
{
"name": None,
"description": "",
"type": "ASSIGNED",
"id": None,
"adultsOnly": True,
"assignments": {"memberIds": [], "profiles": [], "remove": []},
}
],
},
"recipients": {"group": {"id": None}},
"tasks": {"openTasks": [], "assignedTasks": []},
}
data = dict(base_event)

for key in base_event:
if event.get(key) is not None and not updates.get(key):
base_event[key] = event[key]
elif updates.get(key) is not None:
base_event[key] = updates[key]
if not self.events:
await self.get_events()
for event in self.events:
if event["id"] == uid:
data.update(event)
url = f"{self.api_url}sponds/{uid}"
break
else:
# No event of that id, create a new one (id to be set by Spond)
if (
len(updates.get("owners", [])) < 1
or updates["owners"][0].get("id") is None
):
errmsg = '"owners" need to have a valid user id'
raise ValueError(errmsg)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the second part should become a LookupError in the spirit of #135; for the first part, which covers an empty or altogether missing list of owners, it is not as clear – could of course handle both exceptions separately.
More or less the same for recipients below.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the second part should become a LookupError in the spirit of #135

Actually my error has confused things - as per discussion at #113, PR #135 was to change failed lookups to return KeyError, and I've now amended so it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I forgot I already argued for that myself! ;-)

if (
"recipients" not in updates
or updates["recipients"].get("group").get("id") is None
):
errmsg = '"recipients" need to contain a "group" with valid id'
raise ValueError(errmsg)
updates.pop("id", None)
url = f"{self.api_url}sponds"

for key in data:
if updates.get(key) is not None:
data[key] = updates[key]

data = dict(base_event)
async with self.clientsession.post(
url, json=data, headers=self.auth_headers
) as r:
Expand Down