Merge branch 'main' into security

pull/755/head
Nicco Kunzmann 2025-01-18 16:51:21 +01:00 zatwierdzone przez GitHub
commit 0bcaf62232
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
17 zmienionych plików z 88 dodań i 53 usunięć

Wyświetl plik

@ -6,9 +6,11 @@ Changelog
Minor changes:
- Add a ``weekday`` attribute to ``vWeekday`` components. See `Issue 749 <https://github.com/collective/icalendar/issues/749>`_.
- Document ``vRecur`` property. See `Issue 758 <https://github.com/collective/icalendar/issues/758>`_.
- Add a ``weekday`` attribute to :class:`icalendar.prop.vWeekday` components. See `Issue 749 <https://github.com/collective/icalendar/issues/749>`_.
- Document :class:`icalendar.prop.vRecur` property. See `Issue 758 <https://github.com/collective/icalendar/issues/758>`_.
- Print failure of doctest to aid debugging.
- Improve documentation of :class:`icalendar.prop.vGeo`
- Fix tests, improve code readability, fix typing. See `Issue 766 <https://github.com/collective/icalendar/issues/766>`_ and `Issue 765 <https://github.com/collective/icalendar/issues/765>`_.
Breaking changes:
@ -17,6 +19,7 @@ Breaking changes:
New features:
- Add :ref:`Security Policy`
- Python types in documentation now link to their documentation pages using ``intersphinx``.
Bug fixes:

Wyświetl plik

@ -8,8 +8,9 @@ files.
----
:Homepage: https://icalendar.readthedocs.io
:Community Discussions: https://github.com/collective/icalendar/discussions
:Issue Tracker: https://github.com/collective/icalendar/issues
:Code: https://github.com/collective/icalendar
:Mailing list: https://github.com/collective/icalendar/issues
:Dependencies: `python-dateutil`_ and `tzdata`_.
:License: `BSD`_

Wyświetl plik

@ -129,7 +129,7 @@ if version is None and not options.accept_buildout_test_releases:
def _final_version(parsed_version):
for part in parsed_version:
if (part[:1] == '*') and (part not in _final_parts):
if (part.startswith('*')) and (part not in _final_parts):
return False
return True
index = setuptools.package_index.PackageIndex(

Wyświetl plik

@ -43,3 +43,7 @@ man_pages = [
('index', 'icalendar', 'icalendar Documentation',
['Plone Foundation'], 1)
]
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}

Wyświetl plik

@ -491,6 +491,7 @@ class Component(CaselessDict):
'Found no components where exactly one is required', st))
return comps[0]
@staticmethod
def _format_error(error_description, bad_input, elipsis='[...]'):
# there's three character more in the error, ie. ' ' x2 and a ':'
max_error_length = 100 - 3

Wyświetl plik

