From 72e8a40fc8a044d2f6bc4749b4f943b8451ac2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Silva?= Date: Thu, 24 Apr 2025 03:56:31 +0100 Subject: [PATCH] Fix compatibility with Python 3.9 and 3.10 Closes #194 --- CHANGELOG.md | 6 +++++- pyproject.toml | 2 +- tests/data/test_defaults.py | 2 +- tests/test_api_completed_tasks.py | 8 +++++++- tests/test_api_tasks.py | 8 +++++++- todoist_api_python/_core/utils.py | 16 +++++++++++++--- todoist_api_python/api.py | 12 ++++++++---- todoist_api_python/api_async.py | 11 ++++++++--- todoist_api_python/models.py | 2 +- 9 files changed, 51 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5051745..0eb1834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Compatibility with Python 3.9 and Python 3.10 + ## [3.0.1] - 2025-04-15 ### Fixed @@ -37,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `filter_tasks`, extracting that workflow from `get_tasks` - Paginate results via an `Iterator` in `get_tasks`, `filter_task`, `get_projects`, `get_collaborators`, `get_sections`, `get_comments`, `get_labels`, `get_shared_labels` - - Receive `date` and `datetime` arguments as objects, not strings + - Receive `date` and `datetime` arguments as objects, not strings - Remove support for `X-Request-Id` header, unused on the API level - "Hide" internal modules and functions - Task URLs are now obtained on demand, improving performance when not needed diff --git a/pyproject.toml b/pyproject.toml index 6ac5457..a4e8f1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ warn_required_dynamic_aliases = true warn_untyped_fields = true [tool.ruff] -target-version = "py313" # used by some linters like UP, FA, PERF +target-version = "py39" # used by some linters like UP, FA, PERF [tool.ruff.lint] select = [ diff --git a/tests/data/test_defaults.py b/tests/data/test_defaults.py index 5087c11..75c5b0c 100644 --- a/tests/data/test_defaults.py +++ b/tests/data/test_defaults.py @@ -216,7 +216,7 @@ class PaginatedItems(TypedDict): "id": "6X7rM8997g3RQmvh", "content": "A comment", "posted_uid": "34567", - "posted_at": "2019-09-22T07:00:00.00000Z", + "posted_at": "2019-09-22T07:00:00.000000Z", "task_id": "6X7rM8997g3RQmvh", "project_id": "6X7rfEVP8hvv25ZQ", "attachment": DEFAULT_ATTACHMENT_RESPONSE, diff --git a/tests/test_api_completed_tasks.py b/tests/test_api_completed_tasks.py index c277864..331d1a2 100644 --- a/tests/test_api_completed_tasks.py +++ b/tests/test_api_completed_tasks.py @@ -1,8 +1,14 @@ from __future__ import annotations -from datetime import UTC, datetime +import sys +from datetime import datetime, timezone from typing import TYPE_CHECKING, Any +if sys.version_info >= (3, 11): + from datetime import UTC +else: + UTC = timezone.utc + import pytest import responses diff --git a/tests/test_api_tasks.py b/tests/test_api_tasks.py index 53fc9ff..29b586a 100644 --- a/tests/test_api_tasks.py +++ b/tests/test_api_tasks.py @@ -1,8 +1,14 @@ from __future__ import annotations -from datetime import UTC, datetime +import sys +from datetime import datetime, timezone from typing import TYPE_CHECKING, Any +if sys.version_info >= (3, 11): + from datetime import UTC +else: + UTC = timezone.utc + import pytest import responses diff --git a/todoist_api_python/_core/utils.py b/todoist_api_python/_core/utils.py index 429fd9a..0415931 100644 --- a/todoist_api_python/_core/utils.py +++ b/todoist_api_python/_core/utils.py @@ -1,7 +1,17 @@ +from __future__ import annotations + import asyncio -from collections.abc import AsyncGenerator, Callable, Iterator -from datetime import UTC, date, datetime -from typing import TypeVar, cast +import sys +from datetime import date, datetime, timezone +from typing import TYPE_CHECKING, TypeVar, cast + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Callable, Iterator + +if sys.version_info >= (3, 11): + from datetime import UTC +else: + UTC = timezone.utc T = TypeVar("T") diff --git a/todoist_api_python/api.py b/todoist_api_python/api.py index 086db93..f946175 100644 --- a/todoist_api_python/api.py +++ b/todoist_api_python/api.py @@ -1,7 +1,8 @@ from __future__ import annotations +import sys from collections.abc import Callable, Iterator -from typing import TYPE_CHECKING, Annotated, Any, Literal, Self, TypeVar +from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar from weakref import finalize import requests @@ -41,6 +42,11 @@ from datetime import date, datetime from types import TracebackType +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = TypeVar("Self", bound="TodoistAPI") + LanguageCode = Annotated[str, Predicate(lambda x: len(x) == 2)] # noqa: PLR2004 ColorString = Annotated[ @@ -103,8 +109,7 @@ def __enter__(self) -> Self: The with statement will bind this method's return value to the target(s) specified in the as clause of the statement, if any. - :return: The TodoistAPI instance. - :rtype: Self + :return: This TodoistAPI instance. """ return self @@ -463,7 +468,6 @@ def uncomplete_task(self, task_id: str) -> bool: :param task_id: The ID of the task to reopen. :return: True if the task was uncompleted successfully, False otherwise (possibly raise `HTTPError` instead). - :rtype: bool :raises requests.exceptions.HTTPError: If the API request fails. """ endpoint = get_api_url(f"{TASKS_PATH}/{task_id}/reopen") diff --git a/todoist_api_python/api_async.py b/todoist_api_python/api_async.py index 164f6e4..ba78db5 100644 --- a/todoist_api_python/api_async.py +++ b/todoist_api_python/api_async.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Annotated, Literal, Self +import sys +from typing import TYPE_CHECKING, Annotated, Literal, TypeVar from annotated_types import Ge, Le, MaxLen, MinLen @@ -30,6 +31,11 @@ ViewStyle, ) +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = TypeVar("Self", bound="TodoistAPIAsync") + class TodoistAPIAsync: """ @@ -58,7 +64,7 @@ async def __aenter__(self) -> Self: The with statement will bind this method's return value to the target(s) specified in the as clause of the statement, if any. - :return: The TodoistAPIAsync instance. + :return: This TodoistAPIAsync instance. """ return self @@ -332,7 +338,6 @@ async def uncomplete_task(self, task_id: str) -> bool: :param task_id: The ID of the task to reopen. :return: True if the task was uncompleted successfully, False otherwise (possibly raise `HTTPError` instead). - :rtype: bool :raises requests.exceptions.HTTPError: If the API request fails. """ return await run_async(lambda: self._api.uncomplete_task(task_id)) diff --git a/todoist_api_python/models.py b/todoist_api_python/models.py index e97b2ad..99f6496 100644 --- a/todoist_api_python/models.py +++ b/todoist_api_python/models.py @@ -12,7 +12,7 @@ ViewStyle = Literal["list", "board", "calendar"] DurationUnit = Literal["minute", "day"] ApiDate = UTCDateTimePattern["%FT%T.%fZ"] # type: ignore[valid-type] -ApiDue = Union[ # noqa: UP007 # https://github.com/rnag/dataclass-wizard/issues/189 +ApiDue = Union[ # https://github.com/rnag/dataclass-wizard/issues/189 DatePattern["%F"], DateTimePattern["%FT%T"], UTCDateTimePattern["%FT%TZ"] # type: ignore[valid-type] # noqa: F722 ]