From 82acd96524ce78e1e6fe76d1cd1518dd2e08894d Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 26 Dec 2013 09:27:59 +0100 Subject: [PATCH] Remove ability to add property parameters via a value's params attribute when adding via cal.add (that was only possible for custom value objects and makes up a strange API), but support a parameter attribute on cal.add's method signature to pass a dictionary with property parameter key/value pairs. Fixes #116. --- CHANGES.rst | 7 ++++ src/icalendar/cal.py | 50 ++++++++++++++++++------ src/icalendar/tests/test_fixed_issues.py | 22 +++++++++++ src/icalendar/tests/test_unit_cal.py | 10 +++++ 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f768b46..95b73c1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,13 @@ Changelog 4.0.dev (unreleased) -------------------- +- Remove ability to add property parameters via a value's params attribute when + adding via cal.add (that was only possible for custom value objects and makes + up a strange API), but support a parameter attribute on cal.add's method + signature to pass a dictionary with property parameter key/value pairs. + Fixes #116. + [thet] + - Backport some of Regebro's changes from his regebro-refactor branch. [thet] diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index dfdc2bf..12e6e74 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -92,32 +92,56 @@ class Component(CaselessDict): ############################# # handling of property values - def _encode(self, name, value, cond=1): + def _encode(self, name, value, parameters=None, encode=1): """Conditional convertion of values. """ - if not cond: + if not encode: return value if isinstance(value, types_factory.all_types): # Don't encode already encoded values. return value klass = types_factory.for_property(name) obj = klass(value) - if hasattr(value, 'params') and len(value.params.keys()) > 0: - # TODO: How can a python native value have params? - obj.params = value.params + if parameters: + if isinstance(parameters, dict): + params = Parameters() + for key, item in parameters.items(): + params[key] = item + parameters = params + assert isinstance(parameters, Parameters) + obj.params = parameters return obj - def set(self, name, value, encode=1): + def set(self, name, value, parameters=None, encode=1): if encode and isinstance(value, list) \ and name.lower() not in ['rdate', 'exdate']: # Individually convert each value to an ical type except rdate and # exdate, where lists of dates might be passed to vDDDLists. - self[name] = [self._encode(name, v, encode) for v in value] + self[name] = [self._encode(name, v, parameters, encode) + for v in value] else: - self[name] = self._encode(name, value, encode) + self[name] = self._encode(name, value, parameters, encode) - def add(self, name, value, encode=1): + def add(self, name, value, parameters=None, encode=1): """Add a property. + + :param name: Key name of the property. + :type name: string + + :param value: Value of the property. Either of a basic Python type of + any of the icalendar's own property types. + :type value: Python native type or icalendar property type. + + :param parameters: Property parameter dictionary for the value. Only + available, if encode is set to True. + :type parameters: Dictionary + + :param encode: True, if the value should be encoded to one of + icalendar's own property types (Fallback is "vText") + or False, if not. + :type encode: Boolean + + :returns: None """ if isinstance(value, datetime) and\ name.lower() in ('dtstamp', 'created', 'last-modified'): @@ -131,13 +155,13 @@ class Component(CaselessDict): # If property already exists, append it. Otherwise create and set it. if name in self: oldval = self[name] - value = self._encode(name, value, encode) + value = self._encode(name, value, parameters, encode) if isinstance(oldval, list): oldval.append(value) else: - self.set(name, [oldval, value], encode=0) + self.set(name, [oldval, value], None, encode=0) else: - self.set(name, value, encode) + self.set(name, value, parameters, encode) def _decode(self, name, value): """Internal for decoding property values. @@ -191,7 +215,7 @@ class Component(CaselessDict): to that. """ if encode: - values = [self._encode(name, value, 1) for value in values] + values = [self._encode(name, value, encode=1) for value in values] self[name] = types_factory['inline'](q_join(values)) ######################### diff --git a/src/icalendar/tests/test_fixed_issues.py b/src/icalendar/tests/test_fixed_issues.py index 7732a35..28e48e8 100644 --- a/src/icalendar/tests/test_fixed_issues.py +++ b/src/icalendar/tests/test_fixed_issues.py @@ -189,3 +189,25 @@ END:VCALENDAR""" cal = icalendar.Calendar.from_ical(ics.read()) cal # pep 8 ics.close() + + def test_issue_116(self): + """Issue #116/#117 - How to add 'X-APPLE-STRUCTURED-LOCATION' + """ + event = icalendar.Event() + event.add( + "X-APPLE-STRUCTURED-LOCATION", + "geo:-33.868900,151.207000", + parameters={ + "VALUE": "URI", + "X-ADDRESS": "367 George Street Sydney CBD NSW 2000", + "X-APPLE-RADIUS": "72", + "X-TITLE": "367 George Street" + } + ) + self.assertEqual( + event.to_ical(), + b'BEGIN:VEVENT\r\nX-APPLE-STRUCTURED-LOCATION;VALUE=URI;' + b'X-ADDRESS="367 George Street Sydney \r\n CBD NSW 2000";' + b'X-APPLE-RADIUS=72;X-TITLE="367 George Street":' + b'geo:-33.868900\r\n \\,151.207000\r\nEND:VEVENT\r\n' + ) diff --git a/src/icalendar/tests/test_unit_cal.py b/src/icalendar/tests/test_unit_cal.py index f684b98..4bdb371 100644 --- a/src/icalendar/tests/test_unit_cal.py +++ b/src/icalendar/tests/test_unit_cal.py @@ -222,6 +222,16 @@ class TestCalComponent(unittest.TestCase): self.assertEqual(comp['ATTACH'], [u'me', 'you', binary]) + def test_cal_Component_add_property_parameter(self): + # Test the for timezone correctness: dtstart should preserve it's + # timezone, crated, dtstamp and last-modified must be in UTC. + Component = icalendar.cal.Component + comp = Component() + comp.add('X-TEST-PROP', 'tryout.', + parameters={'prop1': 'val1', 'prop2': 'val2'}) + lines = comp.to_ical().splitlines() + self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines) + def test_cal_Component_from_ical(self): # Check for proper handling of TZID parameter of datetime properties Component = icalendar.cal.Component