diff --git a/CHANGES.rst b/CHANGES.rst index d863d5b2..bfe45e33 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Changelog Minor changes: +- Format test code with Ruff. See `Issue 672 `_. - Document the Debian package. See `Issue 701 `_. Breaking changes: diff --git a/src/icalendar/tests/conftest.py b/src/icalendar/tests/conftest.py index 28119db3..8db1941e 100644 --- a/src/icalendar/tests/conftest.py +++ b/src/icalendar/tests/conftest.py @@ -3,25 +3,28 @@ except ImportError: import zoneinfo import pytest + import icalendar + try: import pytz except ImportError: pytz = None -from datetime import datetime -from dateutil import tz -from icalendar.cal import Component, Calendar -from icalendar.timezone import tzp as _tzp -from icalendar.timezone import TZP -from pathlib import Path import itertools import sys +from pathlib import Path + +from dateutil import tz + +from icalendar.cal import Calendar, Component +from icalendar.timezone import TZP +from icalendar.timezone import tzp as _tzp HAS_PYTZ = pytz is not None if HAS_PYTZ: PYTZ_UTC = [ pytz.utc, - pytz.timezone('UTC'), + pytz.timezone("UTC"), ] PYTZ_IN_TIMEZONE = [ lambda dt, tzname: pytz.timezone(tzname).localize(dt), @@ -34,14 +37,19 @@ class DataSource: - '''A collection of parsed ICS elements (e.g calendars, timezones, events)''' - def __init__(self, data_source_folder:Path, parser): + """A collection of parsed ICS elements (e.g calendars, timezones, events)""" + + def __init__(self, data_source_folder: Path, parser): self._parser = parser self._data_source_folder = data_source_folder def keys(self): """Return all the files that could be used.""" - return [p.stem for p in self._data_source_folder.iterdir() if p.suffix.lower() == ".ics"] + return [ + p.stem + for p in self._data_source_folder.iterdir() + if p.suffix.lower() == ".ics" + ] def __getitem__(self, attribute): """Parse a file and return the result stored in the attribute.""" @@ -49,11 +57,11 @@ def __getitem__(self, attribute): source_file = attribute attribute = attribute[:-4] else: - source_file = attribute + '.ics' + source_file = attribute + ".ics" source_path = self._data_source_folder / source_file if not source_path.is_file(): raise AttributeError(f"{source_path} does not exist.") - with source_path.open('rb') as f: + with source_path.open("rb") as f: raw_ics = f.read() source = self._parser(raw_ics) if not isinstance(source, list): @@ -76,51 +84,67 @@ def __repr__(self): @property def multiple(self): """Return a list of all components parsed.""" - return self.__class__(self._data_source_folder, lambda data: self._parser(data, multiple=True)) + return self.__class__( + self._data_source_folder, lambda data: self._parser(data, multiple=True) + ) + HERE = Path(__file__).parent -CALENDARS_FOLDER = HERE / 'calendars' -TIMEZONES_FOLDER = HERE / 'timezones' -EVENTS_FOLDER = HERE / 'events' +CALENDARS_FOLDER = HERE / "calendars" +TIMEZONES_FOLDER = HERE / "timezones" +EVENTS_FOLDER = HERE / "events" + @pytest.fixture(scope="module") def calendars(tzp): return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical) + @pytest.fixture(scope="module") def timezones(tzp): return DataSource(TIMEZONES_FOLDER, icalendar.Timezone.from_ical) + @pytest.fixture(scope="module") def events(tzp): return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical) -@pytest.fixture(params=PYTZ_UTC + [ - zoneinfo.ZoneInfo('UTC'), - tz.UTC, - tz.gettz('UTC')]) + +@pytest.fixture(params=PYTZ_UTC + [zoneinfo.ZoneInfo("UTC"), tz.UTC, tz.gettz("UTC")]) def utc(request, tzp): return request.param -@pytest.fixture(params=PYTZ_IN_TIMEZONE + [ - lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)), - lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname)) -]) + +@pytest.fixture( + params=PYTZ_IN_TIMEZONE + + [ + lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)), + lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname)), + ] +) def in_timezone(request, tzp): return request.param # exclude broken calendars here ICS_FILES_EXCLUDE = ( - "big_bad_calendar.ics", "issue_104_broken_calendar.ics", "small_bad_calendar.ics", - "multiple_calendar_components.ics", "pr_480_summary_with_colon.ics", - "parsing_error_in_UTC_offset.ics", "parsing_error.ics", + "big_bad_calendar.ics", + "issue_104_broken_calendar.ics", + "small_bad_calendar.ics", + "multiple_calendar_components.ics", + "pr_480_summary_with_colon.ics", + "parsing_error_in_UTC_offset.ics", + "parsing_error.ics", ) ICS_FILES = [ - file.name for file in - itertools.chain(CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir()) + file.name + for file in itertools.chain( + CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir() + ) if file.name not in ICS_FILES_EXCLUDE ] + + @pytest.fixture(params=ICS_FILES) def ics_file(tzp, calendars, timezones, events, request): """An example ICS file.""" @@ -133,71 +157,76 @@ def ics_file(tzp, calendars, timezones, events, request): FUZZ_V1 = [key for key in CALENDARS_FOLDER.iterdir() if "fuzz-testcase" in str(key)] + + @pytest.fixture(params=FUZZ_V1) def fuzz_v1_calendar(request): """Clusterfuzz calendars.""" return request.param -@pytest.fixture() +@pytest.fixture def x_sometime(): """Map x_sometime to time""" - icalendar.cal.types_factory.types_map['X-SOMETIME'] = 'time' + icalendar.cal.types_factory.types_map["X-SOMETIME"] = "time" yield - icalendar.cal.types_factory.types_map.pop('X-SOMETIME') + icalendar.cal.types_factory.types_map.pop("X-SOMETIME") -@pytest.fixture() +@pytest.fixture def factory(): """Return a new component factory.""" return icalendar.ComponentFactory() -@pytest.fixture() +@pytest.fixture def vUTCOffset_ignore_exceptions(): icalendar.vUTCOffset.ignore_exceptions = True yield icalendar.vUTCOffset.ignore_exceptions = False -@pytest.fixture() +@pytest.fixture def event_component(tzp): """Return an event component.""" c = Component() - c.name = 'VEVENT' + c.name = "VEVENT" return c -@pytest.fixture() +@pytest.fixture def c(tzp): """Return an empty component.""" c = Component() return c + + comp = c -@pytest.fixture() + +@pytest.fixture def calendar_component(tzp): """Return an empty component.""" c = Component() - c.name = 'VCALENDAR' + c.name = "VCALENDAR" return c -@pytest.fixture() +@pytest.fixture def filled_event_component(c, calendar_component): """Return an event with some values and add it to calendar_component.""" - e = Component(summary='A brief history of time') - e.name = 'VEVENT' - e.add('dtend', '20000102T000000', encode=0) - e.add('dtstart', '20000101T000000', encode=0) + e = Component(summary="A brief history of time") + e.name = "VEVENT" + e.add("dtend", "20000102T000000", encode=0) + e.add("dtstart", "20000101T000000", encode=0) calendar_component.add_component(e) return e -@pytest.fixture() +@pytest.fixture def calendar_with_resources(tzp): c = Calendar() - c['resources'] = 'Chair, Table, "Room: 42"' + c["resources"] = 'Chair, Table, "Room: 42"' return c @@ -220,13 +249,13 @@ def other_tzp(request, tzp): return tzp -@pytest.fixture() +@pytest.fixture def pytz_only(tzp): """Skip tests that are not running under pytz.""" assert tzp.uses_pytz() -@pytest.fixture() +@pytest.fixture def zoneinfo_only(tzp, request, tzp_name): """Skip tests that are not running under zoneinfo.""" assert tzp.uses_zoneinfo() @@ -247,14 +276,18 @@ def pytest_generate_tests(metafunc): tzp_names = ["zoneinfo"] if "pytz_only" in metafunc.fixturenames: tzp_names = PYTZ_TZP - assert not ("zoneinfo_only" in metafunc.fixturenames and "pytz_only" in metafunc.fixturenames), "Use pytz_only or zoneinfo_only but not both!" + assert not ( + "zoneinfo_only" in metafunc.fixturenames + and "pytz_only" in metafunc.fixturenames + ), "Use pytz_only or zoneinfo_only but not both!" metafunc.parametrize("tzp_name", tzp_names, scope="module") class DoctestZoneInfo(zoneinfo.ZoneInfo): """Constent ZoneInfo representation for tests.""" + def __repr__(self): - return f"ZoneInfo(key={repr(self.key)})" + return f"ZoneInfo(key={self.key!r})" def doctest_print(obj): @@ -270,13 +303,13 @@ def doctest_import(name, *args, **kw): return pytz return __import__(name, *args, **kw) -@pytest.fixture() + +@pytest.fixture def env_for_doctest(monkeypatch): """Modify the environment to make doctests run.""" monkeypatch.setitem(sys.modules, "zoneinfo", zoneinfo) monkeypatch.setattr(zoneinfo, "ZoneInfo", DoctestZoneInfo) from icalendar.timezone.zoneinfo import ZONEINFO + monkeypatch.setattr(ZONEINFO, "utc", zoneinfo.ZoneInfo("UTC")) - return { - "print": doctest_print - } + return {"print": doctest_print} diff --git a/src/icalendar/tests/fuzzed/__init__.py b/src/icalendar/tests/fuzzed/__init__.py index 2a70d948..17048e6d 100644 --- a/src/icalendar/tests/fuzzed/__init__.py +++ b/src/icalendar/tests/fuzzed/__init__.py @@ -5,7 +5,10 @@ Some more tests can be added to make sure that the behavior works properly. """ -def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_walk: bool): + +def fuzz_calendar_v1( + from_ical, calendar_string: str, multiple: bool, should_walk: bool +): """Take a from_ical function and reproduce the error. The calendar_string is a fuzzed input. @@ -16,7 +19,7 @@ def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_wal cal = [cal] for c in cal: if should_walk: - for event in cal.walk('VEVENT'): + for event in cal.walk("VEVENT"): event.to_ical() else: cal.to_ical() diff --git a/src/icalendar/tests/fuzzed/test_fuzzed_calendars.py b/src/icalendar/tests/fuzzed/test_fuzzed_calendars.py index c38d36fb..31d3ad32 100644 --- a/src/icalendar/tests/fuzzed/test_fuzzed_calendars.py +++ b/src/icalendar/tests/fuzzed/test_fuzzed_calendars.py @@ -1,13 +1,12 @@ """This test tests all fuzzed calendars.""" -from icalendar.tests.fuzzed import fuzz_calendar_v1 + import icalendar +from icalendar.tests.fuzzed import fuzz_calendar_v1 + def test_fuzz_v1(fuzz_v1_calendar): """Test a calendar.""" with open(fuzz_v1_calendar, "rb") as f: fuzz_calendar_v1( - icalendar.Calendar.from_ical, - f.read(), - multiple=True, - should_walk=True + icalendar.Calendar.from_ical, f.read(), multiple=True, should_walk=True ) diff --git a/src/icalendar/tests/hypothesis/test_fuzzing.py b/src/icalendar/tests/hypothesis/test_fuzzing.py index 051f5f53..24f5012f 100644 --- a/src/icalendar/tests/hypothesis/test_fuzzing.py +++ b/src/icalendar/tests/hypothesis/test_fuzzing.py @@ -1,28 +1,24 @@ import string +import unittest -from hypothesis import given, settings import hypothesis.strategies as st +from hypothesis import given, settings from icalendar.parser import Contentline, Contentlines, Parameters -import unittest def printable_characters(**kw): - return st.text( - st.characters(blacklist_categories=( - 'Cc', 'Cs' - ), **kw) - ) + return st.text(st.characters(blacklist_categories=("Cc", "Cs"), **kw)) + key = st.text(string.ascii_letters + string.digits, min_size=1) -value = printable_characters(blacklist_characters='\\;:\"') +value = printable_characters(blacklist_characters='\\;:"') -class TestFuzzing(unittest.TestCase): - @given(lines=st.lists( - st.tuples(key, st.dictionaries(key, value), value), - min_size=1 - )) +class TestFuzzing(unittest.TestCase): + @given( + lines=st.lists(st.tuples(key, st.dictionaries(key, value), value), min_size=1) + ) @settings(max_examples=10**3) def test_main(self, lines): cl = Contentlines() @@ -33,6 +29,6 @@ def test_main(self, lines): # Happens when there is a random parameter 'self'... continue cl.append(Contentline.from_parts(key, params, value)) - cl.append('') + cl.append("") assert Contentlines.from_ical(cl.to_ical()) == cl diff --git a/src/icalendar/tests/prop/__init__.py b/src/icalendar/tests/prop/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/icalendar/tests/prop/test_identity_and_equality.py b/src/icalendar/tests/prop/test_identity_and_equality.py index c2aed769..550bd378 100644 --- a/src/icalendar/tests/prop/test_identity_and_equality.py +++ b/src/icalendar/tests/prop/test_identity_and_equality.py @@ -1,47 +1,70 @@ """Test the identity and equality between properties.""" + +from datetime import date, datetime, time + from icalendar import vDDDTypes -from datetime import datetime, date, time from icalendar.timezone.zoneinfo import zoneinfo + try: import pytz except ImportError: pytz = None -from dateutil import tz -import pytest from copy import deepcopy - +import pytest +from dateutil import tz vDDDTypes_list = [ - vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=zoneinfo.ZoneInfo("Europe/London"))), + vDDDTypes( + datetime( + year=2022, + month=7, + day=22, + hour=12, + minute=7, + tzinfo=zoneinfo.ZoneInfo("Europe/London"), + ) + ), vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7)), vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=tz.UTC)), vDDDTypes(date(year=2022, month=7, day=22)), vDDDTypes(date(year=2022, month=7, day=23)), - vDDDTypes(time(hour=22, minute=7, second=2)) + vDDDTypes(time(hour=22, minute=7, second=2)), ] if pytz: - vDDDTypes_list.append(vDDDTypes(pytz.timezone('EST').localize(datetime(year=2022, month=7, day=22, hour=12, minute=7))),) + vDDDTypes_list.append( + vDDDTypes( + pytz.timezone("EST").localize( + datetime(year=2022, month=7, day=22, hour=12, minute=7) + ) + ), + ) + def identity(x): return x -@pytest.mark.parametrize("map", [ - deepcopy, - identity, - hash, -]) + +@pytest.mark.parametrize( + "map", + [ + deepcopy, + identity, + hash, + ], +) @pytest.mark.parametrize("v_type", vDDDTypes_list) @pytest.mark.parametrize("other", vDDDTypes_list) def test_vDDDTypes_equivalance(map, v_type, other): if v_type is other: assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()" - assert not (map(v_type) != map(other)), f"identity implies equality: {map.__name__}()" + assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()" else: assert map(v_type) != map(other), f"expected inequality: {map.__name__}()" - assert not (map(v_type) == map(other)), f"expected inequality: {map.__name__}()" + assert map(v_type) != map(other), f"expected inequality: {map.__name__}()" + @pytest.mark.parametrize("v_type", vDDDTypes_list) def test_inequality_with_different_types(v_type): assert v_type != 42 - assert v_type != 'test' + assert v_type != "test" diff --git a/src/icalendar/tests/prop/test_property_values.py b/src/icalendar/tests/prop/test_property_values.py index 193c7ba8..d47f523b 100644 --- a/src/icalendar/tests/prop/test_property_values.py +++ b/src/icalendar/tests/prop/test_property_values.py @@ -1,17 +1,19 @@ """Test that composed values are properly converted.""" -from icalendar import Event + from datetime import datetime +from icalendar import Event + def test_vDDDLists_timezone(tzp): """Test vDDDLists with timezone information.""" vevent = Event() - dt1 = tzp.localize(datetime(2013, 1, 1), 'Europe/Vienna') - dt2 = tzp.localize(datetime(2013, 1, 2), 'Europe/Vienna') - dt3 = tzp.localize(datetime(2013, 1, 3), 'Europe/Vienna') - vevent.add('rdate', [dt1, dt2]) - vevent.add('exdate', dt3) + dt1 = tzp.localize(datetime(2013, 1, 1), "Europe/Vienna") + dt2 = tzp.localize(datetime(2013, 1, 2), "Europe/Vienna") + dt3 = tzp.localize(datetime(2013, 1, 3), "Europe/Vienna") + vevent.add("rdate", [dt1, dt2]) + vevent.add("exdate", dt3) ical = vevent.to_ical() - assert b'RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000' in ical - assert b'EXDATE;TZID=Europe/Vienna:20130103T000000' in ical + assert b"RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000" in ical + assert b"EXDATE;TZID=Europe/Vienna:20130103T000000" in ical diff --git a/src/icalendar/tests/prop/test_unit.py b/src/icalendar/tests/prop/test_unit.py index 9240036e..87a725b8 100644 --- a/src/icalendar/tests/prop/test_unit.py +++ b/src/icalendar/tests/prop/test_unit.py @@ -1,118 +1,112 @@ -from datetime import date -from datetime import datetime -from datetime import time -from datetime import timedelta -from icalendar.parser import Parameters import unittest +from datetime import date, datetime, time, timedelta +from icalendar.parser import Parameters -class TestProp(unittest.TestCase): +class TestProp(unittest.TestCase): def test_prop_vFloat(self): from icalendar.prop import vFloat - self.assertEqual(vFloat(1.0).to_ical(), b'1.0') - self.assertEqual(vFloat.from_ical('42'), 42.0) - self.assertEqual(vFloat(42).to_ical(), b'42.0') - self.assertRaises(ValueError, vFloat.from_ical, '1s3') + + self.assertEqual(vFloat(1.0).to_ical(), b"1.0") + self.assertEqual(vFloat.from_ical("42"), 42.0) + self.assertEqual(vFloat(42).to_ical(), b"42.0") + self.assertRaises(ValueError, vFloat.from_ical, "1s3") def test_prop_vInt(self): from icalendar.prop import vInt - self.assertEqual(vInt(42).to_ical(), b'42') - self.assertEqual(vInt.from_ical('13'), 13) - self.assertRaises(ValueError, vInt.from_ical, '1s3') + + self.assertEqual(vInt(42).to_ical(), b"42") + self.assertEqual(vInt.from_ical("13"), 13) + self.assertRaises(ValueError, vInt.from_ical, "1s3") def test_prop_vDDDLists(self): from icalendar.prop import vDDDLists - dt_list = vDDDLists.from_ical('19960402T010000Z') + dt_list = vDDDLists.from_ical("19960402T010000Z") self.assertTrue(isinstance(dt_list, list)) self.assertEqual(len(dt_list), 1) self.assertTrue(isinstance(dt_list[0], datetime)) - self.assertEqual(str(dt_list[0]), '1996-04-02 01:00:00+00:00') + self.assertEqual(str(dt_list[0]), "1996-04-02 01:00:00+00:00") - p = '19960402T010000Z,19960403T010000Z,19960404T010000Z' + p = "19960402T010000Z,19960403T010000Z,19960404T010000Z" dt_list = vDDDLists.from_ical(p) self.assertEqual(len(dt_list), 3) - self.assertEqual(str(dt_list[0]), '1996-04-02 01:00:00+00:00') - self.assertEqual(str(dt_list[2]), '1996-04-04 01:00:00+00:00') + self.assertEqual(str(dt_list[0]), "1996-04-02 01:00:00+00:00") + self.assertEqual(str(dt_list[2]), "1996-04-04 01:00:00+00:00") dt_list = vDDDLists([]) - self.assertEqual(dt_list.to_ical(), b'') + self.assertEqual(dt_list.to_ical(), b"") dt_list = vDDDLists([datetime(2000, 1, 1)]) - self.assertEqual(dt_list.to_ical(), b'20000101T000000') + self.assertEqual(dt_list.to_ical(), b"20000101T000000") dt_list = vDDDLists([datetime(2000, 1, 1), datetime(2000, 11, 11)]) - self.assertEqual(dt_list.to_ical(), b'20000101T000000,20001111T000000') - + self.assertEqual(dt_list.to_ical(), b"20000101T000000,20001111T000000") + instance = vDDDLists([]) self.assertFalse(instance == "value") def test_prop_vDate(self): from icalendar.prop import vDate - self.assertEqual(vDate(date(2001, 1, 1)).to_ical(), b'20010101') - self.assertEqual(vDate(date(1899, 1, 1)).to_ical(), b'18990101') + self.assertEqual(vDate(date(2001, 1, 1)).to_ical(), b"20010101") + self.assertEqual(vDate(date(1899, 1, 1)).to_ical(), b"18990101") - self.assertEqual(vDate.from_ical('20010102'), date(2001, 1, 2)) + self.assertEqual(vDate.from_ical("20010102"), date(2001, 1, 2)) - self.assertRaises(ValueError, vDate, 'd') - self.assertRaises(ValueError, vDate.from_ical, '200102') + self.assertRaises(ValueError, vDate, "d") + self.assertRaises(ValueError, vDate.from_ical, "200102") def test_prop_vDuration(self): from icalendar.prop import vDuration - self.assertEqual(vDuration(timedelta(11)).to_ical(), b'P11D') - self.assertEqual(vDuration(timedelta(-14)).to_ical(), b'-P14D') - self.assertEqual( - vDuration(timedelta(1, 7384)).to_ical(), - b'P1DT2H3M4S' - ) - self.assertEqual(vDuration(timedelta(1, 7380)).to_ical(), b'P1DT2H3M') - self.assertEqual(vDuration(timedelta(1, 7200)).to_ical(), b'P1DT2H') - self.assertEqual(vDuration(timedelta(0, 7200)).to_ical(), b'PT2H') - self.assertEqual(vDuration(timedelta(0, 7384)).to_ical(), b'PT2H3M4S') - self.assertEqual(vDuration(timedelta(0, 184)).to_ical(), b'PT3M4S') - self.assertEqual(vDuration(timedelta(0, 22)).to_ical(), b'PT22S') - self.assertEqual(vDuration(timedelta(0, 3622)).to_ical(), b'PT1H0M22S') - self.assertEqual(vDuration(timedelta(days=1, hours=5)).to_ical(), - b'P1DT5H') - self.assertEqual(vDuration(timedelta(hours=-5)).to_ical(), b'-PT5H') - self.assertEqual(vDuration(timedelta(days=-1, hours=-5)).to_ical(), - b'-P1DT5H') + self.assertEqual(vDuration(timedelta(11)).to_ical(), b"P11D") + self.assertEqual(vDuration(timedelta(-14)).to_ical(), b"-P14D") + self.assertEqual(vDuration(timedelta(1, 7384)).to_ical(), b"P1DT2H3M4S") + self.assertEqual(vDuration(timedelta(1, 7380)).to_ical(), b"P1DT2H3M") + self.assertEqual(vDuration(timedelta(1, 7200)).to_ical(), b"P1DT2H") + self.assertEqual(vDuration(timedelta(0, 7200)).to_ical(), b"PT2H") + self.assertEqual(vDuration(timedelta(0, 7384)).to_ical(), b"PT2H3M4S") + self.assertEqual(vDuration(timedelta(0, 184)).to_ical(), b"PT3M4S") + self.assertEqual(vDuration(timedelta(0, 22)).to_ical(), b"PT22S") + self.assertEqual(vDuration(timedelta(0, 3622)).to_ical(), b"PT1H0M22S") + self.assertEqual(vDuration(timedelta(days=1, hours=5)).to_ical(), b"P1DT5H") + self.assertEqual(vDuration(timedelta(hours=-5)).to_ical(), b"-PT5H") + self.assertEqual(vDuration(timedelta(days=-1, hours=-5)).to_ical(), b"-P1DT5H") # How does the parsing work? - self.assertEqual(vDuration.from_ical('PT1H0M22S'), timedelta(0, 3622)) + self.assertEqual(vDuration.from_ical("PT1H0M22S"), timedelta(0, 3622)) - self.assertRaises(ValueError, vDuration.from_ical, 'kox') + self.assertRaises(ValueError, vDuration.from_ical, "kox") - self.assertEqual(vDuration.from_ical('-P14D'), timedelta(-14)) + self.assertEqual(vDuration.from_ical("-P14D"), timedelta(-14)) self.assertRaises(ValueError, vDuration, 11) # calling to_ical twice should result in same output duration = vDuration(timedelta(days=-1, hours=-5)) - self.assertEqual(duration.to_ical(), b'-P1DT5H') - self.assertEqual(duration.to_ical(), b'-P1DT5H') + self.assertEqual(duration.to_ical(), b"-P1DT5H") + self.assertEqual(duration.to_ical(), b"-P1DT5H") def test_prop_vWeekday(self): from icalendar.prop import vWeekday - self.assertEqual(vWeekday('mo').to_ical(), b'MO') - self.assertRaises(ValueError, vWeekday, 'erwer') - self.assertEqual(vWeekday.from_ical('mo'), 'MO') - self.assertEqual(vWeekday.from_ical('+3mo'), '+3MO') - self.assertRaises(ValueError, vWeekday.from_ical, 'Saturday') - self.assertEqual(vWeekday('+mo').to_ical(), b'+MO') - self.assertEqual(vWeekday('+3mo').to_ical(), b'+3MO') - self.assertEqual(vWeekday('-tu').to_ical(), b'-TU') + self.assertEqual(vWeekday("mo").to_ical(), b"MO") + self.assertRaises(ValueError, vWeekday, "erwer") + self.assertEqual(vWeekday.from_ical("mo"), "MO") + self.assertEqual(vWeekday.from_ical("+3mo"), "+3MO") + self.assertRaises(ValueError, vWeekday.from_ical, "Saturday") + self.assertEqual(vWeekday("+mo").to_ical(), b"+MO") + self.assertEqual(vWeekday("+3mo").to_ical(), b"+3MO") + self.assertEqual(vWeekday("-tu").to_ical(), b"-TU") def test_prop_vFrequency(self): from icalendar.prop import vFrequency - self.assertRaises(ValueError, vFrequency, 'bad test') - self.assertEqual(vFrequency('daily').to_ical(), b'DAILY') - self.assertEqual(vFrequency('daily').from_ical('MONTHLY'), 'MONTHLY') + self.assertRaises(ValueError, vFrequency, "bad test") + self.assertEqual(vFrequency("daily").to_ical(), b"DAILY") + self.assertEqual(vFrequency("daily").from_ical("MONTHLY"), "MONTHLY") self.assertRaises(ValueError, vFrequency.from_ical, 234) def test_prop_vRecur(self): @@ -121,123 +115,117 @@ def test_prop_vRecur(self): # Let's see how close we can get to one from the rfc: # FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 - r = dict({'freq': 'yearly', 'interval': 2}) - r.update({ - 'bymonth': 1, - 'byday': 'su', - 'byhour': [8, 9], - 'byminute': 30 - }) + r = dict({"freq": "yearly", "interval": 2}) + r.update({"bymonth": 1, "byday": "su", "byhour": [8, 9], "byminute": 30}) self.assertEqual( vRecur(r).to_ical(), - b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1' + b"FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1", ) - r = vRecur(FREQ='yearly', INTERVAL=2) - r.update({ - 'BYMONTH': 1, - 'BYDAY': 'su', - 'BYHOUR': [8, 9], - 'BYMINUTE': 30, - }) + r = vRecur(FREQ="yearly", INTERVAL=2) + r.update( + { + "BYMONTH": 1, + "BYDAY": "su", + "BYHOUR": [8, 9], + "BYMINUTE": 30, + } + ) self.assertEqual( r.to_ical(), - b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1' + b"FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1", ) - r = vRecur(freq='DAILY', count=10) - r['bysecond'] = [0, 15, 30, 45] - self.assertEqual(r.to_ical(), - b'FREQ=DAILY;COUNT=10;BYSECOND=0,15,30,45') + r = vRecur(freq="DAILY", count=10) + r["bysecond"] = [0, 15, 30, 45] + self.assertEqual(r.to_ical(), b"FREQ=DAILY;COUNT=10;BYSECOND=0,15,30,45") - r = vRecur(freq='DAILY', until=datetime(2005, 1, 1, 12, 0, 0)) - self.assertEqual(r.to_ical(), b'FREQ=DAILY;UNTIL=20050101T120000') + r = vRecur(freq="DAILY", until=datetime(2005, 1, 1, 12, 0, 0)) + self.assertEqual(r.to_ical(), b"FREQ=DAILY;UNTIL=20050101T120000") # How do we fare with regards to parsing? - r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10') - self.assertEqual(r, - {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]}) - self.assertEqual( - vRecur(r).to_ical(), - b'FREQ=DAILY;COUNT=10;INTERVAL=2' - ) + r = vRecur.from_ical("FREQ=DAILY;INTERVAL=2;COUNT=10") + self.assertEqual(r, {"COUNT": [10], "FREQ": ["DAILY"], "INTERVAL": [2]}) + self.assertEqual(vRecur(r).to_ical(), b"FREQ=DAILY;COUNT=10;INTERVAL=2") - r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;' - 'BYHOUR=8,9;BYMINUTE=30') + r = vRecur.from_ical( + "FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;" "BYHOUR=8,9;BYMINUTE=30" + ) self.assertEqual( r, - {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], - 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]} + { + "BYHOUR": [8, 9], + "BYDAY": ["-SU"], + "BYMINUTE": [30], + "BYMONTH": [1], + "FREQ": ["YEARLY"], + "INTERVAL": [2], + }, ) self.assertEqual( vRecur(r).to_ical(), - b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;' - b'BYMONTH=1' + b"FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;" b"BYMONTH=1", ) - r = vRecur.from_ical('FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH') + r = vRecur.from_ical("FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH") - self.assertEqual( - r, - {'FREQ': ['WEEKLY'], 'INTERVAL': [1], 'BYWEEKDAY': ['TH']} - ) + self.assertEqual(r, {"FREQ": ["WEEKLY"], "INTERVAL": [1], "BYWEEKDAY": ["TH"]}) - self.assertEqual( - vRecur(r).to_ical(), - b'FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH' - ) + self.assertEqual(vRecur(r).to_ical(), b"FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH") # Some examples from the spec - r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1') - self.assertEqual(vRecur(r).to_ical(), - b'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1') + r = vRecur.from_ical("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1") + self.assertEqual( + vRecur(r).to_ical(), b"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1" + ) - p = 'FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30' + p = "FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30" r = vRecur.from_ical(p) self.assertEqual( vRecur(r).to_ical(), - b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1' + b"FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1", ) # and some errors - self.assertRaises(ValueError, vRecur.from_ical, 'BYDAY=12') + self.assertRaises(ValueError, vRecur.from_ical, "BYDAY=12") # when key is not RFC-compliant, parse it as vText - r = vRecur.from_ical('FREQ=MONTHLY;BYOTHER=TEXT;BYEASTER=-3') - self.assertEqual(vRecur(r).to_ical(), - b'FREQ=MONTHLY;BYEASTER=-3;BYOTHER=TEXT') + r = vRecur.from_ical("FREQ=MONTHLY;BYOTHER=TEXT;BYEASTER=-3") + self.assertEqual(vRecur(r).to_ical(), b"FREQ=MONTHLY;BYEASTER=-3;BYOTHER=TEXT") def test_prop_vText(self): from icalendar.prop import vText - self.assertEqual(vText('Simple text').to_ical(), b'Simple text') + self.assertEqual(vText("Simple text").to_ical(), b"Simple text") # Escaped text - t = vText('Text ; with escaped, chars') - self.assertEqual(t.to_ical(), b'Text \\; with escaped\\, chars') + t = vText("Text ; with escaped, chars") + self.assertEqual(t.to_ical(), b"Text \\; with escaped\\, chars") # Escaped newlines - self.assertEqual(vText('Text with escaped\\N chars').to_ical(), - b'Text with escaped\\n chars') + self.assertEqual( + vText("Text with escaped\\N chars").to_ical(), b"Text with escaped\\n chars" + ) # If you pass a unicode object, it will be utf-8 encoded. As this is # the (only) standard that RFC 5545 support. - t = vText('international chars \xe4\xf6\xfc') - self.assertEqual(t.to_ical(), - b'international chars \xc3\xa4\xc3\xb6\xc3\xbc') + t = vText("international chars \xe4\xf6\xfc") + self.assertEqual(t.to_ical(), b"international chars \xc3\xa4\xc3\xb6\xc3\xbc") # and parsing? - self.assertEqual(vText.from_ical('Text \\; with escaped\\, chars'), - 'Text ; with escaped, chars') + self.assertEqual( + vText.from_ical("Text \\; with escaped\\, chars"), + "Text ; with escaped, chars", + ) - t = vText.from_ical('A string with\\; some\\\\ characters in\\it') + t = vText.from_ical("A string with\\; some\\\\ characters in\\it") self.assertEqual(t, "A string with; some\\ characters in\\it") # We are forgiving to utf-8 encoding errors: # We intentionally use a string with unexpected encoding # - self.assertEqual(vText.from_ical(b'Ol\xe9'), 'Ol\ufffd') + self.assertEqual(vText.from_ical(b"Ol\xe9"), "Ol\ufffd") # Notice how accented E character, encoded with latin-1, got replaced # with the official U+FFFD REPLACEMENT CHARACTER. @@ -245,100 +233,96 @@ def test_prop_vText(self): def test_prop_vTime(self): from icalendar.prop import vTime - self.assertEqual(vTime(12, 30, 0).to_ical(), '123000') - self.assertEqual(vTime.from_ical('123000'), time(12, 30)) + self.assertEqual(vTime(12, 30, 0).to_ical(), "123000") + self.assertEqual(vTime.from_ical("123000"), time(12, 30)) # We should also fail, right? - self.assertRaises(ValueError, vTime.from_ical, '263000') + self.assertRaises(ValueError, vTime.from_ical, "263000") - self.assertRaises(ValueError, vTime, '263000') + self.assertRaises(ValueError, vTime, "263000") def test_prop_vUri(self): from icalendar.prop import vUri - self.assertEqual(vUri('http://www.example.com/').to_ical(), - b'http://www.example.com/') - self.assertEqual(vUri.from_ical('http://www.example.com/'), - 'http://www.example.com/') + self.assertEqual( + vUri("http://www.example.com/").to_ical(), b"http://www.example.com/" + ) + self.assertEqual( + vUri.from_ical("http://www.example.com/"), "http://www.example.com/" + ) def test_prop_vGeo(self): from icalendar.prop import vGeo # Pass a list - self.assertEqual(vGeo([1.2, 3.0]).to_ical(), '1.2;3.0') + self.assertEqual(vGeo([1.2, 3.0]).to_ical(), "1.2;3.0") # Pass a tuple - self.assertEqual(vGeo((1.2, 3.0)).to_ical(), '1.2;3.0') + self.assertEqual(vGeo((1.2, 3.0)).to_ical(), "1.2;3.0") - g = vGeo.from_ical('37.386013;-122.082932') - self.assertEqual(g, (float('37.386013'), float('-122.082932'))) + g = vGeo.from_ical("37.386013;-122.082932") + self.assertEqual(g, (float("37.386013"), float("-122.082932"))) - self.assertEqual(vGeo(g).to_ical(), '37.386013;-122.082932') + self.assertEqual(vGeo(g).to_ical(), "37.386013;-122.082932") - self.assertRaises(ValueError, vGeo, 'g') - self.assertRaises(ValueError, vGeo.from_ical, '1s3;1s3') + self.assertRaises(ValueError, vGeo, "g") + self.assertRaises(ValueError, vGeo.from_ical, "1s3;1s3") def test_prop_vUTCOffset(self): from icalendar.prop import vUTCOffset - self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), '+0200') + self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), "+0200") - self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), '-0500') + self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), "-0500") - self.assertEqual(vUTCOffset(timedelta()).to_ical(), '+0000') + self.assertEqual(vUTCOffset(timedelta()).to_ical(), "+0000") - self.assertEqual(vUTCOffset(timedelta(minutes=-30)).to_ical(), - '-0030') + self.assertEqual(vUTCOffset(timedelta(minutes=-30)).to_ical(), "-0030") - self.assertEqual( - vUTCOffset(timedelta(hours=2, minutes=-30)).to_ical(), - '+0130' - ) + self.assertEqual(vUTCOffset(timedelta(hours=2, minutes=-30)).to_ical(), "+0130") - self.assertEqual(vUTCOffset(timedelta(hours=1, minutes=30)).to_ical(), - '+0130') + self.assertEqual(vUTCOffset(timedelta(hours=1, minutes=30)).to_ical(), "+0130") # Support seconds - self.assertEqual(vUTCOffset(timedelta(hours=1, - minutes=30, - seconds=7)).to_ical(), '+013007') + self.assertEqual( + vUTCOffset(timedelta(hours=1, minutes=30, seconds=7)).to_ical(), "+013007" + ) # Parsing - self.assertEqual(vUTCOffset.from_ical('0000'), timedelta(0)) - self.assertEqual(vUTCOffset.from_ical('-0030'), timedelta(-1, 84600)) - self.assertEqual(vUTCOffset.from_ical('+0200'), timedelta(0, 7200)) - self.assertEqual(vUTCOffset.from_ical('+023040'), timedelta(0, 9040)) + self.assertEqual(vUTCOffset.from_ical("0000"), timedelta(0)) + self.assertEqual(vUTCOffset.from_ical("-0030"), timedelta(-1, 84600)) + self.assertEqual(vUTCOffset.from_ical("+0200"), timedelta(0, 7200)) + self.assertEqual(vUTCOffset.from_ical("+023040"), timedelta(0, 9040)) - self.assertEqual(vUTCOffset(vUTCOffset.from_ical('+0230')).to_ical(), - '+0230') + self.assertEqual(vUTCOffset(vUTCOffset.from_ical("+0230")).to_ical(), "+0230") # And a few failures - self.assertRaises(ValueError, vUTCOffset.from_ical, '+323k') + self.assertRaises(ValueError, vUTCOffset.from_ical, "+323k") - self.assertRaises(ValueError, vUTCOffset.from_ical, '+2400') + self.assertRaises(ValueError, vUTCOffset.from_ical, "+2400") - self.assertRaises(ValueError, vUTCOffset, '0:00:00') + self.assertRaises(ValueError, vUTCOffset, "0:00:00") def test_prop_vInline(self): from icalendar.prop import vInline - self.assertEqual(vInline('Some text'), 'Some text') - self.assertEqual(vInline('Some text').to_ical(), b'Some text') - self.assertEqual(vInline.from_ical('Some text'), 'Some text') + self.assertEqual(vInline("Some text"), "Some text") + self.assertEqual(vInline("Some text").to_ical(), b"Some text") + self.assertEqual(vInline.from_ical("Some text"), "Some text") - t2 = vInline('other text') - t2.params['cn'] = 'Test Osterone' + t2 = vInline("other text") + t2.params["cn"] = "Test Osterone" self.assertIsInstance(t2.params, Parameters) - self.assertEqual(t2.params, {'CN': 'Test Osterone'}) + self.assertEqual(t2.params, {"CN": "Test Osterone"}) def test_prop_vCategory(self): from icalendar.prop import vCategory - catz = ['cat 1', 'cat 2', 'cat 3'] + catz = ["cat 1", "cat 2", "cat 3"] v_cat = vCategory(catz) - self.assertEqual(v_cat.to_ical(), b'cat 1,cat 2,cat 3') + self.assertEqual(v_cat.to_ical(), b"cat 1,cat 2,cat 3") self.assertEqual(vCategory.from_ical(v_cat.to_ical()), catz) c = vCategory(vCategory.from_ical("APPOINTMENT,EDUCATION")) cats = list(c) @@ -349,27 +333,31 @@ def test_prop_TypesFactory(self): # To get a type you can use it like this. factory = TypesFactory() - datetime_parser = factory['date-time'] - self.assertEqual(datetime_parser(datetime(2001, 1, 1)).to_ical(), - b'20010101T000000') + datetime_parser = factory["date-time"] + self.assertEqual( + datetime_parser(datetime(2001, 1, 1)).to_ical(), b"20010101T000000" + ) # A typical use is when the parser tries to find a content type and use # text as the default - value = '20050101T123000' - value_type = 'date-time' - self.assertEqual(factory.get(value_type, 'text').from_ical(value), - datetime(2005, 1, 1, 12, 30)) + value = "20050101T123000" + value_type = "date-time" + self.assertEqual( + factory.get(value_type, "text").from_ical(value), + datetime(2005, 1, 1, 12, 30), + ) # It can also be used to directly encode property and parameter values self.assertEqual( - factory.to_ical('comment', 'by Rasmussen, Max M\xfcller'), - b'by Rasmussen\\, Max M\xc3\xbcller' + factory.to_ical("comment", "by Rasmussen, Max M\xfcller"), + b"by Rasmussen\\, Max M\xc3\xbcller", ) - self.assertEqual(factory.to_ical('priority', 1), b'1') - self.assertEqual(factory.to_ical('cn', 'Rasmussen, Max M\xfcller'), - b'Rasmussen\\, Max M\xc3\xbcller') + self.assertEqual(factory.to_ical("priority", 1), b"1") self.assertEqual( - factory.from_ical('cn', b'Rasmussen\\, Max M\xc3\xb8ller'), - 'Rasmussen, Max M\xf8ller' + factory.to_ical("cn", "Rasmussen, Max M\xfcller"), + b"Rasmussen\\, Max M\xc3\xbcller", + ) + self.assertEqual( + factory.from_ical("cn", b"Rasmussen\\, Max M\xc3\xb8ller"), + "Rasmussen, Max M\xf8ller", ) - diff --git a/src/icalendar/tests/prop/test_vBinary.py b/src/icalendar/tests/prop/test_vBinary.py index d3a44893..f8be3e34 100644 --- a/src/icalendar/tests/prop/test_vBinary.py +++ b/src/icalendar/tests/prop/test_vBinary.py @@ -1,4 +1,5 @@ """Test vBinary""" + import pytest from icalendar import vBinary @@ -6,36 +7,39 @@ def test_text(): - txt = b'This is gibberish' - txt_ical = b'VGhpcyBpcyBnaWJiZXJpc2g=' - assert (vBinary(txt).to_ical() == txt_ical) - assert (vBinary.from_ical(txt_ical) == txt) + txt = b"This is gibberish" + txt_ical = b"VGhpcyBpcyBnaWJiZXJpc2g=" + assert vBinary(txt).to_ical() == txt_ical + assert vBinary.from_ical(txt_ical) == txt + def test_binary(): - txt = b'Binary data \x13 \x56' - txt_ical = b'QmluYXJ5IGRhdGEgEyBW' - assert (vBinary(txt).to_ical() == txt_ical) - assert (vBinary.from_ical(txt_ical) == txt) + txt = b"Binary data \x13 \x56" + txt_ical = b"QmluYXJ5IGRhdGEgEyBW" + assert vBinary(txt).to_ical() == txt_ical + assert vBinary.from_ical(txt_ical) == txt + def test_param(): - assert isinstance(vBinary('txt').params, Parameters) - assert ( - vBinary('txt').params == {'VALUE': 'BINARY', 'ENCODING': 'BASE64'} - ) + assert isinstance(vBinary("txt").params, Parameters) + assert vBinary("txt").params == {"VALUE": "BINARY", "ENCODING": "BASE64"} + def test_long_data(): """Long data should not have line breaks, as that would interfere""" - txt = b'a' * 99 - txt_ical = b'YWFh' * 33 - assert (vBinary(txt).to_ical() == txt_ical) - assert (vBinary.from_ical(txt_ical) == txt) - + txt = b"a" * 99 + txt_ical = b"YWFh" * 33 + assert vBinary(txt).to_ical() == txt_ical + assert vBinary.from_ical(txt_ical) == txt + + def test_repr(): instance = vBinary("value") assert repr(instance) == "vBinary(b'dmFsdWU=')" - + + def test_from_ical(): - with pytest.raises(ValueError, match='Not valid base 64 encoding.'): + with pytest.raises(ValueError, match="Not valid base 64 encoding."): vBinary.from_ical("value") - with pytest.raises(ValueError, match='Not valid base 64 encoding.'): - vBinary.from_ical("áèਮ") \ No newline at end of file + with pytest.raises(ValueError, match="Not valid base 64 encoding."): + vBinary.from_ical("áèਮ") diff --git a/src/icalendar/tests/prop/test_vBoolean.py b/src/icalendar/tests/prop/test_vBoolean.py index c9fc433a..67a852f3 100644 --- a/src/icalendar/tests/prop/test_vBoolean.py +++ b/src/icalendar/tests/prop/test_vBoolean.py @@ -1,17 +1,22 @@ -from icalendar.prop import vBoolean import pytest +from icalendar.prop import vBoolean + + def test_true(): - assert (vBoolean(True).to_ical() == b'TRUE') + assert vBoolean(True).to_ical() == b"TRUE" + def test_false(): - assert (vBoolean(0).to_ical() == b'FALSE') + assert vBoolean(0).to_ical() == b"FALSE" + def test_roundtrip(): - assert (vBoolean.from_ical(vBoolean(True).to_ical()) == True) - assert (vBoolean.from_ical('true') == True) + assert vBoolean.from_ical(vBoolean(True).to_ical()) == True + assert vBoolean.from_ical("true") == True + def test_error(): """Error: key not exists""" with pytest.raises(ValueError): - vBoolean.from_ical('ture') + vBoolean.from_ical("ture") diff --git a/src/icalendar/tests/prop/test_vCalAddress.py b/src/icalendar/tests/prop/test_vCalAddress.py index b8e1f789..4079af8c 100644 --- a/src/icalendar/tests/prop/test_vCalAddress.py +++ b/src/icalendar/tests/prop/test_vCalAddress.py @@ -1,9 +1,9 @@ -from icalendar.prop import vCalAddress from icalendar.parser import Parameters +from icalendar.prop import vCalAddress -txt = b'MAILTO:maxm@mxm.dk' +txt = b"MAILTO:maxm@mxm.dk" a = vCalAddress(txt) -a.params['cn'] = 'Max M' +a.params["cn"] = "Max M" def test_to_ical(): @@ -11,14 +11,14 @@ def test_to_ical(): def test_params(): - assert isinstance(a.params, Parameters) - assert a.params == {'CN': 'Max M'} + assert isinstance(a.params, Parameters) + assert a.params == {"CN": "Max M"} def test_from_ical(): - assert vCalAddress.from_ical(txt) == 'MAILTO:maxm@mxm.dk' - + assert vCalAddress.from_ical(txt) == "MAILTO:maxm@mxm.dk" + def test_repr(): instance = vCalAddress("value") - assert repr(instance) == "vCalAddress('value')" \ No newline at end of file + assert repr(instance) == "vCalAddress('value')" diff --git a/src/icalendar/tests/prop/test_vDDDTypes.py b/src/icalendar/tests/prop/test_vDDDTypes.py index 4a54723a..9579758e 100644 --- a/src/icalendar/tests/prop/test_vDDDTypes.py +++ b/src/icalendar/tests/prop/test_vDDDTypes.py @@ -1,30 +1,35 @@ -from icalendar.prop import vDDDTypes -from datetime import date, datetime, timedelta, time +from datetime import date, datetime, time, timedelta + import pytest +from icalendar.prop import vDDDTypes + def test_instance(): - assert isinstance(vDDDTypes.from_ical('20010101T123000'), datetime) - assert isinstance(vDDDTypes.from_ical('20010101'), date) + assert isinstance(vDDDTypes.from_ical("20010101T123000"), datetime) + assert isinstance(vDDDTypes.from_ical("20010101"), date) def test_datetime_with_timezone(tzp): - assert vDDDTypes.from_ical('20010101T123000Z') == \ - tzp.localize_utc(datetime(2001, 1, 1, 12, 30)) + assert vDDDTypes.from_ical("20010101T123000Z") == tzp.localize_utc( + datetime(2001, 1, 1, 12, 30) + ) def test_timedelta(): - assert vDDDTypes.from_ical('P31D') == timedelta(31) - assert vDDDTypes.from_ical('-P31D') == timedelta(-31) + assert vDDDTypes.from_ical("P31D") == timedelta(31) + assert vDDDTypes.from_ical("-P31D") == timedelta(-31) def test_bad_input(): with pytest.raises(ValueError): vDDDTypes(42) + def test_time_from_string(): - assert vDDDTypes.from_ical('123000') == time(12, 30) - assert isinstance(vDDDTypes.from_ical('123000'), time) + assert vDDDTypes.from_ical("123000") == time(12, 30) + assert isinstance(vDDDTypes.from_ical("123000"), time) + def test_invalid_period_to_ical(): invalid_period = (datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 2)) diff --git a/src/icalendar/tests/prop/test_vDatetime.py b/src/icalendar/tests/prop/test_vDatetime.py index 489edff4..00a89726 100644 --- a/src/icalendar/tests/prop/test_vDatetime.py +++ b/src/icalendar/tests/prop/test_vDatetime.py @@ -1,42 +1,47 @@ -from icalendar.prop import vDatetime -import pytest from datetime import datetime +import pytest + +from icalendar.prop import vDatetime + def test_to_ical(): - assert vDatetime(datetime(2001, 1, 1, 12, 30, 0)).to_ical() == b'20010101T123000' + assert vDatetime(datetime(2001, 1, 1, 12, 30, 0)).to_ical() == b"20010101T123000" + def test_from_ical(): - assert vDatetime.from_ical('20000101T120000') == datetime(2000, 1, 1, 12, 0) - assert vDatetime.from_ical('20010101T000000') == datetime(2001, 1, 1, 0, 0) + assert vDatetime.from_ical("20000101T120000") == datetime(2000, 1, 1, 12, 0) + assert vDatetime.from_ical("20010101T000000") == datetime(2001, 1, 1, 0, 0) + def test_to_ical_utc(tzp): dutc = tzp.localize_utc(datetime(2001, 1, 1, 12, 30, 0)) - assert vDatetime(dutc).to_ical() == b'20010101T123000Z' + assert vDatetime(dutc).to_ical() == b"20010101T123000Z" + def test_to_ical_utc_1899(tzp): dutc = tzp.localize_utc(datetime(1899, 1, 1, 12, 30, 0)) - assert vDatetime(dutc).to_ical() == b'18990101T123000Z' + assert vDatetime(dutc).to_ical() == b"18990101T123000Z" def test_bad_ical(): with pytest.raises(ValueError): - vDatetime.from_ical('20010101T000000A') + vDatetime.from_ical("20010101T000000A") def test_roundtrip(): - utc = vDatetime.from_ical('20010101T000000Z') - assert vDatetime(utc).to_ical() == b'20010101T000000Z' + utc = vDatetime.from_ical("20010101T000000Z") + assert vDatetime(utc).to_ical() == b"20010101T000000Z" def test_transition(tzp): # 1 minute before transition to DST - dat = vDatetime.from_ical('20120311T015959', 'America/Denver') - assert dat.strftime('%Y%m%d%H%M%S %z') =='20120311015959 -0700' + dat = vDatetime.from_ical("20120311T015959", "America/Denver") + assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311015959 -0700" # After transition to DST - dat = vDatetime.from_ical('20120311T030000', 'America/Denver') - assert dat.strftime('%Y%m%d%H%M%S %z') == '20120311030000 -0600' + dat = vDatetime.from_ical("20120311T030000", "America/Denver") + assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311030000 -0600" - dat = vDatetime.from_ical('20101010T000000', 'Europe/Vienna') - assert vDatetime(dat).to_ical() == b'20101010T000000' + dat = vDatetime.from_ical("20101010T000000", "Europe/Vienna") + assert vDatetime(dat).to_ical() == b"20101010T000000" diff --git a/src/icalendar/tests/prop/test_vPeriod.py b/src/icalendar/tests/prop/test_vPeriod.py index 5b951774..c1485f22 100644 --- a/src/icalendar/tests/prop/test_vPeriod.py +++ b/src/icalendar/tests/prop/test_vPeriod.py @@ -1,63 +1,61 @@ import unittest -from icalendar.prop import vPeriod from datetime import datetime, timedelta + import pytest +from icalendar.prop import vPeriod -class TestProp(unittest.TestCase): +class TestProp(unittest.TestCase): def test_one_day(self): # One day in exact datetimes per = (datetime(2000, 1, 1), datetime(2000, 1, 2)) - self.assertEqual(vPeriod(per).to_ical(), - b'20000101T000000/20000102T000000') + self.assertEqual(vPeriod(per).to_ical(), b"20000101T000000/20000102T000000") per = (datetime(2000, 1, 1), timedelta(days=31)) - self.assertEqual(vPeriod(per).to_ical(), b'20000101T000000/P31D') + self.assertEqual(vPeriod(per).to_ical(), b"20000101T000000/P31D") def test_roundtrip(self): - p = vPeriod.from_ical('20000101T000000/20000102T000000') + p = vPeriod.from_ical("20000101T000000/20000102T000000") + self.assertEqual(p, (datetime(2000, 1, 1, 0, 0), datetime(2000, 1, 2, 0, 0))) + self.assertEqual(vPeriod(p).to_ical(), b"20000101T000000/20000102T000000") + self.assertEqual( - p, - (datetime(2000, 1, 1, 0, 0), datetime(2000, 1, 2, 0, 0)) + vPeriod.from_ical("20000101T000000/P31D"), + (datetime(2000, 1, 1, 0, 0), timedelta(31)), ) - self.assertEqual(vPeriod(p).to_ical(), - b'20000101T000000/20000102T000000') - - self.assertEqual(vPeriod.from_ical('20000101T000000/P31D'), - (datetime(2000, 1, 1, 0, 0), timedelta(31))) def test_round_trip_with_absolute_time(self): - p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z') - self.assertEqual(vPeriod(p).to_ical(), - b'20000101T000000Z/20000102T000000Z') + p = vPeriod.from_ical("20000101T000000Z/20000102T000000Z") + self.assertEqual(vPeriod(p).to_ical(), b"20000101T000000Z/20000102T000000Z") def test_bad_input(self): - self.assertRaises(ValueError, - vPeriod.from_ical, '20000101T000000/Psd31D') + self.assertRaises(ValueError, vPeriod.from_ical, "20000101T000000/Psd31D") def test_timezoned(tzp): - start = tzp.localize(datetime(2000, 1, 1), 'Europe/Copenhagen') - end = tzp.localize(datetime(2000, 1, 2), 'Europe/Copenhagen') + start = tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen") + end = tzp.localize(datetime(2000, 1, 2), "Europe/Copenhagen") per = (start, end) - assert vPeriod(per).to_ical() == b'20000101T000000/20000102T000000' - assert vPeriod(per).params['TZID'] == 'Europe/Copenhagen' + assert vPeriod(per).to_ical() == b"20000101T000000/20000102T000000" + assert vPeriod(per).params["TZID"] == "Europe/Copenhagen" def test_timezoned_with_timedelta(tzp): - p = vPeriod((tzp.localize(datetime(2000, 1, 1), 'Europe/Copenhagen'), timedelta(days=31))) - assert p.to_ical() == b'20000101T000000/P31D' + p = vPeriod( + (tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen"), timedelta(days=31)) + ) + assert p.to_ical() == b"20000101T000000/P31D" @pytest.mark.parametrize( "params", [ - ('20000101T000000', datetime(2000, 1, 2)), - (datetime(2000, 1, 1), '20000102T000000'), + ("20000101T000000", datetime(2000, 1, 2)), + (datetime(2000, 1, 1), "20000102T000000"), (datetime(2000, 1, 2), datetime(2000, 1, 1)), (datetime(2000, 1, 2), timedelta(-1)), - ] + ], ) def test_invalid_parameters(params): """The parameters are of wrong type or of wrong order.""" diff --git a/src/icalendar/tests/prop/test_windows_to_olson_mapping.py b/src/icalendar/tests/prop/test_windows_to_olson_mapping.py index dfb2b9a7..2202c589 100644 --- a/src/icalendar/tests/prop/test_windows_to_olson_mapping.py +++ b/src/icalendar/tests/prop/test_windows_to_olson_mapping.py @@ -1,14 +1,17 @@ """Test the mappings from windows to olson tzids""" -from icalendar.timezone.windows_to_olson import WINDOWS_TO_OLSON + +from datetime import datetime + import pytest + from icalendar import vDatetime -from datetime import datetime +from icalendar.timezone.windows_to_olson import WINDOWS_TO_OLSON def test_windows_timezone(tzp): """Test that the timezone is mapped correctly to olson.""" - dt = vDatetime.from_ical('20170507T181920', 'Eastern Standard Time') - expected = tzp.localize(datetime(2017, 5, 7, 18, 19, 20), 'America/New_York') + dt = vDatetime.from_ical("20170507T181920", "Eastern Standard Time") + expected = tzp.localize(datetime(2017, 5, 7, 18, 19, 20), "America/New_York") assert dt.tzinfo == dt.tzinfo assert dt == expected diff --git a/src/icalendar/tests/test_bom_calendar.py b/src/icalendar/tests/test_bom_calendar.py index b6f2d020..c5888190 100644 --- a/src/icalendar/tests/test_bom_calendar.py +++ b/src/icalendar/tests/test_bom_calendar.py @@ -1,2 +1,4 @@ def test_bom_calendar(calendars): - assert calendars.bom_calendar.walk('VCALENDAR'), "Unable to parse a calendar starting with an Unicode BOM" + assert calendars.bom_calendar.walk( + "VCALENDAR" + ), "Unable to parse a calendar starting with an Unicode BOM" diff --git a/src/icalendar/tests/test_cli_tool.py b/src/icalendar/tests/test_cli_tool.py index 802a1132..2a54f0c0 100644 --- a/src/icalendar/tests/test_cli_tool.py +++ b/src/icalendar/tests/test_cli_tool.py @@ -1,13 +1,14 @@ import unittest - from datetime import datetime + from icalendar import Calendar, cli + try: import zoneinfo except ModuleNotFoundError: from backports import zoneinfo -INPUT = ''' +INPUT = """ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -39,16 +40,23 @@ DURATION:P5D END:VEVENT END:VCALENDAR -''' +""" + def local_datetime(dt): - return datetime.strptime(dt, "%Y%m%dT%H%M%S").replace(tzinfo=zoneinfo.ZoneInfo("Europe/Warsaw")).astimezone().strftime('%c') + return ( + datetime.strptime(dt, "%Y%m%dT%H%M%S") + .replace(tzinfo=zoneinfo.ZoneInfo("Europe/Warsaw")) + .astimezone() + .strftime("%c") + ) + # datetimes are displayed in the local timezone, so we cannot just hardcode them -firststart = local_datetime('20220820T103400') -firstend = local_datetime('20220820T113400') -secondstart = local_datetime('20220820T200000') -secondend = local_datetime('20220820T203000') +firststart = local_datetime("20220820T103400") +firstend = local_datetime("20220820T113400") +secondstart = local_datetime("20220820T200000") +secondend = local_datetime("20220820T203000") PROPER_OUTPUT = f""" Organizer: organizer Attendees: @@ -91,15 +99,16 @@ def local_datetime(dt): """ + class CLIToolTest(unittest.TestCase): def test_output_is_proper(self): self.maxDiff = None calendar = Calendar.from_ical(INPUT) - output = '' - for event in calendar.walk('vevent'): - output += cli.view(event) + '\n\n' + output = "" + for event in calendar.walk("vevent"): + output += cli.view(event) + "\n\n" self.assertEqual(PROPER_OUTPUT, output) -if __name__ == '__main__': - unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/src/icalendar/tests/test_components_break_on_bad_ics.py b/src/icalendar/tests/test_components_break_on_bad_ics.py index 5a2f2d8b..8d86b237 100644 --- a/src/icalendar/tests/test_components_break_on_bad_ics.py +++ b/src/icalendar/tests/test_components_break_on_bad_ics.py @@ -2,36 +2,47 @@ def test_ignore_exceptions_on_broken_events_issue_104(events): - ''' Issue #104 - line parsing error in a VEVENT + """Issue #104 - line parsing error in a VEVENT (which has ignore_exceptions). Should mark the event broken but not raise an exception. https://github.com/collective/icalendar/issues/104 - ''' - assert events.issue_104_mark_events_broken.errors == [(None, "Content line could not be parsed into parts: 'X': Invalid content line")] + """ + assert events.issue_104_mark_events_broken.errors == [ + (None, "Content line could not be parsed into parts: 'X': Invalid content line") + ] + def test_dont_ignore_exceptions_on_broken_calendars_issue_104(calendars): - '''Issue #104 - line parsing error in a VCALENDAR + """Issue #104 - line parsing error in a VCALENDAR (which doesn't have ignore_exceptions). Should raise an exception. - ''' + """ with pytest.raises(ValueError): calendars.issue_104_broken_calendar + def test_rdate_dosent_become_none_on_invalid_input_issue_464(events): - '''Issue #464 - [BUG] RDATE can become None if value is invalid + """Issue #464 - [BUG] RDATE can become None if value is invalid https://github.com/collective/icalendar/issues/464 - ''' - assert ('RDATE', 'Expected period format, got: 199709T180000Z/PT5H30M') in events.issue_464_invalid_rdate.errors - assert b'RDATE:None' not in events.issue_464_invalid_rdate.to_ical() - -@pytest.mark.parametrize('calendar_name', [ - 'big_bad_calendar', - 'small_bad_calendar', - 'multiple_calendar_components', - 'pr_480_summary_with_colon', -]) + """ + assert ( + "RDATE", + "Expected period format, got: 199709T180000Z/PT5H30M", + ) in events.issue_464_invalid_rdate.errors + assert b"RDATE:None" not in events.issue_464_invalid_rdate.to_ical() + + +@pytest.mark.parametrize( + "calendar_name", + [ + "big_bad_calendar", + "small_bad_calendar", + "multiple_calendar_components", + "pr_480_summary_with_colon", + ], +) def test_error_message_doesnt_get_too_big(calendars, calendar_name): with pytest.raises(ValueError) as exception: calendars[calendar_name] # Ignore part before first : for the test. - assert len(str(exception).split(': ', 1)[1]) <= 100 + assert len(str(exception).split(": ", 1)[1]) <= 100 diff --git a/src/icalendar/tests/test_encoding.py b/src/icalendar/tests/test_encoding.py index 77bcb26d..4cc1557b 100644 --- a/src/icalendar/tests/test_encoding.py +++ b/src/icalendar/tests/test_encoding.py @@ -1,71 +1,98 @@ -import pytest import datetime -@pytest.mark.parametrize('field, expected_value', [ - ('PRODID', '-//Plönë.org//NONSGML plone.app.event//EN'), - ('X-WR-CALDESC', 'test non ascii: äöü ÄÖÜ €'), -]) +import pytest + + +@pytest.mark.parametrize( + ("field", "expected_value"), + [ + ("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN"), + ("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €"), + ], +) def test_calendar_from_ical_respects_unicode(field, expected_value, calendars): cal = calendars.calendar_with_unicode - assert cal[field].to_ical().decode('utf-8') == expected_value - -@pytest.mark.parametrize('test_input, field, expected_value', [ - ('event_with_unicode_fields', 'SUMMARY', 'Non-ASCII Test: ÄÖÜ äöü €'), - ('event_with_unicode_fields', 'DESCRIPTION', 'icalendar should be able to handle non-ascii: €äüöÄÜÖ.'), - ('event_with_unicode_fields', 'LOCATION', 'Tribstrül'), - # Non-unicode characters in summary - # https://github.com/collective/icalendar/issues/64 - ('issue_64_event_with_non_ascii_summary', 'SUMMARY', 'åäö'), - # Unicode characters in summary - ('issue_64_event_with_ascii_summary', 'SUMMARY', 'abcdef'), -]) + assert cal[field].to_ical().decode("utf-8") == expected_value + + +@pytest.mark.parametrize( + ("test_input", "field", "expected_value"), + [ + ("event_with_unicode_fields", "SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €"), + ( + "event_with_unicode_fields", + "DESCRIPTION", + "icalendar should be able to handle non-ascii: €äüöÄÜÖ.", + ), + ("event_with_unicode_fields", "LOCATION", "Tribstrül"), + # Non-unicode characters in summary + # https://github.com/collective/icalendar/issues/64 + ("issue_64_event_with_non_ascii_summary", "SUMMARY", "åäö"), + # Unicode characters in summary + ("issue_64_event_with_ascii_summary", "SUMMARY", "abcdef"), + ], +) def test_event_from_ical_respects_unicode(test_input, field, expected_value, events): event = events[test_input] - assert event[field].to_ical().decode('utf-8') == expected_value - -@pytest.mark.parametrize('test_input, expected_output', [ - # chokes on umlauts in ORGANIZER - # https://github.com/collective/icalendar/issues/101 - ('issue_101_icalendar_chokes_on_umlauts_in_organizer', 'acme, ädmin'), - ('event_with_unicode_organizer', 'Джон Доу'), -]) + assert event[field].to_ical().decode("utf-8") == expected_value + + +@pytest.mark.parametrize( + ("test_input", "expected_output"), + [ + # chokes on umlauts in ORGANIZER + # https://github.com/collective/icalendar/issues/101 + ("issue_101_icalendar_chokes_on_umlauts_in_organizer", "acme, ädmin"), + ("event_with_unicode_organizer", "Джон Доу"), + ], +) def test_events_parameter_unicoded(events, test_input, expected_output): - assert events[test_input]['ORGANIZER'].params['CN'] == expected_output + assert events[test_input]["ORGANIZER"].params["CN"] == expected_output + def test_parses_event_with_non_ascii_tzid_issue_237(calendars, in_timezone): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ - start = calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.walk('VEVENT')[0].decoded('DTSTART') - expected = in_timezone(datetime.datetime(2017, 5, 11, 13, 30), 'America/Sao_Paulo') + start = calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.walk( + "VEVENT" + )[0].decoded("DTSTART") + expected = in_timezone(datetime.datetime(2017, 5, 11, 13, 30), "America/Sao_Paulo") assert not calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.errors assert start == expected + def test_parses_timezone_with_non_ascii_tzid_issue_237(timezones): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ - assert timezones.issue_237_brazilia_standard['tzid'] == '(UTC-03:00) Brasília' + assert timezones.issue_237_brazilia_standard["tzid"] == "(UTC-03:00) Brasília" -@pytest.mark.parametrize('timezone_name', ['standard', 'daylight']) + +@pytest.mark.parametrize("timezone_name", ["standard", "daylight"]) def test_parses_timezone_with_non_ascii_tzname_issue_273(timezones, timezone_name): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ - assert timezones.issue_237_brazilia_standard.walk(timezone_name)[0]['TZNAME'] == f'Brasília {timezone_name}' + assert ( + timezones.issue_237_brazilia_standard.walk(timezone_name)[0]["TZNAME"] + == f"Brasília {timezone_name}" + ) + def test_broken_property(calendars): """ Test if error messages are encoded properly. """ - for event in calendars.broken_ical.walk('vevent'): - assert len(event.errors) == 1, 'Not the right amount of errors.' + for event in calendars.broken_ical.walk("vevent"): + assert len(event.errors) == 1, "Not the right amount of errors." error = event.errors[0][1] - assert error.startswith('Content line could not be parsed into parts') + assert error.startswith("Content line could not be parsed into parts") + def test_apple_xlocation(calendars): """ Test if we support base64 encoded binary data in parameter values. """ - for event in calendars.x_location.walk('vevent'): - assert len(event.errors) == 0, 'Got too many errors' + for event in calendars.x_location.walk("vevent"): + assert len(event.errors) == 0, "Got too many errors" diff --git a/src/icalendar/tests/test_equality.py b/src/icalendar/tests/test_equality.py index 5cb1cac8..3a76cef3 100644 --- a/src/icalendar/tests/test_equality.py +++ b/src/icalendar/tests/test_equality.py @@ -1,22 +1,42 @@ """Test the equality and inequality of components.""" + +import contextlib import copy + try: - import pytz + from pytz import UnknownTimeZoneError except ImportError: - pytz = None -from icalendar.prop import * -from datetime import datetime, date, time, timedelta + class UnknownTimeZoneError(Exception): + pass +from datetime import date, datetime, time, timedelta + import pytest +from icalendar.prop import ( + vBinary, + vBoolean, + vCategory, + vDate, + vDatetime, + vDDDLists, + vDDDTypes, + vDuration, + vGeo, + vPeriod, + vText, + vTime, +) + + def assert_equal(actual_value, expected_value): """Make sure both values are equal""" assert actual_value == expected_value - assert not actual_value != expected_value + assert actual_value == expected_value def assert_not_equal(actual_value, expected_value): """Make sure both values are not equal""" - assert not actual_value == expected_value + assert actual_value != expected_value assert actual_value != expected_value @@ -42,8 +62,10 @@ def test_parsed_calendars_are_equal_if_from_same_source(ics_file, tzp): def test_copies_are_equal(ics_file, tzp): """Ensure that copies are equal.""" - copy1 = ics_file.copy(); copy1.subcomponents = ics_file.subcomponents - copy2 = ics_file.copy(); copy2.subcomponents = ics_file.subcomponents[:] + copy1 = ics_file.copy() + copy1.subcomponents = ics_file.subcomponents + copy2 = ics_file.copy() + copy2.subcomponents = ics_file.subcomponents[:] assert_equal(copy1, copy2) assert_equal(copy1, ics_file) assert_equal(copy2, ics_file) @@ -56,20 +78,15 @@ def test_copy_does_not_copy_subcomponents(calendars, tzp): def test_deep_copies_are_equal(ics_file, tzp): - """Ensure that deep copies are equal.""" - try: + """Ensure that deep copies are equal. + + Ignore errors when a custom time zone is used. + This is still covered by the parsing test. + """ + with contextlib.suppress(UnknownTimeZoneError): assert_equal(copy.deepcopy(ics_file), copy.deepcopy(ics_file)) - except pytz.UnknownTimeZoneError: - # Ignore errors when a custom time zone is used. - # This is still covered by the parsing test. - pass - try: + with contextlib.suppress(UnknownTimeZoneError): assert_equal(copy.deepcopy(ics_file), ics_file) - except pytz.UnknownTimeZoneError: - # Ignore errors when a custom time zone is used. - # This is still covered by the parsing test. - pass - def test_vGeo(): @@ -80,20 +97,20 @@ def test_vGeo(): def test_vBinary(): - assert_equal(vBinary('asd'), vBinary('asd')) - assert_not_equal(vBinary('asdf'), vBinary('asd')) + assert_equal(vBinary("asd"), vBinary("asd")) + assert_not_equal(vBinary("asdf"), vBinary("asd")) def test_vBoolean(): - assert_equal(vBoolean.from_ical('TRUE'), vBoolean.from_ical('TRUE')) - assert_equal(vBoolean.from_ical('FALSE'), vBoolean.from_ical('FALSE')) - assert_not_equal(vBoolean.from_ical('TRUE'), vBoolean.from_ical('FALSE')) + assert_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("TRUE")) + assert_equal(vBoolean.from_ical("FALSE"), vBoolean.from_ical("FALSE")) + assert_not_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("FALSE")) def test_vCategory(): assert_equal(vCategory("HELLO"), vCategory("HELLO")) - assert_equal(vCategory(["a","b"]), vCategory(["a","b"])) - assert_not_equal(vCategory(["a","b"]), vCategory(["a","b", "c"])) + assert_equal(vCategory(["a", "b"]), vCategory(["a", "b"])) + assert_not_equal(vCategory(["a", "b"]), vCategory(["a", "b", "c"])) def test_vText(): @@ -102,21 +119,33 @@ def test_vText(): @pytest.mark.parametrize( - "vType,v1,v2", + ("vType", "v1", "v2"), [ (vDatetime, datetime(2023, 11, 1, 10, 11), datetime(2023, 11, 1, 10, 10)), (vDate, date(2023, 11, 1), date(2023, 10, 31)), (vDuration, timedelta(3, 11, 1), timedelta(23, 10, 31)), - (vPeriod, (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(23, 10, 31))), - (vPeriod, (datetime(2023, 11, 1, 10, 1), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1))), - (vPeriod, (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 3)), (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 2))), + ( + vPeriod, + (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), + (datetime(2023, 11, 1, 10, 11), timedelta(23, 10, 31)), + ), + ( + vPeriod, + (datetime(2023, 11, 1, 10, 1), timedelta(3, 11, 1)), + (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), + ), + ( + vPeriod, + (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 3)), + (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 2)), + ), (vTime, time(10, 10, 10), time(10, 10, 11)), - ] + ], ) @pytest.mark.parametrize("eq", ["==", "!="]) @pytest.mark.parametrize("cls1", [0, 1]) @pytest.mark.parametrize("cls2", [0, 1]) -@pytest.mark.parametrize("hash", [lambda x:x, hash]) +@pytest.mark.parametrize("hash", [lambda x: x, hash]) def test_vDDDTypes_and_others(vType, v1, v2, cls1, cls2, eq, hash): """Check equality and inequality.""" t1 = (vType, vDDDTypes)[cls1] @@ -124,7 +153,7 @@ def test_vDDDTypes_and_others(vType, v1, v2, cls1, cls2, eq, hash): if eq == "==": assert hash(v1) == hash(v1) assert hash(t1(v1)) == hash(t2(v1)) - assert not hash(t1(v1)) != hash(t2(v1)) + assert hash(t1(v1)) == hash(t2(v1)) else: assert hash(v1) != hash(v2) assert hash(t1(v1)) != hash(t2(v2)) @@ -134,11 +163,13 @@ def test_repr_vDDDTypes(): assert "vDDDTypes" in repr(vDDDTypes(timedelta(3, 11, 1))) -vDDDLists_examples = [ +vDDDLists_examples = [ # noqa: N816 vDDDLists([]), vDDDLists([datetime(2023, 11, 1, 10, 1)]), vDDDLists([datetime(2023, 11, 1, 10, 1), date(2023, 11, 1)]), ] + + @pytest.mark.parametrize("l1", vDDDLists_examples) @pytest.mark.parametrize("l2", vDDDLists_examples) def test_vDDDLists(l1, l2): diff --git a/src/icalendar/tests/test_examples.py b/src/icalendar/tests/test_examples.py index 3443d066..9f9b891b 100644 --- a/src/icalendar/tests/test_examples.py +++ b/src/icalendar/tests/test_examples.py @@ -1,51 +1,54 @@ -'''tests ensuring that *the* way of doing things works''' +"""tests ensuring that *the* way of doing things works""" import datetime -from icalendar import Calendar, Event, Timezone + import pytest +from icalendar import Calendar, Event, Timezone + def test_creating_calendar_with_unicode_fields(calendars, utc): - ''' create a calendar with events that contain unicode characters in their fields ''' + """create a calendar with events that contain unicode characters in their fields""" cal = Calendar() - cal.add('PRODID', '-//Plönë.org//NONSGML plone.app.event//EN') - cal.add('VERSION', '2.0') - cal.add('X-WR-CALNAME', 'äöü ÄÖÜ €') - cal.add('X-WR-CALDESC', 'test non ascii: äöü ÄÖÜ €') - cal.add('X-WR-RELCALID', '12345') + cal.add("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN") + cal.add("VERSION", "2.0") + cal.add("X-WR-CALNAME", "äöü ÄÖÜ €") + cal.add("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €") + cal.add("X-WR-RELCALID", "12345") event = Event() - event.add('DTSTART', datetime.datetime(2010, 10, 10, 10, 0, 0, tzinfo=utc)) - event.add('DTEND', datetime.datetime(2010, 10, 10, 12, 0, 0, tzinfo=utc)) - event.add('CREATED', datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) - event.add('UID', '123456') - event.add('SUMMARY', 'Non-ASCII Test: ÄÖÜ äöü €') - event.add('DESCRIPTION', 'icalendar should be able to de/serialize non-ascii.') - event.add('LOCATION', 'Tribstrül') + event.add("DTSTART", datetime.datetime(2010, 10, 10, 10, 0, 0, tzinfo=utc)) + event.add("DTEND", datetime.datetime(2010, 10, 10, 12, 0, 0, tzinfo=utc)) + event.add("CREATED", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) + event.add("UID", "123456") + event.add("SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €") + event.add("DESCRIPTION", "icalendar should be able to de/serialize non-ascii.") + event.add("LOCATION", "Tribstrül") cal.add_component(event) # test_create_event_simple event1 = Event() - event1.add('DTSTART', datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) - event1.add('SUMMARY', 'åäö') + event1.add("DTSTART", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) + event1.add("SUMMARY", "åäö") cal.add_component(event1) # test_unicode_parameter_name # test for issue #80 https://github.com/collective/icalendar/issues/80 event2 = Event() - event2.add('DESCRIPTION', 'äöüßÄÖÜ') + event2.add("DESCRIPTION", "äöüßÄÖÜ") cal.add_component(event2) assert cal.to_ical() == calendars.created_calendar_with_unicode_fields.raw_ics -@pytest.mark.parametrize("component,example", +@pytest.mark.parametrize( + ("component", "example"), [ (Calendar, "example"), (Calendar, "example.ics"), (Event, "event_with_rsvp"), (Timezone, "pacific_fiji"), - ] + ], ) def test_component_has_examples(tzp, calendars, timezones, events, component, example): """Check that the examples function works.""" diff --git a/src/icalendar/tests/test_icalendar.py b/src/icalendar/tests/test_icalendar.py index 8d52f237..36314aee 100644 --- a/src/icalendar/tests/test_icalendar.py +++ b/src/icalendar/tests/test_icalendar.py @@ -1,59 +1,73 @@ -from ..parser import Contentlines, Contentline, Parameters, foldline -from ..parser import q_join, q_split, dquote -from ..prop import vText - import unittest +from icalendar.parser import ( + Contentline, + Contentlines, + Parameters, + dquote, + foldline, + q_join, + q_split, +) +from icalendar.prop import vText -class IcalendarTestCase (unittest.TestCase): +class IcalendarTestCase(unittest.TestCase): def setUp(self): - if not hasattr(self, 'assertRaisesRegex'): + if not hasattr(self, "assertRaisesRegex"): self.assertRaisesRegex = self.assertRaisesRegexp def test_long_lines(self): - c = Contentlines([Contentline('BEGIN:VEVENT')]) - c.append(Contentline(''.join('123456789 ' * 10))) + c = Contentlines([Contentline("BEGIN:VEVENT")]) + c.append(Contentline("".join("123456789 " * 10))) self.assertEqual( c.to_ical(), - b'BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 ' - b'123456789 123456789 123456789 1234\r\n 56789 123456789 ' - b'123456789 \r\n' + b"BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 " + b"123456789 123456789 123456789 1234\r\n 56789 123456789 " + b"123456789 \r\n", ) # from doctests # Notice that there is an extra empty string in the end of the content # lines. That is so they can be easily joined with: # '\r\n'.join(contentlines)) - self.assertEqual(Contentlines.from_ical('A short line\r\n'), - ['A short line', '']) - self.assertEqual(Contentlines.from_ical('A faked\r\n long line\r\n'), - ['A faked long line', '']) self.assertEqual( - Contentlines.from_ical('A faked\r\n long line\r\nAnd another ' - 'lin\r\n\te that is folded\r\n'), - ['A faked long line', 'And another line that is folded', ''] + Contentlines.from_ical("A short line\r\n"), ["A short line", ""] + ) + self.assertEqual( + Contentlines.from_ical("A faked\r\n long line\r\n"), + ["A faked long line", ""], + ) + self.assertEqual( + Contentlines.from_ical( + "A faked\r\n long line\r\nAnd another " "lin\r\n\te that is folded\r\n" + ), + ["A faked long line", "And another line that is folded", ""], ) def test_contentline_class(self): self.assertEqual( - Contentline('Si meliora dies, ut vina, poemata reddit').to_ical(), - b'Si meliora dies, ut vina, poemata reddit' + Contentline("Si meliora dies, ut vina, poemata reddit").to_ical(), + b"Si meliora dies, ut vina, poemata reddit", ) # A long line gets folded - c = Contentline(''.join(['123456789 '] * 10)).to_ical() + c = Contentline("".join(["123456789 "] * 10)).to_ical() self.assertEqual( c, - (b'123456789 123456789 123456789 123456789 123456789 123456789 ' - b'123456789 1234\r\n 56789 123456789 123456789 ') + ( + b"123456789 123456789 123456789 123456789 123456789 123456789 " + b"123456789 1234\r\n 56789 123456789 123456789 " + ), ) # A folded line gets unfolded self.assertEqual( Contentline.from_ical(c), - ('123456789 123456789 123456789 123456789 123456789 123456789 ' - '123456789 123456789 123456789 123456789 ') + ( + "123456789 123456789 123456789 123456789 123456789 123456789 " + "123456789 123456789 123456789 123456789 " + ), ) # https://tools.ietf.org/html/rfc5545#section-3.3.11 @@ -63,219 +77,228 @@ def test_contentline_class(self): # N or a LATIN CAPITAL LETTER N, that is "\n" or "\N". # Newlines are not allowed in content lines - self.assertRaises(AssertionError, Contentline, b'1234\r\n\r\n1234') + self.assertRaises(AssertionError, Contentline, b"1234\r\n\r\n1234") - self.assertEqual( - Contentline('1234\\n\\n1234').to_ical(), - b'1234\\n\\n1234' - ) + self.assertEqual(Contentline("1234\\n\\n1234").to_ical(), b"1234\\n\\n1234") # We do not fold within a UTF-8 character - c = Contentline(b'This line has a UTF-8 character where it should be ' - b'folded. Make sure it g\xc3\xabts folded before that ' - b'character.') + c = Contentline( + b"This line has a UTF-8 character where it should be " + b"folded. Make sure it g\xc3\xabts folded before that " + b"character." + ) - self.assertIn(b'\xc3\xab', c.to_ical()) + self.assertIn(b"\xc3\xab", c.to_ical()) # Another test of the above - c = Contentline(b'x' * 73 + b'\xc3\xab' + b'\\n ' + b'y' * 10) + c = Contentline(b"x" * 73 + b"\xc3\xab" + b"\\n " + b"y" * 10) - self.assertEqual(c.to_ical().count(b'\xc3'), 1) + self.assertEqual(c.to_ical().count(b"\xc3"), 1) # Don't fail if we fold a line that is exactly X times 74 characters # long - c = Contentline(''.join(['x'] * 148)).to_ical() + c = Contentline("".join(["x"] * 148)).to_ical() # It can parse itself into parts, # which is a tuple of (name, params, vals) self.assertEqual( - Contentline('dtstart:20050101T120000').parts(), - ('dtstart', Parameters({}), '20050101T120000') + Contentline("dtstart:20050101T120000").parts(), + ("dtstart", Parameters({}), "20050101T120000"), ) self.assertEqual( - Contentline('dtstart;value=datetime:20050101T120000').parts(), - ('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000') + Contentline("dtstart;value=datetime:20050101T120000").parts(), + ("dtstart", Parameters({"VALUE": "datetime"}), "20050101T120000"), ) - c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:' - 'MAILTO:maxm@example.com') + c = Contentline( + "ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com" + ) self.assertEqual( c.parts(), - ('ATTENDEE', - Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), - 'MAILTO:maxm@example.com') + ( + "ATTENDEE", + Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}), + "MAILTO:maxm@example.com", + ), ) self.assertEqual( - c.to_ical().decode('utf-8'), - 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:' - 'MAILTO:maxm@example.com' + c.to_ical().decode("utf-8"), + "ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com", ) # and back again # NOTE: we are quoting property values with spaces in it. - parts = ('ATTENDEE', - Parameters({'ROLE': 'REQ-PARTICIPANT', - 'CN': 'Max Rasmussen'}), - 'MAILTO:maxm@example.com') + parts = ( + "ATTENDEE", + Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}), + "MAILTO:maxm@example.com", + ) self.assertEqual( Contentline.from_parts(*parts), 'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:' - 'MAILTO:maxm@example.com' + "MAILTO:maxm@example.com", ) # and again - parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com') + parts = ("ATTENDEE", Parameters(), "MAILTO:maxm@example.com") self.assertEqual( - Contentline.from_parts(*parts), - 'ATTENDEE:MAILTO:maxm@example.com' + Contentline.from_parts(*parts), "ATTENDEE:MAILTO:maxm@example.com" ) # A value can also be any of the types defined in PropertyValues - parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com')) + parts = ("ATTENDEE", Parameters(), vText("MAILTO:test@example.com")) self.assertEqual( - Contentline.from_parts(*parts), - 'ATTENDEE:MAILTO:test@example.com' + Contentline.from_parts(*parts), "ATTENDEE:MAILTO:test@example.com" ) # A value in UTF-8 - parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å')) + parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å")) self.assertEqual( - Contentline.from_parts(*parts), - 'SUMMARY:INternational char æ ø å' + Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å" ) # A value can also be unicode - parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å')) + parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å")) self.assertEqual( - Contentline.from_parts(*parts), - 'SUMMARY:INternational char æ ø å' + Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å" ) # Traversing could look like this. name, params, vals = c.parts() - self.assertEqual(name, 'ATTENDEE') - self.assertEqual(vals, 'MAILTO:maxm@example.com') + self.assertEqual(name, "ATTENDEE") + self.assertEqual(vals, "MAILTO:maxm@example.com") self.assertEqual( sorted(params.items()), - sorted([('ROLE', 'REQ-PARTICIPANT'), ('CN', 'Max Rasmussen')]) + sorted([("ROLE", "REQ-PARTICIPANT"), ("CN", "Max Rasmussen")]), ) # And the traditional failure with self.assertRaisesRegex( - ValueError, - 'Content line could not be parsed into parts' + ValueError, "Content line could not be parsed into parts" ): - Contentline('ATTENDEE;maxm@example.com').parts() + Contentline("ATTENDEE;maxm@example.com").parts() # Another failure: with self.assertRaisesRegex( - ValueError, - 'Content line could not be parsed into parts' + ValueError, "Content line could not be parsed into parts" ): - Contentline(':maxm@example.com').parts() + Contentline(":maxm@example.com").parts() self.assertEqual( - Contentline('key;param=:value').parts(), - ('key', Parameters({'PARAM': ''}), 'value') + Contentline("key;param=:value").parts(), + ("key", Parameters({"PARAM": ""}), "value"), ) self.assertEqual( Contentline('key;param="pvalue":value').parts(), - ('key', Parameters({'PARAM': 'pvalue'}), 'value') + ("key", Parameters({"PARAM": "pvalue"}), "value"), ) # Should bomb on missing param: with self.assertRaisesRegex( - ValueError, - 'Content line could not be parsed into parts' + ValueError, "Content line could not be parsed into parts" ): Contentline.from_ical("k;:no param").parts() self.assertEqual( - Contentline('key;param=pvalue:value', strict=False).parts(), - ('key', Parameters({'PARAM': 'pvalue'}), 'value') + Contentline("key;param=pvalue:value", strict=False).parts(), + ("key", Parameters({"PARAM": "pvalue"}), "value"), ) # If strict is set to True, uppercase param values that are not # double-quoted, this is because the spec says non-quoted params are # case-insensitive. self.assertEqual( - Contentline('key;param=pvalue:value', strict=True).parts(), - ('key', Parameters({'PARAM': 'PVALUE'}), 'value') + Contentline("key;param=pvalue:value", strict=True).parts(), + ("key", Parameters({"PARAM": "PVALUE"}), "value"), ) self.assertEqual( Contentline('key;param="pValue":value', strict=True).parts(), - ('key', Parameters({'PARAM': 'pValue'}), 'value') + ("key", Parameters({"PARAM": "pValue"}), "value"), ) contains_base64 = ( - b'X-APPLE-STRUCTURED-LOCATION;' + b"X-APPLE-STRUCTURED-LOCATION;" b'VALUE=URI;X-ADDRESS="Kaiserliche Hofburg, 1010 Wien";' - b'X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;' - b'X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;' - b'X-TITLE=Heldenplatz:geo:48.206686,16.363235' + b"X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;" + b"X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;" + b"X-TITLE=Heldenplatz:geo:48.206686,16.363235" ) self.assertEqual( Contentline(contains_base64, strict=True).parts(), - ('X-APPLE-STRUCTURED-LOCATION', - Parameters({ - 'X-APPLE-RADIUS': '328.7978217977285', - 'X-ADDRESS': 'Kaiserliche Hofburg, 1010 Wien', - 'X-APPLE-REFERENCEFRAME': '1', - 'X-TITLE': 'HELDENPLATZ', - 'X-APPLE-MAPKIT-HANDLE': - 'CAESXQEZGR3QZXJYZWLJAA==', - 'VALUE': 'URI', - }), - 'geo:48.206686,16.363235' - ) + ( + "X-APPLE-STRUCTURED-LOCATION", + Parameters( + { + "X-APPLE-RADIUS": "328.7978217977285", + "X-ADDRESS": "Kaiserliche Hofburg, 1010 Wien", + "X-APPLE-REFERENCEFRAME": "1", + "X-TITLE": "HELDENPLATZ", + "X-APPLE-MAPKIT-HANDLE": "CAESXQEZGR3QZXJYZWLJAA==", + "VALUE": "URI", + } + ), + "geo:48.206686,16.363235", + ), ) def test_fold_line(self): - self.assertEqual(foldline('foo'), 'foo') + self.assertEqual(foldline("foo"), "foo") self.assertEqual( - foldline("Lorem ipsum dolor sit amet, consectetur adipiscing " - "elit. Vestibulum convallis imperdiet dui posuere."), - ('Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Vestibulum conval\r\n lis imperdiet dui posuere.') + foldline( + "Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit. Vestibulum convallis imperdiet dui posuere." + ), + ( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Vestibulum conval\r\n lis imperdiet dui posuere." + ), ) # I don't really get this test # at least just but bytes in there # porting it to "run" under python 2 & 3 makes it not much better with self.assertRaises(AssertionError): - foldline('привет'.encode(), limit=3) + foldline("привет".encode(), limit=3) - self.assertEqual(foldline('foobar', limit=4), 'foo\r\n bar') + self.assertEqual(foldline("foobar", limit=4), "foo\r\n bar") self.assertEqual( - foldline('Lorem ipsum dolor sit amet, consectetur adipiscing elit' - '. Vestibulum convallis imperdiet dui posuere.'), - ('Lorem ipsum dolor sit amet, consectetur adipiscing elit.' - ' Vestibulum conval\r\n lis imperdiet dui posuere.') + foldline( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + ". Vestibulum convallis imperdiet dui posuere." + ), + ( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + " Vestibulum conval\r\n lis imperdiet dui posuere." + ), ) self.assertEqual( - foldline('DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ'), - 'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ' + foldline("DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"), + "DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ", ) def test_value_double_quoting(self): - self.assertEqual(dquote('Max'), 'Max') - self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"') - self.assertEqual(dquote('name:value'), '"name:value"') + self.assertEqual(dquote("Max"), "Max") + self.assertEqual(dquote("Rasmussen, Max"), '"Rasmussen, Max"') + self.assertEqual(dquote("name:value"), '"name:value"') def test_q_split(self): - self.assertEqual(q_split('Max,Moller,"Rasmussen, Max"'), - ['Max', 'Moller', '"Rasmussen, Max"']) + self.assertEqual( + q_split('Max,Moller,"Rasmussen, Max"'), + ["Max", "Moller", '"Rasmussen, Max"'], + ) def test_q_split_bin(self): - for s in ('X-SOMETHING=ABCDE==', ',,,'): + for s in ("X-SOMETHING=ABCDE==", ",,,"): for maxsplit in range(-1, 3): - self.assertEqual(q_split(s, '=', maxsplit=maxsplit), - s.split('=', maxsplit)) + self.assertEqual( + q_split(s, "=", maxsplit=maxsplit), s.split("=", maxsplit) + ) def test_q_join(self): - self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']), - 'Max,Moller,"Rasmussen, Max"') + self.assertEqual( + q_join(["Max", "Moller", "Rasmussen, Max"]), 'Max,Moller,"Rasmussen, Max"' + ) diff --git a/src/icalendar/tests/test_issue_116.py b/src/icalendar/tests/test_issue_116.py index dca39902..a2ef0730 100644 --- a/src/icalendar/tests/test_issue_116.py +++ b/src/icalendar/tests/test_issue_116.py @@ -1,4 +1,3 @@ - import icalendar @@ -15,14 +14,14 @@ def test_issue_116(): "VALUE": "URI", "X-ADDRESS": "367 George Street Sydney CBD NSW 2000", "X-APPLE-RADIUS": "72", - "X-TITLE": "367 George Street" - } + "X-TITLE": "367 George Street", + }, ) assert event.to_ical() == ( - b'BEGIN:VEVENT\r\nX-APPLE-STRUCTURED-LOCATION;VALUE=URI;' + b"BEGIN:VEVENT\r\nX-APPLE-STRUCTURED-LOCATION;VALUE=URI;" b'X-ADDRESS="367 George Street Sydney \r\n CBD NSW 2000";' b'X-APPLE-RADIUS=72;X-TITLE="367 George Street":' - b'geo:-33.868900\r\n \\,151.207000\r\nEND:VEVENT\r\n' + b"geo:-33.868900\r\n \\,151.207000\r\nEND:VEVENT\r\n" ) # roundtrip diff --git a/src/icalendar/tests/test_issue_165_missing_event.py b/src/icalendar/tests/test_issue_165_missing_event.py index e48c3962..8e9e7368 100644 --- a/src/icalendar/tests/test_issue_165_missing_event.py +++ b/src/icalendar/tests/test_issue_165_missing_event.py @@ -1,8 +1,9 @@ -'''Issue #165 - Problem parsing a file with event recurring on weekdays +"""Issue #165 - Problem parsing a file with event recurring on weekdays + +https://github.com/collective/icalendar/issues/165 +""" - https://github.com/collective/icalendar/issues/165 -''' def test_issue_165_missing_event(calendars): - events = list(calendars.issue_165_missing_event.walk('VEVENT')) + events = list(calendars.issue_165_missing_event.walk("VEVENT")) assert len(events) == 1, "There was an event missing from the parsed events' list." diff --git a/src/icalendar/tests/test_issue_168_parsing_invalid_calendars_no_warning.py b/src/icalendar/tests/test_issue_168_parsing_invalid_calendars_no_warning.py index 77abdf19..f7472387 100644 --- a/src/icalendar/tests/test_issue_168_parsing_invalid_calendars_no_warning.py +++ b/src/icalendar/tests/test_issue_168_parsing_invalid_calendars_no_warning.py @@ -1,9 +1,16 @@ -'''Issue #168 - Parsing invalid icalendars fails without any warning +"""Issue #168 - Parsing invalid icalendars fails without any warning + +https://github.com/collective/icalendar/issues/168 +""" - https://github.com/collective/icalendar/issues/168 -''' def test_issue_168_parsing_inavlid_calendars_no_warning(calendars): - expected_error = (None, "Content line could not be parsed into parts: 'X-APPLE-RADIUS=49.91307046514149': X-APPLE-RADIUS=49.91307046514149") - assert expected_error in calendars.issue_168_input.walk('VEVENT')[0].errors - assert calendars.issue_168_input.to_ical() == calendars.issue_168_expected_output.raw_ics + expected_error = ( + None, + "Content line could not be parsed into parts: 'X-APPLE-RADIUS=49.91307046514149': X-APPLE-RADIUS=49.91307046514149", + ) + assert expected_error in calendars.issue_168_input.walk("VEVENT")[0].errors + assert ( + calendars.issue_168_input.to_ical() + == calendars.issue_168_expected_output.raw_ics + ) diff --git a/src/icalendar/tests/test_issue_27_period.py b/src/icalendar/tests/test_issue_27_period.py index 71eeb530..a25af916 100644 --- a/src/icalendar/tests/test_issue_27_period.py +++ b/src/icalendar/tests/test_issue_27_period.py @@ -1,13 +1,19 @@ -'''Issue #27 - multiple periods +"""Issue #27 - multiple periods + +https://github.com/collective/icalendar/issues/27 +""" - https://github.com/collective/icalendar/issues/27 -''' def test_issue_27_multiple_periods(calendars): - free_busy = list(calendars.issue_27_multiple_periods_in_freebusy_multiple_freebusies.walk('VFREEBUSY'))[0] - free_busy_period = free_busy['freebusy'] - print(free_busy['freebusy']) - equivalent_way_of_defining_free_busy = list(calendars.issue_27_multiple_periods_in_freebusy_one_freebusy.walk('VFREEBUSY'))[0] - free_busy_period_equivalent = equivalent_way_of_defining_free_busy['freebusy'] + free_busy = list( + calendars.issue_27_multiple_periods_in_freebusy_multiple_freebusies.walk( + "VFREEBUSY" + ) + )[0] + free_busy_period = free_busy["freebusy"] + print(free_busy["freebusy"]) + equivalent_way_of_defining_free_busy = list( + calendars.issue_27_multiple_periods_in_freebusy_one_freebusy.walk("VFREEBUSY") + )[0] + free_busy_period_equivalent = equivalent_way_of_defining_free_busy["freebusy"] assert free_busy_period == free_busy_period_equivalent - diff --git a/src/icalendar/tests/test_issue_318_skip_default_parameters.py b/src/icalendar/tests/test_issue_318_skip_default_parameters.py index 7d780bf4..78e5a097 100644 --- a/src/icalendar/tests/test_issue_318_skip_default_parameters.py +++ b/src/icalendar/tests/test_issue_318_skip_default_parameters.py @@ -8,20 +8,25 @@ DTSTART;VALUE=DATE-TIME:20190616T050000Z equals DTSTART:20190616T050000Z """ + +from datetime import datetime + import pytest + from icalendar import Event -from datetime import datetime -@pytest.mark.parametrize("attr", [ - "DTSTART", - "DTEND", - "DTSTAMP", -]) +@pytest.mark.parametrize( + "attr", + [ + "DTSTART", + "DTEND", + "DTSTAMP", + ], +) def test_datetime_in_event(attr): """Check that the "VALUE=DATE-TIME" is absent because not needed.""" event = Event() event.add(attr, datetime(2022, 10, 13, 9, 16, 42)) ics = event.to_ical() assert b"VALUE=DATE-TIME" not in ics - diff --git a/src/icalendar/tests/test_issue_322_single_strings_characters_split_into_multiple_categories.py b/src/icalendar/tests/test_issue_322_single_strings_characters_split_into_multiple_categories.py index f4e539fc..d99ccb3e 100644 --- a/src/icalendar/tests/test_issue_322_single_strings_characters_split_into_multiple_categories.py +++ b/src/icalendar/tests/test_issue_322_single_strings_characters_split_into_multiple_categories.py @@ -4,7 +4,7 @@ def test_issue_322_single_string_split_into_multiple_categories(calendars): calendar = Calendar() event = Event() - event.add('summary', 'Event with bare string as argument for categories') - event.add('categories', "Lecture") + event.add("summary", "Event with bare string as argument for categories") + event.add("categories", "Lecture") calendar.add_component(event) assert calendar.to_ical() == calendars.issue_322_expected_calendar.raw_ics diff --git a/src/icalendar/tests/test_issue_348_exception_parsing_value.py b/src/icalendar/tests/test_issue_348_exception_parsing_value.py index 5991e61e..80a3440b 100644 --- a/src/icalendar/tests/test_issue_348_exception_parsing_value.py +++ b/src/icalendar/tests/test_issue_348_exception_parsing_value.py @@ -7,7 +7,7 @@ def test_calendar_can_be_parsed_correctly(calendars): """Exception when there's no ':' when parsing value #348 - see https://github.com/collective/icalendar/issues/348 + see https://github.com/collective/icalendar/issues/348 """ freebusy = calendars.issue_348_exception_parsing_value.walk("VFREEBUSY")[0] assert freebusy["ORGANIZER"].params["CN"] == "Sixt SE" diff --git a/src/icalendar/tests/test_issue_350.py b/src/icalendar/tests/test_issue_350.py index 7939c04c..d7ad5455 100644 --- a/src/icalendar/tests/test_issue_350.py +++ b/src/icalendar/tests/test_issue_350.py @@ -1,8 +1,9 @@ -'''Issue #350 - Ignore X-... properties also at end of file? +"""Issue #350 - Ignore X-... properties also at end of file? + +https://github.com/collective/icalendar/issues/350 +""" - https://github.com/collective/icalendar/issues/350 -''' def test_issue_350(calendars): - calendar = list(calendars.issue_350.walk('X-COMMENT')) + calendar = list(calendars.issue_350.walk("X-COMMENT")) assert len(calendar) == 0, "X-COMMENT at the end of the file was parsed" diff --git a/src/icalendar/tests/test_issue_500_vboolean_for_parameter.py b/src/icalendar/tests/test_issue_500_vboolean_for_parameter.py index 3bb95c87..23b6b2af 100644 --- a/src/icalendar/tests/test_issue_500_vboolean_for_parameter.py +++ b/src/icalendar/tests/test_issue_500_vboolean_for_parameter.py @@ -1,9 +1,10 @@ from icalendar import Event, vBoolean, vCalAddress + def test_vBoolean_can_be_used_as_parameter_issue_500(events): - '''https://github.com/collective/icalendar/issues/500''' - attendee = vCalAddress('mailto:someone@example.com') - attendee.params['rsvp'] = vBoolean(True) + """https://github.com/collective/icalendar/issues/500""" + attendee = vCalAddress("mailto:someone@example.com") + attendee.params["rsvp"] = vBoolean(True) event = Event() - event.add('attendee', attendee) + event.add("attendee", attendee) assert event.to_ical() == events.event_with_rsvp.raw_ics diff --git a/src/icalendar/tests/test_issue_557_encode_native_parameters.py b/src/icalendar/tests/test_issue_557_encode_native_parameters.py index 99d9f051..0c19cf4d 100644 --- a/src/icalendar/tests/test_issue_557_encode_native_parameters.py +++ b/src/icalendar/tests/test_issue_557_encode_native_parameters.py @@ -6,7 +6,6 @@ see https://github.com/collective/icalendar/issues/557""" - import unittest from icalendar.cal import Component diff --git a/src/icalendar/tests/test_multiple.py b/src/icalendar/tests/test_multiple.py index e87486df..67f430d4 100644 --- a/src/icalendar/tests/test_multiple.py +++ b/src/icalendar/tests/test_multiple.py @@ -1,6 +1,6 @@ """An example with multiple VCALENDAR components""" -from icalendar.prop import vText +from icalendar.prop import vText def test_multiple(calendars): @@ -9,6 +9,8 @@ def test_multiple(calendars): cals = calendars.multiple.multiple_calendar_components assert len(cals) == 2 - assert [comp.name for comp in cals[0].walk()] == ['VCALENDAR', 'VEVENT'] - assert [comp.name for comp in cals[1].walk()] == ['VCALENDAR', 'VEVENT', 'VEVENT'] - assert cals[0]['prodid'] == vText('-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN') + assert [comp.name for comp in cals[0].walk()] == ["VCALENDAR", "VEVENT"] + assert [comp.name for comp in cals[1].walk()] == ["VCALENDAR", "VEVENT", "VEVENT"] + assert cals[0]["prodid"] == vText( + "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN" + ) diff --git a/src/icalendar/tests/test_oss_fuzz_errors.py b/src/icalendar/tests/test_oss_fuzz_errors.py index a6ded1d6..235c947d 100644 --- a/src/icalendar/tests/test_oss_fuzz_errors.py +++ b/src/icalendar/tests/test_oss_fuzz_errors.py @@ -1,10 +1,12 @@ """This file collects errors that the OSS FUZZ build has found.""" + from datetime import time -from icalendar import Calendar -from icalendar.prop import vDDDLists import pytest +from icalendar import Calendar +from icalendar.prop import vDDDLists + def test_stack_is_empty(): """If we get passed an invalid string, we expect to get a ValueError.""" @@ -15,4 +17,4 @@ def test_stack_is_empty(): def test_vdd_list_type_mismatch(): """If we pass in a string type, we expect it to be converted to bytes""" vddd_list = vDDDLists([time(hour=6, minute=6, second=6)]) - assert vddd_list.to_ical() == b'060606' + assert vddd_list.to_ical() == b"060606" diff --git a/src/icalendar/tests/test_parsing.py b/src/icalendar/tests/test_parsing.py index cf1541bd..acb67ad4 100644 --- a/src/icalendar/tests/test_parsing.py +++ b/src/icalendar/tests/test_parsing.py @@ -1,194 +1,257 @@ -'''Tests checking that parsing works''' -import pytest +"""Tests checking that parsing works""" + import base64 -from icalendar import Calendar, vRecur, vBinary, Event from datetime import datetime + +import pytest + +from icalendar import Calendar, Event, vBinary, vRecur from icalendar.parser import Contentline, Parameters, unescape_char -@pytest.mark.parametrize('calendar_name', [ - # Issue #178 - A component with an unknown/invalid name is represented - # as one of the known components, the information about the original - # component name is lost. - # https://github.com/collective/icalendar/issues/178 https://github.com/collective/icalendar/pull/180 - # Parsing of a nonstandard component - 'issue_178_component_with_invalid_name_represented', - # Nonstandard component inside other components, also has properties - 'issue_178_custom_component_inside_other', - # Nonstandard component is able to contain other components - 'issue_178_custom_component_contains_other', -]) + +@pytest.mark.parametrize( + "calendar_name", + [ + # Issue #178 - A component with an unknown/invalid name is represented + # as one of the known components, the information about the original + # component name is lost. + # https://github.com/collective/icalendar/issues/178 https://github.com/collective/icalendar/pull/180 + # Parsing of a nonstandard component + "issue_178_component_with_invalid_name_represented", + # Nonstandard component inside other components, also has properties + "issue_178_custom_component_inside_other", + # Nonstandard component is able to contain other components + "issue_178_custom_component_contains_other", + ], +) def test_calendar_to_ical_is_inverse_of_from_ical(calendars, calendar_name): calendar = getattr(calendars, calendar_name) assert calendar.to_ical().splitlines() == calendar.raw_ics.splitlines() assert calendar.to_ical() == calendar.raw_ics -@pytest.mark.parametrize('raw_content_line, expected_output', [ - # Issue #142 - Multivalued parameters. This is needed for VCard 3.0. - # see https://github.com/collective/icalendar/pull/142 - ('TEL;TYPE=HOME,VOICE:000000000', ('TEL', Parameters({'TYPE': ['HOME', 'VOICE']}), '000000000')), - # Issue #143 - Allow dots in property names. Another vCard related issue. - # see https://github.com/collective/icalendar/pull/143 - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany', \ - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR', \ - Parameters(),\ - ';;This is the Adress 08; Some City;;12345;Germany')), - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:', \ - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL', \ - Parameters(), \ - '')) -]) + +@pytest.mark.parametrize( + ("raw_content_line", "expected_output"), + [ + # Issue #142 - Multivalued parameters. This is needed for VCard 3.0. + # see https://github.com/collective/icalendar/pull/142 + ( + "TEL;TYPE=HOME,VOICE:000000000", + ("TEL", Parameters({"TYPE": ["HOME", "VOICE"]}), "000000000"), + ), + # Issue #143 - Allow dots in property names. Another vCard related issue. + # see https://github.com/collective/icalendar/pull/143 + ( + "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany", + ( + "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR", + Parameters(), + ";;This is the Adress 08; Some City;;12345;Germany", + ), + ), + ( + "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:", + ( + "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL", + Parameters(), + "", + ), + ), + ], +) def test_content_lines_parsed_properly(raw_content_line, expected_output): assert Contentline.from_ical(raw_content_line).parts() == expected_output -@pytest.mark.parametrize('timezone_info', [ - # General timezone aware dates in ical string - (b'DTSTART;TZID=America/New_York:20130907T120000'), - (b'DTEND;TZID=America/New_York:20130907T170000'), - # Specific timezone aware exdates in ical string - (b'EXDATE;TZID=America/New_York:20131012T120000'), - (b'EXDATE;TZID=America/New_York:20131011T120000') -]) +@pytest.mark.parametrize( + "timezone_info", + [ + # General timezone aware dates in ical string + (b"DTSTART;TZID=America/New_York:20130907T120000"), + (b"DTEND;TZID=America/New_York:20130907T170000"), + # Specific timezone aware exdates in ical string + (b"EXDATE;TZID=America/New_York:20131012T120000"), + (b"EXDATE;TZID=America/New_York:20131011T120000"), + ], +) def test_timezone_info_present_in_ical_issue_112(events, timezone_info): - '''Issue #112 - No timezone info on EXDATE + """Issue #112 - No timezone info on EXDATE https://github.com/collective/icalendar/issues/112 - ''' - timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical() + """ + assert timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical() + def test_timezone_name_parsed_issue_112(events): - '''Issue #112 - No timezone info on EXDATE + """Issue #112 - No timezone info on EXDATE https://github.com/collective/icalendar/issues/112 - ''' - assert events.issue_112_missing_tzinfo_on_exdate['exdate'][0].dts[0].dt.tzname() == 'EDT' + """ + assert ( + events.issue_112_missing_tzinfo_on_exdate["exdate"][0].dts[0].dt.tzname() + == "EDT" + ) + def test_issue_157_removes_trailing_semicolon(events): - '''Issue #157 - Recurring rules and trailing semicolons + """Issue #157 - Recurring rules and trailing semicolons https://github.com/collective/icalendar/pull/157 - ''' + """ recur = events.issue_157_removes_trailing_semicolon.decoded("RRULE") assert isinstance(recur, vRecur) - assert recur.to_ical() == b'FREQ=YEARLY;BYDAY=1SU;BYMONTH=11' - -@pytest.mark.parametrize('event_name', [ - # https://github.com/collective/icalendar/pull/100 - ('issue_100_transformed_doctests_into_unittests'), - ('issue_184_broken_representation_of_period'), - # PERIOD should be put back into shape - 'issue_156_RDATE_with_PERIOD', - 'issue_156_RDATE_with_PERIOD_list', - 'event_with_unicode_organizer', -]) + assert recur.to_ical() == b"FREQ=YEARLY;BYDAY=1SU;BYMONTH=11" + + +@pytest.mark.parametrize( + "event_name", + [ + # https://github.com/collective/icalendar/pull/100 + ("issue_100_transformed_doctests_into_unittests"), + ("issue_184_broken_representation_of_period"), + # PERIOD should be put back into shape + "issue_156_RDATE_with_PERIOD", + "issue_156_RDATE_with_PERIOD_list", + "event_with_unicode_organizer", + ], +) def test_event_to_ical_is_inverse_of_from_ical(events, event_name): """Make sure that an event's ICS is equal to the ICS it was made from.""" event = events[event_name] assert event.to_ical().splitlines() == event.raw_ics.splitlines() assert event.to_ical() == event.raw_ics + def test_decode_rrule_attribute_error_issue_70(events): # Issue #70 - e.decode("RRULE") causes Attribute Error # see https://github.com/collective/icalendar/issues/70 - recur = events.issue_70_rrule_causes_attribute_error.decoded('RRULE') + recur = events.issue_70_rrule_causes_attribute_error.decoded("RRULE") assert isinstance(recur, vRecur) - assert recur.to_ical() == b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1' + assert recur.to_ical() == b"FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1" + def test_description_parsed_properly_issue_53(events): - '''Issue #53 - Parsing failure on some descriptions? + """Issue #53 - Parsing failure on some descriptions? https://github.com/collective/icalendar/issues/53 - ''' - assert b'July 12 at 6:30 PM' in events.issue_53_description_parsed_properly['DESCRIPTION'].to_ical() + """ + assert ( + b"July 12 at 6:30 PM" + in events.issue_53_description_parsed_properly["DESCRIPTION"].to_ical() + ) + def test_raises_value_error_for_properties_without_parent_pull_179(): - '''Found an issue where from_ical() would raise IndexError for - properties without parent components. + """Found an issue where from_ical() would raise IndexError for + properties without parent components. + + https://github.com/collective/icalendar/pull/179 + """ + with pytest.raises(ValueError): + Calendar.from_ical("VERSION:2.0") - https://github.com/collective/icalendar/pull/179 - ''' - with pytest.raises(ValueError): - Calendar.from_ical('VERSION:2.0') def test_tzid_parsed_properly_issue_53(timezones): - '''Issue #53 - Parsing failure on some descriptions? + """Issue #53 - Parsing failure on some descriptions? https://github.com/collective/icalendar/issues/53 - ''' - assert timezones.issue_53_tzid_parsed_properly['tzid'].to_ical() == b'America/New_York' + """ + assert ( + timezones.issue_53_tzid_parsed_properly["tzid"].to_ical() == b"America/New_York" + ) + def test_timezones_to_ical_is_inverse_of_from_ical(timezones): - '''Issue #55 - Parse error on utc-offset with seconds value - see https://github.com/collective/icalendar/issues/55''' - timezone = timezones['issue_55_parse_error_on_utc_offset_with_seconds'] + """Issue #55 - Parse error on utc-offset with seconds value + see https://github.com/collective/icalendar/issues/55""" + timezone = timezones["issue_55_parse_error_on_utc_offset_with_seconds"] assert timezone.to_ical() == timezone.raw_ics -@pytest.mark.parametrize('date, expected_output', [ - (datetime(2012, 7, 16, 0, 0, 0), b'DTSTART:20120716T000000Z'), - (datetime(2021, 11, 17, 15, 9, 15), b'DTSTART:20211117T150915Z') -]) + +@pytest.mark.parametrize( + ("date", "expected_output"), + [ + (datetime(2012, 7, 16, 0, 0, 0), b"DTSTART:20120716T000000Z"), + (datetime(2021, 11, 17, 15, 9, 15), b"DTSTART:20211117T150915Z"), + ], +) def test_no_tzid_when_utc(utc, date, expected_output): - '''Issue #58 - TZID on UTC DATE-TIMEs + """Issue #58 - TZID on UTC DATE-TIMEs Issue #335 - UTC timezone identification is broken https://github.com/collective/icalendar/issues/58 https://github.com/collective/icalendar/issues/335 - ''' + """ # According to RFC 5545: "The TZID property parameter MUST NOT be # applied to DATE-TIME or TIME properties whose time values are # specified in UTC. date = date.replace(tzinfo=utc) event = Event() - event.add('dtstart', date) + event.add("dtstart", date) assert expected_output in event.to_ical() + def test_vBinary_base64_encoded_issue_82(): - '''Issue #82 - vBinary __repr__ called rather than to_ical from + """Issue #82 - vBinary __repr__ called rather than to_ical from container types https://github.com/collective/icalendar/issues/82 - ''' - b = vBinary('text') - b.params['FMTTYPE'] = 'text/plain' - assert b.to_ical() == base64.b64encode(b'text') + """ + b = vBinary("text") + b.params["FMTTYPE"] = "text/plain" + assert b.to_ical() == base64.b64encode(b"text") + def test_creates_event_with_base64_encoded_attachment_issue_82(events): - '''Issue #82 - vBinary __repr__ called rather than to_ical from + """Issue #82 - vBinary __repr__ called rather than to_ical from container types https://github.com/collective/icalendar/issues/82 - ''' - b = vBinary('text') - b.params['FMTTYPE'] = 'text/plain' + """ + b = vBinary("text") + b.params["FMTTYPE"] = "text/plain" event = Event() - event.add('ATTACH', b) + event.add("ATTACH", b) assert event.to_ical() == events.issue_82_expected_output.raw_ics -@pytest.mark.parametrize('calendar_name', [ - # Issue #466 - [BUG] TZID timezone is ignored when forward-slash is used - # https://github.com/collective/icalendar/issues/466 - 'issue_466_respect_unique_timezone', - 'issue_466_convert_tzid_with_slash' -]) + +@pytest.mark.parametrize( + "calendar_name", + [ + # Issue #466 - [BUG] TZID timezone is ignored when forward-slash is used + # https://github.com/collective/icalendar/issues/466 + "issue_466_respect_unique_timezone", + "issue_466_convert_tzid_with_slash", + ], +) def test_handles_unique_tzid(calendars, in_timezone, calendar_name): calendar = calendars[calendar_name] - event = calendar.walk('VEVENT')[0] + event = calendar.walk("VEVENT")[0] print(vars(event)) - start_dt = event['dtstart'].dt - end_dt = event['dtend'].dt - assert start_dt == in_timezone(datetime(2022, 10, 21, 20, 0, 0), 'Europe/Stockholm') - assert end_dt == in_timezone(datetime(2022, 10, 21, 21, 0, 0), 'Europe/Stockholm') - -@pytest.mark.parametrize('event_name, expected_cn, expected_ics', [ - ('event_with_escaped_characters', r'that, that; %th%%at%\ that:', 'это, то; that\\ %th%%at%:'), - ('event_with_escaped_character1', r'Society, 2014', 'that'), - ('event_with_escaped_character2', r'Society\ 2014', 'that'), - ('event_with_escaped_character3', r'Society; 2014', 'that'), - ('event_with_escaped_character4', r'Society: 2014', 'that'), -]) + start_dt = event["dtstart"].dt + end_dt = event["dtend"].dt + assert start_dt == in_timezone(datetime(2022, 10, 21, 20, 0, 0), "Europe/Stockholm") + assert end_dt == in_timezone(datetime(2022, 10, 21, 21, 0, 0), "Europe/Stockholm") + + +@pytest.mark.parametrize( + ("event_name", "expected_cn", "expected_ics"), + [ + ( + "event_with_escaped_characters", + r"that, that; %th%%at%\ that:", + "это, то; that\\ %th%%at%:", + ), + ("event_with_escaped_character1", r"Society, 2014", "that"), + ("event_with_escaped_character2", r"Society\ 2014", "that"), + ("event_with_escaped_character3", r"Society; 2014", "that"), + ("event_with_escaped_character4", r"Society: 2014", "that"), + ], +) def test_escaped_characters_read(event_name, expected_cn, expected_ics, events): event = events[event_name] - assert event['ORGANIZER'].params['CN'] == expected_cn - assert event['ORGANIZER'].to_ical() == expected_ics.encode('utf-8') - + assert event["ORGANIZER"].params["CN"] == expected_cn + assert event["ORGANIZER"].to_ical() == expected_ics.encode("utf-8") + + def test_unescape_char(): - assert unescape_char(b'123') == b'123' + assert unescape_char(b"123") == b"123" assert unescape_char(b"\\n") == b"\n" diff --git a/src/icalendar/tests/test_period.py b/src/icalendar/tests/test_period.py index ccb334ad..c59e8388 100644 --- a/src/icalendar/tests/test_period.py +++ b/src/icalendar/tests/test_period.py @@ -4,29 +4,62 @@ - https://github.com/collective/icalendar/issues/156 - https://github.com/pimutils/khal/issues/152#issuecomment-933635248 """ + +import datetime + import pytest + from icalendar.prop import vDDDTypes -import datetime -@pytest.mark.parametrize("calname,tzname,index,period_string", [ - ("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 0, "20211101T160000/20211101T163000"), - ("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 1, "20211206T160000/20211206T163000"), - ("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 2, "20220103T160000/20220103T163000"), - ("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 3, "20220207T160000/20220207T163000"), -] + [ - ("issue_156_RDATE_with_PERIOD_TZID_khal", "America/Chicago", i, period) - for i, period in enumerate(("20180327T080000/20180327T0" - "90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018" - "0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000" - "0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515" - "T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20" - "180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080" - "000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807" - "03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000," - "20180724T080000/20180724T090000,20180731T080000/20180731T090000").split(",")) -]) -def test_issue_156_period_list_in_rdate(calendars, calname, tzname, index, period_string): +@pytest.mark.parametrize( + ("calname", "tzname", "index", "period_string"), + [ + ( + "issue_156_RDATE_with_PERIOD_TZID_khal_2", + "Europe/Berlin", + 0, + "20211101T160000/20211101T163000", + ), + ( + "issue_156_RDATE_with_PERIOD_TZID_khal_2", + "Europe/Berlin", + 1, + "20211206T160000/20211206T163000", + ), + ( + "issue_156_RDATE_with_PERIOD_TZID_khal_2", + "Europe/Berlin", + 2, + "20220103T160000/20220103T163000", + ), + ( + "issue_156_RDATE_with_PERIOD_TZID_khal_2", + "Europe/Berlin", + 3, + "20220207T160000/20220207T163000", + ), + ] + + [ + ("issue_156_RDATE_with_PERIOD_TZID_khal", "America/Chicago", i, period) + for i, period in enumerate( + ( + "20180327T080000/20180327T0" + "90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018" + "0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000" + "0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515" + "T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20" + "180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080" + "000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807" + "03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000," + "20180724T080000/20180724T090000,20180731T080000/20180731T090000" + ).split(",") + ) + ], +) +def test_issue_156_period_list_in_rdate( + calendars, calname, tzname, index, period_string +): """Check items in a list of period values.""" calendar = calendars[calname] rdate = calendar.walk("vevent")[0]["rdate"] @@ -58,5 +91,7 @@ def test_tzid_is_part_of_the_period_values(calendars, tzp): """The TZID should be set in the datetime.""" event = list(calendars.period_with_timezone.walk("VEVENT"))[0] start, end = event["RDATE"].dts[0].dt - assert start == tzp.localize(datetime.datetime(2023, 12, 13, 12), "America/Vancouver") + assert start == tzp.localize( + datetime.datetime(2023, 12, 13, 12), "America/Vancouver" + ) assert end == tzp.localize(datetime.datetime(2023, 12, 13, 15), "America/Vancouver") diff --git a/src/icalendar/tests/test_property_params.py b/src/icalendar/tests/test_property_params.py index 48d37633..6af3ec95 100644 --- a/src/icalendar/tests/test_property_params.py +++ b/src/icalendar/tests/test_property_params.py @@ -1,94 +1,129 @@ +import re + import pytest + from icalendar import Calendar, Event, Parameters, vCalAddress -import re -@pytest.mark.parametrize('parameter, expected', [ - # Simple parameter:value pair - (Parameters(parameter1='Value1'), b'PARAMETER1=Value1'), - # Parameter with list of values must be separated by comma - (Parameters({'parameter1': ['Value1', 'Value2']}), b'PARAMETER1=Value1,Value2'), - # Multiple parameters must be separated by a semicolon - (Parameters({'RSVP': 'TRUE', 'ROLE': 'REQ-PARTICIPANT'}), b'ROLE=REQ-PARTICIPANT;RSVP=TRUE'), - # Parameter values containing ',;:' must be double quoted - (Parameters({'ALTREP': 'http://www.wiz.org'}), b'ALTREP="http://www.wiz.org"'), - # list items must be quoted separately - (Parameters({'MEMBER': ['MAILTO:projectA@host.com', - 'MAILTO:projectB@host.com']}), - b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'), - (Parameters({'parameter1': 'Value1', - 'parameter2': ['Value2', 'Value3'], - 'ALTREP': ['http://www.wiz.org', 'value4']}), - b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'), - # Including empty strings - (Parameters({'PARAM': ''}), b'PARAM='), - # We can also parse parameter strings - (Parameters({'MEMBER': ['MAILTO:projectA@host.com', - 'MAILTO:projectB@host.com']}), - b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'), - # We can also parse parameter strings - (Parameters({'PARAMETER1': 'Value1', - 'ALTREP': ['http://www.wiz.org', 'value4'], - 'PARAMETER2': ['Value2', 'Value3']}), - b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'), -]) +@pytest.mark.parametrize( + ("parameter", "expected"), + [ + # Simple parameter:value pair + (Parameters(parameter1="Value1"), b"PARAMETER1=Value1"), + # Parameter with list of values must be separated by comma + (Parameters({"parameter1": ["Value1", "Value2"]}), b"PARAMETER1=Value1,Value2"), + # Multiple parameters must be separated by a semicolon + ( + Parameters({"RSVP": "TRUE", "ROLE": "REQ-PARTICIPANT"}), + b"ROLE=REQ-PARTICIPANT;RSVP=TRUE", + ), + # Parameter values containing ',;:' must be double quoted + (Parameters({"ALTREP": "http://www.wiz.org"}), b'ALTREP="http://www.wiz.org"'), + # list items must be quoted separately + ( + Parameters( + {"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]} + ), + b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"', + ), + ( + Parameters( + { + "parameter1": "Value1", + "parameter2": ["Value2", "Value3"], + "ALTREP": ["http://www.wiz.org", "value4"], + } + ), + b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3', + ), + # Including empty strings + (Parameters({"PARAM": ""}), b"PARAM="), + # We can also parse parameter strings + ( + Parameters( + {"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]} + ), + b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"', + ), + # We can also parse parameter strings + ( + Parameters( + { + "PARAMETER1": "Value1", + "ALTREP": ["http://www.wiz.org", "value4"], + "PARAMETER2": ["Value2", "Value3"], + } + ), + b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3', + ), + ], +) def test_parameter_to_ical_is_inverse_of_from_ical(parameter, expected): assert parameter.to_ical() == expected - assert Parameters.from_ical(expected.decode('utf-8')) == parameter + assert Parameters.from_ical(expected.decode("utf-8")) == parameter def test_parse_parameter_string_without_quotes(): - assert Parameters.from_ical('PARAM1=Value 1;PARA2=Value 2') == Parameters({'PARAM1': 'Value 1', 'PARA2': 'Value 2'}) + assert Parameters.from_ical("PARAM1=Value 1;PARA2=Value 2") == Parameters( + {"PARAM1": "Value 1", "PARA2": "Value 2"} + ) def test_parametr_is_case_insensitive(): - parameter = Parameters(parameter1='Value1') - assert parameter['parameter1'] == parameter['PARAMETER1'] == parameter['PaRaMeTer1'] + parameter = Parameters(parameter1="Value1") + assert parameter["parameter1"] == parameter["PARAMETER1"] == parameter["PaRaMeTer1"] def test_parameter_keys_are_uppercase(): - parameter = Parameters(parameter1='Value1') - assert list(parameter.keys()) == ['PARAMETER1'] - - -@pytest.mark.parametrize('cn_param, cn_quoted', [ - # not double-quoted - ('Aramis', 'Aramis'), - # if a space is present - enclose in double quotes - ('Aramis Alameda', '"Aramis Alameda"'), - # a single quote in parameter value - double quote the value - ('Aramis d\'Alameda', '"Aramis d\'Alameda"'), - ('Арамис д\'Аламеда', '"Арамис д\'Аламеда"'), - # double quote is replaced with single quote - ('Aramis d\"Alameda', '"Aramis d\'Alameda"'), -]) + parameter = Parameters(parameter1="Value1") + assert list(parameter.keys()) == ["PARAMETER1"] + + +@pytest.mark.parametrize( + ("cn_param", "cn_quoted"), + [ + # not double-quoted + ("Aramis", "Aramis"), + # if a space is present - enclose in double quotes + ("Aramis Alameda", '"Aramis Alameda"'), + # a single quote in parameter value - double quote the value + ("Aramis d'Alameda", '"Aramis d\'Alameda"'), + ("Арамис д'Аламеда", '"Арамис д\'Аламеда"'), + # double quote is replaced with single quote + ('Aramis d"Alameda', '"Aramis d\'Alameda"'), + ], +) def test_quoting(cn_param, cn_quoted): event = Event() - attendee = vCalAddress('test@example.com') - attendee.params['CN'] = cn_param - event.add('ATTENDEE', attendee) - assert f'ATTENDEE;CN={cn_quoted}:test@example.com' in event.to_ical().decode('utf-8') + attendee = vCalAddress("test@example.com") + attendee.params["CN"] = cn_param + event.add("ATTENDEE", attendee) + assert f"ATTENDEE;CN={cn_quoted}:test@example.com" in event.to_ical().decode( + "utf-8" + ) def test_property_params(): """Property parameters with values containing a COLON character, a SEMICOLON character or a COMMA character MUST be placed in quoted text.""" - cal_address = vCalAddress('mailto:john.doe@example.org') + cal_address = vCalAddress("mailto:john.doe@example.org") cal_address.params["CN"] = "Doe, John" ical = Calendar() - ical.add('organizer', cal_address) + ical.add("organizer", cal_address) ical_str = Calendar.to_ical(ical) - exp_str = b"""BEGIN:VCALENDAR\r\nORGANIZER;CN="Doe, John":"""\ - b"""mailto:john.doe@example.org\r\nEND:VCALENDAR\r\n""" + exp_str = ( + b"""BEGIN:VCALENDAR\r\nORGANIZER;CN="Doe, John":""" + b"""mailto:john.doe@example.org\r\nEND:VCALENDAR\r\n""" + ) assert ical_str == exp_str # other way around: ensure the property parameters can be restored from # an icalendar string. ical2 = Calendar.from_ical(ical_str) - assert ical2.get('ORGANIZER').params.get('CN') == 'Doe, John' + assert ical2.get("ORGANIZER").params.get("CN") == "Doe, John" def test_parse_and_access_property_params(calendars): @@ -97,13 +132,13 @@ def test_parse_and_access_property_params(calendars): """ event = calendars.property_params.walk("VEVENT")[0] - attendee = event['attendee'][0] - assert attendee.to_ical() == b'MAILTO:rembrand@xs4all.nl' - assert attendee.params.to_ical() == b'CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE' - assert attendee.params['cn'] == 'RembrandXS' + attendee = event["attendee"][0] + assert attendee.to_ical() == b"MAILTO:rembrand@xs4all.nl" + assert attendee.params.to_ical() == b"CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE" + assert attendee.params["cn"] == "RembrandXS" + def test_repr(): - """Test correct class representation. - """ - it = Parameters(parameter1='Value1') + """Test correct class representation.""" + it = Parameters(parameter1="Value1") assert re.match(r"Parameters\({u?'PARAMETER1': u?'Value1'}\)", str(it)) diff --git a/src/icalendar/tests/test_pytz_zoneinfo_integration.py b/src/icalendar/tests/test_pytz_zoneinfo_integration.py index 372c351c..a02aa10d 100644 --- a/src/icalendar/tests/test_pytz_zoneinfo_integration.py +++ b/src/icalendar/tests/test_pytz_zoneinfo_integration.py @@ -2,19 +2,23 @@ These are mostly located in icalendar.timezone. """ + try: import pytz + from icalendar.timezone.pytz import PYTZ except ImportError: pytz = None -from icalendar.timezone.zoneinfo import zoneinfo, ZONEINFO -from dateutil.tz.tz import _tzicalvtz -import pytest import copy import pickle -from dateutil.rrule import rrule, MONTHLY from datetime import datetime +import pytest +from dateutil.rrule import MONTHLY, rrule +from dateutil.tz.tz import _tzicalvtz + +from icalendar.timezone.zoneinfo import ZONEINFO, zoneinfo + if pytz: PYTZ_TIMEZONES = pytz.all_timezones TZP_ = [PYTZ(), ZONEINFO()] @@ -25,17 +29,27 @@ NEW_TZP_NAME = ["zoneinfo"] -@pytest.mark.parametrize("tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones())) +@pytest.mark.parametrize( + "tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones()) +) @pytest.mark.parametrize("tzp_", TZP_) def test_timezone_names_are_known(tz_name, tzp_): """Make sure that all timezones are understood.""" if tz_name in ("Factory", "localtime"): pytest.skip() - assert tzp_.knows_timezone_id(tz_name), f"{tzp_.__class__.__name__} should know {tz_name}" + assert tzp_.knows_timezone_id( + tz_name + ), f"{tzp_.__class__.__name__} should know {tz_name}" @pytest.mark.parametrize("func", [pickle.dumps, copy.copy, copy.deepcopy]) -@pytest.mark.parametrize("obj", [_tzicalvtz("id"), rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True)]) +@pytest.mark.parametrize( + "obj", + [ + _tzicalvtz("id"), + rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True), + ], +) def test_can_pickle_timezone(func, tzp, obj): """Check that we can serialize and copy timezones.""" func(obj) diff --git a/src/icalendar/tests/test_recurrence.py b/src/icalendar/tests/test_recurrence.py index 03ea321b..e237a8ce 100644 --- a/src/icalendar/tests/test_recurrence.py +++ b/src/icalendar/tests/test_recurrence.py @@ -1,22 +1,32 @@ -from icalendar import Event from datetime import date, datetime import pytest +from icalendar import Event + + def test_recurrence_properly_parsed(events): - assert events.event_with_recurrence['rrule'] == {'COUNT': [100], 'FREQ': ['DAILY']} + assert events.event_with_recurrence["rrule"] == {"COUNT": [100], "FREQ": ["DAILY"]} + -@pytest.mark.parametrize('i, exception_date', [ - (0, datetime(1996, 4, 2, 1, 0)), - (1, datetime(1996, 4, 3, 1, 0)), - (2, datetime(1996, 4, 4, 1, 0)) -]) +@pytest.mark.parametrize( + ("i", "exception_date"), + [ + (0, datetime(1996, 4, 2, 1, 0)), + (1, datetime(1996, 4, 3, 1, 0)), + (2, datetime(1996, 4, 4, 1, 0)), + ], +) def test_exdate_properly_parsed(events, i, exception_date, in_timezone): - assert events.event_with_recurrence['exdate'].dts[i].dt == in_timezone(exception_date, 'UTC') + assert events.event_with_recurrence["exdate"].dts[i].dt == in_timezone( + exception_date, "UTC" + ) + def test_exdate_properly_marshalled(events): - actual = events.event_with_recurrence['exdate'].to_ical() - assert actual == b'19960402T010000Z,19960403T010000Z,19960404T010000Z' + actual = events.event_with_recurrence["exdate"].to_ical() + assert actual == b"19960402T010000Z,19960403T010000Z,19960404T010000Z" + # TODO: DOCUMENT BETTER! # In this case we have multiple EXDATE definitions, one per line. @@ -26,49 +36,95 @@ def test_exdate_properly_marshalled(events): # code has to handle this as list and not blindly expecting to be able # to call event['EXDATE'].to_ical() on it: def test_exdate_formed_from_exdates_on_multiple_lines_is_a_list(events): - exdate = events.event_with_recurrence_exdates_on_different_lines['exdate'] + exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"] assert isinstance(exdate, list) -@pytest.mark.parametrize('i, exception_date, exception_date_ics', [ - (0, datetime(2012, 5, 29, 10, 0), b'20120529T100000'), - (1, datetime(2012, 4, 3, 10, 0), b'20120403T100000'), - (2, datetime(2012, 4, 10, 10, 0), b'20120410T100000'), - (3, datetime(2012, 5, 1, 10, 0), b'20120501T100000'), - (4, datetime(2012, 4, 17, 10, 0), b'20120417T100000') -]) -def test_list_exdate_to_ical_is_inverse_of_from_ical(events, i, exception_date, exception_date_ics, in_timezone): - exdate = events.event_with_recurrence_exdates_on_different_lines['exdate'] - assert exdate[i].dts[0].dt == in_timezone(exception_date, 'Europe/Vienna') + +@pytest.mark.parametrize( + ("i", "exception_date", "exception_date_ics"), + [ + (0, datetime(2012, 5, 29, 10, 0), b"20120529T100000"), + (1, datetime(2012, 4, 3, 10, 0), b"20120403T100000"), + (2, datetime(2012, 4, 10, 10, 0), b"20120410T100000"), + (3, datetime(2012, 5, 1, 10, 0), b"20120501T100000"), + (4, datetime(2012, 4, 17, 10, 0), b"20120417T100000"), + ], +) +def test_list_exdate_to_ical_is_inverse_of_from_ical( + events, i, exception_date, exception_date_ics, in_timezone +): + exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"] + assert exdate[i].dts[0].dt == in_timezone(exception_date, "Europe/Vienna") assert exdate[i].to_ical() == exception_date_ics -@pytest.mark.parametrize('freq, byday, dtstart, expected', [ - # Test some YEARLY BYDAY repeats - ('YEARLY', '1SU', date(2016,1,3), # 1st Sunday in year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n'), - ('YEARLY', '53MO', date(1984,12,31), # 53rd Monday in (leap) year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n'), - ('YEARLY', '-1TU', date(1999,12,28), # Last Tuesday in year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n'), - ('YEARLY', '-17WE', date(2000,9,6), # 17th-to-last Wednesday in year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n'), - # Test some MONTHLY BYDAY repeats - ('MONTHLY', '2TH', date(2003,4,10), # 2nd Thursday in month - b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n'), - ('MONTHLY', '-3FR', date(2017,5,12), # 3rd-to-last Friday in month - b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n'), - ('MONTHLY', '-5SA', date(2053,11,1), # 5th-to-last Saturday in month - b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n'), - # Specifically test examples from the report of Issue #518 - # https://github.com/collective/icalendar/issues/518 - ('YEARLY', '9MO', date(2023,2,27), # 9th Monday in year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n'), - ('YEARLY', '10MO', date(2023,3,6), # 10th Monday in year - b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n'), -]) + +@pytest.mark.parametrize( + ("freq", "byday", "dtstart", "expected"), + [ + # Test some YEARLY BYDAY repeats + ( + "YEARLY", + "1SU", + date(2016, 1, 3), # 1st Sunday in year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n", + ), + ( + "YEARLY", + "53MO", + date(1984, 12, 31), # 53rd Monday in (leap) year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n", + ), + ( + "YEARLY", + "-1TU", + date(1999, 12, 28), # Last Tuesday in year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n", + ), + ( + "YEARLY", + "-17WE", + date(2000, 9, 6), # 17th-to-last Wednesday in year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n", + ), + # Test some MONTHLY BYDAY repeats + ( + "MONTHLY", + "2TH", + date(2003, 4, 10), # 2nd Thursday in month + b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n", + ), + ( + "MONTHLY", + "-3FR", + date(2017, 5, 12), # 3rd-to-last Friday in month + b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n", + ), + ( + "MONTHLY", + "-5SA", + date(2053, 11, 1), # 5th-to-last Saturday in month + b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n", + ), + # Specifically test examples from the report of Issue #518 + # https://github.com/collective/icalendar/issues/518 + ( + "YEARLY", + "9MO", + date(2023, 2, 27), # 9th Monday in year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n", + ), + ( + "YEARLY", + "10MO", + date(2023, 3, 6), # 10th Monday in year + b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n", + ), + ], +) def test_byday_to_ical(freq, byday, dtstart, expected): - 'Test the BYDAY rule is correctly processed by to_ical().' + """Test the BYDAY rule is correctly processed by to_ical().""" event = Event() - event.add('SUMMARY', ' '.join(['Event', freq, byday])) - event.add('DTSTART', dtstart) - event.add('RRULE', {'FREQ':[freq], 'BYDAY':byday}) + event.add("SUMMARY", " ".join(["Event", freq, byday])) + event.add("DTSTART", dtstart) + event.add("RRULE", {"FREQ": [freq], "BYDAY": byday}) assert event.to_ical() == expected diff --git a/src/icalendar/tests/test_rfc_7529.py b/src/icalendar/tests/test_rfc_7529.py index dd869e2a..a8e22d5a 100644 --- a/src/icalendar/tests/test_rfc_7529.py +++ b/src/icalendar/tests/test_rfc_7529.py @@ -4,18 +4,20 @@ - https://github.com/collective/icalendar/issues/653 - https://www.rfc-editor.org/rfc/rfc7529.html """ + import pytest -from icalendar.prop import vRecur, vMonth, vSkip + +from icalendar.prop import vMonth, vRecur, vSkip @pytest.mark.parametrize( - "uid,scale", + ("uid", "scale"), [ ("4.3.1", "CHINESE"), ("4.3.2", "ETHIOPIC"), ("4.3.3", "HEBREW"), ("4.3.4", "GREGORIAN"), - ] + ], ) def test_rscale(calendars, uid, scale): """Check that the RSCALE is parsed correctly.""" @@ -27,13 +29,13 @@ def test_rscale(calendars, uid, scale): @pytest.mark.parametrize( - "uid,skip", + ("uid", "skip"), [ ("4.3.2", None), ("4.3.3", ["FORWARD"]), - ] + ], ) -def test_rscale(calendars, uid, skip): +def test_rscale_with_skip(calendars, uid, skip): """Check that the RSCALE is parsed correctly.""" event = calendars.rfc_7529.walk(select=lambda c: c.get("UID") == uid)[0] recur = event["RRULE"] @@ -48,9 +50,13 @@ def test_leap_month(calendars): @pytest.mark.parametrize( - "ty, recur, ics", + ("ty", "recur", "ics"), [ - (vRecur, vRecur(rscale="CHINESE", freq="YEARLY"), b"RSCALE=CHINESE;FREQ=YEARLY"), + ( + vRecur, + vRecur(rscale="CHINESE", freq="YEARLY"), + b"RSCALE=CHINESE;FREQ=YEARLY", + ), (vRecur, vRecur(bymonth=vMonth(10)), b"BYMONTH=10"), (vRecur, vRecur(bymonth=vMonth("5L")), b"BYMONTH=5L"), (vMonth, vMonth(10), b"10"), @@ -61,9 +67,17 @@ def test_leap_month(calendars): (vSkip, vSkip("OMIT"), b"OMIT"), (vSkip, vSkip("BACKWARD"), b"BACKWARD"), (vSkip, vSkip("FORWARD"), b"FORWARD"), - (vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip='FORWARD'), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD"), - (vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip=vSkip.FORWARD), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD"), - ] + ( + vRecur, + vRecur(rscale="GREGORIAN", freq="YEARLY", skip="FORWARD"), + b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD", + ), + ( + vRecur, + vRecur(rscale="GREGORIAN", freq="YEARLY", skip=vSkip.FORWARD), + b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD", + ), + ], ) def test_conversion(ty, recur, ics): """Test string conversion.""" diff --git a/src/icalendar/tests/test_time.py b/src/icalendar/tests/test_time.py index b31cb7c1..ce127521 100644 --- a/src/icalendar/tests/test_time.py +++ b/src/icalendar/tests/test_time.py @@ -1,28 +1,30 @@ import datetime -import icalendar import os +import icalendar + + def test_value_type_is_not_mapped(): """Usually, the value should be absent.""" - assert 'X-SOMETIME' not in icalendar.cal.types_factory.types_map + assert "X-SOMETIME" not in icalendar.cal.types_factory.types_map def test_value_type_is_mapped(x_sometime): """The value is mapped for the test.""" - assert 'X-SOMETIME' in icalendar.cal.types_factory.types_map + assert "X-SOMETIME" in icalendar.cal.types_factory.types_map def test_create_from_ical(x_sometime): directory = os.path.dirname(__file__) - ics = open(os.path.join(directory, 'calendars', 'time.ics'), 'rb') + ics = open(os.path.join(directory, "calendars", "time.ics"), "rb") cal = icalendar.Calendar.from_ical(ics.read()) ics.close() - assert cal['X-SOMETIME'].dt == datetime.time(17, 20, 10) - assert cal['X-SOMETIME'].to_ical() == '172010' + assert cal["X-SOMETIME"].dt == datetime.time(17, 20, 10) + assert cal["X-SOMETIME"].to_ical() == "172010" def test_create_to_ical(x_sometime): cal = icalendar.Calendar() - cal.add('X-SOMETIME', datetime.time(17, 20, 10)) - assert b'X-SOMETIME;VALUE=TIME:172010' in cal.to_ical().splitlines() + cal.add("X-SOMETIME", datetime.time(17, 20, 10)) + assert b"X-SOMETIME;VALUE=TIME:172010" in cal.to_ical().splitlines() diff --git a/src/icalendar/tests/test_timezoned.py b/src/icalendar/tests/test_timezoned.py index 1f2795af..abbdc2de 100644 --- a/src/icalendar/tests/test_timezoned.py +++ b/src/icalendar/tests/test_timezoned.py @@ -1,6 +1,7 @@ - import datetime + import dateutil.parser + import icalendar from icalendar.prop import tzid_from_dt @@ -9,49 +10,53 @@ def test_create_from_ical(calendars, other_tzp): """Create a calendar from a .ics file.""" cal = calendars.timezoned - assert cal['prodid'].to_ical() == b"-//Plone.org//NONSGML plone.app.event//EN" + assert cal["prodid"].to_ical() == b"-//Plone.org//NONSGML plone.app.event//EN" - timezones = cal.walk('VTIMEZONE') + timezones = cal.walk("VTIMEZONE") assert len(timezones) == 1 tz = timezones[0] - assert tz['tzid'].to_ical() == b"Europe/Vienna" + assert tz["tzid"].to_ical() == b"Europe/Vienna" - std = tz.walk('STANDARD')[0] - assert std.decoded('TZOFFSETFROM') == datetime.timedelta(0, 7200) + std = tz.walk("STANDARD")[0] + assert std.decoded("TZOFFSETFROM") == datetime.timedelta(0, 7200) - ev1 = cal.walk('VEVENT')[0] - assert ev1.decoded('DTSTART') == other_tzp.localize(datetime.datetime(2012, 2, 13, 10, 0, 0), 'Europe/Vienna') - assert ev1.decoded('DTSTAMP') == other_tzp.localize(datetime.datetime(2010, 10, 10, 9, 10, 10), 'UTC') + ev1 = cal.walk("VEVENT")[0] + assert ev1.decoded("DTSTART") == other_tzp.localize( + datetime.datetime(2012, 2, 13, 10, 0, 0), "Europe/Vienna" + ) + assert ev1.decoded("DTSTAMP") == other_tzp.localize( + datetime.datetime(2010, 10, 10, 9, 10, 10), "UTC" + ) def test_create_to_ical(tzp): cal = icalendar.Calendar() - cal.add('prodid', "-//Plone.org//NONSGML plone.app.event//EN") - cal.add('version', "2.0") - cal.add('x-wr-calname', "test create calendar") - cal.add('x-wr-caldesc', "icalendar tests") - cal.add('x-wr-relcalid', "12345") - cal.add('x-wr-timezone', "Europe/Vienna") + cal.add("prodid", "-//Plone.org//NONSGML plone.app.event//EN") + cal.add("version", "2.0") + cal.add("x-wr-calname", "test create calendar") + cal.add("x-wr-caldesc", "icalendar tests") + cal.add("x-wr-relcalid", "12345") + cal.add("x-wr-timezone", "Europe/Vienna") tzc = icalendar.Timezone() - tzc.add('tzid', 'Europe/Vienna') - tzc.add('x-lic-location', 'Europe/Vienna') + tzc.add("tzid", "Europe/Vienna") + tzc.add("x-lic-location", "Europe/Vienna") tzs = icalendar.TimezoneStandard() - tzs.add('tzname', 'CET') - tzs.add('dtstart', datetime.datetime(1970, 10, 25, 3, 0, 0)) - tzs.add('rrule', {'freq': 'yearly', 'bymonth': 10, 'byday': '-1su'}) - tzs.add('TZOFFSETFROM', datetime.timedelta(hours=2)) - tzs.add('TZOFFSETTO', datetime.timedelta(hours=1)) + tzs.add("tzname", "CET") + tzs.add("dtstart", datetime.datetime(1970, 10, 25, 3, 0, 0)) + tzs.add("rrule", {"freq": "yearly", "bymonth": 10, "byday": "-1su"}) + tzs.add("TZOFFSETFROM", datetime.timedelta(hours=2)) + tzs.add("TZOFFSETTO", datetime.timedelta(hours=1)) tzd = icalendar.TimezoneDaylight() - tzd.add('tzname', 'CEST') - tzd.add('dtstart', datetime.datetime(1970, 3, 29, 2, 0, 0)) - tzs.add('rrule', {'freq': 'yearly', 'bymonth': 3, 'byday': '-1su'}) - tzd.add('TZOFFSETFROM', datetime.timedelta(hours=1)) - tzd.add('TZOFFSETTO', datetime.timedelta(hours=2)) + tzd.add("tzname", "CEST") + tzd.add("dtstart", datetime.datetime(1970, 3, 29, 2, 0, 0)) + tzs.add("rrule", {"freq": "yearly", "bymonth": 3, "byday": "-1su"}) + tzd.add("TZOFFSETFROM", datetime.timedelta(hours=1)) + tzd.add("TZOFFSETTO", datetime.timedelta(hours=2)) tzc.add_component(tzs) tzc.add_component(tzd) @@ -59,36 +64,41 @@ def test_create_to_ical(tzp): event = icalendar.Event() event.add( - 'dtstart', - tzp.localize(datetime.datetime(2012, 2, 13, 10, 00, 00), "Europe/Vienna")) + "dtstart", + tzp.localize(datetime.datetime(2012, 2, 13, 10, 00, 00), "Europe/Vienna"), + ) event.add( - 'dtend', - tzp.localize(datetime.datetime(2012, 2, 17, 18, 00, 00), "Europe/Vienna")) + "dtend", + tzp.localize(datetime.datetime(2012, 2, 17, 18, 00, 00), "Europe/Vienna"), + ) event.add( - 'dtstamp', - tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna")) + "dtstamp", + tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), + ) event.add( - 'created', - tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna")) - event.add('uid', '123456') + "created", + tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), + ) + event.add("uid", "123456") event.add( - 'last-modified', - tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna")) - event.add('summary', 'artsprint 2012') + "last-modified", + tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), + ) + event.add("summary", "artsprint 2012") # event.add('rrule', 'FREQ=YEARLY;INTERVAL=1;COUNT=10') - event.add('description', 'sprinting at the artsprint') - event.add('location', 'aka bild, wien') - event.add('categories', 'first subject') - event.add('categories', 'second subject') - event.add('attendee', 'häns') - event.add('attendee', 'franz') - event.add('attendee', 'sepp') - event.add('contact', 'Max Mustermann, 1010 Wien') - event.add('url', 'https://plone.org') + event.add("description", "sprinting at the artsprint") + event.add("location", "aka bild, wien") + event.add("categories", "first subject") + event.add("categories", "second subject") + event.add("attendee", "häns") + event.add("attendee", "franz") + event.add("attendee", "sepp") + event.add("contact", "Max Mustermann, 1010 Wien") + event.add("url", "https://plone.org") cal.add_component(event) - test_out = b'|'.join(cal.to_ical().splitlines()) - test_out = test_out.decode('utf-8') + test_out = b"|".join(cal.to_ical().splitlines()) + test_out = test_out.decode("utf-8") vtimezone_lines = "BEGIN:VTIMEZONE|TZID:Europe/Vienna|X-LIC-LOCATION:" "Europe/Vienna|BEGIN:STANDARD|DTSTART:19701025T03" @@ -100,61 +110,67 @@ def test_create_to_ical(tzp): assert vtimezone_lines in test_out test_str = "DTSTART;TZID=Europe/Vienna:20120213T100000" - assert (test_str in test_out) - assert ("ATTENDEE:sepp" in test_out) + assert test_str in test_out + assert "ATTENDEE:sepp" in test_out # ical standard expects DTSTAMP and CREATED in UTC - assert ("DTSTAMP:20101010T081010Z" in test_out) - assert ("CREATED:20101010T081010Z" in test_out) + assert "DTSTAMP:20101010T081010Z" in test_out + assert "CREATED:20101010T081010Z" in test_out def test_tzinfo_dateutil(): """Test for issues #77, #63 references: #73,7430b66862346fe3a6a100ab25e35a8711446717 """ - date = dateutil.parser.parse('2012-08-30T22:41:00Z') - date2 = dateutil.parser.parse('2012-08-30T22:41:00 +02:00') - assert (date.tzinfo.__module__.startswith('dateutil.tz')) - assert (date2.tzinfo.__module__.startswith('dateutil.tz')) + date = dateutil.parser.parse("2012-08-30T22:41:00Z") + date2 = dateutil.parser.parse("2012-08-30T22:41:00 +02:00") + assert date.tzinfo.__module__.startswith("dateutil.tz") + assert date2.tzinfo.__module__.startswith("dateutil.tz") # make sure, it's parsed properly and doesn't throw an error - assert (icalendar.vDDDTypes(date).to_ical() - == b'20120830T224100Z') - assert (icalendar.vDDDTypes(date2).to_ical() - == b'20120830T224100') + assert icalendar.vDDDTypes(date).to_ical() == b"20120830T224100Z" + assert icalendar.vDDDTypes(date2).to_ical() == b"20120830T224100" def test_create_america_new_york(calendars, tzp): """testing America/New_York, the most complex example from the RFC""" cal = calendars.america_new_york - dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt - assert tzid_from_dt(dt) in ('custom_America/New_York', "EDT") + dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt + assert tzid_from_dt(dt) in ("custom_America/New_York", "EDT") def test_america_new_york_with_pytz(calendars, tzp, pytz_only): """Create a custom timezone with pytz and test the transition times.""" print(tzp) cal = calendars.america_new_york - dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt + dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt tz = dt.tzinfo - tz_new_york = tzp.timezone('America/New_York') + tz_new_york = tzp.timezone("America/New_York") # for reasons (tm) the locally installed version of the timezone # database isn't always complete, therefore we only compare some # transition times ny_transition_times = [] ny_transition_info = [] for num, date in enumerate(tz_new_york._utc_transition_times): - if datetime.datetime(1967, 4, 30, 7, 0)\ - <= date <= datetime.datetime(2037, 11, 1, 6, 0): + if ( + datetime.datetime(1967, 4, 30, 7, 0) + <= date + <= datetime.datetime(2037, 11, 1, 6, 0) + ): ny_transition_times.append(date) ny_transition_info.append(tz_new_york._transition_info[num]) assert tz._utc_transition_times[:142] == ny_transition_times assert tz._transition_info[0:142] == ny_transition_info assert ( - datetime.timedelta(-1, 72000), - datetime.timedelta(0, 3600), 'EDT' - ) in tz._tzinfos.keys() - assert (datetime.timedelta(-1, 68400), datetime.timedelta(0), 'EST') in tz._tzinfos.keys() + datetime.timedelta(-1, 72000), + datetime.timedelta(0, 3600), + "EDT", + ) in tz._tzinfos.keys() + assert ( + datetime.timedelta(-1, 68400), + datetime.timedelta(0), + "EST", + ) in tz._tzinfos.keys() fiji_transition_times = [ @@ -221,68 +237,85 @@ def test_america_new_york_with_pytz(calendars, tzp, pytz_only): datetime.datetime(2037, 1, 17, 13, 0), datetime.datetime(2037, 10, 24, 14, 0), datetime.datetime(2038, 1, 23, 13, 0), - datetime.datetime(2038, 10, 23, 14, 0) + datetime.datetime(2038, 10, 23, 14, 0), ] fiji_transition_info = ( - [( - datetime.timedelta(0, 43200), - datetime.timedelta(0), - 'custom_Pacific/Fiji_19151026T000000_+115544_+1200' - )] + - 3 * [( - datetime.timedelta(0, 46800), - datetime.timedelta(0, 3600), - 'custom_Pacific/Fiji_19981101T020000_+1200_+1300' - ), ( - datetime.timedelta(0, 43200), - datetime.timedelta(0), - 'custom_Pacific/Fiji_19990228T030000_+1300_+1200') - ] + - 3 * [( - datetime.timedelta(0, 46800), - datetime.timedelta(0, 3600), - 'custom_Pacific/Fiji_20101024T020000_+1200_+1300' - ), ( - datetime.timedelta(0, 43200), - datetime.timedelta(0), - 'custom_Pacific/Fiji_19990228T030000_+1300_+1200' - )] + - 25 * [( - datetime.timedelta(0, 46800), - datetime.timedelta(0, 3600), - 'custom_Pacific/Fiji_20101024T020000_+1200_+1300' - ), ( - datetime.timedelta(0, 43200), - datetime.timedelta(0), - 'custom_Pacific/Fiji_20140119T020000_+1300_+1200' - )] + - [( - datetime.timedelta(0, 46800), - datetime.timedelta(0, 3600), - 'custom_Pacific/Fiji_20101024T020000_+1200_+1300' - )] + [ + ( + datetime.timedelta(0, 43200), + datetime.timedelta(0), + "custom_Pacific/Fiji_19151026T000000_+115544_+1200", + ) + ] + + 3 + * [ + ( + datetime.timedelta(0, 46800), + datetime.timedelta(0, 3600), + "custom_Pacific/Fiji_19981101T020000_+1200_+1300", + ), + ( + datetime.timedelta(0, 43200), + datetime.timedelta(0), + "custom_Pacific/Fiji_19990228T030000_+1300_+1200", + ), + ] + + 3 + * [ + ( + datetime.timedelta(0, 46800), + datetime.timedelta(0, 3600), + "custom_Pacific/Fiji_20101024T020000_+1200_+1300", + ), + ( + datetime.timedelta(0, 43200), + datetime.timedelta(0), + "custom_Pacific/Fiji_19990228T030000_+1300_+1200", + ), + ] + + 25 + * [ + ( + datetime.timedelta(0, 46800), + datetime.timedelta(0, 3600), + "custom_Pacific/Fiji_20101024T020000_+1200_+1300", + ), + ( + datetime.timedelta(0, 43200), + datetime.timedelta(0), + "custom_Pacific/Fiji_20140119T020000_+1300_+1200", + ), + ] + + [ + ( + datetime.timedelta(0, 46800), + datetime.timedelta(0, 3600), + "custom_Pacific/Fiji_20101024T020000_+1200_+1300", + ) + ] ) + def test_create_pacific_fiji(calendars, pytz_only): """testing Pacific/Fiji, another pretty complex example with more than one RDATE property per subcomponent""" cal = calendars.pacific_fiji - tz = cal.walk('VEVENT')[0]['DTSTART'][0].dt.tzinfo - assert str(tz) == 'custom_Pacific/Fiji' + tz = cal.walk("VEVENT")[0]["DTSTART"][0].dt.tzinfo + assert str(tz) == "custom_Pacific/Fiji" assert tz._utc_transition_times == fiji_transition_times assert tz._transition_info == fiji_transition_info assert ( - datetime.timedelta(0, 46800), - datetime.timedelta(0, 3600), - 'custom_Pacific/Fiji_19981101T020000_+1200_+1300' - ) in tz._tzinfos.keys() + datetime.timedelta(0, 46800), + datetime.timedelta(0, 3600), + "custom_Pacific/Fiji_19981101T020000_+1200_+1300", + ) in tz._tzinfos.keys() assert ( - datetime.timedelta(0, 43200), - datetime.timedelta(0), - 'custom_Pacific/Fiji_19990228T030000_+1300_+1200' - ) in tz._tzinfos.keys() + datetime.timedelta(0, 43200), + datetime.timedelta(0), + "custom_Pacific/Fiji_19990228T030000_+1300_+1200", + ) in tz._tzinfos.keys() # these are the expected offsets before and after the fiji_transition_times @@ -357,7 +390,7 @@ def test_create_pacific_fiji(calendars, pytz_only): def test_transition_times_fiji(tzp, timezones): """The transition times are computed.""" tz = timezones.pacific_fiji.to_tz(tzp) - offsets = [] # [(before, after), ...] + offsets = [] # [(before, after), ...] for i, transition_time in enumerate(fiji_transition_times): before_after_offset = [] for offset in (datetime.timedelta(hours=-1), datetime.timedelta(hours=+1)): @@ -372,42 +405,43 @@ def test_same_start_date(calendars): """testing if we can handle VTIMEZONEs whose different components have the same start DTIMEs.""" cal = calendars.timezone_same_start - d = cal.subcomponents[1]['DTSTART'].dt - assert d.strftime('%c') == 'Fri Feb 24 12:00:00 2017' + d = cal.subcomponents[1]["DTSTART"].dt + assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017" + def test_same_start_date_and_offset(calendars): """testing if we can handle VTIMEZONEs whose different components have the same DTSTARTs, TZOFFSETFROM, and TZOFFSETTO.""" cal = calendars.timezone_same_start_and_offset - d = cal.subcomponents[1]['DTSTART'].dt - assert d.strftime('%c') == 'Fri Feb 24 12:00:00 2017' + d = cal.subcomponents[1]["DTSTART"].dt + assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017" + def test_rdate(calendars): - """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE - """ + """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE""" cal = calendars.timezone_rdate - vevent = cal.walk('VEVENT')[0] - assert tzid_from_dt(vevent['DTSTART'].dt) in ('posix/Europe/Vaduz', "CET") + vevent = cal.walk("VEVENT")[0] + assert tzid_from_dt(vevent["DTSTART"].dt) in ("posix/Europe/Vaduz", "CET") + def test_rdate_pytz(calendars, pytz_only): - """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE - """ + """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE""" cal = calendars.timezone_rdate - vevent = cal.walk('VEVENT')[0] - tz = vevent['DTSTART'].dt.tzinfo + vevent = cal.walk("VEVENT")[0] + tz = vevent["DTSTART"].dt.tzinfo assert tz._utc_transition_times[:6] == [ - datetime.datetime(1901, 12, 13, 20, 45, 38), - datetime.datetime(1941, 5, 5, 0, 0, 0), - datetime.datetime(1941, 10, 6, 0, 0, 0), - datetime.datetime(1942, 5, 4, 0, 0, 0), - datetime.datetime(1942, 10, 5, 0, 0, 0), - datetime.datetime(1981, 3, 29, 1, 0), - ] + datetime.datetime(1901, 12, 13, 20, 45, 38), + datetime.datetime(1941, 5, 5, 0, 0, 0), + datetime.datetime(1941, 10, 6, 0, 0, 0), + datetime.datetime(1942, 5, 4, 0, 0, 0), + datetime.datetime(1942, 10, 5, 0, 0, 0), + datetime.datetime(1981, 3, 29, 1, 0), + ] assert tz._transition_info[:6] == [ - (datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'), - (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'), - (datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'), - (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'), - (datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'), - (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'), - ] + (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), + (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), + (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), + (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), + (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), + (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), + ] diff --git a/src/icalendar/tests/test_unit_cal.py b/src/icalendar/tests/test_unit_cal.py index 7e927401..84e8d390 100644 --- a/src/icalendar/tests/test_unit_cal.py +++ b/src/icalendar/tests/test_unit_cal.py @@ -1,13 +1,12 @@ import itertools -from datetime import datetime -from datetime import timedelta +import re +from datetime import datetime, timedelta import pytest import icalendar -import re -from icalendar.cal import Component, Calendar, Event from icalendar import prop +from icalendar.cal import Calendar, Component, Event from icalendar.prop import tzid_from_dt @@ -21,15 +20,15 @@ def test_nonempty_calendar_component(calendar_component): """Every key defines a property.A property can consist of either a single item. This can be set with a single value... """ - calendar_component['prodid'] = '-//max m//icalendar.mxm.dk/' + calendar_component["prodid"] = "-//max m//icalendar.mxm.dk/" assert not calendar_component.is_empty() - assert calendar_component == Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'}) + assert calendar_component == Calendar({"PRODID": "-//max m//icalendar.mxm.dk/"}) # or with a list - calendar_component['ATTENDEE'] = ['Max M', 'Rasmussen'] + calendar_component["ATTENDEE"] = ["Max M", "Rasmussen"] assert calendar_component == Calendar( - {'ATTENDEE': ['Max M', 'Rasmussen'], - 'PRODID': '-//max m//icalendar.mxm.dk/'}) + {"ATTENDEE": ["Max M", "Rasmussen"], "PRODID": "-//max m//icalendar.mxm.dk/"} + ) def test_add_multiple_values(event_component): @@ -39,57 +38,62 @@ def test_add_multiple_values(event_component): a list or not. """ # add multiple values at once - event_component.add('attendee', - ['test@test.com', 'test2@test.com']) + event_component.add("attendee", ["test@test.com", "test2@test.com"]) # or add one per line - event_component.add('attendee', 'maxm@mxm.dk') - event_component.add('attendee', 'test@example.dk') + event_component.add("attendee", "maxm@mxm.dk") + event_component.add("attendee", "test@example.dk") # add again multiple values at once to very concatenaton of lists - event_component.add('attendee', - ['test3@test.com', 'test4@test.com']) - - assert event_component == Event({'ATTENDEE': [ - prop.vCalAddress('test@test.com'), - prop.vCalAddress('test2@test.com'), - prop.vCalAddress('maxm@mxm.dk'), - prop.vCalAddress('test@example.dk'), - prop.vCalAddress('test3@test.com'), - prop.vCalAddress('test4@test.com') - ]}) + event_component.add("attendee", ["test3@test.com", "test4@test.com"]) + + assert event_component == Event( + { + "ATTENDEE": [ + prop.vCalAddress("test@test.com"), + prop.vCalAddress("test2@test.com"), + prop.vCalAddress("maxm@mxm.dk"), + prop.vCalAddress("test@example.dk"), + prop.vCalAddress("test3@test.com"), + prop.vCalAddress("test4@test.com"), + ] + } + ) def test_get_content_directly(c): """You can get the values back directly ...""" - c.add('prodid', '-//my product//') - assert c['prodid'] == prop.vText('-//my product//') + c.add("prodid", "-//my product//") + assert c["prodid"] == prop.vText("-//my product//") # ... or decoded to a python type - assert c.decoded('prodid') == b'-//my product//' + assert c.decoded("prodid") == b"-//my product//" def test_get_default_value(c): """With default values for non existing properties""" - assert c.decoded('version', 'No Version') == 'No Version' + assert c.decoded("version", "No Version") == "No Version" def test_default_list_example(c): - c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)]) - assert isinstance(c.decoded('rdate'), prop.vDDDLists) + c.add("rdate", [datetime(2013, 3, 28), datetime(2013, 3, 27)]) + assert isinstance(c.decoded("rdate"), prop.vDDDLists) def test_render_component(calendar_component): """The component can render itself in the RFC 5545 format.""" - calendar_component.add('attendee', 'Max M') - assert calendar_component.to_ical() == b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n' + calendar_component.add("attendee", "Max M") + assert ( + calendar_component.to_ical() + == b"BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n" + ) def test_nested_component_event_ics(filled_event_component): """Check the ical string of the event component.""" assert filled_event_component.to_ical() == ( - b'BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n' - + b'DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r' - + b'\nEND:VEVENT\r\n' + b"BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n" + + b"DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r" + + b"\nEND:VEVENT\r\n" ) @@ -98,58 +102,75 @@ def test_nested_components(calendar_component, filled_event_component): holds events.""" self.assertEqual( calendar_component.subcomponents, - [Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', - 'SUMMARY': 'A brief history of time'})] + [ + Event( + { + "DTEND": "20000102T000000", + "DTSTART": "20000101T000000", + "SUMMARY": "A brief history of time", + } + ) + ], ) def test_walk_filled_calendar_component(calendar_component, filled_event_component): """We can walk over nested componentes with the walk method.""" - assert [i.name for i in calendar_component.walk()] == ['VCALENDAR', 'VEVENT'] + assert [i.name for i in calendar_component.walk()] == ["VCALENDAR", "VEVENT"] def test_filter_walk(calendar_component, filled_event_component): """We can also just walk over specific component types, by filtering them on their name.""" - assert [i.name for i in calendar_component.walk('VEVENT')] == ['VEVENT'] - assert [i['dtstart'] for i in calendar_component.walk('VEVENT')] == ['20000101T000000'] + assert [i.name for i in calendar_component.walk("VEVENT")] == ["VEVENT"] + assert [i["dtstart"] for i in calendar_component.walk("VEVENT")] == [ + "20000101T000000" + ] def test_recursive_property_items(calendar_component, filled_event_component): """We can enumerate property items recursively with the property_items method.""" - calendar_component.add('attendee', 'Max M') + calendar_component.add("attendee", "Max M") assert calendar_component.property_items() == [ - ('BEGIN', b'VCALENDAR'), ('ATTENDEE', prop.vCalAddress('Max M')), - ('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), - ('DTSTART', '20000101T000000'), - ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT'), - ('END', b'VCALENDAR')] + ("BEGIN", b"VCALENDAR"), + ("ATTENDEE", prop.vCalAddress("Max M")), + ("BEGIN", b"VEVENT"), + ("DTEND", "20000102T000000"), + ("DTSTART", "20000101T000000"), + ("SUMMARY", "A brief history of time"), + ("END", b"VEVENT"), + ("END", b"VCALENDAR"), + ] def test_flat_property_items(calendar_component, filled_event_component): """We can also enumerate property items just under the component.""" assert calendar_component.property_items(recursive=False) == [ - ('BEGIN', b'VCALENDAR'), - ('ATTENDEE', prop.vCalAddress('Max M')), - ('END', b'VCALENDAR')] + ("BEGIN", b"VCALENDAR"), + ("ATTENDEE", prop.vCalAddress("Max M")), + ("END", b"VCALENDAR"), + ] def test_flat_property_items(filled_event_component): """Flat enumeration on the event.""" assert filled_event_component.property_items(recursive=False) == [ - ('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), - ('DTSTART', '20000101T000000'), - ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')] + ("BEGIN", b"VEVENT"), + ("DTEND", "20000102T000000"), + ("DTSTART", "20000101T000000"), + ("SUMMARY", "A brief history of time"), + ("END", b"VEVENT"), + ] def test_indent(): """Text fields which span multiple mulitple lines require proper indenting""" c = Calendar() - c['description'] = 'Paragraph one\n\nParagraph two' + c["description"] = "Paragraph one\n\nParagraph two" assert c.to_ical() == ( - b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two' - + b'\r\nEND:VCALENDAR\r\n' + b"BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two" + + b"\r\nEND:VCALENDAR\r\n" ) @@ -157,10 +178,12 @@ def test_INLINE_properties(calendar_with_resources): """INLINE properties have their values on one property line. Note the double quoting of the value with a colon in it. """ - assert calendar_with_resources == Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'}) + assert calendar_with_resources == Calendar( + {"RESOURCES": 'Chair, Table, "Room: 42"'} + ) assert calendar_with_resources.to_ical() == ( b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n' - + b'END:VCALENDAR\r\n' + + b"END:VCALENDAR\r\n" ) @@ -168,32 +191,48 @@ def test_get_inline(calendar_with_resources): """The inline values must be handled by the get_inline() and set_inline() methods. """ - assert calendar_with_resources.get_inline('resources', decode=0) == \ - ['Chair', 'Table', 'Room: 42'] + assert calendar_with_resources.get_inline("resources", decode=0) == [ + "Chair", + "Table", + "Room: 42", + ] def test_get_inline_decoded(calendar_with_resources): """These can also be decoded""" - assert calendar_with_resources.get_inline('resources', decode=1) == \ - [b'Chair', b'Table', b'Room: 42'] + assert calendar_with_resources.get_inline("resources", decode=1) == [ + b"Chair", + b"Table", + b"Room: 42", + ] def test_set_inline(calendar_with_resources): """You can set them directly ...""" - calendar_with_resources.set_inline('resources', ['A', 'List', 'of', 'some, recources'], - encode=1) - assert calendar_with_resources['resources'] == 'A,List,of,"some, recources"' - assert calendar_with_resources.get_inline('resources', decode=0) == ['A', 'List', 'of', 'some, recources'] + calendar_with_resources.set_inline( + "resources", ["A", "List", "of", "some, recources"], encode=1 + ) + assert calendar_with_resources["resources"] == 'A,List,of,"some, recources"' + assert calendar_with_resources.get_inline("resources", decode=0) == [ + "A", + "List", + "of", + "some, recources", + ] def test_inline_free_busy_inline(c): - c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\ - + '19970308T230000Z/19970309T000000Z' - assert c.get_inline('freebusy', decode=0) == \ - ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', - '19970308T230000Z/19970309T000000Z'] + c["freebusy"] = ( + "19970308T160000Z/PT3H,19970308T200000Z/PT1H," + + "19970308T230000Z/19970309T000000Z" + ) + assert c.get_inline("freebusy", decode=0) == [ + "19970308T160000Z/PT3H", + "19970308T200000Z/PT1H", + "19970308T230000Z/19970309T000000Z", + ] - freebusy = c.get_inline('freebusy', decode=1) + freebusy = c.get_inline("freebusy", decode=1) assert isinstance(freebusy[0][0], datetime) assert isinstance(freebusy[0][1], timedelta) @@ -202,11 +241,10 @@ def test_cal_Component_add(comp, tzp): """Test the for timezone correctness: dtstart should preserve it's timezone, created, dtstamp and last-modified must be in UTC. """ - comp.add('dtstart', tzp.localize(datetime(2010, 10, 10, 10, 0, 0), "Europe/Vienna")) - comp.add('created', datetime(2010, 10, 10, 12, 0, 0)) - comp.add('dtstamp', tzp.localize(datetime(2010, 10, 10, 14, 0, 0), "Europe/Vienna")) - comp.add('last-modified', tzp.localize_utc( - datetime(2010, 10, 10, 16, 0, 0))) + comp.add("dtstart", tzp.localize(datetime(2010, 10, 10, 10, 0, 0), "Europe/Vienna")) + comp.add("created", datetime(2010, 10, 10, 12, 0, 0)) + comp.add("dtstamp", tzp.localize(datetime(2010, 10, 10, 14, 0, 0), "Europe/Vienna")) + comp.add("last-modified", tzp.localize_utc(datetime(2010, 10, 10, 16, 0, 0))) lines = comp.to_ical().splitlines() assert b"DTSTART;TZID=Europe/Vienna:20101010T100000" in lines @@ -216,22 +254,20 @@ def test_cal_Component_add(comp, tzp): def test_cal_Component_add_no_reencode(comp): - """Already encoded values should not be re-encoded. - """ - comp.add('ATTACH', 'me') - comp.add('ATTACH', 'you', encode=False) - binary = prop.vBinary('us') - comp.add('ATTACH', binary) + """Already encoded values should not be re-encoded.""" + comp.add("ATTACH", "me") + comp.add("ATTACH", "you", encode=False) + binary = prop.vBinary("us") + comp.add("ATTACH", binary) - assert comp['ATTACH'] == ['me', 'you', binary] + assert comp["ATTACH"] == ["me", "you", binary] def test_cal_Component_add_property_parameter(comp): """Test the for timezone correctness: dtstart should preserve it's timezone, crated, dtstamp and last-modified must be in UTC. """ - comp.add('X-TEST-PROP', 'tryout.', - parameters={'prop1': 'val1', 'prop2': 'val2'}) + comp.add("X-TEST-PROP", "tryout.", parameters={"prop1": "val1", "prop2": "val2"}) lines = comp.to_ical().splitlines() assert b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines @@ -239,20 +275,20 @@ def test_cal_Component_add_property_parameter(comp): comp_prop = pytest.mark.parametrize( "component_name, property_name", [ - ('VEVENT', 'DTSTART'), - ('VEVENT', 'DTEND'), - ('VEVENT', 'RECURRENCE-ID'), - ('VTODO', 'DUE') - ] + ("VEVENT", "DTSTART"), + ("VEVENT", "DTEND"), + ("VEVENT", "RECURRENCE-ID"), + ("VTODO", "DUE"), + ], ) @comp_prop def test_cal_Component_from_ical(component_name, property_name, tzp): """Check for proper handling of TZID parameter of datetime properties""" - component_str = 'BEGIN:' + component_name + '\n' - component_str += property_name + ';TZID=America/Denver:' - component_str += '20120404T073000\nEND:' + component_name + component_str = "BEGIN:" + component_name + "\n" + component_str += property_name + ";TZID=America/Denver:" + component_str += "20120404T073000\nEND:" + component_name component = Component.from_ical(component_str) assert tzid_from_dt(component[property_name].dt) == "America/Denver" @@ -260,20 +296,22 @@ def test_cal_Component_from_ical(component_name, property_name, tzp): @comp_prop def test_cal_Component_from_ical_2(component_name, property_name, tzp): """Check for proper handling of TZID parameter of datetime properties""" - component_str = 'BEGIN:' + component_name + '\n' - component_str += property_name + ':' - component_str += '20120404T073000\nEND:' + component_name + component_str = "BEGIN:" + component_name + "\n" + component_str += property_name + ":" + component_str += "20120404T073000\nEND:" + component_name component = Component.from_ical(component_str) assert component[property_name].dt.tzinfo == None def test_cal_Component_to_ical_property_order(): - component_str = [b'BEGIN:VEVENT', - b'DTSTART:19970714T170000Z', - b'DTEND:19970715T035959Z', - b'SUMMARY:Bastille Day Party', - b'END:VEVENT'] - component = Component.from_ical(b'\r\n'.join(component_str)) + component_str = [ + b"BEGIN:VEVENT", + b"DTSTART:19970714T170000Z", + b"DTEND:19970715T035959Z", + b"SUMMARY:Bastille Day Party", + b"END:VEVENT", + ] + component = Component.from_ical(b"\r\n".join(component_str)) sorted_str = component.to_ical().splitlines() assert sorted_str != component_str @@ -284,39 +322,43 @@ def test_cal_Component_to_ical_property_order(): def test_cal_Component_to_ical_parameter_order(): - component_str = [b'BEGIN:VEVENT', - b'X-FOOBAR;C=one;A=two;B=three:helloworld.', - b'END:VEVENT'] - component = Component.from_ical(b'\r\n'.join(component_str)) + component_str = [ + b"BEGIN:VEVENT", + b"X-FOOBAR;C=one;A=two;B=three:helloworld.", + b"END:VEVENT", + ] + component = Component.from_ical(b"\r\n".join(component_str)) sorted_str = component.to_ical().splitlines() assert sorted_str[0] == component_str[0] - assert sorted_str[1] == b'X-FOOBAR;A=two;B=three;C=one:helloworld.' + assert sorted_str[1] == b"X-FOOBAR;A=two;B=three;C=one:helloworld." assert sorted_str[2] == component_str[2] preserved_str = component.to_ical(sorted=False).splitlines() assert preserved_str == component_str -@pytest.fixture() +@pytest.fixture def repr_example(c): class ReprExample: component = c - component['key1'] = 'value1' + component["key1"] = "value1" calendar = Calendar() - calendar['key1'] = 'value1' + calendar["key1"] = "value1" event = Event() - event['key1'] = 'value1' - nested = Component(key1='VALUE1') + event["key1"] = "value1" + nested = Component(key1="VALUE1") nested.add_component(component) nested.add_component(calendar) + return ReprExample + def test_repr_component(repr_example): - """Test correct class representation. - """ + """Test correct class representation.""" assert re.match(r"Component\({u?'KEY1': u?'value1'}\)", str(repr_example.component)) + def test_repr_calendar(repr_example): assert re.match(r"VCALENDAR\({u?'KEY1': u?'value1'}\)", str(repr_example.calendar)) @@ -330,24 +372,24 @@ def test_nested_components(repr_example): repr_example.calendar.add_component(repr_example.event) print(repr_example.nested) assert re.match( - r"Component\({u?'KEY1': u?'VALUE1'}, " - r"Component\({u?'KEY1': u?'value1'}\), " - r"VCALENDAR\({u?'KEY1': u?'value1'}, " - r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)", - str(repr_example.nested) - ) + r"Component\({u?'KEY1': u?'VALUE1'}, " + r"Component\({u?'KEY1': u?'value1'}\), " + r"VCALENDAR\({u?'KEY1': u?'value1'}, " + r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)", + str(repr_example.nested), + ) def test_component_factory_VEVENT(factory): """Check the events in the component factory""" - component = factory['VEVENT'] - event = component(dtstart='19700101') - assert event.to_ical() == b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n' + component = factory["VEVENT"] + event = component(dtstart="19700101") + assert event.to_ical() == b"BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n" def test_component_factory_VCALENDAR(factory): """Check the VCALENDAR in the factory.""" - assert factory.get('VCALENDAR') == icalendar.cal.Calendar + assert factory.get("VCALENDAR") == icalendar.cal.Calendar def test_minimal_calendar_component_with_one_event(): @@ -355,19 +397,21 @@ def test_minimal_calendar_component_with_one_event(): cal = Calendar() # Some properties are required to be compliant - cal['prodid'] = '-//My calendar product//mxm.dk//' - cal['version'] = '2.0' + cal["prodid"] = "-//My calendar product//mxm.dk//" + cal["version"] = "2.0" # We also need at least one subcomponent for a calendar to be compliant event = Event() - event['summary'] = 'Python meeting about calendaring' - event['uid'] = '42' - event.add('dtstart', datetime(2005, 4, 4, 8, 0, 0)) + event["summary"] = "Python meeting about calendaring" + event["uid"] = "42" + event.add("dtstart", datetime(2005, 4, 4, 8, 0, 0)) cal.add_component(event) - assert cal.subcomponents[0].to_ical() == \ - b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n' \ - + b'DTSTART:20050404T080000\r\nUID:42\r\n' \ - + b'END:VEVENT\r\n' + assert ( + cal.subcomponents[0].to_ical() + == b"BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n" + + b"DTSTART:20050404T080000\r\nUID:42\r\n" + + b"END:VEVENT\r\n" + ) def test_calendar_with_parsing_errors_includes_all_events(calendars): @@ -376,8 +420,10 @@ def test_calendar_with_parsing_errors_includes_all_events(calendars): attribute. The error in the following is the third EXDATE: it has an empty DATE. """ - event_descriptions = [e['DESCRIPTION'].to_ical() for e in calendars.parsing_error.walk('VEVENT')] - assert event_descriptions == [b'Perfectly OK event', b'Wrong event'] + event_descriptions = [ + e["DESCRIPTION"].to_ical() for e in calendars.parsing_error.walk("VEVENT") + ] + assert event_descriptions == [b"Perfectly OK event", b"Wrong event"] def test_calendar_with_parsing_errors_has_an_error_in_one_event(calendars): @@ -386,8 +432,8 @@ def test_calendar_with_parsing_errors_has_an_error_in_one_event(calendars): attribute. The error in the following is the third EXDATE: it has an empty DATE. """ - errors = [e.errors for e in calendars.parsing_error.walk('VEVENT')] - assert errors == [[], [('EXDATE', "Expected datetime, date, or time, got: ''")]] + errors = [e.errors for e in calendars.parsing_error.walk("VEVENT")] + assert errors == [[], [("EXDATE", "Expected datetime, date, or time, got: ''")]] def test_cal_strict_parsing(calendars): @@ -398,21 +444,26 @@ def test_cal_strict_parsing(calendars): def test_cal_ignore_errors_parsing(calendars, vUTCOffset_ignore_exceptions): """If we diable the errors, we should be able to put the calendar back together.""" - assert calendars.parsing_error_in_UTC_offset.to_ical() == calendars.parsing_error_in_UTC_offset.raw_ics - + assert ( + calendars.parsing_error_in_UTC_offset.to_ical() + == calendars.parsing_error_in_UTC_offset.raw_ics + ) @pytest.mark.parametrize( - 'calendar, other_calendar', - itertools.product([ - 'issue_156_RDATE_with_PERIOD_TZID_khal', - 'issue_156_RDATE_with_PERIOD_TZID_khal_2', - 'issue_178_custom_component_contains_other', - 'issue_178_custom_component_inside_other', - 'issue_526_calendar_with_events', - 'issue_526_calendar_with_different_events', - 'issue_526_calendar_with_event_subset', - ], repeat=2) + ("calendar", "other_calendar"), + itertools.product( + [ + "issue_156_RDATE_with_PERIOD_TZID_khal", + "issue_156_RDATE_with_PERIOD_TZID_khal_2", + "issue_178_custom_component_contains_other", + "issue_178_custom_component_inside_other", + "issue_526_calendar_with_events", + "issue_526_calendar_with_different_events", + "issue_526_calendar_with_event_subset", + ], + repeat=2, + ), ) def test_comparing_calendars(calendars, calendar, other_calendar, tzp): are_calendars_equal = calendars[calendar] == calendars[other_calendar] @@ -420,12 +471,19 @@ def test_comparing_calendars(calendars, calendar, other_calendar, tzp): assert are_calendars_equal == are_calendars_actually_equal -@pytest.mark.parametrize('calendar, shuffeled_calendar', [ - ( - 'issue_526_calendar_with_events', - 'issue_526_calendar_with_shuffeled_events', - ), -]) -def test_calendars_with_same_subcomponents_in_different_order_are_equal(calendars, calendar, shuffeled_calendar): - assert not calendars[calendar].subcomponents == calendars[shuffeled_calendar].subcomponents +@pytest.mark.parametrize( + ("calendar", "shuffeled_calendar"), + [ + ( + "issue_526_calendar_with_events", + "issue_526_calendar_with_shuffeled_events", + ), + ], +) +def test_calendars_with_same_subcomponents_in_different_order_are_equal( + calendars, calendar, shuffeled_calendar +): + assert ( + calendars[calendar].subcomponents != calendars[shuffeled_calendar].subcomponents + ) assert calendars[calendar] == calendars[shuffeled_calendar] diff --git a/src/icalendar/tests/test_unit_caselessdict.py b/src/icalendar/tests/test_unit_caselessdict.py index 421a9945..852d468a 100644 --- a/src/icalendar/tests/test_unit_caselessdict.py +++ b/src/icalendar/tests/test_unit_caselessdict.py @@ -4,64 +4,93 @@ class TestCaselessdict(unittest.TestCase): - def test_caselessdict_canonsort_keys(self): canonsort_keys = icalendar.caselessdict.canonsort_keys - keys = ['DTEND', 'DTSTAMP', 'DTSTART', 'UID', 'SUMMARY', 'LOCATION'] + keys = ["DTEND", "DTSTAMP", "DTSTART", "UID", "SUMMARY", "LOCATION"] out = canonsort_keys(keys) self.assertEqual( - out, - ['DTEND', 'DTSTAMP', 'DTSTART', 'LOCATION', 'SUMMARY', 'UID'] + out, ["DTEND", "DTSTAMP", "DTSTART", "LOCATION", "SUMMARY", "UID"] ) - out = canonsort_keys(keys, ('SUMMARY', 'DTSTART', 'DTEND', )) - self.assertEqual( - out, - ['SUMMARY', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'UID'] + out = canonsort_keys( + keys, + ( + "SUMMARY", + "DTSTART", + "DTEND", + ), ) - - out = canonsort_keys(keys, ('UID', 'DTSTART', 'DTEND', )) self.assertEqual( - out, - ['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY'] + out, ["SUMMARY", "DTSTART", "DTEND", "DTSTAMP", "LOCATION", "UID"] ) out = canonsort_keys( keys, - ('UID', 'DTSTART', 'DTEND', 'RRULE', 'EXDATE') + ( + "UID", + "DTSTART", + "DTEND", + ), ) self.assertEqual( - out, - ['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY'] + out, ["UID", "DTSTART", "DTEND", "DTSTAMP", "LOCATION", "SUMMARY"] + ) + + out = canonsort_keys(keys, ("UID", "DTSTART", "DTEND", "RRULE", "EXDATE")) + self.assertEqual( + out, ["UID", "DTSTART", "DTEND", "DTSTAMP", "LOCATION", "SUMMARY"] ) def test_caselessdict_canonsort_items(self): canonsort_items = icalendar.caselessdict.canonsort_items d = { - 'i': 7, 'c': 'at', 'a': 3.5, 'l': (2, 3), 'e': [4, 5], 'n': 13, 'd': {'x': 'y'}, 'r': 1.0, + "i": 7, + "c": "at", + "a": 3.5, + "l": (2, 3), + "e": [4, 5], + "n": 13, + "d": {"x": "y"}, + "r": 1.0, } out = canonsort_items(d) self.assertEqual( out, - [('a', 3.5), ('c', 'at'), ('d', {'x': 'y'}), ('e', [4, 5]), - ('i', 7), ('l', (2, 3)), ('n', 13), ('r', 1.0)] + [ + ("a", 3.5), + ("c", "at"), + ("d", {"x": "y"}), + ("e", [4, 5]), + ("i", 7), + ("l", (2, 3)), + ("n", 13), + ("r", 1.0), + ], ) - out = canonsort_items(d, ('i', 'c', 'a')) + out = canonsort_items(d, ("i", "c", "a")) self.assertTrue( out, - [('i', 7), ('c', 'at'), ('a', 3.5), ('d', {'x': 'y'}), - ('e', [4, 5]), ('l', (2, 3)), ('n', 13), ('r', 1.0)] + [ + ("i", 7), + ("c", "at"), + ("a", 3.5), + ("d", {"x": "y"}), + ("e", [4, 5]), + ("l", (2, 3)), + ("n", 13), + ("r", 1.0), + ], ) def test_caselessdict_copy(self): CaselessDict = icalendar.caselessdict.CaselessDict - original_dict = CaselessDict(key1='val1', key2='val2') + original_dict = CaselessDict(key1="val1", key2="val2") copied_dict = original_dict.copy() self.assertEqual(original_dict, copied_dict) @@ -69,31 +98,28 @@ def test_caselessdict_copy(self): def test_CaselessDict(self): CaselessDict = icalendar.caselessdict.CaselessDict - ncd = CaselessDict(key1='val1', key2='val2') - self.assertEqual( - ncd, - CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'}) - ) + ncd = CaselessDict(key1="val1", key2="val2") + self.assertEqual(ncd, CaselessDict({"KEY2": "val2", "KEY1": "val1"})) - self.assertEqual(ncd['key1'], 'val1') - self.assertEqual(ncd['KEY1'], 'val1') + self.assertEqual(ncd["key1"], "val1") + self.assertEqual(ncd["KEY1"], "val1") - ncd['KEY3'] = 'val3' - self.assertEqual(ncd['key3'], 'val3') + ncd["KEY3"] = "val3" + self.assertEqual(ncd["key3"], "val3") - self.assertEqual(ncd.setdefault('key3', 'FOUND'), 'val3') - self.assertEqual(ncd.setdefault('key4', 'NOT FOUND'), 'NOT FOUND') - self.assertEqual(ncd['key4'], 'NOT FOUND') - self.assertEqual(ncd.get('key1'), 'val1') - self.assertEqual(ncd.get('key3', 'NOT FOUND'), 'val3') - self.assertEqual(ncd.get('key4', 'NOT FOUND'), 'NOT FOUND') - self.assertTrue('key4' in ncd) + self.assertEqual(ncd.setdefault("key3", "FOUND"), "val3") + self.assertEqual(ncd.setdefault("key4", "NOT FOUND"), "NOT FOUND") + self.assertEqual(ncd["key4"], "NOT FOUND") + self.assertEqual(ncd.get("key1"), "val1") + self.assertEqual(ncd.get("key3", "NOT FOUND"), "val3") + self.assertEqual(ncd.get("key4", "NOT FOUND"), "NOT FOUND") + self.assertTrue("key4" in ncd) - del ncd['key4'] - self.assertFalse('key4' in ncd) + del ncd["key4"] + self.assertFalse("key4" in ncd) - ncd.update({'key5': 'val5', 'KEY6': 'val6', 'KEY5': 'val7'}) - self.assertEqual(ncd['key6'], 'val6') + ncd.update({"key5": "val5", "KEY6": "val6", "KEY5": "val7"}) + self.assertEqual(ncd["key6"], "val6") keys = sorted(ncd.keys()) - self.assertEqual(keys, ['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6']) + self.assertEqual(keys, ["KEY1", "KEY2", "KEY3", "KEY5", "KEY6"]) diff --git a/src/icalendar/tests/test_unit_parser_tools.py b/src/icalendar/tests/test_unit_parser_tools.py index 2879b351..a6d8fcff 100644 --- a/src/icalendar/tests/test_unit_parser_tools.py +++ b/src/icalendar/tests/test_unit_parser_tools.py @@ -1,28 +1,30 @@ -from icalendar.parser_tools import data_encode -from icalendar.parser_tools import to_unicode import unittest +from icalendar.parser_tools import data_encode, to_unicode -class TestParserTools(unittest.TestCase): +class TestParserTools(unittest.TestCase): def test_parser_tools_to_unicode(self): - - self.assertEqual(to_unicode(b'spam'), 'spam') - self.assertEqual(to_unicode('spam'), 'spam') - self.assertEqual(to_unicode(b'spam'), 'spam') - self.assertEqual(to_unicode(b'\xc6\xb5'), '\u01b5') - self.assertEqual(to_unicode(b'\xc6\xb5'), - '\u01b5') - self.assertEqual(to_unicode(b'\xc6\xb5', encoding='ascii'), '\u01b5') + self.assertEqual(to_unicode(b"spam"), "spam") + self.assertEqual(to_unicode("spam"), "spam") + self.assertEqual(to_unicode(b"spam"), "spam") + self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5") + self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5") + self.assertEqual(to_unicode(b"\xc6\xb5", encoding="ascii"), "\u01b5") self.assertEqual(to_unicode(1), 1) self.assertEqual(to_unicode(None), None) def test_parser_tools_data_encode(self): - data1 = { - 'k1': 'v1', 'k2': 'v2', 'k3': 'v3', - 'li1': ['it1', 'it2', {'k4': 'v4', 'k5': 'v5'}, 123] + "k1": "v1", + "k2": "v2", + "k3": "v3", + "li1": ["it1", "it2", {"k4": "v4", "k5": "v5"}, 123], + } + res = { + b"k3": b"v3", + b"k2": b"v2", + b"k1": b"v1", + b"li1": [b"it1", b"it2", {b"k5": b"v5", b"k4": b"v4"}, 123], } - res = {b'k3': b'v3', b'k2': b'v2', b'k1': b'v1', - b'li1': [b'it1', b'it2', {b'k5': b'v5', b'k4': b'v4'}, 123]} self.assertEqual(data_encode(data1), res) diff --git a/src/icalendar/tests/test_unit_tools.py b/src/icalendar/tests/test_unit_tools.py index f88e6055..b396e55f 100644 --- a/src/icalendar/tests/test_unit_tools.py +++ b/src/icalendar/tests/test_unit_tools.py @@ -1,12 +1,12 @@ -import pytest import unittest +import pytest + from icalendar.tools import UIDGenerator -class TestTools(unittest.TestCase): +class TestTools(unittest.TestCase): def test_tools_UIDGenerator(self): - # Automatic semi-random uid g = UIDGenerator() uid = g.uid() @@ -14,41 +14,68 @@ def test_tools_UIDGenerator(self): txt = uid.to_ical() length = 15 + 1 + 16 + 1 + 11 self.assertTrue(len(txt) == length) - self.assertTrue(b'@example.com' in txt) + self.assertTrue(b"@example.com" in txt) # You should at least insert your own hostname to be more compliant - uid = g.uid('Example.ORG') + uid = g.uid("Example.ORG") txt = uid.to_ical() self.assertTrue(len(txt) == length) - self.assertTrue(b'@Example.ORG' in txt) + self.assertTrue(b"@Example.ORG" in txt) # You can also insert a path or similar - uid = g.uid('Example.ORG', '/path/to/content') + uid = g.uid("Example.ORG", "/path/to/content") txt = uid.to_ical() self.assertTrue(len(txt) == length) - self.assertTrue(b'-/path/to/content@Example.ORG' in txt) - - -@pytest.mark.parametrize('split,expected,args,kw', [ - # default argument host_name - ("@", "example.com", (), {},), - ("@", "example.com", ("example.com",), {}), - ("@", "example.com", (), {"host_name":"example.com"}), - # replaced host_name - ("@", "test.test", ("test.test",), {}), - ("@", "test.test", (), {"host_name":"test.test"}), - # replace unique - ("-", "123@example.com", (), {"unique": "123"},), - ("-", "abc@example.com", (), {"unique": "abc"},), - # replace host_name and unique - ("-", "1234@test.icalendar", (), {"unique": "1234", "host_name":"test.icalendar"},), - ("-", "abc@test.example.com", ("test.example.com", "abc"), {},), - -]) + self.assertTrue(b"-/path/to/content@Example.ORG" in txt) + + +@pytest.mark.parametrize( + ("split", "expected", "args", "kw"), + [ + # default argument host_name + ( + "@", + "example.com", + (), + {}, + ), + ("@", "example.com", ("example.com",), {}), + ("@", "example.com", (), {"host_name": "example.com"}), + # replaced host_name + ("@", "test.test", ("test.test",), {}), + ("@", "test.test", (), {"host_name": "test.test"}), + # replace unique + ( + "-", + "123@example.com", + (), + {"unique": "123"}, + ), + ( + "-", + "abc@example.com", + (), + {"unique": "abc"}, + ), + # replace host_name and unique + ( + "-", + "1234@test.icalendar", + (), + {"unique": "1234", "host_name": "test.icalendar"}, + ), + ( + "-", + "abc@test.example.com", + ("test.example.com", "abc"), + {}, + ), + ], +) def test_uid_generator_issue_345(args, kw, split, expected): - '''Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module? + """Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module? see https://github.com/collective/icalendar/issues/345 - ''' + """ uid = UIDGenerator.uid(*args, **kw) assert uid.split(split)[1] == expected diff --git a/src/icalendar/tests/test_with_doctest.py b/src/icalendar/tests/test_with_doctest.py index 8842912f..1e8e511b 100644 --- a/src/icalendar/tests/test_with_doctest.py +++ b/src/icalendar/tests/test_with_doctest.py @@ -10,12 +10,13 @@ Hello World! """ + import doctest -import os -import pytest import importlib +import os import sys -import re + +import pytest HERE = os.path.dirname(__file__) or "." ICALENDAR_PATH = os.path.dirname(HERE) @@ -23,17 +24,21 @@ PYTHON_FILES = [ "/".join((dirpath, filename)) for dirpath, dirnames, filenames in os.walk(ICALENDAR_PATH) - for filename in filenames if filename.lower().endswith(".py") and 'fuzzing' not in dirpath + for filename in filenames + if filename.lower().endswith(".py") and "fuzzing" not in dirpath ] MODULE_NAMES = [ - "icalendar" + python_file[len(ICALENDAR_PATH):-3].replace("\\", "/").replace("/", ".") + "icalendar" + + python_file[len(ICALENDAR_PATH) : -3].replace("\\", "/").replace("/", ".") for python_file in PYTHON_FILES ] + def test_this_module_is_among_them(): assert __name__ in MODULE_NAMES + @pytest.mark.parametrize("module_name", MODULE_NAMES) def test_docstring_of_python_file(module_name, env_for_doctest): """This test runs doctest on the Python module.""" @@ -58,12 +63,18 @@ def test_docstring_of_python_file(module_name, env_for_doctest): if filename.lower().endswith(".rst") ] except FileNotFoundError: - raise OSError("Could not find the documentation - remove the build folder and try again.") - -@pytest.mark.parametrize("filename", [ - "README.rst", - "index.rst", -]) + raise OSError( + "Could not find the documentation - remove the build folder and try again." + ) + + +@pytest.mark.parametrize( + "filename", + [ + "README.rst", + "index.rst", + ], +) def test_files_is_included(filename): assert any(path.endswith(filename) for path in DOCUMENT_PATHS) @@ -76,14 +87,18 @@ def test_documentation_file(document, zoneinfo_only, env_for_doctest, tzp): """ try: # set raise_on_error to False if you wand to see the error for debug - test_result = doctest.testfile(document, module_relative=False, globs=env_for_doctest, raise_on_error=True) + test_result = doctest.testfile( + document, module_relative=False, globs=env_for_doctest, raise_on_error=True + ) except doctest.UnexpectedException as e: ty, err, tb = e.exc_info if issubclass(ty, ModuleNotFoundError) and err.name == "pytz": pytest.skip("pytz not installed, skipping this file.") finally: tzp.use_zoneinfo() - assert test_result.failed == 0, f"{test_result.failed} errors in {os.path.basename(document)}" + assert ( + test_result.failed == 0 + ), f"{test_result.failed} errors in {os.path.basename(document)}" def test_can_import_zoneinfo(env_for_doctest):