From 804ab5297092428c2ec0c440f28d50db5ac686ba Mon Sep 17 00:00:00 2001 From: Abe Hanoka Date: Mon, 9 Dec 2024 15:01:15 -0500 Subject: [PATCH] Add from_file and to_file method --- CHANGES.rst | 3 +- src/icalendar/cal.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d50a55c7..9aeb1467 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,8 +13,7 @@ Breaking changes: - The ``relative`` attribute of ``vWeekday`` components has the correct sign now. See `Issue 749 `_. New features: - -- ... +- Add ``from_file()`` and ``to_file()`` methods to ``Component`` class for easier file handling of iCalendar data. See `Issue 756 `_. Bug fixes: diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index bae86bd8..209e5bc0 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -10,6 +10,7 @@ from datetime import date, datetime, timedelta, tzinfo from typing import List, Optional, Tuple from datetime import date, datetime, timedelta +from pathlib import Path from typing import TYPE_CHECKING, List, NamedTuple, Optional, Tuple, Union import dateutil.rrule @@ -587,6 +588,74 @@ def is_thunderbird(self) -> bool: return any(attr.startswith("X-MOZ-") for attr in self.keys()) + @classmethod + def from_file(cls, file: Union[str, Path], multiple: bool = False): + """Create a Component from a file. + + This class method can be used by any Component subclass (Calendar, Event, etc.) + to read their data from a file. + + Args: + file: The file to read from. Can be: + - A string path to a file + - A Path object + multiple: If True, allows parsing multiple components from the file. + + Returns: + If multiple=False (default): + A single Component instance of the appropriate type + If multiple=True: + A list of Component instances + + Raises: + FileNotFoundError: If the file path doesn't exist + ValueError: If the file contents are not valid iCalendar format + + Example: + >>> from icalendar import Calendar + >>> # Read a calendar file + >>> cal = Calendar.from_file("src/icalendar/tests/calendars/example.ics") + >>> # Read multiple calendars + >>> cals = Calendar.from_file("src/icalendar/tests/calendars/multiple_calendar_components.ics", multiple=True) + """ + # Handle string path by converting to Path + if isinstance(file, str): + file = Path(file) + + return cls.from_ical(file.read_bytes(), multiple=multiple) + + def to_file(self, file: Union[str, Path], sorted: bool = True) -> None: + """Write the component to a file. + + This method can be used by any Component subclass (Calendar, Event, etc.) + to write their data to a file. + + Args: + file: Where to write the component. Can be: + - A string path to a file + - A Path object + sorted: Whether parameters and properties should be lexicographically sorted. + + Example: + >>> from icalendar import Calendar + >>> from pathlib import Path + >>> # Read a calendar file + >>> cal = Calendar.from_file("src/icalendar/tests/calendars/example.ics") + >>> # or pass a Path object + >>> path = Path("src/icalendar/tests/calendars/example.ics") + >>> cal = Calendar.from_file(path) + >>> # Read multiple calendars + >>> cals = Calendar.from_file("src/icalendar/tests/calendars/multiple_calendar_components.ics", multiple=True) + + """ + + # Handle string path + if isinstance(file, str): + file = Path(file) + + file.write_bytes(self.to_ical(sorted=sorted)) + + ####################################### # components defined in RFC 5545