~ihabunek/toot-discuss

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
5 2

[PATCH tooi 1/5] api.fetch_timeline(): add since_id parameter

Lexi Winter <lexi@le-Fay.ORG>
Details
Message ID
<20240109231037.28622-1-lexi@le-Fay.ORG>
DKIM signature
missing
Download raw message
Patch: +3 -1
---
 tooi/api/timeline.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/tooi/api/timeline.py b/tooi/api/timeline.py
index d93e46b..d8c0d07 100644
--- a/tooi/api/timeline.py
+++ b/tooi/api/timeline.py
@@ -40,10 +40,12 @@ async def fetch_timeline(
        instance: InstanceInfo,
        path: str,
        params: Params | None = None,
        limit: int | None = None):
        limit: int | None = None,
        since_id: str | None = None):

    _params = dict(params or {})
    _params["limit"] = limit or DEFAULT_LIMIT
    _params["since_id"] = since_id

    next_path = path
    while next_path:
-- 
2.43.0

[PATCH tooi 3/5] StatusTimeline: make fetch() a StatusTimeline member

Lexi Winter <lexi@le-Fay.ORG>
Details
Message ID
<20240109231037.28622-3-lexi@le-Fay.ORG>
In-Reply-To
<20240109231037.28622-1-lexi@le-Fay.ORG> (view parent)
DKIM signature
missing
Download raw message
Patch: +17 -31
StatusTimeline now takes path and params as constructor parameters and
provides an implementation of fetch() based on those.  This removes the
need to reimplement fetch() in every derived class.

There should be no reason for these parameters to change after the class
is constructed.
---
 tooi/api/timeline.py | 48 ++++++++++++++++----------------------------
 1 file changed, 17 insertions(+), 31 deletions(-)

diff --git a/tooi/api/timeline.py b/tooi/api/timeline.py
index ccb3d30..af5b105 100644
--- a/tooi/api/timeline.py
+++ b/tooi/api/timeline.py
@@ -73,16 +73,13 @@ class StatusTimeline(Timeline):
    StatusTimeline is the base class for timelines which only return statuses.
    """

    def __init__(self, name: str, instance: InstanceInfo):
    def __init__(self, name: str, instance: InstanceInfo, path: str, params: Params | None = None):
        super().__init__(name, instance)
        self.path = path
        self.params = params

    async def status_generator(
            self,
            path: str,
            params: Params = None,
            limit: int | None = None) -> EventGenerator:

        async for events in fetch_timeline(self.instance, path, params, limit):
    async def fetch(self, limit: int | None = None) -> EventGenerator:
        async for events in fetch_timeline(self.instance, self.path, self.params, limit):
            yield [StatusEvent(self.instance, from_dict(Status, s)) for s in events]


@@ -93,21 +90,15 @@ class HomeTimeline(StatusTimeline):
    """

    def __init__(self, instance: InstanceInfo):
        super().__init__("Home", instance)

    def fetch(self, limit: int | None = None):
        return self.status_generator("/api/v1/timelines/home", limit=limit)
        super().__init__("Home", instance, "/api/v1/timelines/home")


class PublicTimeline(StatusTimeline):
    """PublicTimeline loads events from the public timeline."""
    def __init__(self, name: str, instance: InstanceInfo, local: bool):
        super().__init__(name, instance)
        super().__init__(name, instance, "/api/v1/timelines/public", {"local": local})
        self.local = local

    def public_timeline_generator(self, limit: int | None = None):
        return self.status_generator("/api/v1/timelines/public", {"local": self.local}, limit)


class LocalTimeline(PublicTimeline):
    """
@@ -117,9 +108,6 @@ class LocalTimeline(PublicTimeline):
    def __init__(self, instance: InstanceInfo):
        super().__init__("Local", instance, True)

    def fetch(self, limit: int | None = None):
        return self.public_timeline_generator(limit=limit)


class FederatedTimeline(PublicTimeline):
    """
@@ -129,9 +117,6 @@ class FederatedTimeline(PublicTimeline):
    def __init__(self, instance: InstanceInfo):
        super().__init__("Federated", instance, False)

    def fetch(self, limit: int | None = None):
        return self.public_timeline_generator(limit=limit)