@ -1,4 +1,4 @@
from typing import Any, Union
from typing import List, Union
SEQUENCE_TYPES = (list, tuple)
DEFAULT_ENCODING = 'utf-8'
@ -13,13 +13,14 @@ def from_unicode(value: ICAL_TYPE, encoding='utf-8') -> bytes:
:return: The bytes representation of the value
"""
if isinstance(value, bytes):
value = value
return value
elif isinstance(value, str):
try:
value = value.encode(encoding)
return value.encode(encoding)
except UnicodeEncodeError:
value = value.encode('utf-8', 'replace')
return value
return value.encode('utf-8', 'replace')
else:
return value
def to_unicode(value: ICAL_TYPE, encoding='utf-8-sig') -> str:
@ -29,13 +30,16 @@ def to_unicode(value: ICAL_TYPE, encoding='utf-8-sig') -> str:
return value
elif isinstance(value, bytes):
try:
value = value.decode(encoding)
return value.decode(encoding)
except UnicodeDecodeError:
value = value.decode('utf-8-sig', 'replace')
return value
return value.decode('utf-8-sig', 'replace')
else:
return value
def data_encode(data: Union[ICAL_TYPE, dict, list], encoding=DEFAULT_ENCODING) -> bytes:
def data_encode(
data: Union[ICAL_TYPE, dict, list], encoding=DEFAULT_ENCODING
) -> Union[bytes, List[bytes], dict]:
"""Encode all datastructures to the given encoding.
Currently unicode strings, dicts and lists are supported.
"""

Wyświetl plik

@ -1042,7 +1042,7 @@ class vMonth(int):
month_index = int(month)
leap = False
else:
if not month[-1] == "L" and month[:-1].isdigit():
if month[-1] != "L" and month[:-1].isdigit():
raise ValueError(f"Invalid month: {month!r}")
month_index = int(month[:-1])
leap = True
@ -1478,40 +1478,62 @@ class vGeo:
GEO:37.386013;-122.082932
Parse vGeo:
.. code-block:: pycon
>>> from icalendar.prop import vGeo
>>> geo = vGeo.from_ical('37.386013;-122.082932')
>>> geo
(37.386013, -122.082932)
Add a geo location to an event:
.. code-block:: pycon
>>> from icalendar import Event
>>> event = Event()
>>> latitude = 37.386013
>>> longitude = -122.082932
>>> event.add('GEO', (latitude, longitude))
>>> event['GEO']
vGeo((37.386013, -122.082932))
"""
def __init__(self, geo):
def __init__(self, geo: tuple[float|str|int, float|str|int]):
"""Create a new vGeo from a tuple of (latitude, longitude).
Raises:
ValueError: if geo is not a tuple of (latitude, longitude)
"""
try:
latitude, longitude = (geo[0], geo[1])
latitude = float(latitude)
longitude = float(longitude)
except Exception:
raise ValueError('Input must be (float, float) for '
'latitude and longitude')
except Exception as e:
raise ValueError("Input must be (float, float) for "
"latitude and longitude") from e
self.latitude = latitude
self.longitude = longitude
self.params = Parameters()
def to_ical(self):
return f'{self.latitude};{self.longitude}'
return f"{self.latitude};{self.longitude}"
@staticmethod
def from_ical(ical):
try:
latitude, longitude = ical.split(';')
latitude, longitude = ical.split(";")
return (float(latitude), float(longitude))
except Exception:
raise ValueError(f"Expected 'float;float' , got: {ical}")
except Exception as e:
raise ValueError(f"Expected 'float;float' , got: {ical}") from e
def __eq__(self, other):
return self.to_ical() == other.to_ical()
def __repr__(self):
"""repr(self)"""
return f"{self.__class__.__name__}(({self.latitude}, {self.longitude}))"
class vUTCOffset:
"""UTC Offset

Wyświetl plik

@ -19,7 +19,7 @@ def fuzz_calendar_v1(
cal = [cal]
for c in cal:
if should_walk:
for event in cal.walk("VEVENT"):
for event in c.walk("VEVENT"):
event.to_ical()
else:
cal.to_ical()
c.to_ical()

Wyświetl plik

@ -24,9 +24,9 @@ class TestProp(unittest.TestCase):
from icalendar.prop import vDDDLists
dt_list = vDDDLists.from_ical("19960402T010000Z")
self.assertTrue(isinstance(dt_list, list))
self.assertIsInstance(dt_list, list)
self.assertEqual(len(dt_list), 1)
self.assertTrue(isinstance(dt_list[0], datetime))
self.assertIsInstance(dt_list[0], datetime)
self.assertEqual(str(dt_list[0]), "1996-04-02 01:00:00+00:00")
p = "19960402T010000Z,19960403T010000Z,19960404T010000Z"
@ -45,7 +45,7 @@ class TestProp(unittest.TestCase):
self.assertEqual(dt_list.to_ical(), b"20000101T000000,20001111T000000")
instance = vDDDLists([])
self.assertFalse(instance == "value")
self.assertNotEqual(instance, "value")
def test_prop_vDate(self):
from icalendar.prop import vDate
@ -149,7 +149,7 @@ class TestProp(unittest.TestCase):
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"
"FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30"
)
self.assertEqual(
r,
@ -165,7 +165,7 @@ class TestProp(unittest.TestCase):
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;BYMONTH=1",
)
r = vRecur.from_ical("FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH")

Wyświetl plik

@ -12,7 +12,7 @@ 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")
assert dt.tzinfo == dt.tzinfo
assert dt.tzinfo == expected.tzinfo
assert dt == expected

Wyświetl plik

@ -40,7 +40,7 @@ class IcalendarTestCase(unittest.TestCase):
)
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\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", ""],
)
@ -112,7 +112,7 @@ class IcalendarTestCase(unittest.TestCase):
)
c = Contentline(
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com"
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com"
)
self.assertEqual(
c.parts(),
@ -124,7 +124,7 @@ class IcalendarTestCase(unittest.TestCase):
)
self.assertEqual(
c.to_ical().decode("utf-8"),
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com",
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com",
)
# and back again

Wyświetl plik

@ -56,7 +56,7 @@ def test_conversion_converges(tzp, tzid):
tzinfo2 = generated1.to_tz()
generated2 = Timezone.from_tzinfo(tzinfo2, "test-generated")
tzinfo3 = generated2.to_tz()
generated3 = Timezone.from_tzinfo(tzinfo2, "test-generated")
generated3 = Timezone.from_tzinfo(tzinfo3, "test-generated")
# pprint(generated1.get_transitions())
# pprint(generated2.get_transitions())
assert_components_equal(generated1, generated2)

Wyświetl plik

@ -115,10 +115,10 @@ class TestCaselessdict(unittest.TestCase):
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.assertIn("key4", ncd)
del ncd["key4"]
self.assertFalse("key4" in ncd)
self.assertNotIn("key4", ncd)
ncd.update({"key5": "val5", "KEY6": "val6", "KEY5": "val7"})
self.assertEqual(ncd["key6"], "val6")

Wyświetl plik

@ -12,7 +12,7 @@ class TestParserTools(unittest.TestCase):
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)
self.assertIsNone(to_unicode(None))
def test_parser_tools_from_unicode(self):
self.assertEqual(from_unicode("\u01b5", encoding="ascii"), b"\xc6\xb5")

Wyświetl plik

@ -13,20 +13,20 @@ class TestTools(unittest.TestCase):
txt = uid.to_ical()
length = 15 + 1 + 16 + 1 + 11
self.assertTrue(len(txt) == length)
self.assertTrue(b"@example.com" in txt)
self.assertEqual(len(txt), length)
self.assertIn(b"@example.com", txt)
# You should at least insert your own hostname to be more compliant
uid = g.uid("Example.ORG")
txt = uid.to_ical()
self.assertTrue(len(txt) == length)
self.assertTrue(b"@Example.ORG" in txt)
self.assertEqual(len(txt), length)
self.assertIn(b"@Example.ORG", txt)
# You can also insert a path or similar
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)
self.assertEqual(len(txt), length)
self.assertIn(b"-/path/to/content@Example.ORG", txt)
@pytest.mark.parametrize(

Wyświetl plik

@ -20,7 +20,7 @@ from multiprocessing import Pool, cpu_count
from pathlib import Path
from pprint import pprint
from time import time
from typing import Callable, NamedTuple, Optional
from typing import Callable, NamedTuple, Optional, Any, Tuple, List
from zoneinfo import ZoneInfo, available_timezones
@ -30,7 +30,7 @@ from pytz import AmbiguousTimeError, NonExistentTimeError
def check(dt, tz:tzinfo):
return (dt, tz.utcoffset(dt))
def checks(tz:tzinfo) -> tuple:
def checks(tz:tzinfo) -> List[Tuple[Any, Optional[timedelta]]]:
result = []
for dt in DTS:
try:
@ -123,7 +123,7 @@ def main(
with file.open("w") as f:
f.write(f"'''This file is automatically generated by {Path(__file__).name}'''\n")
f.write("import datetime\n\n")
f.write(f"\nlookup = ")
f.write("\nlookup = ")
pprint(lookup, stream=f)
f.write("\n\n__all__ = ['lookup']\n")

Wyświetl plik

@ -105,16 +105,16 @@ class TZP:
"""
return tzid.strip("/")
def timezone(self, id: str) -> Optional[datetime.tzinfo]:
def timezone(self, tz_id: str) -> Optional[datetime.tzinfo]:
"""Return a timezone with an id or None if we cannot find it."""
_unclean_id = id
id = self.clean_timezone_id(id)
tz = self.__provider.timezone(id)
_unclean_id = tz_id
tz_id = self.clean_timezone_id(tz_id)
tz = self.__provider.timezone(tz_id)
if tz is not None:
return tz
if id in WINDOWS_TO_OLSON:
tz = self.__provider.timezone(WINDOWS_TO_OLSON[id])
return tz or self.__provider.timezone(_unclean_id) or self.__tz_cache.get(id)
if tz_id in WINDOWS_TO_OLSON:
tz = self.__provider.timezone(WINDOWS_TO_OLSON[tz_id])
return tz or self.__provider.timezone(_unclean_id) or self.__tz_cache.get(tz_id)
def uses_pytz(self) -> bool:
"""Whether we use pytz at all."""