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

Adding a CALDAV todo item with the radicale server does not work #105383

Closed
FrnchFrgg opened this issue Dec 9, 2023 · 3 comments · Fixed by #105508
Closed

Adding a CALDAV todo item with the radicale server does not work #105383

FrnchFrgg opened this issue Dec 9, 2023 · 3 comments · Fixed by #105508

Comments

@FrnchFrgg
Copy link
Contributor

The problem

Trying to add a todo item to a todo list coming from CALDAV with a radicale server errors with
"VTODO components cannot contain more than 1 STATUS"

(Reading the traceback below it seems that the message is sent by the caldav library but that it only validates the event on failure which would explain why a more forgiving server would allow the problem to stay hidden ?)

The problem is somewhat in the caldav library you use since I was able to reproduce it like so:

>>> import caldav
>>> client = caldav.DAVClient("xxx", username="foo", password="bar")
>>> cal = client.principal().calendars()[0]
>>> x = cal.save_todo(summary="works")
>>> y = cal.save_todo(summary="chokes", status="COMPLETED")
ValidateError                             Traceback (most recent call last)
File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:768, in Calendar.save_todo(self, ical, no_overwrite, no_create, **ical_data)
    757 """
    758 Add a new task to the calendar, with the given ical.
    759 
    760 Parameters:
    761  * ical - ical object (text)
    762 """
    763 t = Todo(
    764     self.client,
    765     data=self._use_or_create_ics(ical, objtype="VTODO", **ical_data),
    766     parent=self,
    767 )
--> 768 t.save(no_overwrite=no_overwrite, no_create=no_create, obj_type="todo")
    769 self._handle_relations(t.id, ical_data)
    770 return t

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2283, in CalendarObjectResource.save(self, no_overwrite, no_create, obj_type, increase_seqno, if_schedule_tag_match)
   2280     if seqno is not None:
   2281         self.icalendar_component.add("SEQUENCE", seqno + 1)
-> 2283 self._create(id=self.id, path=path)
   2284 return self

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2155, in CalendarObjectResource._create(self, id, path, retry_on_failure)
   2150 def _create(self, id=None, path=None, retry_on_failure=True):
   2151     ## We're efficiently running the icalendar code through the icalendar
   2152     ## library.  This may cause data modifications and may "unfix"
   2153     ## https://github.com/python-caldav/caldav/issues/43
   2154     self._find_id_path(id=id, path=path)
-> 2155     self._put()

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2146, in CalendarObjectResource._put(self, retry_on_failure)
   2142 if retry_on_failure:
   2143     ## This looks like a noop, but the object may be "cleaned".
   2144     ## See https://github.com/python-caldav/caldav/issues/43
   2145     self.vobject_instance
-> 2146     return self._put(False)
   2147 else:
   2148     raise error.PutError(errmsg(r))

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2137, in CalendarObjectResource._put(self, retry_on_failure)
   2134 def _put(self, retry_on_failure=True):
   2135     ## SECURITY TODO: we should probably have a check here to verify that no such object exists already
   2136     r = self.client.put(
-> 2137         self.url, self.data, {"Content-Type": 'text/calendar; charset="utf-8"'}
   2138     )
   2139     if r.status == 302:
   2140         path = [x[1] for x in r.headers if x[0] == "location"][0]

File ~/aaaaa/hh/lib/python3.11/site-packages/caldav/objects.py:2321, in CalendarObjectResource._get_data(self)
   2319     return to_normal_str(self._data)
   2320 elif self._vobject_instance:
-> 2321     return to_normal_str(self._vobject_instance.serialize())
   2322 elif self._icalendar_instance:
   2323     return to_normal_str(self._icalendar_instance.to_ical())

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/base.py:254, in VBase.serialize(self, buf, lineLength, validate, behavior)
    252     if DEBUG:
    253         logger.debug("serializing {0!s} with behavior {1!s}".format(self.name, behavior))
--> 254     return behavior.serialize(self, buf, lineLength, validate)
    255 else:
    256     if DEBUG:

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/icalendar.py:1001, in VCalendar2_0.serialize(cls, obj, buf, lineLength, validate)
    999 cls.generateImplicitParameters(obj)
   1000 if validate:
-> 1001     cls.validate(obj, raiseException=True)
   1002 if obj.isNative:
   1003     transformed = obj.transformFromNative()

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/behavior.py:85, in Behavior.validate(cls, obj, raiseException, complainUnrecognized)
     83 count = {}
     84 for child in obj.getChildren():
---> 85     if not child.validate(raiseException, complainUnrecognized):
     86         return False
     87     name = child.name.upper()

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/base.py:124, in VBase.validate(self, *args, **kwds)
    120 """
    121 Call the behavior's validate method, or return True.
    122 """
    123 if self.behavior:
--> 124     return self.behavior.validate(self, *args, **kwds)
    125 return True

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/icalendar.py:1228, in VTodo.validate(cls, obj, raiseException, *args)
   1226     return False
   1227 else:
-> 1228     return super(VTodo, cls).validate(obj, raiseException, *args)

File ~/aaaaa/hh/lib/python3.11/site-packages/vobject/behavior.py:98, in Behavior.validate(cls, obj, raiseException, complainUnrecognized)
     96         if raiseException:
     97             m = "{0} components cannot contain more than {1} {2}"
---> 98             raise base.ValidateError(m.format(cls.name, val[1], key))
     99         return False
    100 return True

ValidateError: 'VTODO components cannot contain more than 1 STATUS'

The bug is that the caldav library does not consider status and STATUS as the same VEVENT property even though ICAL property names are always uppercase AFAICT.

A simple solution is to pass arguments with the correct casing instead of relying on caldav to sanitize it:

z = cal.save_todo(summary="thisworks", STATUS="COMPLETED")

That is fix

item_data["status"] = TODO_STATUS_MAP_INV.get(status, "NEEDS-ACTION")
to read:

        item_data["STATUS"] = TODO_STATUS_MAP_INV.get(status, "NEEDS-ACTION")

I'll send a MR and open a bug upstream as soon as I can.

What version of Home Assistant Core has the issue?

core-2023.12.0

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant OS

Integration causing the issue

caldav

Link to integration documentation on our website

https://www.home-assistant.io/integrations/caldav/

Diagnostics information

No response

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

No response

@home-assistant
Copy link

home-assistant bot commented Dec 9, 2023

@FrnchFrgg
Copy link
Contributor Author

Upstream issue: python-caldav/caldav#355

@FrnchFrgg
Copy link
Contributor Author

Sorry, bad manipulation in my clone actually closed that issue. A case of github trying to be too smart I guess (how come a merge can close issues across projects baffles me)

@github-actions github-actions bot locked and limited conversation to collaborators Jan 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
1 participant