class AccountTimeline(StatusTimeline):
    """
@@ -145,11 +130,20 @@ class AccountTimeline(StatusTimeline):
                 account_id: str,
                 replies=True,
                 reblogs=True):
        super().__init__(title, instance)

        self.account_id = account_id
        self.replies = replies
        self.reblogs = reblogs

        super().__init__(
                title,
                instance,
                f"/api/v1/accounts/{self.account_id}/statuses",
                {
                    "exclude_replies": not self.replies,
                    "exclude_reblogs": not self.reblogs
                })

    @staticmethod
    async def from_name(
            instance: InstanceInfo,
@@ -159,14 +153,6 @@ class AccountTimeline(StatusTimeline):
        account = await get_account_by_name(account_name)
        return AccountTimeline(instance, account_name, account.id, replies, reblogs)

    def fetch(self, limit: int | None = None):
        path = f"/api/v1/accounts/{self.account_id}/statuses"
        params = {
            "exclude_replies": not self.replies,
            "exclude_reblogs": not self.reblogs
        }
        return self.status_generator(path, params, limit)


class NotificationTimeline(Timeline):
    """
-- 
2.43.0

[PATCH tooi 5/5] TimelineTab: use update() to refresh

Lexi Winter <lexi@le-Fay.ORG>
Details
Message ID
<20240109231037.28622-5-lexi@le-Fay.ORG>
In-Reply-To
<20240109231037.28622-1-lexi@le-Fay.ORG> (view parent)
DKIM signature
missing
Download raw message
Patch: +33 -6
When refreshing a timeline, instead of calling fetch() again, call
update() and prepend the new events to the event_list.

This has three benefits:

- it's quicker
- it creates less network traffic
- it leaves the EventList cursor where it is, so the user can easily see
  which statuses are new.
---
 tooi/tabs/timeline.py      | 19 +++++++++++++++++--
 tooi/widgets/event_list.py | 20 ++++++++++++++++----
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/tooi/tabs/timeline.py b/tooi/tabs/timeline.py
index 59cee63..5cc56c4 100644
--- a/tooi/tabs/timeline.py
+++ b/tooi/tabs/timeline.py
@@ -65,7 +65,7 @@ class TimelineTab(TabPane):

    async def on_mount(self, message):
        self.event_detail.focus()
        await self.refresh_timeline()
        await self.fetch_timeline()
        if self.initial_focus:
            self.event_list.focus_event(self.initial_focus)

@@ -81,6 +81,21 @@ class TimelineTab(TabPane):
        return make_event_detail(event)

    async def refresh_timeline(self):
        # Handle timelines that don't support updating.
        if not hasattr(self.timeline, 'update'):
            await self.fetch_timeline()
            return

        newevents = []

        async for eventslist in self.timeline.update():
            newevents += eventslist

        # The updates are returned in inverse chronological order, so reverse them before adding.
        newevents.reverse()
        self.event_list.prepend_events(newevents)

    async def fetch_timeline(self):
        self.generator = self.timeline.fetch()
        events = await anext(self.generator)
        self.event_list.replace(events)
@@ -151,7 +166,7 @@ class TimelineTab(TabPane):
            # TODO: handle exceptions
            try:
                next_events = await anext(self.generator)
                self.event_list.update(next_events)
                self.event_list.append_events(next_events)
            finally:
                self.fetching = False
                self.status_bar.update()
diff --git a/tooi/widgets/event_list.py b/tooi/widgets/event_list.py
index e6bf8e6..d7dee17 100644
--- a/tooi/widgets/event_list.py
+++ b/tooi/widgets/event_list.py
@@ -31,15 +31,14 @@ class EventList(ListView):
        super().__init__()
        self.events = []
        self.current = None
        self.update(events)
        self.append_events(events)

    def replace(self, next_events: list[Event]):
        self.events = []
        self.clear()
        self.current = None
        self.update(next_events)
        self.append_events(next_events)

    def update(self, next_events: list[Event]):
    def append_events(self, next_events: list[Event]):
        self.events += next_events

        for event in next_events:
@@ -52,6 +51,19 @@ class EventList(ListView):
        if self.current is not None:
            self.post_message(EventHighlighted(self.current))

    def prepend_events(self, next_events: list[Event]):
        self.events = next_events + self.events

        for event in next_events:
            self.mount(EventListItem(event), before=0)

        if self.current is None and len(self.events) > 0:
            self.index = 0
            self.current = self.events[0]

        if self.current is not None:
            self.post_message(EventHighlighted(self.current))

    def focus_event(self, event_id: str):
        for i, event in enumerate(self.events):
            if event.id == event_id:
-- 
2.43.0

[PATCH tooi 2/5] api.timeline: rename create_generator() to fetch()

