From d8fe234ada9389b98880f7658fbc7a8eaed6db05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 17:34:35 +0200 Subject: [PATCH 01/16] Rename UserListEntry to ListDescription --- trakt/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index 9fab669d..669a6db2 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -66,7 +66,7 @@ def unfollow(user_name): @dataclass(frozen=True) -class UserListEntry: +class ListDescription: name: str description: str privacy: str @@ -85,7 +85,7 @@ class UserListEntry: creator: str -class UserList(DataClassMixin(UserListEntry), IdsMixin): +class UserList(DataClassMixin(ListDescription), IdsMixin): """A list created by a Trakt.tv :class:`User`""" def __init__(self, ids=None, **kwargs): From 541cec6f65f55366a167ccdf8d07d35cc10fcd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 18:13:06 +0200 Subject: [PATCH 02/16] Make creator optional in ListEntry --- trakt/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index 669a6db2..bd8def12 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Interfaces to all of the User objects offered by the Trakt.tv API""" from dataclasses import dataclass -from typing import Any, NamedTuple +from typing import Any, NamedTuple, Optional from trakt.core import delete, get, post from trakt.mixins import DataClassMixin, IdsMixin @@ -82,7 +82,7 @@ class ListDescription: comment_count: int likes: int user: Any - creator: str + creator: Optional[str] = None class UserList(DataClassMixin(ListDescription), IdsMixin): From e02c5c7d21e33bbb5b9466a8b0573f8ee2efee48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 18:49:47 +0200 Subject: [PATCH 03/16] Add PublicList class for official lists --- trakt/users.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index bd8def12..154badfa 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -85,6 +85,70 @@ class ListDescription: creator: Optional[str] = None +class PublicList(DataClassMixin(ListDescription), IdsMixin): + """A record for public lists """ + + def __init__(self, ids=None, **kwargs): + super().__init__(**kwargs) + self._ids = ids + self._items = None + + @classmethod + @get + def load(cls, id: int) -> "PublicList": + """ + https://trakt.docs.apiary.io/#reference/lists/list/get-list + """ + data = yield f"lists/{id}" + yield cls(**data) + + @property + def items(self): + if self._items is None: + self._load_items() + return self._items + + @get + def _load_items(self): + """ + https://trakt.docs.apiary.io/#reference/lists/list-items + """ + data = yield f"lists/{self.trakt}/items" + self._items = list(self._process_items(data)) + yield self._items + + @staticmethod + def _process_items(items): + for item in items: + # match list item type + if "type" not in item: + continue + item_type = item["type"] + data = item.pop(item_type) + if item_type == "movie": + title = data.pop("title") + movie = Movie(title, **data) + yield movie + elif item_type == "show": + show = TVShow(data["title"], data["ids"]["slug"]) + yield show + elif item_type == "season": + show_data = item.pop("show") + season = TVSeason(show_data["title"], + data["number"], + show_data["ids"]["slug"]) + yield season + elif item_type == "episode": + show_data = item.pop("show") + episode = TVEpisode(show_data["title"], data["season"], + data["number"], + show_id=show_data["ids"]["trakt"]) + yield episode + elif item_type == "person": + person = Person(data["name"], data["ids"]["slug"]) + yield person + + class UserList(DataClassMixin(ListDescription), IdsMixin): """A list created by a Trakt.tv :class:`User`""" From 47cc7824f0dec32449dda694bc5cd7d3cbd9390b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 18:10:37 +0200 Subject: [PATCH 04/16] Register PublicList in all --- trakt/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trakt/users.py b/trakt/users.py index 154badfa..d7c16837 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -11,7 +11,7 @@ from trakt.utils import slugify __author__ = 'Jon Nappi' -__all__ = ['User', 'UserList', 'Request', 'follow', 'get_all_requests', +__all__ = ['User', 'UserList', 'PublicList', 'Request', 'follow', 'get_all_requests', 'get_user_settings', 'unfollow'] From 8f136f9541838dd3108d20adc6e47d6f6652491a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:20:06 +0200 Subject: [PATCH 05/16] Enable annotations for users.py --- trakt/users.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index d7c16837..0681a5bb 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- """Interfaces to all of the User objects offered by the Trakt.tv API""" +from __future__ import annotations + from dataclasses import dataclass from typing import Any, NamedTuple, Optional From 35c253145d89f605cb6f663821dba653f6d57300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:20:27 +0200 Subject: [PATCH 06/16] Add return type annotation for PublicList.load --- trakt/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index 0681a5bb..753fd66e 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, NamedTuple, Optional +from typing import Any, NamedTuple, Optional, Union from trakt.core import delete, get, post from trakt.mixins import DataClassMixin, IdsMixin @@ -97,7 +97,7 @@ def __init__(self, ids=None, **kwargs): @classmethod @get - def load(cls, id: int) -> "PublicList": + def load(cls, id: int) -> Union[ListEntry, PublicList]: """ https://trakt.docs.apiary.io/#reference/lists/list/get-list """ From 52b6fa46ddb1eb37c5f8e9e7fc7f9a8785215dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:21:24 +0200 Subject: [PATCH 07/16] Add __len__ method to PublicList --- trakt/users.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index 753fd66e..12ec6f99 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -95,6 +95,9 @@ def __init__(self, ids=None, **kwargs): self._ids = ids self._items = None + def __len__(self): + return len(self.items) + @classmethod @get def load(cls, id: int) -> Union[ListEntry, PublicList]: From 31cd4834a5fbf8b52940d884ef7ea0bf3f12ac1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:26:52 +0200 Subject: [PATCH 08/16] Add iter method --- trakt/users.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index 12ec6f99..25ec8b2e 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -95,6 +95,9 @@ def __init__(self, ids=None, **kwargs): self._ids = ids self._items = None + def __iter__(self): + return iter(self.items) + def __len__(self): return len(self.items) From 9507dd69a66329553af75414485412bd22eeb5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:29:49 +0200 Subject: [PATCH 09/16] Add ListEntry class --- trakt/users.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index 25ec8b2e..c2b9fcc0 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -67,6 +67,37 @@ def unfollow(user_name): yield 'users/{username}/follow'.format(username=slugify(user_name)) +@dataclass(frozen=True) +class ListEntry: + id: int + rank: int + listed_at: str + type: str + # data for "type" structure + data: Any + notes: Optional[str] = None + + @property + def item(self): + # Poor man's cached_property + if self.type not in self.__dict__: + self.__dict__[self.type] = getattr(self, self.type) + + return self.__dict__[self.type] + + @property + def movie(self): + data = self.data.copy() + title = data.pop("title") + return Movie(title, **data) + + def __getattr__(self, name): + """ + Delegate everything missing to sub-item + """ + return self.item.__getattribute__(name) + + @dataclass(frozen=True) class ListDescription: name: str From 09b96bb7efd1f9614fb8140ab2b7c09780e5287f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 19:41:41 +0200 Subject: [PATCH 10/16] Use ListEntry dataclass --- trakt/users.py | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index c2b9fcc0..570e535c 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -159,33 +159,10 @@ def _load_items(self): @staticmethod def _process_items(items): for item in items: - # match list item type if "type" not in item: continue - item_type = item["type"] - data = item.pop(item_type) - if item_type == "movie": - title = data.pop("title") - movie = Movie(title, **data) - yield movie - elif item_type == "show": - show = TVShow(data["title"], data["ids"]["slug"]) - yield show - elif item_type == "season": - show_data = item.pop("show") - season = TVSeason(show_data["title"], - data["number"], - show_data["ids"]["slug"]) - yield season - elif item_type == "episode": - show_data = item.pop("show") - episode = TVEpisode(show_data["title"], data["season"], - data["number"], - show_id=show_data["ids"]["trakt"]) - yield episode - elif item_type == "person": - person = Person(data["name"], data["ids"]["slug"]) - yield person + data = item.pop(item["type"]) + yield ListEntry(**item, data=data) class UserList(DataClassMixin(ListDescription), IdsMixin): From ef624e9b2f5cbed894ef1b11a57e2ee8fbc7c721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 29 Feb 2024 20:09:12 +0200 Subject: [PATCH 11/16] Restore compat with py 3.6 from trakt.users import User E File "/home/runner/work/python-pytrakt/python-pytrakt/trakt/users.py", line 3 E from __future__ import annotations E ^ E SyntaxError: future feature annotations is not defined --- trakt/users.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index 570e535c..9a8d9cac 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- """Interfaces to all of the User objects offered by the Trakt.tv API""" -from __future__ import annotations - from dataclasses import dataclass from typing import Any, NamedTuple, Optional, Union @@ -134,7 +132,7 @@ def __len__(self): @classmethod @get - def load(cls, id: int) -> Union[ListEntry, PublicList]: + def load(cls, id: int) -> Union[ListEntry, "PublicList"]: """ https://trakt.docs.apiary.io/#reference/lists/list/get-list """ From da3b5087af3499f909de7ab6fb567cfac74a6001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Fri, 1 Mar 2024 00:10:16 +0200 Subject: [PATCH 12/16] Fix cached property recursion problem --- trakt/users.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/trakt/users.py b/trakt/users.py index 9a8d9cac..a0e6a30f 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -79,7 +79,13 @@ class ListEntry: def item(self): # Poor man's cached_property if self.type not in self.__dict__: - self.__dict__[self.type] = getattr(self, self.type) + # Avoid recursion + error = object() + self.__dict__[self.type] = error + value = getattr(self, self.type, None) + if value is error: + raise RuntimeError(f"Invalid type: {self.type}") + self.__dict__[self.type] = value return self.__dict__[self.type] From e1662ab1cd984944b9a5f6599efe78513dd17a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Fri, 1 Mar 2024 00:16:14 +0200 Subject: [PATCH 13/16] Add show to ListEntry --- trakt/users.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index a0e6a30f..82c3d4da 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -95,6 +95,10 @@ def movie(self): title = data.pop("title") return Movie(title, **data) + @property + def show(self): + return TVShow(**self.data) + def __getattr__(self, name): """ Delegate everything missing to sub-item From a5567ce00b1245490a6183bfca5edf088636ddc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Fri, 1 Mar 2024 00:30:40 +0200 Subject: [PATCH 14/16] Add season to ListEntry --- trakt/users.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index 82c3d4da..c7808320 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -99,6 +99,13 @@ def movie(self): def show(self): return TVShow(**self.data) + @property + def season(self): + show = self.data["show"] + season = self.data["number"] + ids = show["ids"] + return TVSeason(show=show["title"], season=season, slug=ids["slug"]) + def __getattr__(self, name): """ Delegate everything missing to sub-item @@ -170,6 +177,8 @@ def _process_items(items): if "type" not in item: continue data = item.pop(item["type"]) + if "show" in item: + data["show"] = item.pop("show") yield ListEntry(**item, data=data) From 9aa9e6927668ca8bdf93761204194b0deb069556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Fri, 1 Mar 2024 00:39:23 +0200 Subject: [PATCH 15/16] Add episode to ListEntry --- trakt/users.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/trakt/users.py b/trakt/users.py index c7808320..4ecf1234 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -106,6 +106,13 @@ def season(self): ids = show["ids"] return TVSeason(show=show["title"], season=season, slug=ids["slug"]) + @property + def episode(self): + data = self.data.copy() + show = data.pop("show") + ids = show["ids"] + return TVEpisode(show=show["title"], show_id=ids["trakt"], **data) + def __getattr__(self, name): """ Delegate everything missing to sub-item From b560cd555b8f6c73b6504d492a2d22bf788aea1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Fri, 8 Mar 2024 15:44:02 +0200 Subject: [PATCH 16/16] Update season builder not to trigger fetch --- trakt/users.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/trakt/users.py b/trakt/users.py index 4ecf1234..db286f94 100644 --- a/trakt/users.py +++ b/trakt/users.py @@ -101,10 +101,11 @@ def show(self): @property def season(self): - show = self.data["show"] - season = self.data["number"] + data = self.data.copy() + show = data.pop("show") + season = data.pop("number") ids = show["ids"] - return TVSeason(show=show["title"], season=season, slug=ids["slug"]) + return TVSeason(show=show["title"], season=season, slug=ids["slug"], **data) @property def episode(self):