diff --git a/CHANGES.rst b/CHANGES.rst index 42e382a..2de4d03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,12 @@ Changelog 3.8 (unreleased) ---------------- - + +- Change class representation for CaselessDict objects to always include the + class name or the class' name attribute, if available. Also show + subcomponents for Component objects. + [thet] + - Don't use data_encode for CaselessDict class representation but use dict's __repr__ method. [t-8ch] diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index 836cd05..1f18cfe 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -12,7 +12,6 @@ from icalendar.parser import Parameters from icalendar.parser import q_join from icalendar.parser import q_split from icalendar.parser_tools import DEFAULT_ENCODING -from icalendar.parser_tools import data_encode from icalendar.prop import TypesFactory from icalendar.prop import vText, vDDDLists @@ -59,13 +58,13 @@ class Component(CaselessDict): directy, but rather one of the subclasses. """ - name = '' # must be defined in each component - required = () # These properties are required - singletons = () # These properties must only appear once - multiple = () # may occur more than once - exclusive = () # These properties are mutually exclusive - inclusive = () # if any occurs the other(s) MUST occur - # ('duration', 'repeat') + name = None # should be defined in each component + required = () # These properties are required + singletons = () # These properties must only appear once + multiple = () # may occur more than once + exclusive = () # These properties are mutually exclusive + inclusive = () # if any occurs the other(s) MUST occur + # ('duration', 'repeat') ignore_exceptions = False # if True, and we cannot parse this # component, we will silently ignore # it, rather than let the exception @@ -262,7 +261,7 @@ class Component(CaselessDict): """Recursively traverses component and subcomponents. Returns sequence of same. If name is passed, only components with name will be returned. """ - if not name is None: + if name is not None: name = name.upper() return self._walk(name) @@ -368,9 +367,6 @@ class Component(CaselessDict): '{st!r}'.format(**locals())) return comps[0] - def __repr__(self): - return '%s(%s)' % (self.name, data_encode(self)) - def content_line(self, name, value, sorted=True): """Returns property as content line. """ @@ -396,6 +392,16 @@ class Component(CaselessDict): content_lines = self.content_lines(sorted=sorted) return content_lines.to_ical() + def __repr__(self): + """String representation of class with all of it's subcomponents. + """ + subs = ', '.join([str(it) for it in self.subcomponents]) + return '%s(%s%s)' % ( + self.name, + dict(self), + ', %s' % subs if subs else '' + ) + ####################################### # components defined in RFC 2445 diff --git a/src/icalendar/caselessdict.py b/src/icalendar/caselessdict.py index d266661..7c35db0 100644 --- a/src/icalendar/caselessdict.py +++ b/src/icalendar/caselessdict.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from icalendar.compat import iteritems from icalendar.parser_tools import to_unicode -from icalendar.parser_tools import data_encode try: from collections import OrderedDict @@ -41,6 +40,9 @@ class CaselessDict(OrderedDict): super(CaselessDict, self).__delitem__(key) self[key_upper] = value + # If name not set, use class name instead + self.name = getattr(self, 'name', None) or type(self).__name__ + def __getitem__(self, key): key = to_unicode(key) return super(CaselessDict, self).__getitem__(key.upper()) @@ -89,7 +91,7 @@ class CaselessDict(OrderedDict): return type(self)(super(CaselessDict, self).copy()) def __repr__(self): - return 'CaselessDict(%s)' % dict(self) + return '%s(%s)' % (self.name, dict(self)) def __eq__(self, other): return self is other or dict(self.items()) == dict(other.items()) diff --git a/src/icalendar/parser.py b/src/icalendar/parser.py index c8f368e..07807e4 100644 --- a/src/icalendar/parser.py +++ b/src/icalendar/parser.py @@ -191,9 +191,6 @@ class Parameters(CaselessDict): # def decoded(self, name): # "returns a decoded value, or list of same" - def __repr__(self): - return 'Parameters(%s)' % data_encode(self) - def to_ical(self, sorted=True): result = [] items = list(self.items()) diff --git a/src/icalendar/tests/test_property_params.py b/src/icalendar/tests/test_property_params.py index 2dfd49b..2b01f60 100644 --- a/src/icalendar/tests/test_property_params.py +++ b/src/icalendar/tests/test_property_params.py @@ -6,6 +6,7 @@ from icalendar import vCalAddress from icalendar.tests import unittest import icalendar +import re class TestPropertyParams(unittest.TestCase): @@ -205,3 +206,11 @@ END:VCALENDAR""" self.assertEqual(event['attendee'][0].params.to_ical(), b'CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE') self.assertEqual(event['attendee'][0].params['cn'], u'RembrandXS') + + def test_repr(self): + """Test correct class representation. + """ + it = Parameters(parameter1='Value1') + self.assertTrue( + re.match("Parameters\({u?'PARAMETER1': 'Value1'}\)", str(it)) + ) diff --git a/src/icalendar/tests/test_unit_cal.py b/src/icalendar/tests/test_unit_cal.py index b0e42dc..018ddbe 100644 --- a/src/icalendar/tests/test_unit_cal.py +++ b/src/icalendar/tests/test_unit_cal.py @@ -4,6 +4,7 @@ from icalendar.tests import unittest import icalendar import pytz +import re class TestCalComponent(unittest.TestCase): @@ -306,6 +307,46 @@ class TestCalComponent(unittest.TestCase): preserved_str = component.to_ical(sorted=False).splitlines() assert preserved_str == component_str + def test_repr(self): + """Test correct class representation. + """ + from icalendar.cal import Component, Calendar, Event + + component = Component() + component['key1'] = 'value1' + + self.assertTrue( + re.match("Component\({u?'KEY1': 'value1'}\)", str(component)) + ) + + calendar = Calendar() + calendar['key1'] = 'value1' + + self.assertTrue( + re.match("VCALENDAR\({u?'KEY1': 'value1'}\)", str(calendar)) + ) + + event = Event() + event['key1'] = 'value1' + + self.assertTrue( + re.match("VEVENT\({u?'KEY1': 'value1'}\)", str(event)) + ) + + # Representation of nested Components + calendar.add_component(event) + nested = Component(key1='VALUE1', key2='VALUE2') + nested.add_component(component) + nested.add_component(calendar) + nested.add_component(event) + + self.assertTrue( + re.match( + "Component\({u?'KEY2': 'VALUE2', u?'KEY1': 'VALUE1'}, Component\({u?'KEY1': 'value1'}\), VCALENDAR\({u?'KEY1': 'value1'}, VEVENT\({u?'KEY1': 'value1'}\)\), VEVENT\({u?'KEY1': 'value1'}\)\)", # nopep8 + str(nested) + ) + ) + class TestCal(unittest.TestCase):