diff --git a/README.md b/README.md index a31c36e..b354288 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,16 @@ prints out the events in order: ```python from pathlib import Path from ical.calendar_stream import IcsCalendarStream +from ical.exceptions import CalendarParseError filename = Path("example/calendar.ics") with filename.open() as ics_file: - calendar = IcsCalendarStream.calendar_from_ics(ics_file.read()) - -print([event.summary for event in calendar.timeline]) + try: + calendar = IcsCalendarStream.calendar_from_ics(ics_file.read()) + except CalendarParseError as err: + print(f"Failed to parse ics file '{str(filename)}': {err}") + else: + print([event.summary for event in calendar.timeline]) ``` # Writing ics files diff --git a/ical/__init__.py b/ical/__init__.py index 11facda..872e147 100644 --- a/ical/__init__.py +++ b/ical/__init__.py @@ -16,6 +16,7 @@ "todo", "types", "tzif", + "exceptions", "util", "diagnostics", ] diff --git a/ical/calendar_stream.py b/ical/calendar_stream.py index 2327b39..e0118bd 100644 --- a/ical/calendar_stream.py +++ b/ical/calendar_stream.py @@ -29,6 +29,7 @@ from __future__ import annotations import logging +import pyparsing try: from pydantic.v1 import Field @@ -39,6 +40,7 @@ from .component import ComponentModel from .parsing.component import encode_content, parse_content from .types.data_types import DATA_TYPE +from .exceptions import CalendarParseError _LOGGER = logging.getLogger(__name__) @@ -55,13 +57,16 @@ class CalendarStream(ComponentModel): @classmethod def from_ics(cls, content: str) -> "CalendarStream": """Factory method to create a new instance from an rfc5545 iCalendar content.""" - components = parse_content(content) + try: + components = parse_content(content) + except pyparsing.ParseException as err: + raise CalendarParseError(f"Failed to parse calendar stream: {err}") from err result: dict[str, list] = {"vcalendar": []} for component in components: result.setdefault(component.name, []) result[component.name].append(component.as_dict()) _LOGGER.debug("Parsing object %s", result) - return cls.parse_obj(result) + return cls(**result) def ics(self) -> str: """Encode the calendar stream as an rfc5545 iCalendar Stream content.""" @@ -79,7 +84,7 @@ def calendar_from_ics(cls, content: str) -> Calendar: return stream.calendars[0] if len(stream.calendars) == 0: return Calendar() - raise ValueError("Calendar Stream had more than one calendar") + raise CalendarParseError("Calendar Stream had more than one calendar") @classmethod def calendar_to_ics(cls, calendar: Calendar) -> str: diff --git a/ical/component.py b/ical/component.py index 9eeef22..d7a4e8c 100644 --- a/ical/component.py +++ b/ical/component.py @@ -23,15 +23,16 @@ from typing import Any, Union, get_args, get_origin try: - from pydantic.v1 import BaseModel, root_validator + from pydantic.v1 import BaseModel, root_validator, ValidationError from pydantic.v1.fields import SHAPE_LIST except ImportError: - from pydantic import BaseModel, root_validator + from pydantic import BaseModel, root_validator, ValidationError from pydantic.fields import SHAPE_LIST from .parsing.component import ParsedComponent from .parsing.property import ParsedProperty from .types.data_types import DATA_TYPE +from .exceptions import CalendarParseError _LOGGER = logging.getLogger(__name__) @@ -122,6 +123,12 @@ def validate_recurrence_dates( class ComponentModel(BaseModel): """Abstract class for rfc5545 component model.""" + def __init__(self, **data: Any) -> None: + try: + super().__init__(**data) + except ValidationError as err: + raise CalendarParseError(f"Failed to parse component: {err}") from err + @root_validator(pre=True, allow_reuse=True) def parse_extra_fields( cls, values: dict[str, list[ParsedProperty | ParsedComponent]] diff --git a/ical/exceptions.py b/ical/exceptions.py new file mode 100644 index 0000000..9cedb5c --- /dev/null +++ b/ical/exceptions.py @@ -0,0 +1,32 @@ +"""Exceptions for ical library.""" + + +class CalendarError(Exception): + """Base exception for all ical errors.""" + + +class CalendarParseError(CalendarError): + """Exception raised when parsing an ical string.""" + + +class RecurrenceError(CalendarError): + """Exception raised when evaluating a recurrence rule. + + Recurrence rules have complex logic and it is common for there to be + invalid date or bugs, so this special exception exists to help + provide additional debug data to find the source of the issue. Often + `dateutil.rrule` has limitataions and ical has to work around them + by providing special wrapping libraries. + """ + + +class StoreError(CalendarError): + """Exception thrown by a Store.""" + + +class EventStoreError(StoreError): + """Exception thrown by the EventStore.""" + + +class TodoStoreError(StoreError): + """Exception thrown by the TodoStore.""" \ No newline at end of file diff --git a/ical/store.py b/ical/store.py index 4e2aa31..e73ee1f 100644 --- a/ical/store.py +++ b/ical/store.py @@ -17,6 +17,7 @@ from .calendar import Calendar from .event import Event +from .exceptions import StoreError, TodoStoreError, EventStoreError from .todo import Todo from .iter import RulesetIterable from .timezone import Timezone @@ -32,21 +33,10 @@ "EventStoreError", "TodoStore", "TodoStoreError", + "StoreError", ] -class StoreError(Exception): - """Exception thrown by a Store.""" - - -class EventStoreError(StoreError): - """Exception thrown by the EventStore.""" - - -class TodoStoreError(StoreError): - """Exception thrown by the TodoStore.""" - - class EventStore: """An event store manages the lifecycle of events on a Calendar. diff --git a/setup.cfg b/setup.cfg index 98707af..4eaf0f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ical -version = 5.1.1 +version = 6.0.0 description = Python iCalendar implementation (rfc 2445) long_description = file: README.md long_description_content_type = text/markdown diff --git a/tests/test_alarm.py b/tests/test_alarm.py index 60f53d6..76b44b7 100644 --- a/tests/test_alarm.py +++ b/tests/test_alarm.py @@ -5,13 +5,9 @@ import datetime import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError - from ical.alarm import Alarm +from ical.exceptions import CalendarParseError def test_todo() -> None: @@ -38,7 +34,7 @@ def test_duration_and_repeat() -> None: assert alarm.repeat == 2 # Duration but no repeat - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Alarm( action="AUDIO", trigger=datetime.timedelta(minutes=-5), @@ -46,13 +42,13 @@ def test_duration_and_repeat() -> None: ) # Repeat but no duration - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Alarm(action="AUDIO", trigger=datetime.timedelta(minutes=-5), repeat=2) def test_display_required_fields() -> None: """Test required fields for action DISPLAY.""" - with pytest.raises(ValidationError, match="Description value is required for action DISPLAY"): + with pytest.raises(CalendarParseError, match="Description value is required for action DISPLAY"): Alarm(action="DISPLAY", trigger=datetime.timedelta(minutes=-5)) alarm = Alarm( @@ -67,11 +63,11 @@ def test_display_required_fields() -> None: def test_email_required_fields() -> None: """Test required fields for action EMAIL.""" # Missing multiple fields - with pytest.raises(ValidationError, match="Description value is required for action EMAIL"): + with pytest.raises(CalendarParseError, match="Description value is required for action EMAIL"): Alarm(action="EMAIL", trigger=datetime.timedelta(minutes=-5)) # Missing summary - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Alarm( action="EMAIL", trigger=datetime.timedelta(minutes=-5), @@ -79,7 +75,7 @@ def test_email_required_fields() -> None: ) # Missing description - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Alarm( action="EMAIL", trigger=datetime.timedelta(minutes=-5), diff --git a/tests/test_calendar.py b/tests/test_calendar.py index ca58c49..33b843a 100644 --- a/tests/test_calendar.py +++ b/tests/test_calendar.py @@ -457,4 +457,4 @@ def test_floating_time_with_timezone_propagation() -> None: with patch("ical.util.local_timezone", side_effect=ValueError("do not invoke")): it = iter(cal.timeline_tz(zoneinfo.ZoneInfo("Europe/Brussels"))) for i in range(0, 30): - next(it) + next(it) \ No newline at end of file diff --git a/tests/test_calendar_stream.py b/tests/test_calendar_stream.py index e23ff3f..8167a15 100644 --- a/tests/test_calendar_stream.py +++ b/tests/test_calendar_stream.py @@ -2,10 +2,12 @@ from collections.abc import Generator import json +import textwrap import pytest from pytest_golden.plugin import GoldenTestFixture +from ical.exceptions import CalendarParseError from ical.calendar_stream import CalendarStream, IcsCalendarStream @@ -15,20 +17,22 @@ def test_empty_ics(mock_prodid: Generator[None, None, None]) -> None: ics = IcsCalendarStream.calendar_to_ics(calendar) assert ( ics - == """BEGIN:VCALENDAR -PRODID:-//example//1.2.3 -VERSION:2.0 -END:VCALENDAR""" - ) + == textwrap.dedent("""\ + BEGIN:VCALENDAR + PRODID:-//example//1.2.3 + VERSION:2.0 + END:VCALENDAR""" + )) calendar.prodid = "-//example//1.2.4" ics = IcsCalendarStream.calendar_to_ics(calendar) assert ( ics - == """BEGIN:VCALENDAR -PRODID:-//example//1.2.4 -VERSION:2.0 -END:VCALENDAR""" + == textwrap.dedent("""\ + BEGIN:VCALENDAR + PRODID:-//example//1.2.4 + VERSION:2.0 + END:VCALENDAR""") ) @@ -58,3 +62,39 @@ def test_serialize(golden: GoldenTestFixture) -> None: """Fixture to read golden file and compare to golden output.""" cal = IcsCalendarStream.from_ics(golden["input"]) assert cal.ics() == golden.get("encoded", golden["input"]) + + +def test_invalid_ics() -> None: + """Test parsing failures for ics content.""" + with pytest.raises(CalendarParseError, match="Failed to parse calendar stream"): + IcsCalendarStream.calendar_from_ics("invalid") + + +def test_component_failure() -> None: + with pytest.raises(CalendarParseError, match="Failed to parse component"): + IcsCalendarStream.calendar_from_ics( + textwrap.dedent("""\ + BEGIN:VCALENDAR + PRODID:-//example//1.2.3 + VERSION:2.0 + BEGIN:VEVENT + DTSTART:20220724T120000 + DTEND:20220724 + END:VEVENT + END:VCALENDAR + """)) + + +def test_multiple_calendars() -> None: + with pytest.raises(CalendarParseError, match="more than one calendar"): + IcsCalendarStream.calendar_from_ics( + textwrap.dedent("""\ + BEGIN:VCALENDAR + PRODID:-//example//1.2.3 + VERSION:2.0 + END:VCALENDAR + BEGIN:VCALENDAR + PRODID:-//example//1.2.3 + VERSION:2.0 + END:VCALENDAR + """)) \ No newline at end of file diff --git a/tests/test_event.py b/tests/test_event.py index ca4e2f9..03a57c4 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -13,6 +13,7 @@ from pydantic import ValidationError from ical.event import Event +from ical.exceptions import CalendarParseError SUMMARY = "test summary" LOS_ANGELES = zoneinfo.ZoneInfo("America/Los_Angeles") @@ -164,12 +165,12 @@ def test_within_and_includes() -> None: def test_start_end_same_type() -> None: """Verify that the start and end value are the same type.""" - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event( summary=SUMMARY, start=date(2022, 9, 9), end=datetime(2022, 9, 9, 11, 0, 0) ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event( summary=SUMMARY, start=datetime(2022, 9, 9, 10, 0, 0), end=date(2022, 9, 9) ) @@ -190,14 +191,14 @@ def test_start_end_local_time() -> None: end=datetime(2022, 9, 9, 11, 0, 0, tzinfo=timezone.utc), ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event( summary=SUMMARY, start=datetime(2022, 9, 9, 10, 0, 0, tzinfo=timezone.utc), end=datetime(2022, 9, 9, 11, 0, 0), ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event( summary=SUMMARY, start=datetime(2022, 9, 9, 10, 0, 0), @@ -212,10 +213,10 @@ def test_start_and_duration() -> None: assert event.start == date(2022, 9, 9) assert event.end == date(2022, 9, 12) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event(summary=SUMMARY, start=date(2022, 9, 9), duration=timedelta(days=-3)) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Event(summary=SUMMARY, start=date(2022, 9, 9), duration=timedelta(seconds=60)) event = Event( diff --git a/tests/test_freebusy.py b/tests/test_freebusy.py index 0245850..a39e683 100644 --- a/tests/test_freebusy.py +++ b/tests/test_freebusy.py @@ -8,11 +8,8 @@ from unittest.mock import patch import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.freebusy import FreeBusy from ical.types import FreeBusyType, Period @@ -118,7 +115,7 @@ def test_free_busy() -> None: def test_free_busy_requires_utc() -> None: """Test freebusy start date conversions.""" - with pytest.raises(ValidationError, match=r"Freebusy time must be in UTC format.*"): + with pytest.raises(CalendarParseError, match=r"Freebusy time must be in UTC format.*"): FreeBusy( start=datetime.date(2022, 8, 7), end=datetime.date(2022, 8, 10), diff --git a/tests/test_journal.py b/tests/test_journal.py index 9f113be..7ebc68b 100644 --- a/tests/test_journal.py +++ b/tests/test_journal.py @@ -7,11 +7,8 @@ from unittest.mock import patch import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.journal import Journal, JournalStatus @@ -32,7 +29,7 @@ def test_status() -> None: journal = Journal.parse_obj({"status": "DRAFT"}) assert journal.status == JournalStatus.DRAFT - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Journal.parse_obj({"status": "invalid-status"}) diff --git a/tests/test_timezone.py b/tests/test_timezone.py index d67ad82..8041d1b 100644 --- a/tests/test_timezone.py +++ b/tests/test_timezone.py @@ -8,13 +8,10 @@ import pytest from freezegun import freeze_time -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError from ical.calendar import Calendar from ical.calendar_stream import IcsCalendarStream +from ical.exceptions import CalendarParseError from ical.timezone import IcsTimezoneInfo, Observance, Timezone from ical.types import UtcOffset from ical.types.recur import Frequency, Recur, Weekday, WeekdayValue @@ -30,7 +27,7 @@ def test_requires_subcompnent() -> None: """Test Timezone constructor.""" - with pytest.raises(ValidationError, match=r"At least one standard or daylight.*"): + with pytest.raises(CalendarParseError, match=r"At least one standard or daylight.*"): Timezone(tz_id="America/New_York") @@ -68,7 +65,7 @@ def test_daylight() -> None: def test_timezone_observence_start_time_validation() -> None: """Verify that a start time must be in local time.""" with pytest.raises( - ValidationError, match=r".*Start time must be in local time format*" + CalendarParseError, match=r".*Start time must be in local time format*" ): Observance( start=datetime.datetime(1967, 10, 29, 2, tzinfo=datetime.timezone.utc), diff --git a/tests/test_todo.py b/tests/test_todo.py index fa5b6f6..3b5198e 100644 --- a/tests/test_todo.py +++ b/tests/test_todo.py @@ -7,11 +7,8 @@ from unittest.mock import patch import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.todo import Todo @@ -36,7 +33,7 @@ def test_duration() -> None: assert todo.duration # Both due and Duration can't be set - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Todo( start=datetime.date(2022, 8, 7), duration=datetime.timedelta(days=1), @@ -44,7 +41,7 @@ def test_duration() -> None: ) # Duration requires start date - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): Todo(duration=datetime.timedelta(days=1)) todo = Todo(start=datetime.date(2022, 8, 7), due=datetime.date(2022, 8, 8)) diff --git a/tests/types/test_boolean.py b/tests/types/test_boolean.py index cb30e49..a0b6e43 100644 --- a/tests/types/test_boolean.py +++ b/tests/types/test_boolean.py @@ -1,12 +1,9 @@ """Tests for BOOLEAN data types.""" import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError from ical.component import ComponentModel +from ical.exceptions import CalendarParseError from ical.parsing.property import ParsedProperty @@ -29,7 +26,7 @@ def test_bool() -> None: ) assert not model.example - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"example": [ParsedProperty(name="example", value="efd")]}) # Populate based on bool object diff --git a/tests/types/test_date.py b/tests/types/test_date.py index ef76492..635edfa 100644 --- a/tests/types/test_date.py +++ b/tests/types/test_date.py @@ -4,10 +4,7 @@ from typing import Union import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -28,7 +25,7 @@ class TestModel(ComponentModel): ) assert model.d == datetime.date(2022, 7, 24) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): TestModel.parse_obj( { "d": [ diff --git a/tests/types/test_date_time.py b/tests/types/test_date_time.py index 20be165..cf19ed2 100644 --- a/tests/types/test_date_time.py +++ b/tests/types/test_date_time.py @@ -4,12 +4,9 @@ from typing import Union import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.component import ParsedComponent from ical.parsing.property import ParsedProperty, ParsedPropertyParameter @@ -74,7 +71,7 @@ class TestModel(ComponentModel): ) assert model.dt == datetime.date(2022, 7, 24) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): TestModel.parse_obj( { "dt": [ @@ -89,7 +86,7 @@ class TestModel(ComponentModel): } ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): TestModel.parse_obj( { "dt": [ @@ -149,7 +146,7 @@ class Config: ], ) - with pytest.raises(ValueError, match="valid timezone"): + with pytest.raises(CalendarParseError, match="valid timezone"): TestModel.parse_obj( { "dt": [ diff --git a/tests/types/test_float.py b/tests/types/test_float.py index 421cb31..16c5a0f 100644 --- a/tests/types/test_float.py +++ b/tests/types/test_float.py @@ -1,10 +1,7 @@ """Tests for FLOAT data types.""" import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -30,7 +27,7 @@ def test_float() -> None: ) assert model.example == [45, -46.2, 47.32] - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"example": [ParsedProperty(name="example", value="a")]}) model = FakeModel(example=[1, -2.2, 3.5]) diff --git a/tests/types/test_geo.py b/tests/types/test_geo.py index 216a5a9..fc008c0 100644 --- a/tests/types/test_geo.py +++ b/tests/types/test_geo.py @@ -1,10 +1,7 @@ """Library for GEO values.""" import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -25,5 +22,5 @@ class TestModel(ComponentModel): assert model.geo.lat == 120.0 assert model.geo.lng == -30.1 - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): TestModel.parse_obj({"geo": [ParsedProperty(name="geo", value="10")]}) diff --git a/tests/types/test_integer.py b/tests/types/test_integer.py index e0ba8e9..8cd1f55 100644 --- a/tests/types/test_integer.py +++ b/tests/types/test_integer.py @@ -1,10 +1,7 @@ """Tests for INTEGER data types.""" import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -30,5 +27,5 @@ def test_integer() -> None: ) assert model.example == [45, -46, 47] - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"example": [ParsedProperty(name="example", value="a")]}) diff --git a/tests/types/test_period.py b/tests/types/test_period.py index 0741b3c..5a555f2 100644 --- a/tests/types/test_period.py +++ b/tests/types/test_period.py @@ -3,10 +3,7 @@ import datetime import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.component import ParsedComponent @@ -62,20 +59,20 @@ def test_period() -> None: 1997, 1, 1, 23, 30, 0, tzinfo=datetime.timezone.utc ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"example": [ParsedProperty(name="example", value="a")]}) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj( {"example": [ParsedProperty(name="example", value="19970101T180000Z/a")]} ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj( {"example": [ParsedProperty(name="example", value="a/19970102T070000Z")]} ) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj( {"example": [ParsedProperty(name="example", value="a/PT5H30M")]} ) diff --git a/tests/types/test_priority.py b/tests/types/test_priority.py index d986cac..18f4bff 100644 --- a/tests/types/test_priority.py +++ b/tests/types/test_priority.py @@ -1,10 +1,7 @@ """Tests for PRIORITY types.""" import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -26,8 +23,8 @@ def test_priority() -> None: model = FakeModel.parse_obj({"pri": [ParsedProperty(name="dt", value="9")]}) assert model.pri == 9 - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"pri": [ParsedProperty(name="dt", value="-1")]}) - with pytest.raises(ValidationError): + with pytest.raises(CalendarParseError): FakeModel.parse_obj({"pri": [ParsedProperty(name="dt", value="10")]}) diff --git a/tests/types/test_recur.py b/tests/types/test_recur.py index f6ec47e..8623598 100644 --- a/tests/types/test_recur.py +++ b/tests/types/test_recur.py @@ -5,10 +5,7 @@ import zoneinfo import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.calendar import Calendar from ical.component import ComponentModel @@ -627,7 +624,7 @@ def test_until_time_mismatch() -> None: """Test failure case where until has a different timezone than start.""" with pytest.raises( - ValidationError, + CalendarParseError, match="DTSTART was DATE-TIME but UNTIL was DATE", ): Event( @@ -641,7 +638,7 @@ def test_until_time_mismatch() -> None: ) with pytest.raises( - ValidationError, match="DTSTART is date local but UNTIL was not" + CalendarParseError, match="DTSTART is date local but UNTIL was not" ): Event( summary="Bi-annual meeting", @@ -656,7 +653,7 @@ def test_until_time_mismatch() -> None: ) with pytest.raises( - ValidationError, match="DTSTART had UTC or local and UNTIL must be UTC" + CalendarParseError, match="DTSTART had UTC or local and UNTIL must be UTC" ): Event( summary="Bi-annual meeting", @@ -671,7 +668,7 @@ def test_until_time_mismatch() -> None: ) with pytest.raises( - ValidationError, match="DTSTART had UTC or local and UNTIL must be UTC" + CalendarParseError, match="DTSTART had UTC or local and UNTIL must be UTC" ): Event( summary="Bi-annual meeting", diff --git a/tests/types/test_utc_offset.py b/tests/types/test_utc_offset.py index 0694b37..95d6129 100644 --- a/tests/types/test_utc_offset.py +++ b/tests/types/test_utc_offset.py @@ -3,10 +3,7 @@ import datetime import pytest -try: - from pydantic.v1 import ValidationError -except ImportError: - from pydantic import ValidationError +from ical.exceptions import CalendarParseError from ical.component import ComponentModel from ical.parsing.property import ParsedProperty @@ -35,7 +32,7 @@ def test_utc_offset() -> None: model = FakeModel(example=UtcOffset(offset=datetime.timedelta(hours=5))) assert model.example.offset == datetime.timedelta(hours=5) - with pytest.raises(ValidationError, match=r".*match UTC-OFFSET pattern.*"): + with pytest.raises(CalendarParseError, match=r".*match UTC-OFFSET pattern.*"): FakeModel.parse_obj( {"example": [ParsedProperty(name="example", value="abcdef")]}, )