equality of vDDDTypes, vBinary and vCategory

pull/575/head
Nicco Kunzmann 2023-11-01 02:31:17 +00:00
rodzic 0eae8ceb04
commit 6274d88809
2 zmienionych plików z 103 dodań i 28 usunięć

Wyświetl plik

@ -124,7 +124,20 @@ class LocalTimezone(tzinfo):
return tt.tm_isdst > 0
class vBinary:
class EqualityMixin:
"""A class to share equality functions of properties."""
def __eq__(self, other):
"""Check the equality between this property and the other.
You can override this to make comparing faster.
"""
return isinstance(other, EqualityMixin) and \
self.params == other.params and \
self.to_ical() == other.to_ical()
class vBinary(EqualityMixin):
"""Binary property values are base 64 encoded.
"""
@ -263,12 +276,13 @@ class vDDDLists:
return self.dts == other.dts
class vCategory:
class vCategory(EqualityMixin):
def __init__(self, c_list):
if not hasattr(c_list, '__iter__') or isinstance(c_list, str):
c_list = [c_list]
self.cats = [vText(c) for c in c_list]
self.params = Parameters()
def to_ical(self):
return b",".join([c.to_ical() for c in self.cats])
@ -280,7 +294,19 @@ class vCategory:
return out
class vDDDTypes:
class DtEqualityMixin:
"""Make classes with a datetime/date comparable."""
def __eq__(self, other):
if isinstance(other, DtEqualityMixin):
return self.params == other.params and self.dt == other.dt
return False
def __hash__(self):
return hash(self.dt)
class vDDDTypes(DtEqualityMixin):
"""A combined Datetime, Date or Duration parser/generator. Their format
cannot be confused, and often values can be of either types.
So this is practical.
@ -290,7 +316,7 @@ class vDDDTypes:
if not isinstance(dt, (datetime, date, timedelta, time, tuple)):
raise ValueError('You must use datetime, date, timedelta, '
'time or tuple (for periods)')
if isinstance(dt, datetime):
if isinstance(dt, (datetime, timedelta)):
self.params = Parameters()
elif isinstance(dt, date):
self.params = Parameters({'value': 'DATE'})
@ -320,14 +346,6 @@ class vDDDTypes:
else:
raise ValueError(f'Unknown date type: {type(dt)}')
def __eq__(self, other):
if isinstance(other, vDDDTypes):
return self.params == other.params and self.dt == other.dt
return False
def __hash__(self):
return hash(self.dt)
@classmethod
def from_ical(cls, ical, timezone=None):
if isinstance(ical, cls):
@ -349,8 +367,11 @@ class vDDDTypes:
f"Expected datetime, date, or time, got: '{ical}'"
)
def __repr__(self):
"""repr(self)"""
return f"{self.__class__.__name__}({self.dt}, {self.params})"
class vDate:
class vDate(DtEqualityMixin):
"""Render and generates iCalendar date format.
"""
@ -377,7 +398,7 @@ class vDate:
raise ValueError(f'Wrong date format {ical}')
class vDatetime:
class vDatetime(DtEqualityMixin):
"""Render and generates icalendar datetime format.
vDatetime is timezone aware and uses the pytz library, an implementation of
@ -438,7 +459,7 @@ class vDatetime:
raise ValueError(f'Wrong datetime format: {ical}')
class vDuration:
class vDuration(DtEqualityMixin):
"""Subclass of timedelta that renders itself in the iCalendar DURATION
format.
"""
@ -495,8 +516,12 @@ class vDuration:
return value
@property
def dt(self):
"""The time delta for compatibility."""
return self.td
class vPeriod:
class vPeriod(DtEqualityMixin):
"""A precise period of time.
"""
@ -520,7 +545,7 @@ class vPeriod:
if start > end:
raise ValueError("Start time is greater than end time")
self.params = Parameters()
self.params = Parameters({'value': 'PERIOD'})
# set the timezone identifier
# does not support different timezones for start and end
tzid = tzid_from_dt(start)
@ -532,17 +557,6 @@ class vPeriod:
self.by_duration = by_duration
self.duration = duration
def __cmp__(self, other):
if not isinstance(other, vPeriod):
raise NotImplementedError(
f'Cannot compare vPeriod with {other!r}')
return cmp((self.start, self.end), (other.start, other.end))
def __eq__(self, other):
if not isinstance(other, vPeriod):
return False
return (self.start, self.end) == (other.start, other.end)
def overlaps(self, other):
if self.start > other.start:
return other.overlaps(self)
@ -574,6 +588,10 @@ class vPeriod:
p = (self.start, self.end)
return f'vPeriod({p!r})'
@property
def dt(self):
"""Make this cooperate with the other vDDDTypes."""
return (self.start, (self.duration if self.by_duration else self.end))
class vWeekday(str):
"""This returns an unquoted weekday abbrevation.
@ -814,6 +832,8 @@ class vGeo:
except Exception:
raise ValueError(f"Expected 'float;float' , got: {ical}")
def __eq__(self, other):
return self.to_ical() == other.to_ical()
class vUTCOffset:
"""Renders itself as a utc offset.

Wyświetl plik

@ -1,6 +1,9 @@
"""Test the equality and inequality of components."""
import copy
import pytz
from icalendar.prop import *
from datetime import datetime, date, timedelta
import pytest
def test_parsed_calendars_are_equal(ics_file):
@ -41,3 +44,55 @@ def test_a_components_copy_also_copies_subcomponents(calendars):
assert copy.subcomponents
assert copy.subcomponents is not cal.subcomponents
assert copy.subcomponents == cal.subcomponents
def test_vGeo():
"""Check the equality of vGeo."""
assert vGeo(("100", "12.33")) == vGeo(("100.00", "12.330"))
assert vGeo(("100", "12.331")) != vGeo(("100.00", "12.330"))
assert vGeo(("10", "12.33")) != vGeo(("100.00", "12.330"))
def test_vBinary():
assert vBinary('asd') == vBinary('asd')
assert vBinary('asdf') != vBinary('asd')
def test_vBoolean():
assert vBoolean.from_ical('TRUE') == vBoolean.from_ical('TRUE')
assert vBoolean.from_ical('FALSE') == vBoolean.from_ical('FALSE')
assert vBoolean.from_ical('TRUE') != vBoolean.from_ical('FALSE')
def test_vCategory():
assert vCategory("HELLO") == vCategory("HELLO")
assert vCategory(["a","b"]) == vCategory(["a","b"])
assert vCategory(["a","b"]) != vCategory(["a","b", "c"])
@pytest.mark.parametrize(
"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))),
]
)
@pytest.mark.parametrize("eq", ["==", "!="])
@pytest.mark.parametrize("cls1", [0, 1])
@pytest.mark.parametrize("cls2", [0, 1])
@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]
t2 = (vType, vDDDTypes)[cls2]
if eq == "==":
assert hash(v1) == hash(v1)
assert hash(t1(v1)) == hash(t2(v1))
else:
assert hash(v1) != hash(v2)
assert hash(t1(v1)) != hash(t2(v2))
def test_repr_vDDDTypes():
assert "vDDDTypes" in repr(vDDDTypes(timedelta(3, 11, 1)))