diff --git a/CHANGELOG.md b/CHANGELOG.md index aef1e50..d7f185a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Changed -- Use `dataclasses_json` for object mapping +- Use `dataclass-wizard` for object mapping ### Fixes diff --git a/README.md b/README.md index 29b78d6..c008957 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ dependencies = [ ### Supported Python Versions -Python 3.13 is fully supported and tested, and while it should work with other Python 3 versions, we do not test for them. +While we only actively test under Python 3.13, we strive to support all versions from Python 3.9 and above. ### Usage diff --git a/pyproject.toml b/pyproject.toml index 276a9c8..d3003f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] -dependencies = ["requests>=2.32.3,<3", "dataclasses-json>=0.6.7,<0.7"] +dependencies = ["requests>=2.32.3,<3", "dataclass-wizard>=0.35.0,<1.0"] [project.urls] Homepage = "https://github.com/Doist/todoist-api-python" diff --git a/todoist_api_python/models.py b/todoist_api_python/models.py index 7675bfc..1976640 100644 --- a/todoist_api_python/models.py +++ b/todoist_api_python/models.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from typing import Any, Literal -from dataclasses_json import DataClassJsonMixin +from dataclass_wizard import JSONPyWizard from todoist_api_python.utils import get_url_for_task @@ -11,7 +11,10 @@ @dataclass -class Project(DataClassJsonMixin): +class Project(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + color: str comment_count: int id: str @@ -28,7 +31,10 @@ class Project(DataClassJsonMixin): @dataclass -class Section(DataClassJsonMixin): +class Section(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + id: str name: str order: int @@ -36,7 +42,10 @@ class Section(DataClassJsonMixin): @dataclass -class Due(DataClassJsonMixin): +class Due(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + date: str is_recurring: bool string: str @@ -44,24 +53,16 @@ class Due(DataClassJsonMixin): datetime: str | None = None timezone: str | None = None - @classmethod - def from_quick_add_response(cls, obj: dict[str, Any]) -> Due | None: - due = obj.get("due") - - if not due: - return None - - timezone = due.get("timezone") - datetime: str | None = due["date"] if timezone is not None else None - - due["datetime"] = datetime - due["timezone"] = timezone - - return cls.from_dict(due) + def __post_init__(self) -> None: + if not self.datetime and (self.date and self.timezone): + self.datetime = self.date @dataclass -class Task(DataClassJsonMixin): +class Task(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + assignee_id: str | None assigner_id: str | None comment_count: int @@ -91,9 +92,6 @@ def __post_init__(self) -> None: @classmethod def from_quick_add_response(cls, obj: dict[str, Any]) -> Task: obj_copy = obj.copy() - obj_copy["due"] = ( - Due.from_quick_add_response(obj) if obj.get("due") is not None else None - ) obj_copy["comment_count"] = 0 obj_copy["is_completed"] = False obj_copy["created_at"] = obj_copy.pop("added_at", None) @@ -106,7 +104,10 @@ def from_quick_add_response(cls, obj: dict[str, Any]) -> Task: @dataclass -class QuickAddResult(DataClassJsonMixin): +class QuickAddResult(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + task: Task resolved_project_name: str | None = None @@ -145,14 +146,20 @@ def from_quick_add_response(cls, obj: dict[str, Any]) -> QuickAddResult: @dataclass -class Collaborator(DataClassJsonMixin): +class Collaborator(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + id: str email: str name: str @dataclass -class Attachment(DataClassJsonMixin): +class Attachment(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + resource_type: str | None = None file_name: str | None = None @@ -171,7 +178,10 @@ class Attachment(DataClassJsonMixin): @dataclass -class Comment(DataClassJsonMixin): +class Comment(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + content: str id: str posted_at: str @@ -181,7 +191,10 @@ class Comment(DataClassJsonMixin): @dataclass -class Label(DataClassJsonMixin): +class Label(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + id: str name: str color: str @@ -190,13 +203,19 @@ class Label(DataClassJsonMixin): @dataclass -class AuthResult(DataClassJsonMixin): +class AuthResult(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + access_token: str state: str | None @dataclass -class Item(DataClassJsonMixin): +class Item(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + id: str user_id: str project_id: str @@ -221,13 +240,19 @@ class Item(DataClassJsonMixin): @dataclass -class ItemCompletedInfo(DataClassJsonMixin): +class ItemCompletedInfo(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + item_id: str completed_items: int @dataclass -class CompletedItems(DataClassJsonMixin): +class CompletedItems(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + items: list[Item] total: int completed_info: list[ItemCompletedInfo] @@ -236,6 +261,9 @@ class CompletedItems(DataClassJsonMixin): @dataclass -class Duration(DataClassJsonMixin): +class Duration(JSONPyWizard): + class _(JSONPyWizard.Meta): # noqa:N801 + v1 = True + amount: int unit: str diff --git a/uv.lock b/uv.lock index 1d0fe88..e5a1867 100644 --- a/uv.lock +++ b/uv.lock @@ -52,16 +52,12 @@ wheels = [ ] [[package]] -name = "dataclasses-json" -version = "0.6.7" +name = "dataclass-wizard" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "marshmallow" }, - { name = "typing-inspect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +sdist = { url = "https://files.pythonhosted.org/packages/64/c5/10f2bd575b4fee1cf26b0ffa5fead2d6525f950a797566437a51fa08a94f/dataclass-wizard-0.35.0.tar.gz", hash = "sha256:8e4b254991bf93416a48e2911bb985e3787cff11f00270c3d1165d2523cb3fb6", size = 295578 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, + { url = "https://files.pythonhosted.org/packages/3a/95/968dff23cfd82806bf0e2d1c155227717046aed5e7afd8bc0fefdc7eaaf3/dataclass_wizard-0.35.0-py2.py3-none-any.whl", hash = "sha256:3bb19292477f0bebb12e9cc9178f1a6b93d133af4ae065abf14b713142b32edf", size = 176558 }, ] [[package]] @@ -109,18 +105,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, ] -[[package]] -name = "marshmallow" -version = "3.26.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, -] - [[package]] name = "mypy" version = "1.15.0" @@ -304,7 +288,7 @@ name = "todoist-api-python" version = "2.1.7" source = { editable = "." } dependencies = [ - { name = "dataclasses-json" }, + { name = "dataclass-wizard" }, { name = "requests" }, ] @@ -321,7 +305,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "dataclasses-json", specifier = ">=0.6.7,<0.7" }, + { name = "dataclass-wizard", specifier = ">=0.35.0,<1.0" }, { name = "requests", specifier = ">=2.32.3,<3" }, ] @@ -357,19 +341,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, ] -[[package]] -name = "typing-inspect" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, -] - [[package]] name = "urllib3" version = "2.3.0"