Lexi Winter <lexi@le-Fay.ORG>
Details
Message ID
<20240109231037.28622-2-lexi@le-Fay.ORG>
In-Reply-To
<20240109231037.28622-1-lexi@le-Fay.ORG> (view parent)
DKIM signature
missing
Download raw message
Patch: +9 -9
This more accurately describes what the function does.
---
 tooi/api/timeline.py  | 16 ++++++++--------
 tooi/tabs/timeline.py |  2 +-
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/tooi/api/timeline.py b/tooi/api/timeline.py
index d8c0d07..ccb3d30 100644
--- a/tooi/api/timeline.py
+++ b/tooi/api/timeline.py
@@ -64,7 +64,7 @@ class Timeline(ABC):
        self.name = name

    @abstractmethod
    def create_generator(self, limit: int | None = None) -> EventGenerator:
    def fetch(self, limit: int | None = None) -> EventGenerator:
        ...


@@ -95,7 +95,7 @@ class HomeTimeline(StatusTimeline):
    def __init__(self, instance: InstanceInfo):
        super().__init__("Home", instance)

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        return self.status_generator("/api/v1/timelines/home", limit=limit)


@@ -117,7 +117,7 @@ class LocalTimeline(PublicTimeline):
    def __init__(self, instance: InstanceInfo):
        super().__init__("Local", instance, True)

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        return self.public_timeline_generator(limit=limit)


@@ -129,7 +129,7 @@ class FederatedTimeline(PublicTimeline):
    def __init__(self, instance: InstanceInfo):
        super().__init__("Federated", instance, False)

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        return self.public_timeline_generator(limit=limit)


