@@ -62,7 +62,7 @@ def create_podcast(self, feed_url: str, title: str, **kwargs) -> Podcast:
6262 Parameters:
6363 feed_url (str): RSS or Atom feed URL of the podcast.
6464 title (str): Display title for the podcast subscription.
65- **kwargs: Additional Podcast attributes to set (e.g., description, is_subscribed , image_url).
65+ **kwargs: Additional Podcast attributes to set (e.g., description, author , image_url).
6666
6767 Returns:
6868 Podcast: The persisted Podcast instance with updated identifiers and timestamps.
@@ -95,25 +95,22 @@ def get_podcast_by_feed_url(self, feed_url: str) -> Podcast | None:
9595 @abstractmethod
9696 def list_podcasts (
9797 self ,
98- subscribed_only : bool = True ,
9998 limit : int | None = None ,
10099 sort_by : str = "recency" ,
101100 sort_order : str = "desc"
102101 ) -> list [Podcast ]:
103102 """
104- Return podcasts optionally filtered to subscribed ones and limited in count .
103+ Return all podcasts with configurable sorting .
105104
106- Queries podcasts with configurable sorting. If `subscribed_only` is True, only podcasts
107- with an active subscription are returned. `limit` caps the number of results when provided.
105+ Use list_podcasts_with_subscribers() to get only podcasts with active subscribers.
108106
109107 Parameters:
110- subscribed_only (bool): If True, include only subscribed podcasts. Default is True.
111108 limit (Optional[int]): Maximum number of podcasts to return; if None, no limit is applied.
112109 sort_by (str): Field to sort by ("recency", "subscribers", "alphabetical"). Default is "recency".
113110 sort_order (str): Sort direction ("asc" or "desc"). Default is "desc".
114111
115112 Returns:
116- List[Podcast]: Podcasts matching the filters, sorted according to parameters.
113+ List[Podcast]: Podcasts sorted according to parameters.
117114 """
118115 pass
119116
@@ -124,7 +121,7 @@ def update_podcast(self, podcast_id: str, **kwargs) -> Podcast | None:
124121
125122 Parameters:
126123 podcast_id (str): The podcast's primary key.
127- **kwargs: Podcast fields to update (for example `title`, `feed_url`, `is_subscribed `).
124+ **kwargs: Podcast fields to update (for example `title`, `feed_url`, `image_url `).
128125
129126 Returns:
130127 Optional[Podcast]: The updated Podcast instance if found and updated, `None` if no podcast with `podcast_id` exists.
@@ -147,6 +144,23 @@ def delete_podcast(self, podcast_id: str, delete_files: bool = False) -> bool:
147144 """
148145 pass
149146
147+ @abstractmethod
148+ def list_podcasts_with_subscribers (
149+ self , limit : int | None = None
150+ ) -> list [Podcast ]:
151+ """List podcasts that have at least one user subscribed.
152+
153+ This is used by the pipeline to determine which podcasts need to be synced.
154+ Only podcasts with active user subscriptions are returned.
155+
156+ Args:
157+ limit: Maximum number of podcasts to return.
158+
159+ Returns:
160+ List[Podcast]: Podcasts with at least one subscriber.
161+ """
162+ pass
163+
150164 # --- Episode Operations ---
151165
152166 @abstractmethod
@@ -1450,16 +1464,16 @@ def get_podcast_by_feed_url(self, feed_url: str) -> Podcast | None:
14501464
14511465 def list_podcasts (
14521466 self ,
1453- subscribed_only : bool = True ,
14541467 limit : int | None = None ,
14551468 sort_by : str = "recency" ,
14561469 sort_order : str = "desc"
14571470 ) -> list [Podcast ]:
14581471 """
1459- List podcasts, optionally restricting results to subscribed podcasts.
1472+ List all podcasts with configurable sorting.
1473+
1474+ Use list_podcasts_with_subscribers() to get only podcasts with active subscribers.
14601475
14611476 Parameters:
1462- subscribed_only (bool): If True, include only podcasts with `is_subscribed` set to True.
14631477 limit (Optional[int]): Maximum number of podcasts to return; if None, no limit is applied.
14641478 sort_by (str): Field to sort by ("recency", "subscribers", "alphabetical")
14651479 sort_order (str): Sort direction ("asc" or "desc")
@@ -1470,8 +1484,6 @@ def list_podcasts(
14701484 with self ._get_session () as session :
14711485 # Build base query
14721486 stmt = select (Podcast )
1473- if subscribed_only :
1474- stmt = stmt .where (Podcast .is_subscribed .is_ (True ))
14751487
14761488 # Determine sort column
14771489 if sort_by == "recency" :
@@ -1561,6 +1573,32 @@ def delete_podcast(self, podcast_id: str, delete_files: bool = False) -> bool:
15611573 logger .info (f"Deleted podcast: { podcast .title } ({ podcast_id } )" )
15621574 return True
15631575
1576+ def list_podcasts_with_subscribers (
1577+ self , limit : int | None = None
1578+ ) -> list [Podcast ]:
1579+ """List podcasts that have at least one user subscribed.
1580+
1581+ This is used by the pipeline to determine which podcasts need to be synced.
1582+ Only podcasts with active user subscriptions are returned.
1583+
1584+ Args:
1585+ limit: Maximum number of podcasts to return.
1586+
1587+ Returns:
1588+ List[Podcast]: Podcasts with at least one subscriber.
1589+ """
1590+ with self ._get_session () as session :
1591+ # Get distinct podcast IDs that have at least one subscription
1592+ subquery = (
1593+ select (UserSubscription .podcast_id )
1594+ .distinct ()
1595+ .subquery ()
1596+ )
1597+ stmt = select (Podcast ).where (Podcast .id .in_ (select (subquery )))
1598+ if limit :
1599+ stmt = stmt .limit (limit )
1600+ return list (session .scalars (stmt ).all ())
1601+
15641602 # --- Episode Operations ---
15651603
15661604 def create_episode (
@@ -2664,7 +2702,7 @@ def get_overall_stats(self) -> dict[str, Any]:
26642702 @returns:
26652703 stats (Dict[str, Any]): Mapping of statistic names to integer counts:
26662704 - total_podcasts: Total number of podcasts in the repository.
2667- - subscribed_podcasts: Number of podcasts marked as subscribed .
2705+ - subscribed_podcasts: Number of podcasts with at least one user subscriber .
26682706 - total_episodes: Total number of episodes in the repository.
26692707 - pending_download: Episodes with download_status == "pending".
26702708 - downloading: Episodes with download_status == "downloading".
@@ -2683,8 +2721,9 @@ def get_overall_stats(self) -> dict[str, Any]:
26832721 with self ._get_session () as session :
26842722 # Podcast counts (efficient SQL aggregations)
26852723 total_podcasts = session .scalar (select (func .count (Podcast .id ))) or 0
2724+ # Count podcasts with at least one subscriber
26862725 subscribed_podcasts = session .scalar (
2687- select (func .count (Podcast . id )). where ( Podcast . is_subscribed . is_ ( True ))
2726+ select (func .count (func . distinct ( UserSubscription . podcast_id ) ))
26882727 ) or 0
26892728
26902729 # Episode counts by status (single query with conditional aggregation)
0 commit comments