diff --git a/.gitignore b/.gitignore index 31929643..8086c1df 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ doc/_build/* .coverage htmlcov /.pytest_cache/ + +# IDE settings +/.idea/ \ No newline at end of file diff --git a/AUTHORS.rst b/AUTHORS.rst index 7afa0b12..7777197a 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -17,6 +17,7 @@ Other contributors, listed alphabetically, are: * `@guyzmo `_ * `@jammon `_ * `@mrmadcow `_ +* `@PascalBru `_ * `@perette `_ * `@prashnts `_ * `@rkeilty `_ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbc084c1..ea5e5f6f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Ics.py changelog format (thanks @muffl0n, @e-c-d and @chauffer) - Fix all-day events lasting multiple days by using a DTEND with a date and not a datetime (thanks @raspbeguy) - Fix off by one error on the DTEND on all day events (issues #92 and #150) + - Add support for classification (issue #177) ************** 0.4 diff --git a/ics/event.py b/ics/event.py index 0a805c63..8e4329a7 100644 --- a/ics/event.py +++ b/ics/event.py @@ -54,6 +54,7 @@ def __init__(self, alarms=None, categories=None, status=None, + classification=None, ): """Instantiates a new :class:`ics.event.Event`. @@ -71,6 +72,7 @@ def __init__(self, alarms (:class:`ics.alarm.Alarm`) categories (set of string) status (string) + classification (string) Raises: ValueError: if `end` and `duration` are specified at the same time @@ -105,6 +107,7 @@ def __init__(self, if alarms is not None: self.alarms.update(set(alarms)) self.status = status + self.classification = classification if categories is not None: self.categories.update(set(categories)) @@ -253,6 +256,22 @@ def status(self, value): raise ValueError('status must be one of %s' % statuses) self._status = value + @property + def classification(self): + return self._classification + + @classification.setter + def classification(self, value): + if value is not None: + classifications = ('PUBLIC', 'PRIVATE', 'CONFIDENTIAL', 'iana-token', 'x-name') + if not isinstance(value, str): + raise ValueError('classification must be instance of str') + if value not in classifications: + raise ValueError('classification must be one of %s' % ', '.join(classifications)) + self._classification = value + else: + self._classification = None + def __repr__(self): name = "'{}' ".format(self.name) if self.name else '' if self.all_day: @@ -519,6 +538,12 @@ def status(event, line): event.status = line.value +@Event._extracts('CLASS') +def classification(event, line): + if line: + event.classification = line.value + + @Event._extracts('CATEGORIES') def categories(event, line): event.categories = set() @@ -625,6 +650,12 @@ def o_status(event, container): container.append(ContentLine('STATUS', value=event.status)) +@Event._outputs +def o_classification(event, container): + if event.classification: + container.append(ContentLine('CLASS', value=event.classification)) + + @Event._outputs def o_categories(event, container): if event.categories: diff --git a/tests/event.py b/tests/event.py index 576a3ad6..037f0df6 100644 --- a/tests/event.py +++ b/tests/event.py @@ -5,7 +5,8 @@ from ics.event import Event from ics.icalendar import Calendar from ics.parse import Container -from .fixture import cal12, cal13, cal15, cal16, cal17, cal18, cal19, cal20, cal32 +from .fixture import cal12, cal13, cal15, cal16, cal17, cal18, cal19, cal20, cal32,\ + cal33_1, cal33_2, cal33_3, cal33_4, cal33_5, cal34 CRLF = "\r\n" @@ -457,3 +458,59 @@ def test_issue_92(self): assert e.begin == arrow.get('2016-10-04') assert e.end == arrow.get('2016-10-05') + + def test_classification_input(self): + c = Calendar(cal12) + e = next(iter(c.events)) + self.assertEqual(None, e.classification) + + c = Calendar(cal33_1) + e = next(iter(c.events)) + self.assertEqual('PUBLIC', e.classification) + + c = Calendar(cal33_2) + e = next(iter(c.events)) + self.assertEqual('PRIVATE', e.classification) + + c = Calendar(cal33_3) + e = next(iter(c.events)) + self.assertEqual('CONFIDENTIAL', e.classification) + + c = Calendar(cal33_4) + e = next(iter(c.events)) + self.assertEqual('iana-token', e.classification) + + c = Calendar(cal33_5) + e = next(iter(c.events)) + self.assertEqual('x-name', e.classification) + + def test_classification_input_wrong_value(self): + with pytest.raises(ValueError): + Calendar(cal34) + + def test_classification_output(self): + e = Event(name="Name") + self.assertNotIn("CLASS:PUBLIC", str(e).splitlines()) + + e = Event(name="Name", classification='PUBLIC') + self.assertIn("CLASS:PUBLIC", str(e).splitlines()) + + e = Event(name="Name", classification='PRIVATE') + self.assertIn("CLASS:PRIVATE", str(e).splitlines()) + + e = Event(name="Name", classification='CONFIDENTIAL') + self.assertIn("CLASS:CONFIDENTIAL", str(e).splitlines()) + + e = Event(name="Name", classification='iana-token') + self.assertIn("CLASS:iana-token", str(e).splitlines()) + + e = Event(name="Name", classification='x-name') + self.assertIn("CLASS:x-name", str(e).splitlines()) + + def test_classification_bool(self): + with pytest.raises(ValueError): + Event(name="Name", classification=True) + + def test_classification_wrong_value(self): + with pytest.raises(ValueError): + Event(name="Name", classification='wrong_value') diff --git a/tests/fixture.py b/tests/fixture.py index 4b513b71..b2dc84f1 100644 --- a/tests/fixture.py +++ b/tests/fixture.py @@ -611,3 +611,87 @@ 'END:VEVENT', 'END:VCALENDAR', ] + +cal33_1 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR +""" + +cal33_2 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:PRIVATE +END:VEVENT +END:VCALENDAR +""" + +cal33_3 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:CONFIDENTIAL +END:VEVENT +END:VCALENDAR +""" + +cal33_4 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:iana-token +END:VEVENT +END:VCALENDAR +""" + +cal33_5 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:x-name +END:VEVENT +END:VCALENDAR +""" + +cal34 = """ +BEGIN:VCALENDAR +PRODID:-//Apple Inc.//Mac OS X 10.9//EN +BEGIN:VEVENT +DTEND;TZID=Europe/Berlin:20120608T212500 +SUMMARY:Name +DTSTART;TZID=Europe/Berlin:20120608T202500 +LOCATION:MUC +SEQUENCE:0 +CLASS:wrong_value +END:VEVENT +END:VCALENDAR +""" \ No newline at end of file