diff options
author | Jochen Sprickerhof <jspricke@debian.org> | 2023-01-12 07:26:08 +0100 |
---|---|---|
committer | Jochen Sprickerhof <jspricke@debian.org> | 2023-01-12 07:26:08 +0100 |
commit | dcf718d786df384396fd296cb251a7407179a2dd (patch) | |
tree | 2236897c9a0dd6ba5fc566a2910d5b2499d9f402 | |
parent | f6e705a1f23d04cfaa4946d5aef6bbdc0b25482f (diff) | |
parent | d1da6880ba3ede2e0f249e1853d2402ae6ed26ff (diff) |
Update upstream source from tag 'upstream/2.0.0'
Update to upstream version '2.0.0'
with Debian dir ce6daa6a79991f050f08b23cf5613642b7cbb4f0
-rw-r--r-- | .github/ISSUE_TEMPLATE/bug_report.md | 3 | ||||
-rw-r--r-- | README.rst | 30 | ||||
-rw-r--r-- | recurring_ical_events.py | 34 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | test/conftest.py | 17 | ||||
-rw-r--r-- | test/test_issue_101_select_components.py | 45 | ||||
-rw-r--r-- | test/test_issue_97_simple_recurrent_todos_and_journals.py | 12 | ||||
-rw-r--r-- | test/test_properties.py | 3 |
8 files changed, 119 insertions, 27 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 15617fc..d003fe5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -39,5 +39,4 @@ assignees: '' https://github.com/niccokunzmann/python-recurring-ical-events/blob/f4a90b211f30bf0522f03514ba50eb69826f0fdb/test/calendars/issue-15-duplicated-events.ics#L1 - [ ] add a test, example: https://github.com/niccokunzmann/python-recurring-ical-events/blob/f4a90b211f30bf0522f03514ba50eb69826f0fdb/test/test_issue_15.py#L21 -- [ ] implement the test -- [ ] fix the test +- [ ] fix the bug and ensure the test code passes @@ -19,7 +19,7 @@ Recurring ICal events for Python ICal has some complexity to it: -Events can be repeated, removed from the feed and edited later on. +Events, TODOs and Journal entries can be repeated, removed from the feed and edited later on. This tool takes care of these circumstances. Let's put our expertise together and build a tool that can solve this! @@ -152,6 +152,29 @@ However, these attributes may differ from the source event: They are **not** included in repeated events, see `Issue 23 <https://github.com/niccokunzmann/python-recurring-ical-events/issues/23>`_. To change this, use ``of(calendar, keep_recurrence_attributes=True)``. +Different Components, not just Events +************************************* + +By default the ``recurring_ical_events`` only selects events as the name already implies. +However, there are different components available in a calendar. +You can select which components you like to have returned by passing ``components`` to the ``of`` function: + +.. code:: Python + + of(a_calendar, components=["VEVENT"]) + +Here is a template code for choosing the supported types of components: + +.. code:: Python + + events = recurring_ical_events.of(calendar).between(...) + journals = recurring_ical_events.of(calendar, components=["VJOURNAL"]).between(...) + todos = recurring_ical_events.of(calendar, components=["VTODO"]).between(...) + all = recurring_ical_events.of(calendar, components=["VTODO", "VEVENT", "VJOURNAL"]).between(...) + +If a type of component is not listed here, it can be added. +Please create an issue for this in the source code repository. + Speed ***** @@ -253,6 +276,11 @@ how to go about it. Changelog --------- +- v2.0.0b + + - Only return ``VEVENT`` by default. Add ``of(... ,components=...)`` parameter to select which kinds of components should be returned. See `Issue 101 <https://github.com/niccokunzmann/python-recurring-ical-events/issues/101>`_. + - Remove ``beta`` indicator. This library works okay: Feature requests come in, not so much bug reports. + - v1.1.0b - Add repeated TODOs and Journals. See `Pull Request 100 <https://github.com/niccokunzmann/python-recurring-ical-events/pull/100>`_ and `Issue 97 <https://github.com/niccokunzmann/python-recurring-ical-events/issues/97>`_. diff --git a/recurring_ical_events.py b/recurring_ical_events.py index ca4ea94..ab7d7de 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -429,20 +429,29 @@ DATE_MIN = (1970, 1, 1) # The maximum value accepted as date (pytz + zoneinfo) DATE_MAX = (2038, 1, 1) +class UnsupportedComponent(ValueError): + """This error is raised when a component is not supported, yet.""" + class UnfoldableCalendar: '''A calendar that can unfold its events at a certain time.''' + + recurrence_calculators = { + "VEVENT": RepeatedEvent, + "VTODO": RepeatedTodo, + "VJOURNAL": RepeatedJournal, + } - def __init__(self, calendar, keep_recurrence_attributes=False): + def __init__(self, calendar, keep_recurrence_attributes=False, components=["VEVENT"]): """Create an unfoldable calendar from a given calendar.""" assert calendar.get("CALSCALE", "GREGORIAN") == "GREGORIAN", "Only Gregorian calendars are supported." # https://www.kanzaki.com/docs/ical/calscale.html self.repetitions = [] - for event in calendar.walk("VEVENT"): - self.repetitions.append(RepeatedEvent(event, keep_recurrence_attributes)) - for event in calendar.walk("VTODO"): - self.repetitions.append(RepeatedTodo(event, keep_recurrence_attributes)) - for event in calendar.walk("VJOURNAL"): - self.repetitions.append(RepeatedJournal(event, keep_recurrence_attributes)) + for component_name in components: + if component_name not in self.recurrence_calculators: + raise UnsupportedComponent(f"\"{component_name}\" is an unknown name for a component. I only know these: {', '.join(self.recurrence_calculators)}.") + for event in calendar.walk(component_name): + recurrence_calculator = self.recurrence_calculators[component_name] + self.repetitions.append(recurrence_calculator(event, keep_recurrence_attributes)) @staticmethod def to_datetime(date): @@ -566,8 +575,13 @@ class UnfoldableCalendar: return events -def of(a_calendar, keep_recurrence_attributes=False): - """Unfold recurring events of a_calendar""" +def of(a_calendar, keep_recurrence_attributes=False, components=["VEVENT"]): + """Unfold recurring events of a_calendar + + - a_calendar is an icalendar VCALENDAR component or something like that. + - keep_recurrence_attributes - whether to keep attributes that are only used to calculate the recurrence. + - components is a list of component type names of which the recurrences should be returned. + """ a_calendar = x_wr_timezone.to_standard(a_calendar) - return UnfoldableCalendar(a_calendar, keep_recurrence_attributes) + return UnfoldableCalendar(a_calendar, keep_recurrence_attributes, components) @@ -11,7 +11,7 @@ PACKAGE_NAME = "recurring_ical_events" HERE = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, HERE) # for package import -__version__ = "1.1.0b" +__version__ = "2.0.0" __author__ = 'Nicco Kunzmann' diff --git a/test/conftest.py b/test/conftest.py index 67b39fc..2dd8299 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -26,9 +26,12 @@ CALENDARS_FOLDER = os.path.join(HERE, "calendars") time.tzset() class ICSCalendars: - """A collection of parsed ICS calendars""" + """A collection of parsed ICS calendars + + components is the argument to pass to the of function""" Calendar = icalendar.Calendar + components = None def get_calendar(self, content): """Return the calendar given the content.""" @@ -45,6 +48,12 @@ class ICSCalendars: """Make the datetime consistent with the time zones used in these calendars.""" assert dt.tzinfo is None or "pytz" in dt.tzinfo.__class__.__module__, "We need pytz time zones for now." return dt + + def _of(self, calendar): + """Return the calendar but also with selected components.""" + if self.components is None: + return of(calendar) + return of(calendar, components=self.components) for calendar_file in os.listdir(CALENDARS_FOLDER): calendar_path = os.path.join(CALENDARS_FOLDER, calendar_file) @@ -61,7 +70,7 @@ class Calendars(ICSCalendars): """Collection of calendars from recurring_ical_events""" def get_calendar(self, content): - return of(ICSCalendars.get_calendar(self, content)) + return self._of(ICSCalendars.get_calendar(self, content)) class ReversedCalendars(ICSCalendars): """All test should run in reversed item order. @@ -78,7 +87,7 @@ class ReversedCalendars(ICSCalendars): """Return properties in reversed order.""" return reversed(_walk(*args, **kw)) calendar.walk = walk - return of(calendar) + return self._of(calendar) class ZoneInfoConverter(CalendarWalker): @@ -105,7 +114,7 @@ class ZoneInfoCalendars(ICSCalendars): zoneinfo_calendar = self.changer.walk(calendar) # if zoneinfo_calendar is calendar: # pytest.skip("ZoneInfo not in use. Already tested..") - return of(zoneinfo_calendar) + return self._of(zoneinfo_calendar) def consistent_tz(self, dt): """To make the time zones consistent with this one, convert them to zoneinfo.""" diff --git a/test/test_issue_101_select_components.py b/test/test_issue_101_select_components.py new file mode 100644 index 0000000..97efdc2 --- /dev/null +++ b/test/test_issue_101_select_components.py @@ -0,0 +1,45 @@ +"""These tests make sure that you can select which components should be returned. + +By default, it should be events. +If a component is not supported, an error is raised. +""" +import pytest + + +@pytest.mark.parametrize("components,count,calendar,message", [ + (None, 0, "issue-97-simple-todo", "by default, only events are returned"), + (None, 0, "issue-97-simple-journal", "by default, only events are returned"), + ([], 0, "rdate", "no components, no result"), + ([], 0, "issue-97-simple-todo", "no components, no result"), + ([], 0, "issue-97-simple-journal", "no components, no result"), + (["VEVENT"], 0, "issue-97-simple-todo", "no events in the calendar"), + (["VEVENT"], 0, "issue-97-simple-journal", "no events in the calendar"), + + (["VJOURNAL"], 0, "issue-97-simple-todo", "no journal, just a todo"), + (["VTODO"], 1, "issue-97-simple-todo", "one todo is found"), + + (["VTODO"], 0, "issue-97-simple-journal", "no todo, just a journal"), + (["VJOURNAL"], 1, "issue-97-simple-journal", "one journal is found"), + + (["VTODO", "VEVENT"], 0, "issue-97-simple-journal", "no todo, just a journal"), + (["VJOURNAL", "VEVENT"], 1, "issue-97-simple-journal", "one journal is found"), + (["VJOURNAL", "VEVENT", "VTODO"], 1, "issue-97-simple-journal", "one journal is found"), +]) +def test_components_and_their_count(calendars, components, count, calendar, message): + calendars.components = components + repeated_components = calendars[calendar].at(2022) + print(repeated_components) + assert len(repeated_components) == count, f"{message}: {components}, {calendar}" + + +@pytest.mark.parametrize("component", [ + "VALARM", # existing but not supported, yet + "vevent", # misspelled + "ALDHKSJHK", # does not exist +]) +def test_unsupported_component_raises_error(component, calendars): + """If a component is not recognized, we want to inform the user.""" + with pytest.raises(ValueError) as error: + calendars.components = [component] + calendars.rdate + assert f"\"{component}\"" in str(error) diff --git a/test/test_issue_97_simple_recurrent_todos_and_journals.py b/test/test_issue_97_simple_recurrent_todos_and_journals.py index cb33bc9..1ae5e55 100644 --- a/test/test_issue_97_simple_recurrent_todos_and_journals.py +++ b/test/test_issue_97_simple_recurrent_todos_and_journals.py @@ -15,8 +15,8 @@ def test_recurring_task_is_not_included1(calendars, ical_file): Test passes prior to fixing #97, should still pass after #97 is fixed. """ - calendar = getattr(calendars, ical_file) - tasks = calendar.between((1989, 1, 1), (1991,1,1)) + calendars.components = ["VJOURNAL", "VTODO", "VEVENT"] + tasks = calendars[ical_file].between((1989, 1, 1), (1991,1,1)) assert not tasks @calendars_parametrized @@ -27,8 +27,8 @@ def test_recurring_task_is_not_included2(calendars, ical_file): Test passes prior to fixing #97, should still pass after #97 is fixed. """ - calendar = getattr(calendars, ical_file) - tasks = calendar.between((1998, 1, 1), (1998,4,14)) + calendars.components = ["VJOURNAL", "VTODO", "VEVENT"] + tasks = calendars[ical_file].between((1998, 1, 1), (1998,4,14)) assert not tasks @calendars_parametrized @@ -38,7 +38,7 @@ def test_recurring_task_is_repeated(calendars, ical_file): https://github.com/niccokunzmann/python-recurring-ical-events/issues/97 needs to be fixed before this test can pass """ - calendar = getattr(calendars, ical_file) - events = calendar.between((1995, 1, 1), (2002,1,1)) + calendars.components = ["VJOURNAL", "VTODO", "VEVENT"] + events = calendars[ical_file].between((1995, 1, 1), (2002,1,1)) assert len(events) == 7 diff --git a/test/test_properties.py b/test/test_properties.py index 6a0bae7..e84b998 100644 --- a/test/test_properties.py +++ b/test/test_properties.py @@ -9,9 +9,6 @@ def test_event_has_summary(calendars): @pytest.mark.parametrize("attribute", ["DTSTART", "DTEND"]) def test_recurrent_events_change_start_and_end(calendars, attribute): events = calendars.three_events_one_edited.all() - print(events) - if events[0][attribute].__hash__ is None: - pytest.skip() values = set(event[attribute] for event in events) assert len(values) == 3 |