Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add from_file and to_file method #757

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ Breaking changes:
- The ``relative`` attribute of ``vWeekday`` components has the correct sign now. See `Issue 749 <https://github.com/collective/icalendar/issues/749>`_.

New features:

- ...
- Add ``from_file()`` and ``to_file()`` methods to ``Component`` class for easier file handling of iCalendar data. See `Issue 756 <https://github.com/collective/icalendar/issues/756>`_.

Bug fixes:

Expand Down
69 changes: 69 additions & 0 deletions src/icalendar/cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
>>> # Read a calendar file
# Read a calendar file

>>> cal = Calendar.from_file("src/icalendar/tests/calendars/example.ics")
>>> # Read multiple calendars
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the >>> here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggestion to remove the >>> on the lines where there is a comment? or perhaps just remove the comments altogether?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I remove the >>> from the comments then the test fails
This doesn't work:

        Example:
            >>> from icalendar import Calendar
            >>> from pathlib import Path
            # 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)
            # or pass a Path object
            >>> path = Path("src/icalendar/tests/calendars/example.ics")
            >>> cal = Calendar.from_file(path)

This does work:

        Example:
            >>> from icalendar import Calendar
            >>> from pathlib import Path
            >>> # 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)
            >>> # or pass a Path object
            >>> path = Path("src/icalendar/tests/calendars/example.ics")
            >>> cal = Calendar.from_file(path)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs a new line:

>>> from pathlib import Path

# Read a calendar file

Otherwise the comment is considered output.

>>> 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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples here seem to be the same as above. You can use a temporary file for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the examples to use the to_file() method

>>> # 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
Expand Down
Loading