Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.15.4] - 2026-06-30

### Added

- **`client.twitter.tweets.advanced_search()` / `advanced_search_all()`** — aliases for `search()` / `search_all()` that match the `/advanced_search` REST endpoint name, so the endpoint-named call no longer raises `AttributeError`. (SCR-52)

### Fixed

- **Auto-pagination no longer repeats the first page** (`*_all` iterators, e.g. `client.twitter.tweets.search_all`). The shared `paginate()` helper now stops when the backend returns the same cursor it was given, instead of re-fetching the page it just yielded — previously a non-advancing cursor from the API could cause a repeat-page loop. (SCR-52)
Expand Down
30 changes: 30 additions & 0 deletions src/scrapebadger/twitter/tweets.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,36 @@ async def search_all(
):
yield tweet

async def advanced_search(
self,
query: str,
*,
query_type: QueryType = QueryType.TOP,
count: int | None = None,
cursor: str | None = None,
) -> PaginatedResponse[Tweet]:
"""Alias for :meth:`search`, matching the ``/advanced_search`` endpoint name."""
return await self.search(query, query_type=query_type, count=count, cursor=cursor)

async def advanced_search_all(
self,
query: str,
*,
query_type: QueryType = QueryType.TOP,
count: int | None = None,
max_pages: int | None = None,
max_items: int | None = None,
) -> AsyncIterator[Tweet]:
"""Alias for :meth:`search_all`, matching the ``/advanced_search`` endpoint name."""
async for tweet in self.search_all(
query,
query_type=query_type,
count=count,
max_pages=max_pages,
max_items=max_items,
):
yield tweet

async def get_user_tweets(
self,
username: str,
Expand Down
47 changes: 47 additions & 0 deletions tests/test_twitter_tweets_aliases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""SCR-52: advanced_search/_all aliases on TweetsClient.

Customers reached for ``client.twitter.tweets.advanced_search(...)`` (the REST
endpoint name) and hit ``AttributeError``. These thin aliases delegate to
``search`` / ``search_all`` so the endpoint-named call works.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock

import pytest

from scrapebadger._internal.client import BaseClient
from scrapebadger._internal.config import ClientConfig
from scrapebadger.twitter.tweets import TweetsClient


@pytest.fixture
def tweets_client() -> TweetsClient:
return TweetsClient(BaseClient(ClientConfig(api_key="test_key", max_retries=1)))


async def test_advanced_search_delegates_to_search(tweets_client: TweetsClient) -> None:
page: dict[str, Any] = {"data": [{"id": "1"}], "next_cursor": None}
tweets_client._client.get = AsyncMock(return_value=page) # type: ignore[method-assign]

result = await tweets_client.advanced_search("python")

assert [t.id for t in result.data] == ["1"]
assert tweets_client._client.get.call_args[0][0] == "/v1/twitter/tweets/advanced_search"


async def test_advanced_search_all_delegates_to_search_all(
tweets_client: TweetsClient,
) -> None:
page: dict[str, Any] = {"data": [{"id": "1"}, {"id": "2"}]}
headers: dict[str, str] = {}
tweets_client._client.get_with_headers = AsyncMock(return_value=(page, headers)) # type: ignore[method-assign]

ids = [t.id async for t in tweets_client.advanced_search_all("python")]

assert ids == ["1", "2"]
assert tweets_client._client.get_with_headers.call_args[0][0] == (
"/v1/twitter/tweets/advanced_search"
)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading