summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJochen Sprickerhof <jspricke@debian.org>2023-01-12 07:26:08 +0100
committerJochen Sprickerhof <jspricke@debian.org>2023-01-12 07:26:08 +0100
commitdcf718d786df384396fd296cb251a7407179a2dd (patch)
tree2236897c9a0dd6ba5fc566a2910d5b2499d9f402
parentf6e705a1f23d04cfaa4946d5aef6bbdc0b25482f (diff)
parentd1da6880ba3ede2e0f249e1853d2402ae6ed26ff (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.md3
-rw-r--r--README.rst30
-rw-r--r--recurring_ical_events.py34
-rw-r--r--setup.py2
-rw-r--r--test/conftest.py17
-rw-r--r--test/test_issue_101_select_components.py45
-rw-r--r--test/test_issue_97_simple_recurrent_todos_and_journals.py12
-rw-r--r--test/test_properties.py3
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
diff --git a/README.rst b/README.rst
index a0b7b18..1f2f9e5 100644
--- a/README.rst
+++ b/README.rst
@@ -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)
diff --git a/setup.py b/setup.py
index 8bd6a3e..b83bce5 100644
--- a/setup.py
+++ b/setup.py
@@ -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