@@ -159,7 +159,7 @@ class AccountTimeline(StatusTimeline):
        account = await get_account_by_name(account_name)
        return AccountTimeline(instance, account_name, account.id, replies, reblogs)

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        path = f"/api/v1/accounts/{self.account_id}/statuses"
        params = {
            "exclude_replies": not self.replies,
@@ -201,7 +201,7 @@ class NotificationTimeline(Timeline):
        async for events in fetch_timeline(self.instance, path, params, limit):
            yield [e for e in map(self.make_notification_event, events) if e is not None]

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        # TODO: not included: follow_request, poll, update, admin.sign_up, admin.report
        types = ["mention", "status", "reblog", "favourite", "follow"]
        params = {"types[]": types}
@@ -226,7 +226,7 @@ class TagTimeline(StatusTimeline):
        self.hashtag = hashtag
        super().__init__(f"#{self.hashtag}", instance)

    def create_generator(self, limit: int = 40):
    def fetch(self, limit: int = 40):
        path = f"/api/v1/timelines/tag/{quote(self.hashtag)}"
        return self.status_generator(path, limit=limit)

@@ -248,7 +248,7 @@ class ContextTimeline(Timeline):
        all_statuses = ancestors + [status] + descendants
        yield [StatusEvent(self.instance, s) for s in all_statuses]

    def create_generator(self, limit: int | None = None):
    def fetch(self, limit: int | None = None):
        return self.context_timeline_generator(self._status, limit)


diff --git a/tooi/tabs/timeline.py b/tooi/tabs/timeline.py
index 6d950f5..59cee63 100644
--- a/tooi/tabs/timeline.py
+++ b/tooi/tabs/timeline.py
@@ -81,7 +81,7 @@ class TimelineTab(TabPane):
        return make_event_detail(event)

    async def refresh_timeline(self):
        self.generator = self.timeline.create_generator()
        self.generator = self.timeline.fetch()
        events = await anext(self.generator)
        self.event_list.replace(events)
        self.query_one("#main_window").mount(self.event_detail)
-- 
2.43.0

[PATCH tooi 4/5] Timeline: add update() method

Lexi Winter <lexi@le-Fay.ORG>
Details
Message ID
<20240109231037.28622-4-lexi@le-Fay.ORG>
In-Reply-To
<20240109231037.28622-1-lexi@le-Fay.ORG> (view parent)
DKIM signature
missing
Download raw message
Patch: +83 -14
update() is similar to fetch(), but it only fetches events which are new
since the previous call to update() (or the initial call to fetch()).
---
 tooi/api/timeline.py | 97 +++++++++++++++++++++++++++++++++++++-------
 1 file changed, 83 insertions(+), 14 deletions(-)

diff --git a/tooi/api/timeline.py b/tooi/api/timeline.py
index af5b105..d494e64 100644
--- a/tooi/api/timeline.py
+++ b/tooi/api/timeline.py
@@ -50,8 +50,13 @@ async def fetch_timeline(
    next_path = path
    while next_path:
        response = await request("GET", next_path, params=_params)
        yield response.json()
        next_path = _get_next_path(response.headers)
        events = response.json()

        if len(events) == 0:
            next_path = None
        else:
            yield events
            next_path = _get_next_path(response.headers)


class Timeline(ABC):
@@ -77,10 +82,36 @@ class StatusTimeline(Timeline):
        super().__init__(name, instance)
        self.path = path
        self.params = params
        self._most_recent_id = None

    async def fetch(self, limit: int | None = None) -> EventGenerator:
        async for events in fetch_timeline(self.instance, self.path, self.params, limit):
            yield [StatusEvent(self.instance, from_dict(Status, s)) for s in events]
        async for eventlist in fetch_timeline(self.instance, self.path, self.params, limit):
            events = [StatusEvent(self.instance, from_dict(Status, s)) for s in eventlist]

            # Track the most recent id we've fetched, which will be the first, for update().
            if self._most_recent_id is None and len(events) > 0:
                self._most_recent_id = events[0].status.id

            yield events

    async def update(self, limit: int | None = None) -> EventGenerator:
        eventslist = fetch_timeline(
                self.instance,
                self.path,
                self.params,
                limit,
                self._most_recent_id)

        updated_most_recent = False

        async for eventlist in eventslist:
            events = [StatusEvent(self.instance, from_dict(Status, s)) for s in eventlist]

            if updated_most_recent == False and len(events) > 0:
                updated_most_recent = True
                self._most_recent_id = events[0].status.id

            yield events


class HomeTimeline(StatusTimeline):
@@ -159,8 +190,13 @@ class NotificationTimeline(Timeline):
    NotificationTimeline loads events from the user's notifications.
    https://docs.joinmastodon.org/methods/notifications/
    """

    # TODO: not included: follow_request, poll, update, admin.sign_up, admin.report
    TYPES = [ "mention", "follow", "favourite", "reblog"]

    def __init__(self, instance: InstanceInfo):
        super().__init__("Notifications", instance)
        self._most_recent_id = None

    def make_notification_event(self, response: dict) -> Event | None:
        notification = from_dict(Notification, response)
@@ -185,13 +221,35 @@ class NotificationTimeline(Timeline):

        path = "/api/v1/notifications"
        async for events in fetch_timeline(self.instance, path, params, limit):
            yield [e for e in map(self.make_notification_event, events) if e is not None]
            events = [e for e in map(self.make_notification_event, events) if e is not None]

            # Track the most recent id we've fetched, which will be the first, for update().
            if self._most_recent_id is None and len(events) > 0:
                self._most_recent_id = events[0].notification.id

            yield events

    def fetch(self, limit: int | None = None):
        # TODO: not included: follow_request, poll, update, admin.sign_up, admin.report
        types = ["mention", "status", "reblog", "favourite", "follow"]
        params = {"types[]": types}
        return self.notification_generator(params, limit)
        return self.notification_generator({ "types[]": self.TYPES}, limit)

    async def update(self, limit: int | None = None) -> EventGenerator:
        eventslist = fetch_timeline(
                self.instance,
                "/api/v1/notifications",
                params={ "types[]": self.TYPES },
                limit=limit,
                since_id=self._most_recent_id)

        updated_most_recent = False

        async for eventlist in eventslist:
            events = [e for e in map(self.make_notification_event, eventlist) if e is not None]

            if updated_most_recent == False and len(events) > 0:
                updated_most_recent = True
                self._most_recent_id = events[0].notification.id

            yield events


class TagTimeline(StatusTimeline):
@@ -199,7 +257,14 @@ class TagTimeline(StatusTimeline):
    TagTimeline loads events from the given hashtag.
    This timeline only ever returns events of type StatusEvent.
    """
    def __init__(self, instance: InstanceInfo, hashtag: str, local: bool = False):
    def __init__(
            self,
            instance:
            InstanceInfo,
            hashtag: str,
            local: bool = False,
            remote: bool=False):

        self.local = local

        # Normalise the hashtag to not begin with a hash
@@ -210,11 +275,15 @@ class TagTimeline(StatusTimeline):
            raise (ValueError("TagTimeline: tag is empty"))

        self.hashtag = hashtag
        super().__init__(f"#{self.hashtag}", instance)

    def fetch(self, limit: int = 40):
        path = f"/api/v1/timelines/tag/{quote(self.hashtag)}"
        return self.status_generator(path, limit=limit)
        super().__init__(
                f"#{self.hashtag}",
                instance,
                f"/api/v1/timelines/tag/{quote(self.hashtag)}",
                params={
                    "local": local,
                    "remote": remote,
                })


class ContextTimeline(Timeline):
-- 
2.43.0
Details
Message ID
<cc189599-b194-4df2-a27c-632fad4ee268@app.fastmail.com>
In-Reply-To
<20240109231037.28622-1-lexi@le-Fay.ORG> (view parent)
DKIM signature
missing
Download raw message
Thanks, applied.

-- Ivan
Reply to thread Export thread (mbox)