diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 954dfb1..2363ed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 40 strategy: max-parallel: 10 + fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] library : diff --git a/packages/abstractions/kiota_abstractions/date_utils.py b/packages/abstractions/kiota_abstractions/date_utils.py new file mode 100644 index 0000000..05e9572 --- /dev/null +++ b/packages/abstractions/kiota_abstractions/date_utils.py @@ -0,0 +1,119 @@ +from sys import version_info as sys_version_info +import re +from datetime import datetime, time, timedelta + +_ISO8601_DURATION_PATTERN = re.compile( + "^P" # Duration P indicator + # Weeks + "(?P" + r" (?P\d+(?:[.,]\d+)?W)" + ")?" + # Years, Months, Days + "(?P" + r" (?P\d+(?:[.,]\d+)?Y)?" + r" (?P\d+(?:[.,]\d+)?M)?" + r" (?P\d+(?:[.,]\d+)?D)?" + ")?" + # Time + "(?P" + " (?PT)" # Separator (T) + r" (?P\d+(?:[.,]\d+)?H)?" + r" (?P\d+(?:[.,]\d+)?M)?" + r" (?P\d+(?:[.,]\d+)?S)?" + ")?" + "$", + re.VERBOSE, +) + + +def parse_timedelta_from_iso_format(text: str) -> timedelta: + """Parses a ISO8601 duration string into a timedelta object.""" + + m = _ISO8601_DURATION_PATTERN.match(text) + if not m: + raise ValueError(f"Invalid ISO8601 duration string: {text}") + + weeks = float(m.group("weeks").replace(",", ".").replace("W", "")) if m.group("weeks") else 0 + years = float(m.group("years").replace(",", ".").replace("Y", "")) if m.group("years") else 0 + months = float(m.group("months").replace(",", ".").replace("M", "")) if m.group("months") else 0 + days = float(m.group("days").replace(",", ".").replace("D", "")) if m.group("days") else 0 + hours = float(m.group("hours").replace(",", ".").replace("H", "")) if m.group("hours") else 0 + minutes = float(m.group("minutes").replace(",", ".").replace("M", "") + ) if m.group("minutes") else 0 + seconds = float(m.group("seconds").replace(",", ".").replace("S", "") + ) if m.group("seconds") else 0 + _have_date = years or months or days + _have_time = hours or minutes or seconds + if weeks and (_have_date or _have_time): + raise ValueError("Combining weeks with other date/time parts is not supported") + + _total_days = (years * 365) + (months * 30) + days + return timedelta( + days=_total_days, + hours=hours, + minutes=minutes, + seconds=seconds, + weeks=weeks, + ) + + +_TIMEDELTA_PATTERN = re.compile(r"^(?P\d+):(?P\d+)(?::(?P\d+))?$") + + +def parse_timedelta_string(text: str) -> timedelta: + """Checks if a given string is a valid ISO8601 duration string. Or hh:mm:ss format.""" + try: + return parse_timedelta_from_iso_format(text) + except ValueError as exc: + # The previous library also supported hh:mm:ss format + m = _TIMEDELTA_PATTERN.match(text) + if not m: + raise ValueError(f"Invalid timedelta string: {text}") from exc + + hours = int(m.group("hours")) + minutes = int(m.group("minutes")) + seconds = int(m.group("seconds") or 0) + return timedelta(hours=hours, minutes=minutes, seconds=seconds) + + +_TIME_REPLACEMENT_PATTERN = re.compile(r'(\d)([.,])(\d+)') + + +def datetime_from_iso_format_compat(text: str) -> datetime: + """Parses a ISO8601 formatted string into a datetime object.""" + try: + # Try regular first (faster for most cases) + return datetime.fromisoformat(text) + except ValueError as exc: + # Python 3.10 and below only support fractions of seconds in either 3 or 6 digits + # Python 3.11+ supports any number of digits + + if sys_version_info[:3] < (3, 11): + # The following code is a workaround for Python 3.10 and below + fixed_time = re.sub( + _TIME_REPLACEMENT_PATTERN, + lambda x: x.group(1) + "." + x.group(3).ljust(6, '0')[:6], text + ).replace("Z", "+00:00") + return datetime.fromisoformat(fixed_time) + + raise exc + + +def time_from_iso_format_compat(text: str) -> time: + """Parses a ISO8601 formatted string into a time object.""" + try: + # Try regular first (faster for most cases) + return time.fromisoformat(text) + except ValueError as exc: + # Python 3.10 and below only support fractions of seconds in either 3 or 6 digits + # Python 3.11+ supports any number of digits + + if sys_version_info[:3] < (3, 11): + # The following code is a workaround for Python 3.10 and below + fixed_time = re.sub( + _TIME_REPLACEMENT_PATTERN, + lambda x: x.group(1) + "." + x.group(3).ljust(6, '0')[:6], text + ).replace("Z", "+00:00") + return time.fromisoformat(fixed_time) + + raise exc diff --git a/packages/abstractions/tests/test_date_utils.py b/packages/abstractions/tests/test_date_utils.py new file mode 100644 index 0000000..640a239 --- /dev/null +++ b/packages/abstractions/tests/test_date_utils.py @@ -0,0 +1,86 @@ +import pytest + +from kiota_abstractions.date_utils import ( + parse_timedelta_from_iso_format, + parse_timedelta_string, + time_from_iso_format_compat, + datetime_from_iso_format_compat +) + + +@pytest.mark.parametrize("text", ["08:00:00", "08:00:00.0", "08:00:00.00","08:00:00.000", + "08:00:00.0000","08:00:00.00000","08:00:00.000000", "08:00:00.0000000", "08:00:00,0000000", + "08:00:00,0000000Z", "08:00:00.00Z", "08:00:00.00+00:00" ]) +def test_time_from_iso_format_compat(text: str): + result = time_from_iso_format_compat(text) + assert result.hour == 8 + assert result.minute == 0 + assert result.second == 0 + +@pytest.mark.parametrize("text", ["1986-07-28T08:00:00", "1986-07-28T08:00:00.0", "1986-07-28T08:00:00.00", + "1986-07-28T08:00:00.000", "1986-07-28T08:00:00.0000", "1986-07-28T08:00:00.00000", + "1986-07-28T08:00:00.000000", "1986-07-28T08:00:00.0000000", "1986-07-28T08:00:00,0000000", + "1986-07-28T08:00:00.0000000Z", "1986-07-28T08:00:00.00Z", "1986-07-28T08:00:00.00+00:00" ]) +def test_datetime_from_iso_format_compat(text: str): + result = datetime_from_iso_format_compat(text) + assert result.hour == 8 + assert result.minute == 0 + assert result.second == 0 + + +def test_parse_timedelta_from_iso_format_weeks(): + result = parse_timedelta_from_iso_format("P3W") + assert result.days == 21 + + +def test_parse_timedelta_from_iso_format_days(): + result = parse_timedelta_from_iso_format("P3D") + assert result.days == 3 + + +def test_parse_timedelta_from_iso_format_hours(): + result = parse_timedelta_from_iso_format("PT3H") + assert result.seconds == 10800 + + +def test_parse_timedelta_from_iso_format_minutes(): + result = parse_timedelta_from_iso_format("PT3M") + assert result.seconds == 180 + + +def test_parse_timedelta_from_iso_format_seconds(): + result = parse_timedelta_from_iso_format("PT3S") + assert result.seconds == 3 + + +def test_parse_timedelta_from_iso_format_years(): + result = parse_timedelta_from_iso_format("P3Y") + assert result.days == 1095 + + +def test_parse_timedelta_from_iso_format_months(): + result = parse_timedelta_from_iso_format("P3M") + assert result.days == 90 + + +def test_parse_timedelta_from_iso_format_days_and_time(): + result = parse_timedelta_from_iso_format("P3DT3H3M3S") + assert result.days == 3 + assert result.seconds == 10983 + +def test_parse_timedelta_from_iso_format_time_without_p(): + with pytest.raises(ValueError): + parse_timedelta_from_iso_format("T3H3M3S") + +@pytest.mark.parametrize("text", ["P3W3Y", "P3W3Y3D", "P3W3Y3DT3H3M3S"]) +def test_parse_timedelta_from_iso_format_must_raise(text: str): + # assert this raises a ValueError + with pytest.raises(ValueError): + parse_timedelta_from_iso_format(text) + + +@pytest.mark.parametrize("text, expected_hours", [("PT3H", 3), ("2:00:00", 2)]) +def test_parse_timedelta_string_valid(text:str, expected_hours:int): + result = parse_timedelta_string(text) + assert result.days == 0 + assert result.seconds == expected_hours * 3600 diff --git a/packages/serialization/form/kiota_serialization_form/form_parse_node.py b/packages/serialization/form/kiota_serialization_form/form_parse_node.py index f9c73cb..6f5bc5f 100644 --- a/packages/serialization/form/kiota_serialization_form/form_parse_node.py +++ b/packages/serialization/form/kiota_serialization_form/form_parse_node.py @@ -9,7 +9,9 @@ from urllib.parse import unquote_plus from uuid import UUID -import pendulum +from kiota_abstractions.date_utils import ( + parse_timedelta_string, datetime_from_iso_format_compat, time_from_iso_format_compat +) from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode T = TypeVar("T", bool, str, int, float, UUID, datetime, timedelta, date, time, bytes) @@ -93,10 +95,7 @@ def get_datetime_value(self) -> Optional[datetime]: """ if self._node and self._node != "null": try: - datetime_obj = pendulum.parse(self._node, exact=True) - if isinstance(datetime_obj, pendulum.DateTime): - return datetime_obj - return None + return datetime_from_iso_format_compat(self._node) except: return None return None @@ -108,10 +107,7 @@ def get_timedelta_value(self) -> Optional[timedelta]: """ if self._node and self._node != "null": try: - datetime_obj = pendulum.parse(self._node, exact=True) - if isinstance(datetime_obj, pendulum.Duration): - return datetime_obj.as_timedelta() - return None + return parse_timedelta_string(self._node) except: return None return None @@ -123,10 +119,7 @@ def get_date_value(self) -> Optional[date]: """ if self._node and self._node != "null": try: - datetime_obj = pendulum.parse(self._node, exact=True) - if isinstance(datetime_obj, pendulum.Date): - return datetime_obj - return None + return date.fromisoformat(self._node) except: return None return None @@ -138,10 +131,7 @@ def get_time_value(self) -> Optional[time]: """ if self._node and self._node != "null": try: - datetime_obj = pendulum.parse(self._node, exact=True) - if isinstance(datetime_obj, pendulum.Time): - return datetime_obj - return None + return time_from_iso_format_compat(self._node) except: return None return None @@ -315,9 +305,7 @@ def try_get_anything(self, value: Any) -> Any: return dict(map(lambda x: (x[0], self.try_get_anything(x[1])), value.items())) if isinstance(value, str): try: - datetime_obj = pendulum.parse(value) - if isinstance(datetime_obj, pendulum.Duration): - return datetime_obj.as_timedelta() + datetime_obj = datetime_from_iso_format_compat(value) return datetime_obj except ValueError: pass @@ -325,6 +313,18 @@ def try_get_anything(self, value: Any) -> Any: return UUID(value) except ValueError: pass + try: + return parse_timedelta_string(value) + except ValueError: + pass + try: + return date.fromisoformat(value) + except ValueError: + pass + try: + return time_from_iso_format_compat(value) + except ValueError: + pass return value raise ValueError(f"Unexpected additional value type {type(value)} during deserialization.") diff --git a/packages/serialization/form/pyproject.toml b/packages/serialization/form/pyproject.toml index 1cec4a2..0956339 100644 --- a/packages/serialization/form/pyproject.toml +++ b/packages/serialization/form/pyproject.toml @@ -25,7 +25,6 @@ packages = [{include = "kiota_serialization_form"}] [tool.poetry.dependencies] python = ">=3.9,<4.0" microsoft-kiota-abstractions = {path="../../abstractions/", develop=true} -pendulum = ">=3.0.0" [tool.poetry.group.dev.dependencies] yapf = ">=0.40.2,<0.44.0" @@ -52,4 +51,4 @@ profile = "hug" [tool.poetry-monorepo.deps] enabled = true -commands = ["build", "export", "publish"] \ No newline at end of file +commands = ["build", "export", "publish"] diff --git a/packages/serialization/form/tests/unit/test_form_serialization_writer.py b/packages/serialization/form/tests/unit/test_form_serialization_writer.py index e1dd250..951aa1e 100644 --- a/packages/serialization/form/tests/unit/test_form_serialization_writer.py +++ b/packages/serialization/form/tests/unit/test_form_serialization_writer.py @@ -2,7 +2,6 @@ from urllib.parse import unquote_plus import pytest -import pendulum from datetime import datetime, timedelta, date, time from kiota_serialization_form.form_serialization_writer import FormSerializationWriter from ..helpers import TestEntity, TestEnum @@ -11,7 +10,7 @@ @pytest.fixture def user_1(): user = TestEntity() - user.created_date_time = pendulum.parse("2022-01-27T12:59:45.596117") + user.created_date_time = datetime.fromisoformat("2022-01-27T12:59:45.596117+00:00") user.work_duration = timedelta(seconds=7200) user.birthday = date(year=2000,month=9,day=4) user.start_work_time = time(hour=8, minute=0, second=0) @@ -112,7 +111,7 @@ def test_write_time_value(): form_serialization_writer = FormSerializationWriter() form_serialization_writer.write_time_value( "time", - pendulum.parse('2022-01-27T12:59:45.596117').time() + datetime.fromisoformat('2022-01-27T12:59:45.596117').time() ) content = form_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') diff --git a/packages/serialization/json/kiota_serialization_json/json_parse_node.py b/packages/serialization/json/kiota_serialization_json/json_parse_node.py index 1242c9c..f560319 100644 --- a/packages/serialization/json/kiota_serialization_json/json_parse_node.py +++ b/packages/serialization/json/kiota_serialization_json/json_parse_node.py @@ -9,7 +9,9 @@ from typing import Any, Optional, TypeVar from uuid import UUID -import pendulum +from kiota_abstractions.date_utils import ( + parse_timedelta_string, datetime_from_iso_format_compat, time_from_iso_format_compat +) from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode T = TypeVar("T", bool, str, int, float, UUID, datetime, timedelta, date, time, bytes) @@ -96,8 +98,8 @@ def get_datetime_value(self) -> Optional[datetime]: if len(self._json_node) < 10: return None - datetime_obj = pendulum.parse(self._json_node, exact=True) - if isinstance(datetime_obj, pendulum.DateTime): + datetime_obj = datetime_from_iso_format_compat(self._json_node) + if isinstance(datetime_obj, datetime): return datetime_obj return None @@ -109,9 +111,10 @@ def get_timedelta_value(self) -> Optional[timedelta]: if isinstance(self._json_node, timedelta): return self._json_node if isinstance(self._json_node, str): - datetime_obj = pendulum.parse(self._json_node, exact=True) - if isinstance(datetime_obj, pendulum.Duration): - return datetime_obj.as_timedelta() + try: + return parse_timedelta_string(self._json_node) + except ValueError: + return None return None def get_date_value(self) -> Optional[date]: @@ -122,8 +125,8 @@ def get_date_value(self) -> Optional[date]: if isinstance(self._json_node, date): return self._json_node if isinstance(self._json_node, str): - datetime_obj = pendulum.parse(self._json_node, exact=True) - if isinstance(datetime_obj, pendulum.Date): + datetime_obj = date.fromisoformat(self._json_node) + if isinstance(datetime_obj, date): return datetime_obj return None @@ -135,8 +138,8 @@ def get_time_value(self) -> Optional[time]: if isinstance(self._json_node, time): return self._json_node if isinstance(self._json_node, str): - datetime_obj = pendulum.parse(self._json_node, exact=True) - if isinstance(datetime_obj, pendulum.Time): + datetime_obj = time_from_iso_format_compat(self._json_node) + if isinstance(datetime_obj, time): return datetime_obj return None @@ -315,9 +318,7 @@ def try_get_anything(self, value: Any) -> Any: return value if value.isdigit(): return value - datetime_obj = pendulum.parse(value) - if isinstance(datetime_obj, pendulum.Duration): - return datetime_obj.as_timedelta() + datetime_obj = datetime_from_iso_format_compat(value) return datetime_obj except ValueError: pass @@ -325,6 +326,18 @@ def try_get_anything(self, value: Any) -> Any: return UUID(value) except: pass + try: + return parse_timedelta_string(value) + except ValueError: + pass + try: + return date.fromisoformat(value) + except ValueError: + pass + try: + return time_from_iso_format_compat(value) + except ValueError: + pass return value raise ValueError(f"Unexpected additional value type {type(value)} during deserialization.") diff --git a/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py b/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py index e076cef..ecfdd79 100644 --- a/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py +++ b/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py @@ -8,7 +8,7 @@ from typing import Any, Optional, TypeVar from uuid import UUID -import pendulum +from kiota_abstractions.date_utils import parse_timedelta_string from kiota_abstractions.serialization import Parsable, SerializationWriter T = TypeVar("T") @@ -115,7 +115,7 @@ def write_datetime_value(self, key: Optional[str], value: Optional[datetime]) -> self.value = value.isoformat() elif isinstance(value, str): try: - pendulum.parse(value) + datetime.fromisoformat(value) if key: self.writer[key] = value else: @@ -138,7 +138,7 @@ def write_timedelta_value(self, key: Optional[str], value: Optional[timedelta]) self.value = str(value) elif isinstance(value, str): try: - pendulum.parse(value) + parse_timedelta_string(value) if key: self.writer[key] = value else: @@ -161,7 +161,7 @@ def write_date_value(self, key: Optional[str], value: Optional[date]) -> None: self.value = str(value) elif isinstance(value, str): try: - pendulum.parse(value) + date.fromisoformat(value) if key: self.writer[key] = value else: @@ -184,7 +184,7 @@ def write_time_value(self, key: Optional[str], value: Optional[time]) -> None: self.value = str(value) elif isinstance(value, str): try: - pendulum.parse(value) + time.fromisoformat(value) if key: self.writer[key] = value else: diff --git a/packages/serialization/json/pyproject.toml b/packages/serialization/json/pyproject.toml index da5bef2..f1c1513 100644 --- a/packages/serialization/json/pyproject.toml +++ b/packages/serialization/json/pyproject.toml @@ -25,7 +25,6 @@ packages = [{include = "kiota_serialization_json"}] [tool.poetry.dependencies] python = ">=3.9,<4.0" microsoft-kiota-abstractions = {path="../../abstractions/", develop=true} -pendulum = ">=3.0.0" [tool.poetry.group.dev.dependencies] yapf = ">=0.40.2,<0.44.0" diff --git a/packages/serialization/json/tests/unit/test_json_parse_node.py b/packages/serialization/json/tests/unit/test_json_parse_node.py index 965bc8a..21ea6a0 100644 --- a/packages/serialization/json/tests/unit/test_json_parse_node.py +++ b/packages/serialization/json/tests/unit/test_json_parse_node.py @@ -1,10 +1,8 @@ import json -from datetime import date, datetime, time, timedelta +from datetime import date, datetime, time, timedelta, timezone from uuid import UUID -import pendulum import pytest -from pendulum import DateTime, FixedTimezone from kiota_serialization_json.json_parse_node import JsonParseNode from ..helpers import OfficeLocation, User @@ -161,8 +159,9 @@ def test_get_anythin_does_not_convert_any_length_numeric_chars_to_datetime(): def test_get_anythin_does_convert_date_string_to_datetime(): parse_node = JsonParseNode("2023-10-05T14:48:00.000Z") result = parse_node.try_get_anything("2023-10-05T14:48:00.000Z") - assert isinstance(result, pendulum.DateTime) - assert result == pendulum.parse("2023-10-05T14:48:00.000Z") + assert isinstance(result, datetime) + # there is an issue with parsing the original iso string (Z not supported < 3.11) + assert result == datetime(2023, 10, 5, 14, 48, tzinfo=timezone.utc) def test_get_object_value(user1_json): @@ -182,16 +181,14 @@ def test_get_object_value(user1_json): ) assert result.additional_data["additional_data"]["manager"] == { "id": UUID("8f841f30-e6e3-439a-a812-ebd369559c36"), - "updated_at": - DateTime(2022, 1, 27, 12, 59, 45, 596117, tzinfo=FixedTimezone(0, name="+00:00")), + "updated_at": datetime(2022, 1, 27, 12, 59, 45, 596117, timezone.utc), "is_active": True, } assert result.additional_data["additional_data"]["approvers"] == [ { "id": UUID("8f841f30-e6e3-439a-a812-ebd369559c36"), - "updated_at": - DateTime(2022, 1, 27, 12, 59, 45, 596117, tzinfo=FixedTimezone(0, name="+00:00")), + "updated_at": datetime(2022, 1, 27, 12, 59, 45, 596117, timezone.utc), "is_active": True, }, diff --git a/packages/serialization/json/tests/unit/test_json_serialization_writer.py b/packages/serialization/json/tests/unit/test_json_serialization_writer.py index 48ee7f8..ad0bd2a 100644 --- a/packages/serialization/json/tests/unit/test_json_serialization_writer.py +++ b/packages/serialization/json/tests/unit/test_json_serialization_writer.py @@ -1,9 +1,8 @@ -from datetime import date +from datetime import date, datetime from uuid import UUID import pytest -import pendulum from kiota_serialization_json.json_serialization_writer import JsonSerializationWriter from ..helpers import OfficeLocation, User, User2 @@ -12,7 +11,7 @@ @pytest.fixture def user_1(): user = User() - user.updated_at = pendulum.parse("2022-01-27T12:59:45.596117") + user.updated_at = datetime(2022,1,27,12,59,45,596117).fromisoformat("2022-01-27T12:59:45.596117+00:00") user.is_active = True user.id = UUID("8f841f30-e6e3-439a-a812-ebd369559c36") return user @@ -92,7 +91,7 @@ def test_write_uuid_value_with_invalid_string(): def test_write_datetime_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_datetime_value( - "updatedAt", pendulum.parse('2022-01-27T12:59:45.596117') + "updatedAt", datetime.fromisoformat('2022-01-27T12:59:45.596117+00:00') ) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') @@ -118,9 +117,9 @@ def test_write_timedelta_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_timedelta_value( "diff", ( - pendulum.parse('2022-01-27T12:59:45.596117') - - pendulum.parse('2022-01-27T10:59:45.596117') - ).as_timedelta() + datetime.fromisoformat('2022-01-27T12:59:45.596117') - + datetime.fromisoformat('2022-01-27T10:59:45.596117') + ) ) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') @@ -144,7 +143,7 @@ def test_write_timedelta_value_invalid_string(): def test_write_date_value(): json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_date_value("birthday", pendulum.parse("2000-09-04").date()) + json_serialization_writer.write_date_value("birthday", date(2000,9,4)) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"birthday": "2000-09-04"}' @@ -169,7 +168,7 @@ def test_write_time_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_time_value( "time", - pendulum.parse('2022-01-27T12:59:45.596117').time() + datetime.fromisoformat('2022-01-27T12:59:45.596117').time() ) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') diff --git a/packages/serialization/multipart/pyproject.toml b/packages/serialization/multipart/pyproject.toml index 0ef831c..2e4d1f9 100644 --- a/packages/serialization/multipart/pyproject.toml +++ b/packages/serialization/multipart/pyproject.toml @@ -25,7 +25,6 @@ packages = [{include = "kiota_serialization_multipart"}] [tool.poetry.dependencies] python = ">=3.9,<4.0" microsoft-kiota-abstractions = {path="../../abstractions/", develop=true} -pendulum = ">=3.0.0" [tool.poetry.group.dev.dependencies] yapf = "^0.40.2" diff --git a/packages/serialization/text/kiota_serialization_text/text_parse_node.py b/packages/serialization/text/kiota_serialization_text/text_parse_node.py index 0a61a67..caf21cd 100644 --- a/packages/serialization/text/kiota_serialization_text/text_parse_node.py +++ b/packages/serialization/text/kiota_serialization_text/text_parse_node.py @@ -7,7 +7,9 @@ from typing import Optional, TypeVar from uuid import UUID -from dateutil import parser +from kiota_abstractions.date_utils import ( + parse_timedelta_string, datetime_from_iso_format_compat, time_from_iso_format_compat +) from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode T = TypeVar("T", bool, str, int, float, UUID, datetime, timedelta, date, time, bytes) @@ -95,8 +97,7 @@ def get_datetime_value(self) -> Optional[datetime]: """ datetime_str = self.get_str_value() if datetime_str: - datetime_obj = parser.parse(datetime_str) - return datetime_obj + return datetime_from_iso_format_compat(datetime_str) return None def get_timedelta_value(self) -> Optional[timedelta]: @@ -106,10 +107,7 @@ def get_timedelta_value(self) -> Optional[timedelta]: """ datetime_str = self.get_str_value() if datetime_str: - datetime_obj = parser.parse(datetime_str) - return timedelta( - hours=datetime_obj.hour, minutes=datetime_obj.minute, seconds=datetime_obj.second - ) + return parse_timedelta_string(datetime_str) return None def get_date_value(self) -> Optional[date]: @@ -119,8 +117,7 @@ def get_date_value(self) -> Optional[date]: """ datetime_str = self.get_str_value() if datetime_str: - datetime_obj = parser.parse(datetime_str) - return datetime_obj.date() + return date.fromisoformat(datetime_str) return None def get_time_value(self) -> Optional[time]: @@ -130,8 +127,7 @@ def get_time_value(self) -> Optional[time]: """ datetime_str = self.get_str_value() if datetime_str: - datetime_obj = parser.parse(datetime_str) - return datetime_obj.time() + return time_from_iso_format_compat(datetime_str) return None def get_collection_of_primitive_values(self, primitive_type: type[T]) -> list[T]: diff --git a/packages/serialization/text/pyproject.toml b/packages/serialization/text/pyproject.toml index d422f3d..762c676 100644 --- a/packages/serialization/text/pyproject.toml +++ b/packages/serialization/text/pyproject.toml @@ -25,7 +25,6 @@ packages = [{include = "kiota_serialization_text"}] [tool.poetry.dependencies] python = ">=3.9,<4.0" microsoft-kiota-abstractions = {path="../../abstractions/", develop=true} -python-dateutil = "2.9.0.post0" [tool.poetry.group.dev.dependencies] yapf = ">=0.40.2,<0.44.0" @@ -35,7 +34,6 @@ mypy = "^1.11.2" pytest = "^8.3.2" pytest-asyncio = ">=0.24,<0.26" poetry-plugin-mono-repo-deps = ">=0.2.1,<0.4.0" -types-python-dateutil = "^2.9.0.20240906" [tool.mypy] warn_unused_configs = true diff --git a/packages/serialization/text/tests/unit/test_text_parse_node.py b/packages/serialization/text/tests/unit/test_text_parse_node.py index 9a35930..a389b19 100644 --- a/packages/serialization/text/tests/unit/test_text_parse_node.py +++ b/packages/serialization/text/tests/unit/test_text_parse_node.py @@ -66,21 +66,21 @@ def test_get_datetime_value(): def test_get_date_value(): - parse_node = TextParseNode('2015-04-20T11:50:51Z') + parse_node = TextParseNode('2015-04-20') result = parse_node.get_date_value() assert isinstance(result, date) assert str(result) == '2015-04-20' def test_get_time_value(): - parse_node = TextParseNode('2022-01-27T12:59:45.596117') + parse_node = TextParseNode('12:59:45.596117') result = parse_node.get_time_value() assert isinstance(result, time) assert str(result) == '12:59:45.596117' def test_get_timedelta_value(): - parse_node = TextParseNode('2022-01-27T12:59:45.596117') + parse_node = TextParseNode('PT12H59M45S') result = parse_node.get_timedelta_value() assert isinstance(result, timedelta) assert str(result) == '12:59:45' diff --git a/packages/serialization/text/tests/unit/test_text_serialization_writer.py b/packages/serialization/text/tests/unit/test_text_serialization_writer.py index 0911e15..63907ab 100644 --- a/packages/serialization/text/tests/unit/test_text_serialization_writer.py +++ b/packages/serialization/text/tests/unit/test_text_serialization_writer.py @@ -1,7 +1,7 @@ from uuid import UUID import pytest -from dateutil import parser +from datetime import date, datetime from kiota_abstractions.serialization import Parsable, SerializationWriter from kiota_serialization_text.text_serialization_writer import TextSerializationWriter @@ -101,7 +101,7 @@ def test_write_uuid_value_with_key(): def test_write_datetime_value(): text_serialization_writer = TextSerializationWriter() - text_serialization_writer.write_datetime_value("", parser.parse('2022-01-27T12:59:45.596117')) + text_serialization_writer.write_datetime_value("", datetime.fromisoformat('2022-01-27T12:59:45.596117')) content = text_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == "2022-01-27T12:59:45.596117" @@ -111,7 +111,7 @@ def test_write_datetime_value_with_key(): with pytest.raises(Exception) as e_info: text_serialization_writer = TextSerializationWriter() text_serialization_writer.write_datetime_value( - "updatedAt", parser.parse('2022-01-27T12:59:45.596117') + "updatedAt", datetime.fromisoformat('2022-01-27T12:59:45.596117') ) @@ -119,7 +119,7 @@ def test_write_timedelta_value(): text_serialization_writer = TextSerializationWriter() text_serialization_writer.write_timedelta_value( "", - parser.parse('2022-01-27T12:59:45.596117') - parser.parse('2022-01-27T10:59:45.596117') + datetime.fromisoformat('2022-01-27T12:59:45.596117') - datetime.fromisoformat('2022-01-27T10:59:45.596117') ) content = text_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') @@ -131,13 +131,13 @@ def test_write_timedelta_value_with_key(): text_serialization_writer = TextSerializationWriter() text_serialization_writer.write_timedelta_value( "diff", - parser.parse('2022-01-27T12:59:45.596117') - parser.parse('2022-01-27T10:59:45.596117') + datetime.fromisoformat('2022-01-27T12:59:45.596117') - datetime.fromisoformat('2022-01-27T10:59:45.596117') ) def test_write_date_value(): text_serialization_writer = TextSerializationWriter() - text_serialization_writer.write_date_value("", parser.parse("2000-09-04").date()) + text_serialization_writer.write_date_value("", date.fromisoformat("2000-09-04")) content = text_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == "2000-09-04" @@ -146,14 +146,14 @@ def test_write_date_value(): def test_write_date_value_with_key(): with pytest.raises(Exception) as e_info: text_serialization_writer = TextSerializationWriter() - text_serialization_writer.write_date_value("birthday", parser.parse("2000-09-04").date()) + text_serialization_writer.write_date_value("birthday", date.fromisoformat("2000-09-04")) def test_write_time_value(): text_serialization_writer = TextSerializationWriter() text_serialization_writer.write_time_value( "", - parser.parse('2022-01-27T12:59:45.596117').time() + datetime.fromisoformat('2022-01-27T12:59:45.596117').time() ) content = text_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') @@ -165,7 +165,7 @@ def test_write_time_value_with_key(): text_serialization_writer = TextSerializationWriter() text_serialization_writer.write_time_value( "time", - parser.parse('2022-01-27T12:59:45.596117').time() + datetime.fromisoformat('2022-01-27T12:59:45.596117').time() )