Skip to content

Commit 11d014e

Browse files
authored
Merge pull request #25 from zernio-dev/fix/get-820-follower-stats-params
fix(accounts): forward all follower-stats params in typed override (GET-820)
2 parents 20c217e + 008458e commit 11d014e

2 files changed

Lines changed: 89 additions & 25 deletions

File tree

‎src/late/resources/accounts.py‎

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,26 +72,45 @@ def get(self, account_id: str) -> AccountGetResponse:
7272
data = self._client._get(self._path(account_id))
7373
return AccountGetResponse.model_validate(data)
7474

75+
# Signature must mirror _GeneratedAccountsResource.get_follower_stats; this
76+
# override exists ONLY to return a typed FollowerStatsResponse instead of a
77+
# raw dict. Keep the params in sync on every OpenAPI regen, dropping them is
78+
# what caused the TypeError in GET-820. account_ids is a comma-separated
79+
# string (the MCP wrapper and the REST endpoint both use that shape); a list
80+
# is also accepted for backwards compatibility and joined into that shape.
7581
def get_follower_stats(
7682
self,
7783
*,
78-
account_ids: list[str] | None = None,
84+
account_ids: str | list[str] | None = None,
85+
profile_id: str | None = None,
86+
from_date: str | None = None,
87+
to_date: str | None = None,
88+
granularity: str | None = None,
7989
) -> FollowerStatsResponse:
8090
"""
81-
Get follower statistics for accounts.
82-
83-
Requires analytics add-on.
91+
Get follower statistics for accounts. Requires analytics add-on.
8492
8593
Args:
86-
account_ids: Optional list of account IDs to filter
94+
account_ids: Account IDs to filter, as a comma-separated string or a
95+
list of IDs (optional, defaults to all)
96+
profile_id: Filter by profile ID
97+
from_date: Start date YYYY-MM-DD (server defaults to 30 days ago)
98+
to_date: End date YYYY-MM-DD (server defaults to today)
99+
granularity: Aggregation level ('daily' | 'weekly' | 'monthly')
87100
88101
Returns:
89-
FollowerStatsResponse with 'stats' attribute
102+
FollowerStatsResponse with 'accounts', 'dateRange', 'aggregation'
90103
"""
91-
params = None
92-
if account_ids:
93-
params = {"accountIds": ",".join(account_ids)}
94-
data = self._client._get(self._path("follower-stats"), params=params)
104+
if isinstance(account_ids, list):
105+
account_ids = ",".join(account_ids)
106+
params = self._build_params(
107+
account_ids=account_ids,
108+
profile_id=profile_id,
109+
from_date=from_date,
110+
to_date=to_date,
111+
granularity=granularity,
112+
)
113+
data = self._client._get(self._path("follower-stats"), params=params or None)
95114
return FollowerStatsResponse.model_validate(data)
96115

97116
# -------------------------------------------------------------------------
@@ -109,14 +128,40 @@ async def aget(self, account_id: str) -> AccountGetResponse:
109128
data = await self._client._aget(self._path(account_id))
110129
return AccountGetResponse.model_validate(data)
111130

131+
# Same signature/params as the sync version; only the return wrapping differs.
132+
# See GET-820 note on get_follower_stats above.
112133
async def aget_follower_stats(
113134
self,
114135
*,
115-
account_ids: list[str] | None = None,
136+
account_ids: str | list[str] | None = None,
137+
profile_id: str | None = None,
138+
from_date: str | None = None,
139+
to_date: str | None = None,
140+
granularity: str | None = None,
116141
) -> FollowerStatsResponse:
117-
"""Get follower statistics asynchronously."""
118-
params = None
119-
if account_ids:
120-
params = {"accountIds": ",".join(account_ids)}
121-
data = await self._client._aget(self._path("follower-stats"), params=params)
142+
"""Get follower statistics asynchronously.
143+
144+
Args:
145+
account_ids: Account IDs to filter, as a comma-separated string or a
146+
list of IDs (optional, defaults to all)
147+
profile_id: Filter by profile ID
148+
from_date: Start date YYYY-MM-DD
149+
to_date: End date YYYY-MM-DD
150+
granularity: Aggregation level ('daily' | 'weekly' | 'monthly')
151+
152+
Returns:
153+
FollowerStatsResponse with 'accounts', 'dateRange', 'aggregation'
154+
"""
155+
if isinstance(account_ids, list):
156+
account_ids = ",".join(account_ids)
157+
params = self._build_params(
158+
account_ids=account_ids,
159+
profile_id=profile_id,
160+
from_date=from_date,
161+
to_date=to_date,
162+
granularity=granularity,
163+
)
164+
data = await self._client._aget(
165+
self._path("follower-stats"), params=params or None
166+
)
122167
return FollowerStatsResponse.model_validate(data)

‎tests/test_integration.py‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -460,16 +460,10 @@ def test_get_account(self, client: Late, mock_account: dict) -> None:
460460

461461
@respx.mock
462462
def test_get_follower_stats(self, client: Late) -> None:
463-
"""Test getting follower statistics."""
463+
"""Test getting follower statistics. account_ids as a list is still
464+
accepted for backwards compatibility and joined into a comma string."""
464465
route = respx.get("https://api.test.com/v1/accounts/follower-stats").mock(
465-
return_value=httpx.Response(
466-
200,
467-
json={
468-
"stats": [
469-
{"accountId": "acc_123", "followersCount": 1000, "change": 50}
470-
]
471-
},
472-
)
466+
return_value=httpx.Response(200, json={"accounts": []})
473467
)
474468

475469
client.accounts.get_follower_stats(account_ids=["acc_123", "acc_456"])
@@ -482,6 +476,31 @@ def test_get_follower_stats(self, client: Late) -> None:
482476
assert "acc_123" in url_str
483477
assert "acc_456" in url_str
484478

479+
@respx.mock
480+
def test_get_follower_stats_forwards_all_params(self, client: Late) -> None:
481+
"""Regression for GET-820: the manual override dropped profile_id/
482+
from_date/to_date/granularity, raising TypeError. All five params must
483+
reach the endpoint as camelCase query params."""
484+
route = respx.get("https://api.test.com/v1/accounts/follower-stats").mock(
485+
return_value=httpx.Response(200, json={"accounts": []})
486+
)
487+
488+
client.accounts.get_follower_stats(
489+
account_ids="acc_123,acc_456",
490+
profile_id="prof_1",
491+
from_date="2026-01-01",
492+
to_date="2026-01-31",
493+
granularity="weekly",
494+
)
495+
496+
assert route.called
497+
url_str = str(route.calls[0].request.url)
498+
assert "accountIds=acc_123" in url_str
499+
assert "profileId=prof_1" in url_str
500+
assert "fromDate=2026-01-01" in url_str
501+
assert "toDate=2026-01-31" in url_str
502+
assert "granularity=weekly" in url_str
503+
485504

486505
# =============================================================================
487506
# Media Resource Tests

0 commit comments

Comments
 (0)