diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 6360f06..442f2c3 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -12,9 +12,6 @@ jobs:
publish:
name: publish
runs-on: ubuntu-latest
- permissions:
- contents: read
- id-token: write
steps:
- uses: actions/checkout@v6
@@ -27,3 +24,5 @@ jobs:
- name: Publish to PyPI
run: |
bash ./bin/publish-pypi
+ env:
+ PYPI_TOKEN: ${{ secrets.X_TWITTER_SCRAPER_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 8c7b433..a19b2e1 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -17,3 +17,5 @@ jobs:
- name: Check release environment
run: |
bash ./bin/check-release-environment
+ env:
+ PYPI_TOKEN: ${{ secrets.X_TWITTER_SCRAPER_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cce9240..da59f99 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.3.1"
+ ".": "0.4.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index db87955..d470135 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 117
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik%2Fx-twitter-scraper-d40c57a05527faf060d21c0e013729f371d88017b10680cea7c8fd6780ffaef5.yml
-openapi_spec_hash: 597ebc460cf86740b9f6f7c95478dece
-config_hash: 30ce23c9cfbf8fb8be9e5dd28a2124fa
+configured_endpoints: 110
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik%2Fx-twitter-scraper-2adc33156b4b42a4be18cc20c0205b38f0432d7958da99c65ee9b3f6a555ea0e.yml
+openapi_spec_hash: be760f5620a268521d6793f65576a61f
+config_hash: 320a9cb2f1293d1a7b73c63ab5865af5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 388dde5..0e6ef40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## 0.4.0 (2026-04-22)
+
+Full Changelog: [v0.3.1...v0.4.0](https://github.com/Xquik-dev/x-twitter-scraper-python/compare/v0.3.1...v0.4.0)
+
+### Features
+
+* **api:** api update ([ea97c97](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/ea97c97b83f9388dfe339655bd7e1e2f39bf8537))
+* **api:** api update ([9ab5907](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/9ab59079a7538deaedc38cb221ba6421ff4dd39a))
+* **api:** api update ([e6edd37](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/e6edd371ab5a80748ae26a0173206e89634c5da5))
+* **api:** api update ([8e576ed](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/8e576ed647806a054a3f6b7f3be7dbcaffb36aef))
+
+
+### Bug Fixes
+
+* ensure file data are only sent as 1 parameter ([c86dcf1](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/c86dcf186aed37ef154028b708bc9cdf3a56bf39))
+* escape ampersand in OpenAPI summaries for C# XML docs ([d2d9141](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/d2d9141e286e7f032f9aec934e93f33414c7221e))
+
+
+### Performance Improvements
+
+* **client:** optimize file structure copying in multipart requests ([eb0688d](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/eb0688d0ae9da72c17a1b3550e0221d2f81a9ef4))
+
+
+### Chores
+
+* sync OpenAPI spec ([e7bfdb7](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/e7bfdb76972a4d2c6b5ddb5f0f1f815579fd175d))
+* wire production_repo for all targets ([18d4cc3](https://github.com/Xquik-dev/x-twitter-scraper-python/commit/18d4cc3d3c5796aaa637f3841224dfaa9e076a5b))
+
## 0.3.1 (2026-04-08)
Full Changelog: [v0.3.0...v0.3.1](https://github.com/Xquik-dev/x-twitter-scraper-python/compare/v0.3.0...v0.3.1)
diff --git a/README.md b/README.md
index 52b5048..b2230c0 100644
--- a/README.md
+++ b/README.md
@@ -117,24 +117,6 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
-## Nested params
-
-Nested parameters are dictionaries, typed using `TypedDict`, for example:
-
-```python
-from x_twitter_scraper import XTwitterScraper
-
-client = XTwitterScraper()
-
-integration = client.integrations.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
-)
-print(integration.config)
-```
-
## File uploads
Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.
diff --git a/api.md b/api.md
index 925359a..82e047f 100644
--- a/api.md
+++ b/api.md
@@ -1,7 +1,14 @@
# Shared Types
```python
-from x_twitter_scraper.types import Error, EventType, PaginatedTweets, PaginatedUsers
+from x_twitter_scraper.types import (
+ Error,
+ EventType,
+ PaginatedTweets,
+ PaginatedUsers,
+ SearchTweet,
+ UserProfile,
+)
```
# Account
@@ -219,31 +226,6 @@ Methods:
- client.webhooks.list_deliveries(id) -> WebhookListDeliveriesResponse
- client.webhooks.test(id) -> WebhookTestResponse
-# Integrations
-
-Types:
-
-```python
-from x_twitter_scraper.types import (
- Integration,
- IntegrationDelivery,
- IntegrationListResponse,
- IntegrationDeleteResponse,
- IntegrationListDeliveriesResponse,
- IntegrationSendTestResponse,
-)
-```
-
-Methods:
-
-- client.integrations.create(\*\*params) -> Integration
-- client.integrations.retrieve(id) -> Integration
-- client.integrations.update(id, \*\*params) -> Integration
-- client.integrations.list() -> IntegrationListResponse
-- client.integrations.delete(id) -> IntegrationDeleteResponse
-- client.integrations.list_deliveries(id, \*\*params) -> IntegrationListDeliveriesResponse
-- client.integrations.send_test(id) -> IntegrationSendTestResponse
-
# X
Types:
@@ -261,7 +243,7 @@ Methods:
- client.x.get_article(tweet_id) -> XGetArticleResponse
- client.x.get_home_timeline(\*\*params) -> PaginatedTweets
- client.x.get_notifications(\*\*params) -> XGetNotificationsResponse
-- client.x.get_trends() -> XGetTrendsResponse
+- client.x.get_trends(\*\*params) -> XGetTrendsResponse
## Tweets
@@ -269,7 +251,6 @@ Types:
```python
from x_twitter_scraper.types.x import (
- SearchTweet,
TweetAuthor,
TweetDetail,
TweetCreateResponse,
@@ -319,15 +300,9 @@ Methods:
## Users
-Types:
-
-```python
-from x_twitter_scraper.types.x import UserProfile
-```
-
Methods:
-- client.x.users.retrieve(id) -> UserProfile
+- client.x.users.retrieve(id) -> UserProfile
- client.x.users.retrieve_batch(\*\*params) -> PaginatedUsers
- client.x.users.retrieve_followers(id, \*\*params) -> PaginatedUsers
- client.x.users.retrieve_followers_you_know(id, \*\*params) -> PaginatedUsers
diff --git a/bin/check-release-environment b/bin/check-release-environment
index 1e951e9..b845b0f 100644
--- a/bin/check-release-environment
+++ b/bin/check-release-environment
@@ -2,6 +2,10 @@
errors=()
+if [ -z "${PYPI_TOKEN}" ]; then
+ errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
+fi
+
lenErrors=${#errors[@]}
if [[ lenErrors -gt 0 ]]; then
diff --git a/bin/publish-pypi b/bin/publish-pypi
index 5895700..e72ca2f 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -4,8 +4,4 @@ set -eux
rm -rf dist
mkdir -p dist
uv build
-if [ -n "${PYPI_TOKEN:-}" ]; then
- uv publish --token=$PYPI_TOKEN
-else
- uv publish
-fi
+uv publish --token=$PYPI_TOKEN
diff --git a/pyproject.toml b/pyproject.toml
index ad2ba10..785db55 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "x_twitter_scraper"
-version = "0.3.1"
+version = "0.4.0"
description = "The official Python library for the x-twitter-scraper API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/x_twitter_scraper/_client.py b/src/x_twitter_scraper/_client.py
index 0d70bf3..b0e698e 100644
--- a/src/x_twitter_scraper/_client.py
+++ b/src/x_twitter_scraper/_client.py
@@ -50,7 +50,6 @@
webhooks,
subscribe,
extractions,
- integrations,
)
from .resources.x.x import XResource, AsyncXResource
from .resources.draws import DrawsResource, AsyncDrawsResource
@@ -67,7 +66,6 @@
from .resources.webhooks import WebhooksResource, AsyncWebhooksResource
from .resources.subscribe import SubscribeResource, AsyncSubscribeResource
from .resources.extractions import ExtractionsResource, AsyncExtractionsResource
- from .resources.integrations import IntegrationsResource, AsyncIntegrationsResource
from .resources.support.support import SupportResource, AsyncSupportResource
__all__ = [
@@ -143,7 +141,7 @@ def __init__(
@cached_property
def account(self) -> AccountResource:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AccountResource
return AccountResource(self)
@@ -157,35 +155,35 @@ def api_keys(self) -> APIKeysResource:
@cached_property
def subscribe(self) -> SubscribeResource:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import SubscribeResource
return SubscribeResource(self)
@cached_property
def compose(self) -> ComposeResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import ComposeResource
return ComposeResource(self)
@cached_property
def drafts(self) -> DraftsResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import DraftsResource
return DraftsResource(self)
@cached_property
def styles(self) -> StylesResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import StylesResource
return StylesResource(self)
@cached_property
def radar(self) -> RadarResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import RadarResource
return RadarResource(self)
@@ -220,28 +218,20 @@ def draws(self) -> DrawsResource:
@cached_property
def webhooks(self) -> WebhooksResource:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import WebhooksResource
return WebhooksResource(self)
- @cached_property
- def integrations(self) -> IntegrationsResource:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import IntegrationsResource
-
- return IntegrationsResource(self)
-
@cached_property
def x(self) -> XResource:
- """X data lookups (subscription required)"""
from .resources.x import XResource
return XResource(self)
@cached_property
def trends(self) -> TrendsResource:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import TrendsResource
return TrendsResource(self)
@@ -254,7 +244,7 @@ def support(self) -> SupportResource:
@cached_property
def credits(self) -> CreditsResource:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import CreditsResource
return CreditsResource(self)
@@ -462,7 +452,7 @@ def __init__(
@cached_property
def account(self) -> AsyncAccountResource:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AsyncAccountResource
return AsyncAccountResource(self)
@@ -476,35 +466,35 @@ def api_keys(self) -> AsyncAPIKeysResource:
@cached_property
def subscribe(self) -> AsyncSubscribeResource:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import AsyncSubscribeResource
return AsyncSubscribeResource(self)
@cached_property
def compose(self) -> AsyncComposeResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import AsyncComposeResource
return AsyncComposeResource(self)
@cached_property
def drafts(self) -> AsyncDraftsResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import AsyncDraftsResource
return AsyncDraftsResource(self)
@cached_property
def styles(self) -> AsyncStylesResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import AsyncStylesResource
return AsyncStylesResource(self)
@cached_property
def radar(self) -> AsyncRadarResource:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import AsyncRadarResource
return AsyncRadarResource(self)
@@ -539,28 +529,20 @@ def draws(self) -> AsyncDrawsResource:
@cached_property
def webhooks(self) -> AsyncWebhooksResource:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import AsyncWebhooksResource
return AsyncWebhooksResource(self)
- @cached_property
- def integrations(self) -> AsyncIntegrationsResource:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import AsyncIntegrationsResource
-
- return AsyncIntegrationsResource(self)
-
@cached_property
def x(self) -> AsyncXResource:
- """X data lookups (subscription required)"""
from .resources.x import AsyncXResource
return AsyncXResource(self)
@cached_property
def trends(self) -> AsyncTrendsResource:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import AsyncTrendsResource
return AsyncTrendsResource(self)
@@ -573,7 +555,7 @@ def support(self) -> AsyncSupportResource:
@cached_property
def credits(self) -> AsyncCreditsResource:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import AsyncCreditsResource
return AsyncCreditsResource(self)
@@ -728,7 +710,7 @@ def __init__(self, client: XTwitterScraper) -> None:
@cached_property
def account(self) -> account.AccountResourceWithRawResponse:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AccountResourceWithRawResponse
return AccountResourceWithRawResponse(self._client.account)
@@ -742,35 +724,35 @@ def api_keys(self) -> api_keys.APIKeysResourceWithRawResponse:
@cached_property
def subscribe(self) -> subscribe.SubscribeResourceWithRawResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import SubscribeResourceWithRawResponse
return SubscribeResourceWithRawResponse(self._client.subscribe)
@cached_property
def compose(self) -> compose.ComposeResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import ComposeResourceWithRawResponse
return ComposeResourceWithRawResponse(self._client.compose)
@cached_property
def drafts(self) -> drafts.DraftsResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import DraftsResourceWithRawResponse
return DraftsResourceWithRawResponse(self._client.drafts)
@cached_property
def styles(self) -> styles.StylesResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import StylesResourceWithRawResponse
return StylesResourceWithRawResponse(self._client.styles)
@cached_property
def radar(self) -> radar.RadarResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import RadarResourceWithRawResponse
return RadarResourceWithRawResponse(self._client.radar)
@@ -805,28 +787,20 @@ def draws(self) -> draws.DrawsResourceWithRawResponse:
@cached_property
def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import WebhooksResourceWithRawResponse
return WebhooksResourceWithRawResponse(self._client.webhooks)
- @cached_property
- def integrations(self) -> integrations.IntegrationsResourceWithRawResponse:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import IntegrationsResourceWithRawResponse
-
- return IntegrationsResourceWithRawResponse(self._client.integrations)
-
@cached_property
def x(self) -> x.XResourceWithRawResponse:
- """X data lookups (subscription required)"""
from .resources.x import XResourceWithRawResponse
return XResourceWithRawResponse(self._client.x)
@cached_property
def trends(self) -> trends.TrendsResourceWithRawResponse:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import TrendsResourceWithRawResponse
return TrendsResourceWithRawResponse(self._client.trends)
@@ -839,7 +813,7 @@ def support(self) -> support.SupportResourceWithRawResponse:
@cached_property
def credits(self) -> credits.CreditsResourceWithRawResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import CreditsResourceWithRawResponse
return CreditsResourceWithRawResponse(self._client.credits)
@@ -853,7 +827,7 @@ def __init__(self, client: AsyncXTwitterScraper) -> None:
@cached_property
def account(self) -> account.AsyncAccountResourceWithRawResponse:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AsyncAccountResourceWithRawResponse
return AsyncAccountResourceWithRawResponse(self._client.account)
@@ -867,35 +841,35 @@ def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithRawResponse:
@cached_property
def subscribe(self) -> subscribe.AsyncSubscribeResourceWithRawResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import AsyncSubscribeResourceWithRawResponse
return AsyncSubscribeResourceWithRawResponse(self._client.subscribe)
@cached_property
def compose(self) -> compose.AsyncComposeResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import AsyncComposeResourceWithRawResponse
return AsyncComposeResourceWithRawResponse(self._client.compose)
@cached_property
def drafts(self) -> drafts.AsyncDraftsResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import AsyncDraftsResourceWithRawResponse
return AsyncDraftsResourceWithRawResponse(self._client.drafts)
@cached_property
def styles(self) -> styles.AsyncStylesResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import AsyncStylesResourceWithRawResponse
return AsyncStylesResourceWithRawResponse(self._client.styles)
@cached_property
def radar(self) -> radar.AsyncRadarResourceWithRawResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import AsyncRadarResourceWithRawResponse
return AsyncRadarResourceWithRawResponse(self._client.radar)
@@ -930,28 +904,20 @@ def draws(self) -> draws.AsyncDrawsResourceWithRawResponse:
@cached_property
def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import AsyncWebhooksResourceWithRawResponse
return AsyncWebhooksResourceWithRawResponse(self._client.webhooks)
- @cached_property
- def integrations(self) -> integrations.AsyncIntegrationsResourceWithRawResponse:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import AsyncIntegrationsResourceWithRawResponse
-
- return AsyncIntegrationsResourceWithRawResponse(self._client.integrations)
-
@cached_property
def x(self) -> x.AsyncXResourceWithRawResponse:
- """X data lookups (subscription required)"""
from .resources.x import AsyncXResourceWithRawResponse
return AsyncXResourceWithRawResponse(self._client.x)
@cached_property
def trends(self) -> trends.AsyncTrendsResourceWithRawResponse:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import AsyncTrendsResourceWithRawResponse
return AsyncTrendsResourceWithRawResponse(self._client.trends)
@@ -964,7 +930,7 @@ def support(self) -> support.AsyncSupportResourceWithRawResponse:
@cached_property
def credits(self) -> credits.AsyncCreditsResourceWithRawResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import AsyncCreditsResourceWithRawResponse
return AsyncCreditsResourceWithRawResponse(self._client.credits)
@@ -978,7 +944,7 @@ def __init__(self, client: XTwitterScraper) -> None:
@cached_property
def account(self) -> account.AccountResourceWithStreamingResponse:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AccountResourceWithStreamingResponse
return AccountResourceWithStreamingResponse(self._client.account)
@@ -992,35 +958,35 @@ def api_keys(self) -> api_keys.APIKeysResourceWithStreamingResponse:
@cached_property
def subscribe(self) -> subscribe.SubscribeResourceWithStreamingResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import SubscribeResourceWithStreamingResponse
return SubscribeResourceWithStreamingResponse(self._client.subscribe)
@cached_property
def compose(self) -> compose.ComposeResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import ComposeResourceWithStreamingResponse
return ComposeResourceWithStreamingResponse(self._client.compose)
@cached_property
def drafts(self) -> drafts.DraftsResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import DraftsResourceWithStreamingResponse
return DraftsResourceWithStreamingResponse(self._client.drafts)
@cached_property
def styles(self) -> styles.StylesResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import StylesResourceWithStreamingResponse
return StylesResourceWithStreamingResponse(self._client.styles)
@cached_property
def radar(self) -> radar.RadarResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import RadarResourceWithStreamingResponse
return RadarResourceWithStreamingResponse(self._client.radar)
@@ -1055,28 +1021,20 @@ def draws(self) -> draws.DrawsResourceWithStreamingResponse:
@cached_property
def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import WebhooksResourceWithStreamingResponse
return WebhooksResourceWithStreamingResponse(self._client.webhooks)
- @cached_property
- def integrations(self) -> integrations.IntegrationsResourceWithStreamingResponse:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import IntegrationsResourceWithStreamingResponse
-
- return IntegrationsResourceWithStreamingResponse(self._client.integrations)
-
@cached_property
def x(self) -> x.XResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
from .resources.x import XResourceWithStreamingResponse
return XResourceWithStreamingResponse(self._client.x)
@cached_property
def trends(self) -> trends.TrendsResourceWithStreamingResponse:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import TrendsResourceWithStreamingResponse
return TrendsResourceWithStreamingResponse(self._client.trends)
@@ -1089,7 +1047,7 @@ def support(self) -> support.SupportResourceWithStreamingResponse:
@cached_property
def credits(self) -> credits.CreditsResourceWithStreamingResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import CreditsResourceWithStreamingResponse
return CreditsResourceWithStreamingResponse(self._client.credits)
@@ -1103,7 +1061,7 @@ def __init__(self, client: AsyncXTwitterScraper) -> None:
@cached_property
def account(self) -> account.AsyncAccountResourceWithStreamingResponse:
- """Account info & settings"""
+ """Account info and settings"""
from .resources.account import AsyncAccountResourceWithStreamingResponse
return AsyncAccountResourceWithStreamingResponse(self._client.account)
@@ -1117,35 +1075,35 @@ def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithStreamingResponse:
@cached_property
def subscribe(self) -> subscribe.AsyncSubscribeResourceWithStreamingResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.subscribe import AsyncSubscribeResourceWithStreamingResponse
return AsyncSubscribeResourceWithStreamingResponse(self._client.subscribe)
@cached_property
def compose(self) -> compose.AsyncComposeResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.compose import AsyncComposeResourceWithStreamingResponse
return AsyncComposeResourceWithStreamingResponse(self._client.compose)
@cached_property
def drafts(self) -> drafts.AsyncDraftsResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.drafts import AsyncDraftsResourceWithStreamingResponse
return AsyncDraftsResourceWithStreamingResponse(self._client.drafts)
@cached_property
def styles(self) -> styles.AsyncStylesResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.styles import AsyncStylesResourceWithStreamingResponse
return AsyncStylesResourceWithStreamingResponse(self._client.styles)
@cached_property
def radar(self) -> radar.AsyncRadarResourceWithStreamingResponse:
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
from .resources.radar import AsyncRadarResourceWithStreamingResponse
return AsyncRadarResourceWithStreamingResponse(self._client.radar)
@@ -1180,28 +1138,20 @@ def draws(self) -> draws.AsyncDrawsResourceWithStreamingResponse:
@cached_property
def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse:
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse
return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks)
- @cached_property
- def integrations(self) -> integrations.AsyncIntegrationsResourceWithStreamingResponse:
- """Push notification integrations (Telegram)"""
- from .resources.integrations import AsyncIntegrationsResourceWithStreamingResponse
-
- return AsyncIntegrationsResourceWithStreamingResponse(self._client.integrations)
-
@cached_property
def x(self) -> x.AsyncXResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
from .resources.x import AsyncXResourceWithStreamingResponse
return AsyncXResourceWithStreamingResponse(self._client.x)
@cached_property
def trends(self) -> trends.AsyncTrendsResourceWithStreamingResponse:
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
from .resources.trends import AsyncTrendsResourceWithStreamingResponse
return AsyncTrendsResourceWithStreamingResponse(self._client.trends)
@@ -1214,7 +1164,7 @@ def support(self) -> support.AsyncSupportResourceWithStreamingResponse:
@cached_property
def credits(self) -> credits.AsyncCreditsResourceWithStreamingResponse:
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
from .resources.credits import AsyncCreditsResourceWithStreamingResponse
return AsyncCreditsResourceWithStreamingResponse(self._client.credits)
diff --git a/src/x_twitter_scraper/_files.py b/src/x_twitter_scraper/_files.py
index c10891e..75258aa 100644
--- a/src/x_twitter_scraper/_files.py
+++ b/src/x_twitter_scraper/_files.py
@@ -3,8 +3,8 @@
import io
import os
import pathlib
-from typing import overload
-from typing_extensions import TypeGuard
+from typing import Sequence, cast, overload
+from typing_extensions import TypeVar, TypeGuard
import anyio
@@ -17,7 +17,9 @@
HttpxFileContent,
HttpxRequestFiles,
)
-from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
+from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
+
+_T = TypeVar("_T")
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
return await anyio.Path(file).read_bytes()
return file
+
+
+def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
+ """Copy only the containers along the given paths.
+
+ Used to guard against mutation by extract_files without copying the entire structure.
+ Only dicts and lists that lie on a path are copied; everything else
+ is returned by reference.
+
+ For example, given paths=[["foo", "files", "file"]] and the structure:
+ {
+ "foo": {
+ "bar": {"baz": {}},
+ "files": {"file": }
+ }
+ }
+ The root dict, "foo", and "files" are copied (they lie on the path).
+ "bar" and "baz" are returned by reference (off the path).
+ """
+ return _deepcopy_with_paths(item, paths, 0)
+
+
+def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
+ if not paths:
+ return item
+ if is_mapping(item):
+ key_to_paths: dict[str, list[Sequence[str]]] = {}
+ for path in paths:
+ if index < len(path):
+ key_to_paths.setdefault(path[index], []).append(path)
+
+ # if no path continues through this mapping, it won't be mutated and copying it is redundant
+ if not key_to_paths:
+ return item
+
+ result = dict(item)
+ for key, subpaths in key_to_paths.items():
+ if key in result:
+ result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
+ return cast(_T, result)
+ if is_list(item):
+ array_paths = [path for path in paths if index < len(path) and path[index] == ""]
+
+ # if no path expects a list here, nothing will be mutated inside it - return by reference
+ if not array_paths:
+ return cast(_T, item)
+ return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
+ return item
diff --git a/src/x_twitter_scraper/_utils/__init__.py b/src/x_twitter_scraper/_utils/__init__.py
index 10cb66d..1c090e5 100644
--- a/src/x_twitter_scraper/_utils/__init__.py
+++ b/src/x_twitter_scraper/_utils/__init__.py
@@ -24,7 +24,6 @@
coerce_integer as coerce_integer,
file_from_path as file_from_path,
strip_not_given as strip_not_given,
- deepcopy_minimal as deepcopy_minimal,
get_async_library as get_async_library,
maybe_coerce_float as maybe_coerce_float,
get_required_header as get_required_header,
diff --git a/src/x_twitter_scraper/_utils/_utils.py b/src/x_twitter_scraper/_utils/_utils.py
index eec7f4a..771859f 100644
--- a/src/x_twitter_scraper/_utils/_utils.py
+++ b/src/x_twitter_scraper/_utils/_utils.py
@@ -86,8 +86,9 @@ def _extract_items(
index += 1
if is_dict(obj):
try:
- # We are at the last entry in the path so we must remove the field
- if (len(path)) == index:
+ # Remove the field if there are no more dict keys in the path,
+ # only "" traversal markers or end.
+ if all(p == "" for p in path[index:]):
item = obj.pop(key)
else:
item = obj[key]
@@ -176,21 +177,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
return isinstance(obj, Iterable)
-def deepcopy_minimal(item: _T) -> _T:
- """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
-
- - mappings, e.g. `dict`
- - list
-
- This is done for performance reasons.
- """
- if is_mapping(item):
- return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
- if is_list(item):
- return cast(_T, [deepcopy_minimal(entry) for entry in item])
- return item
-
-
# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
size = len(seq)
diff --git a/src/x_twitter_scraper/_version.py b/src/x_twitter_scraper/_version.py
index 8f460b4..d424d9e 100644
--- a/src/x_twitter_scraper/_version.py
+++ b/src/x_twitter_scraper/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "x_twitter_scraper"
-__version__ = "0.3.1" # x-release-please-version
+__version__ = "0.4.0" # x-release-please-version
diff --git a/src/x_twitter_scraper/resources/__init__.py b/src/x_twitter_scraper/resources/__init__.py
index 0170526..8df9f13 100644
--- a/src/x_twitter_scraper/resources/__init__.py
+++ b/src/x_twitter_scraper/resources/__init__.py
@@ -128,14 +128,6 @@
ExtractionsResourceWithStreamingResponse,
AsyncExtractionsResourceWithStreamingResponse,
)
-from .integrations import (
- IntegrationsResource,
- AsyncIntegrationsResource,
- IntegrationsResourceWithRawResponse,
- AsyncIntegrationsResourceWithRawResponse,
- IntegrationsResourceWithStreamingResponse,
- AsyncIntegrationsResourceWithStreamingResponse,
-)
__all__ = [
"AccountResource",
@@ -210,12 +202,6 @@
"AsyncWebhooksResourceWithRawResponse",
"WebhooksResourceWithStreamingResponse",
"AsyncWebhooksResourceWithStreamingResponse",
- "IntegrationsResource",
- "AsyncIntegrationsResource",
- "IntegrationsResourceWithRawResponse",
- "AsyncIntegrationsResourceWithRawResponse",
- "IntegrationsResourceWithStreamingResponse",
- "AsyncIntegrationsResourceWithStreamingResponse",
"XResource",
"AsyncXResource",
"XResourceWithRawResponse",
diff --git a/src/x_twitter_scraper/resources/account.py b/src/x_twitter_scraper/resources/account.py
index 7afa743..0739c88 100644
--- a/src/x_twitter_scraper/resources/account.py
+++ b/src/x_twitter_scraper/resources/account.py
@@ -26,7 +26,7 @@
class AccountResource(SyncAPIResource):
- """Account info & settings"""
+ """Account info and settings"""
@cached_property
def with_raw_response(self) -> AccountResourceWithRawResponse:
@@ -61,11 +61,7 @@ def retrieve(
return self._get(
"/account",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountRetrieveResponse,
)
@@ -131,18 +127,14 @@ def update_locale(
"/account",
body=maybe_transform({"locale": locale}, account_update_locale_params.AccountUpdateLocaleParams),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountUpdateLocaleResponse,
)
class AsyncAccountResource(AsyncAPIResource):
- """Account info & settings"""
+ """Account info and settings"""
@cached_property
def with_raw_response(self) -> AsyncAccountResourceWithRawResponse:
@@ -177,11 +169,7 @@ async def retrieve(
return await self._get(
"/account",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountRetrieveResponse,
)
@@ -251,11 +239,7 @@ async def update_locale(
{"locale": locale}, account_update_locale_params.AccountUpdateLocaleParams
),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountUpdateLocaleResponse,
)
diff --git a/src/x_twitter_scraper/resources/api_keys.py b/src/x_twitter_scraper/resources/api_keys.py
index ff312de..a139ac6 100644
--- a/src/x_twitter_scraper/resources/api_keys.py
+++ b/src/x_twitter_scraper/resources/api_keys.py
@@ -72,11 +72,7 @@ def create(
"/api-keys",
body=maybe_transform({"name": name}, api_key_create_params.APIKeyCreateParams),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyCreateResponse,
)
@@ -95,11 +91,7 @@ def list(
return self._get(
"/api-keys",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyListResponse,
)
@@ -132,11 +124,7 @@ def revoke(
return self._delete(
path_template("/api-keys/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyRevokeResponse,
)
@@ -191,11 +179,7 @@ async def create(
"/api-keys",
body=await async_maybe_transform({"name": name}, api_key_create_params.APIKeyCreateParams),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyCreateResponse,
)
@@ -214,11 +198,7 @@ async def list(
return await self._get(
"/api-keys",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyListResponse,
)
@@ -251,11 +231,7 @@ async def revoke(
return await self._delete(
path_template("/api-keys/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=APIKeyRevokeResponse,
)
diff --git a/src/x_twitter_scraper/resources/compose.py b/src/x_twitter_scraper/resources/compose.py
index 99ccaae..89dc8f4 100644
--- a/src/x_twitter_scraper/resources/compose.py
+++ b/src/x_twitter_scraper/resources/compose.py
@@ -24,7 +24,7 @@
class ComposeResource(SyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> ComposeResourceWithRawResponse:
@@ -126,7 +126,7 @@ def create(
class AsyncComposeResource(AsyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> AsyncComposeResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/credits.py b/src/x_twitter_scraper/resources/credits.py
index abf84a4..6ab90c1 100644
--- a/src/x_twitter_scraper/resources/credits.py
+++ b/src/x_twitter_scraper/resources/credits.py
@@ -23,7 +23,7 @@
class CreditsResource(SyncAPIResource):
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
@cached_property
def with_raw_response(self) -> CreditsResourceWithRawResponse:
@@ -99,7 +99,7 @@ def topup_balance(
class AsyncCreditsResource(AsyncAPIResource):
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
@cached_property
def with_raw_response(self) -> AsyncCreditsResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/drafts.py b/src/x_twitter_scraper/resources/drafts.py
index 817da19..6919d1c 100644
--- a/src/x_twitter_scraper/resources/drafts.py
+++ b/src/x_twitter_scraper/resources/drafts.py
@@ -25,7 +25,7 @@
class DraftsResource(SyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> DraftsResourceWithRawResponse:
@@ -202,7 +202,7 @@ def delete(
class AsyncDraftsResource(AsyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> AsyncDraftsResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/events.py b/src/x_twitter_scraper/resources/events.py
index dffcdbb..d0e2a55 100644
--- a/src/x_twitter_scraper/resources/events.py
+++ b/src/x_twitter_scraper/resources/events.py
@@ -128,7 +128,6 @@ def list(
},
event_list_params.EventListParams,
),
- security={"api_key": True},
),
cast_to=EventListResponse,
)
@@ -239,7 +238,6 @@ async def list(
},
event_list_params.EventListParams,
),
- security={"api_key": True},
),
cast_to=EventListResponse,
)
diff --git a/src/x_twitter_scraper/resources/integrations.py b/src/x_twitter_scraper/resources/integrations.py
deleted file mode 100644
index 75b94aa..0000000
--- a/src/x_twitter_scraper/resources/integrations.py
+++ /dev/null
@@ -1,715 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, List
-from typing_extensions import Literal
-
-import httpx
-
-from ..types import integration_create_params, integration_update_params, integration_list_deliveries_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from .._base_client import make_request_options
-from ..types.integration import Integration
-from ..types.shared.event_type import EventType
-from ..types.integration_list_response import IntegrationListResponse
-from ..types.integration_delete_response import IntegrationDeleteResponse
-from ..types.integration_send_test_response import IntegrationSendTestResponse
-from ..types.integration_list_deliveries_response import IntegrationListDeliveriesResponse
-
-__all__ = ["IntegrationsResource", "AsyncIntegrationsResource"]
-
-
-class IntegrationsResource(SyncAPIResource):
- """Push notification integrations (Telegram)"""
-
- @cached_property
- def with_raw_response(self) -> IntegrationsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/Xquik-dev/x-twitter-scraper-python#accessing-raw-response-data-eg-headers
- """
- return IntegrationsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/Xquik-dev/x-twitter-scraper-python#with_streaming_response
- """
- return IntegrationsResourceWithStreamingResponse(self)
-
- def create(
- self,
- *,
- config: integration_create_params.Config,
- event_types: List[EventType],
- name: str,
- type: Literal["telegram"],
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """Create integration
-
- Args:
- config: Integration config (e.g.
-
- Telegram chatId)
-
- event_types: Array of event types to subscribe to.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._post(
- "/integrations",
- body=maybe_transform(
- {
- "config": config,
- "event_types": event_types,
- "name": name,
- "type": type,
- },
- integration_create_params.IntegrationCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """
- Get integration details
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/integrations/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- def update(
- self,
- id: str,
- *,
- event_types: List[EventType] | Omit = omit,
- filters: Dict[str, object] | Omit = omit,
- is_active: bool | Omit = omit,
- message_template: Dict[str, object] | Omit = omit,
- name: str | Omit = omit,
- scope_all_monitors: bool | Omit = omit,
- silent_push: bool | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """
- Update integration
-
- Args:
- event_types: Array of event types to subscribe to.
-
- filters: Event filter rules (JSON)
-
- message_template: Custom message template (JSON)
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._patch(
- path_template("/integrations/{id}", id=id),
- body=maybe_transform(
- {
- "event_types": event_types,
- "filters": filters,
- "is_active": is_active,
- "message_template": message_template,
- "name": name,
- "scope_all_monitors": scope_all_monitors,
- "silent_push": silent_push,
- },
- integration_update_params.IntegrationUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- def list(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationListResponse:
- """List integrations"""
- return self._get(
- "/integrations",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationListResponse,
- )
-
- def delete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationDeleteResponse:
- """
- Delete integration
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._delete(
- path_template("/integrations/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationDeleteResponse,
- )
-
- def list_deliveries(
- self,
- id: str,
- *,
- limit: int | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationListDeliveriesResponse:
- """
- List integration delivery history
-
- Args:
- limit: Maximum number of items to return (1-100, default 50)
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._get(
- path_template("/integrations/{id}/deliveries", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"limit": limit}, integration_list_deliveries_params.IntegrationListDeliveriesParams
- ),
- ),
- cast_to=IntegrationListDeliveriesResponse,
- )
-
- def send_test(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationSendTestResponse:
- """
- Send test delivery
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/integrations/{id}/test", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationSendTestResponse,
- )
-
-
-class AsyncIntegrationsResource(AsyncAPIResource):
- """Push notification integrations (Telegram)"""
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/Xquik-dev/x-twitter-scraper-python#accessing-raw-response-data-eg-headers
- """
- return AsyncIntegrationsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/Xquik-dev/x-twitter-scraper-python#with_streaming_response
- """
- return AsyncIntegrationsResourceWithStreamingResponse(self)
-
- async def create(
- self,
- *,
- config: integration_create_params.Config,
- event_types: List[EventType],
- name: str,
- type: Literal["telegram"],
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """Create integration
-
- Args:
- config: Integration config (e.g.
-
- Telegram chatId)
-
- event_types: Array of event types to subscribe to.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return await self._post(
- "/integrations",
- body=await async_maybe_transform(
- {
- "config": config,
- "event_types": event_types,
- "name": name,
- "type": type,
- },
- integration_create_params.IntegrationCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- async def retrieve(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """
- Get integration details
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/integrations/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- async def update(
- self,
- id: str,
- *,
- event_types: List[EventType] | Omit = omit,
- filters: Dict[str, object] | Omit = omit,
- is_active: bool | Omit = omit,
- message_template: Dict[str, object] | Omit = omit,
- name: str | Omit = omit,
- scope_all_monitors: bool | Omit = omit,
- silent_push: bool | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Integration:
- """
- Update integration
-
- Args:
- event_types: Array of event types to subscribe to.
-
- filters: Event filter rules (JSON)
-
- message_template: Custom message template (JSON)
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._patch(
- path_template("/integrations/{id}", id=id),
- body=await async_maybe_transform(
- {
- "event_types": event_types,
- "filters": filters,
- "is_active": is_active,
- "message_template": message_template,
- "name": name,
- "scope_all_monitors": scope_all_monitors,
- "silent_push": silent_push,
- },
- integration_update_params.IntegrationUpdateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Integration,
- )
-
- async def list(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationListResponse:
- """List integrations"""
- return await self._get(
- "/integrations",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationListResponse,
- )
-
- async def delete(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationDeleteResponse:
- """
- Delete integration
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._delete(
- path_template("/integrations/{id}", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationDeleteResponse,
- )
-
- async def list_deliveries(
- self,
- id: str,
- *,
- limit: int | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationListDeliveriesResponse:
- """
- List integration delivery history
-
- Args:
- limit: Maximum number of items to return (1-100, default 50)
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._get(
- path_template("/integrations/{id}/deliveries", id=id),
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"limit": limit}, integration_list_deliveries_params.IntegrationListDeliveriesParams
- ),
- ),
- cast_to=IntegrationListDeliveriesResponse,
- )
-
- async def send_test(
- self,
- id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> IntegrationSendTestResponse:
- """
- Send test delivery
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/integrations/{id}/test", id=id),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=IntegrationSendTestResponse,
- )
-
-
-class IntegrationsResourceWithRawResponse:
- def __init__(self, integrations: IntegrationsResource) -> None:
- self._integrations = integrations
-
- self.create = to_raw_response_wrapper(
- integrations.create,
- )
- self.retrieve = to_raw_response_wrapper(
- integrations.retrieve,
- )
- self.update = to_raw_response_wrapper(
- integrations.update,
- )
- self.list = to_raw_response_wrapper(
- integrations.list,
- )
- self.delete = to_raw_response_wrapper(
- integrations.delete,
- )
- self.list_deliveries = to_raw_response_wrapper(
- integrations.list_deliveries,
- )
- self.send_test = to_raw_response_wrapper(
- integrations.send_test,
- )
-
-
-class AsyncIntegrationsResourceWithRawResponse:
- def __init__(self, integrations: AsyncIntegrationsResource) -> None:
- self._integrations = integrations
-
- self.create = async_to_raw_response_wrapper(
- integrations.create,
- )
- self.retrieve = async_to_raw_response_wrapper(
- integrations.retrieve,
- )
- self.update = async_to_raw_response_wrapper(
- integrations.update,
- )
- self.list = async_to_raw_response_wrapper(
- integrations.list,
- )
- self.delete = async_to_raw_response_wrapper(
- integrations.delete,
- )
- self.list_deliveries = async_to_raw_response_wrapper(
- integrations.list_deliveries,
- )
- self.send_test = async_to_raw_response_wrapper(
- integrations.send_test,
- )
-
-
-class IntegrationsResourceWithStreamingResponse:
- def __init__(self, integrations: IntegrationsResource) -> None:
- self._integrations = integrations
-
- self.create = to_streamed_response_wrapper(
- integrations.create,
- )
- self.retrieve = to_streamed_response_wrapper(
- integrations.retrieve,
- )
- self.update = to_streamed_response_wrapper(
- integrations.update,
- )
- self.list = to_streamed_response_wrapper(
- integrations.list,
- )
- self.delete = to_streamed_response_wrapper(
- integrations.delete,
- )
- self.list_deliveries = to_streamed_response_wrapper(
- integrations.list_deliveries,
- )
- self.send_test = to_streamed_response_wrapper(
- integrations.send_test,
- )
-
-
-class AsyncIntegrationsResourceWithStreamingResponse:
- def __init__(self, integrations: AsyncIntegrationsResource) -> None:
- self._integrations = integrations
-
- self.create = async_to_streamed_response_wrapper(
- integrations.create,
- )
- self.retrieve = async_to_streamed_response_wrapper(
- integrations.retrieve,
- )
- self.update = async_to_streamed_response_wrapper(
- integrations.update,
- )
- self.list = async_to_streamed_response_wrapper(
- integrations.list,
- )
- self.delete = async_to_streamed_response_wrapper(
- integrations.delete,
- )
- self.list_deliveries = async_to_streamed_response_wrapper(
- integrations.list_deliveries,
- )
- self.send_test = async_to_streamed_response_wrapper(
- integrations.send_test,
- )
diff --git a/src/x_twitter_scraper/resources/radar.py b/src/x_twitter_scraper/resources/radar.py
index d0c03ad..904cd41 100644
--- a/src/x_twitter_scraper/resources/radar.py
+++ b/src/x_twitter_scraper/resources/radar.py
@@ -24,7 +24,7 @@
class RadarResource(SyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> RadarResourceWithRawResponse:
@@ -48,9 +48,11 @@ def with_streaming_response(self) -> RadarResourceWithStreamingResponse:
def retrieve_trending_topics(
self,
*,
- category: str | Omit = omit,
- count: int | Omit = omit,
+ after: str | Omit = omit,
+ category: Literal["general", "tech", "dev", "science", "culture", "politics", "business", "entertainment"]
+ | Omit = omit,
hours: int | Omit = omit,
+ limit: int | Omit = omit,
region: str | Omit = omit,
source: Literal["github", "google_trends", "hacker_news", "polymarket", "reddit", "trustmrr", "wikipedia"]
| Omit = omit,
@@ -65,11 +67,13 @@ def retrieve_trending_topics(
Get trending topics from curated sources
Args:
- category: Filter by category (general, tech, dev, etc.)
+ after: Cursor for pagination (from prior response nextCursor).
+
+ category: Filter by category.
- count: Number of items to return
+ hours: Lookback window in hours (1-168, default 24).
- hours: Lookback window in hours
+ limit: Number of items to return (1-100, default 50).
region: Region filter (us, global, etc.)
@@ -93,9 +97,10 @@ def retrieve_trending_topics(
timeout=timeout,
query=maybe_transform(
{
+ "after": after,
"category": category,
- "count": count,
"hours": hours,
+ "limit": limit,
"region": region,
"source": source,
},
@@ -107,7 +112,7 @@ def retrieve_trending_topics(
class AsyncRadarResource(AsyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> AsyncRadarResourceWithRawResponse:
@@ -131,9 +136,11 @@ def with_streaming_response(self) -> AsyncRadarResourceWithStreamingResponse:
async def retrieve_trending_topics(
self,
*,
- category: str | Omit = omit,
- count: int | Omit = omit,
+ after: str | Omit = omit,
+ category: Literal["general", "tech", "dev", "science", "culture", "politics", "business", "entertainment"]
+ | Omit = omit,
hours: int | Omit = omit,
+ limit: int | Omit = omit,
region: str | Omit = omit,
source: Literal["github", "google_trends", "hacker_news", "polymarket", "reddit", "trustmrr", "wikipedia"]
| Omit = omit,
@@ -148,11 +155,13 @@ async def retrieve_trending_topics(
Get trending topics from curated sources
Args:
- category: Filter by category (general, tech, dev, etc.)
+ after: Cursor for pagination (from prior response nextCursor).
+
+ category: Filter by category.
- count: Number of items to return
+ hours: Lookback window in hours (1-168, default 24).
- hours: Lookback window in hours
+ limit: Number of items to return (1-100, default 50).
region: Region filter (us, global, etc.)
@@ -176,9 +185,10 @@ async def retrieve_trending_topics(
timeout=timeout,
query=await async_maybe_transform(
{
+ "after": after,
"category": category,
- "count": count,
"hours": hours,
+ "limit": limit,
"region": region,
"source": source,
},
diff --git a/src/x_twitter_scraper/resources/styles.py b/src/x_twitter_scraper/resources/styles.py
index ab4fe05..ebee277 100644
--- a/src/x_twitter_scraper/resources/styles.py
+++ b/src/x_twitter_scraper/resources/styles.py
@@ -27,7 +27,7 @@
class StylesResource(SyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> StylesResourceWithRawResponse:
@@ -295,7 +295,7 @@ def get_performance(
class AsyncStylesResource(AsyncAPIResource):
- """Tweet composition, drafts, writing styles & radar"""
+ """AI tweet composition, drafts, writing styles, and radar"""
@cached_property
def with_raw_response(self) -> AsyncStylesResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/subscribe.py b/src/x_twitter_scraper/resources/subscribe.py
index fe74d90..f5c716c 100644
--- a/src/x_twitter_scraper/resources/subscribe.py
+++ b/src/x_twitter_scraper/resources/subscribe.py
@@ -20,7 +20,7 @@
class SubscribeResource(SyncAPIResource):
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
@cached_property
def with_raw_response(self) -> SubscribeResourceWithRawResponse:
@@ -62,7 +62,7 @@ def create(
class AsyncSubscribeResource(AsyncAPIResource):
- """Subscription & billing"""
+ """Subscription, billing, and credits"""
@cached_property
def with_raw_response(self) -> AsyncSubscribeResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/trends.py b/src/x_twitter_scraper/resources/trends.py
index eee4338..5febbe2 100644
--- a/src/x_twitter_scraper/resources/trends.py
+++ b/src/x_twitter_scraper/resources/trends.py
@@ -22,7 +22,7 @@
class TrendsResource(SyncAPIResource):
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
@cached_property
def with_raw_response(self) -> TrendsResourceWithRawResponse:
@@ -56,7 +56,7 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TrendListResponse:
"""
- Get regional trending topics
+ Get trending hashtags and topics by region (alias)
Args:
count: Number of trending topics to return (1-50, default 30)
@@ -91,7 +91,7 @@ def list(
class AsyncTrendsResource(AsyncAPIResource):
- """Trending topics by region"""
+ """Trending topics and hashtags by region"""
@cached_property
def with_raw_response(self) -> AsyncTrendsResourceWithRawResponse:
@@ -125,7 +125,7 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TrendListResponse:
"""
- Get regional trending topics
+ Get trending hashtags and topics by region (alias)
Args:
count: Number of trending topics to return (1-50, default 30)
diff --git a/src/x_twitter_scraper/resources/webhooks.py b/src/x_twitter_scraper/resources/webhooks.py
index 8d818b9..956941c 100644
--- a/src/x_twitter_scraper/resources/webhooks.py
+++ b/src/x_twitter_scraper/resources/webhooks.py
@@ -30,7 +30,7 @@
class WebhooksResource(SyncAPIResource):
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
@cached_property
def with_raw_response(self) -> WebhooksResourceWithRawResponse:
@@ -260,7 +260,7 @@ def test(
class AsyncWebhooksResource(AsyncAPIResource):
- """Webhook endpoint management & delivery"""
+ """Webhook endpoint management and delivery"""
@cached_property
def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/x/accounts.py b/src/x_twitter_scraper/resources/x/accounts.py
index 0561667..974b964 100644
--- a/src/x_twitter_scraper/resources/x/accounts.py
+++ b/src/x_twitter_scraper/resources/x/accounts.py
@@ -98,11 +98,7 @@ def create(
account_create_params.AccountCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountCreateResponse,
)
@@ -135,11 +131,7 @@ def retrieve(
return self._get(
path_template("/x/accounts/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=XAccountDetail,
)
@@ -158,11 +150,7 @@ def list(
return self._get(
"/x/accounts",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountListResponse,
)
@@ -195,11 +183,7 @@ def delete(
return self._delete(
path_template("/x/accounts/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountDeleteResponse,
)
@@ -221,11 +205,7 @@ def bulk_retry(
return self._post(
"/x/accounts/bulk-retry",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountBulkRetryResponse,
)
@@ -235,6 +215,8 @@ def reauth(
id: str,
*,
password: str,
+ email: str | Omit = omit,
+ proxy_country: str | Omit = omit,
totp_secret: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -249,6 +231,10 @@ def reauth(
Args:
password: Updated account password
+ email: Email for the X account (updates stored email)
+
+ proxy_country: Two-letter country code for login proxy region
+
totp_secret: TOTP secret for 2FA re-authentication
extra_headers: Send extra headers
@@ -266,16 +252,14 @@ def reauth(
body=maybe_transform(
{
"password": password,
+ "email": email,
+ "proxy_country": proxy_country,
"totp_secret": totp_secret,
},
account_reauth_params.AccountReauthParams,
),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountReauthResponse,
)
@@ -353,11 +337,7 @@ async def create(
account_create_params.AccountCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountCreateResponse,
)
@@ -390,11 +370,7 @@ async def retrieve(
return await self._get(
path_template("/x/accounts/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=XAccountDetail,
)
@@ -413,11 +389,7 @@ async def list(
return await self._get(
"/x/accounts",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountListResponse,
)
@@ -450,11 +422,7 @@ async def delete(
return await self._delete(
path_template("/x/accounts/{id}", id=id),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountDeleteResponse,
)
@@ -476,11 +444,7 @@ async def bulk_retry(
return await self._post(
"/x/accounts/bulk-retry",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountBulkRetryResponse,
)
@@ -490,6 +454,8 @@ async def reauth(
id: str,
*,
password: str,
+ email: str | Omit = omit,
+ proxy_country: str | Omit = omit,
totp_secret: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -504,6 +470,10 @@ async def reauth(
Args:
password: Updated account password
+ email: Email for the X account (updates stored email)
+
+ proxy_country: Two-letter country code for login proxy region
+
totp_secret: TOTP secret for 2FA re-authentication
extra_headers: Send extra headers
@@ -521,16 +491,14 @@ async def reauth(
body=await async_maybe_transform(
{
"password": password,
+ "email": email,
+ "proxy_country": proxy_country,
"totp_secret": totp_secret,
},
account_reauth_params.AccountReauthParams,
),
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- security={"api_key": True},
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AccountReauthResponse,
)
diff --git a/src/x_twitter_scraper/resources/x/bookmarks.py b/src/x_twitter_scraper/resources/x/bookmarks.py
index 7de2f2a..5dd4c23 100644
--- a/src/x_twitter_scraper/resources/x/bookmarks.py
+++ b/src/x_twitter_scraper/resources/x/bookmarks.py
@@ -23,7 +23,7 @@
class BookmarksResource(SyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
@cached_property
def with_raw_response(self) -> BookmarksResourceWithRawResponse:
@@ -111,7 +111,7 @@ def retrieve_folders(
class AsyncBookmarksResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
@cached_property
def with_raw_response(self) -> AsyncBookmarksResourceWithRawResponse:
diff --git a/src/x_twitter_scraper/resources/x/communities/communities.py b/src/x_twitter_scraper/resources/x/communities/communities.py
index 6ff5d37..79f91af 100644
--- a/src/x_twitter_scraper/resources/x/communities/communities.py
+++ b/src/x_twitter_scraper/resources/x/communities/communities.py
@@ -55,7 +55,7 @@ def join(self) -> JoinResource:
@cached_property
def tweets(self) -> TweetsResource:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return TweetsResource(self._client)
@cached_property
@@ -182,7 +182,7 @@ def retrieve_info(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CommunityRetrieveInfoResponse:
"""
- Get community details
+ Get community name, description and member count
Args:
extra_headers: Send extra headers
@@ -216,7 +216,7 @@ def retrieve_members(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get community members
+ List members of a community
Args:
cursor: Pagination cursor
@@ -258,7 +258,7 @@ def retrieve_moderators(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get community moderators
+ List moderators of a community
Args:
cursor: Pagination cursor for community moderators
@@ -301,7 +301,7 @@ def retrieve_search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets across communities
+ Search for communities by keyword
Args:
q: Search query
@@ -346,7 +346,7 @@ def join(self) -> AsyncJoinResource:
@cached_property
def tweets(self) -> AsyncTweetsResource:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return AsyncTweetsResource(self._client)
@cached_property
@@ -473,7 +473,7 @@ async def retrieve_info(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CommunityRetrieveInfoResponse:
"""
- Get community details
+ Get community name, description and member count
Args:
extra_headers: Send extra headers
@@ -507,7 +507,7 @@ async def retrieve_members(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get community members
+ List members of a community
Args:
cursor: Pagination cursor
@@ -549,7 +549,7 @@ async def retrieve_moderators(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get community moderators
+ List moderators of a community
Args:
cursor: Pagination cursor for community moderators
@@ -592,7 +592,7 @@ async def retrieve_search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets across communities
+ Search for communities by keyword
Args:
q: Search query
@@ -659,7 +659,7 @@ def join(self) -> JoinResourceWithRawResponse:
@cached_property
def tweets(self) -> TweetsResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return TweetsResourceWithRawResponse(self._communities.tweets)
@@ -693,7 +693,7 @@ def join(self) -> AsyncJoinResourceWithRawResponse:
@cached_property
def tweets(self) -> AsyncTweetsResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return AsyncTweetsResourceWithRawResponse(self._communities.tweets)
@@ -727,7 +727,7 @@ def join(self) -> JoinResourceWithStreamingResponse:
@cached_property
def tweets(self) -> TweetsResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return TweetsResourceWithStreamingResponse(self._communities.tweets)
@@ -761,5 +761,5 @@ def join(self) -> AsyncJoinResourceWithStreamingResponse:
@cached_property
def tweets(self) -> AsyncTweetsResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
return AsyncTweetsResourceWithStreamingResponse(self._communities.tweets)
diff --git a/src/x_twitter_scraper/resources/x/communities/tweets.py b/src/x_twitter_scraper/resources/x/communities/tweets.py
index 3d0a88f..96fd4cf 100644
--- a/src/x_twitter_scraper/resources/x/communities/tweets.py
+++ b/src/x_twitter_scraper/resources/x/communities/tweets.py
@@ -22,7 +22,7 @@
class TweetsResource(SyncAPIResource):
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
@cached_property
def with_raw_response(self) -> TweetsResourceWithRawResponse:
@@ -57,7 +57,7 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets across all communities
+ List tweets across all communities
Args:
q: Search query for cross-community tweets
@@ -106,7 +106,7 @@ def list_by_community(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get community tweets
+ List tweets posted in a community
Args:
cursor: Pagination cursor for community tweets
@@ -135,7 +135,7 @@ def list_by_community(
class AsyncTweetsResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
+ """X Community info, members, and tweets"""
@cached_property
def with_raw_response(self) -> AsyncTweetsResourceWithRawResponse:
@@ -170,7 +170,7 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets across all communities
+ List tweets across all communities
Args:
q: Search query for cross-community tweets
@@ -219,7 +219,7 @@ async def list_by_community(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get community tweets
+ List tweets posted in a community
Args:
cursor: Pagination cursor for community tweets
diff --git a/src/x_twitter_scraper/resources/x/followers.py b/src/x_twitter_scraper/resources/x/followers.py
index f8412e3..76edcb7 100644
--- a/src/x_twitter_scraper/resources/x/followers.py
+++ b/src/x_twitter_scraper/resources/x/followers.py
@@ -22,7 +22,7 @@
class FollowersResource(SyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
@cached_property
def with_raw_response(self) -> FollowersResourceWithRawResponse:
@@ -56,7 +56,7 @@ def check(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FollowerCheckResponse:
"""
- Check follow relationship
+ Check if one user follows another
Args:
source: Username to check (without @)
@@ -91,7 +91,7 @@ def check(
class AsyncFollowersResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
@cached_property
def with_raw_response(self) -> AsyncFollowersResourceWithRawResponse:
@@ -125,7 +125,7 @@ async def check(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FollowerCheckResponse:
"""
- Check follow relationship
+ Check if one user follows another
Args:
source: Username to check (without @)
diff --git a/src/x_twitter_scraper/resources/x/lists.py b/src/x_twitter_scraper/resources/x/lists.py
index a4da5fb..a98e6b3 100644
--- a/src/x_twitter_scraper/resources/x/lists.py
+++ b/src/x_twitter_scraper/resources/x/lists.py
@@ -23,7 +23,7 @@
class ListsResource(SyncAPIResource):
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
@cached_property
def with_raw_response(self) -> ListsResourceWithRawResponse:
@@ -57,7 +57,7 @@ def retrieve_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get list followers
+ List followers of an X List
Args:
cursor: Pagination cursor for list followers
@@ -97,7 +97,7 @@ def retrieve_members(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get list members
+ List members of an X List
Args:
cursor: Pagination cursor for list members
@@ -140,7 +140,7 @@ def retrieve_tweets(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get list tweets
+ List tweets from an X List
Args:
cursor: Pagination cursor for list tweets
@@ -183,7 +183,7 @@ def retrieve_tweets(
class AsyncListsResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
@cached_property
def with_raw_response(self) -> AsyncListsResourceWithRawResponse:
@@ -217,7 +217,7 @@ async def retrieve_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get list followers
+ List followers of an X List
Args:
cursor: Pagination cursor for list followers
@@ -259,7 +259,7 @@ async def retrieve_members(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get list members
+ List members of an X List
Args:
cursor: Pagination cursor for list members
@@ -304,7 +304,7 @@ async def retrieve_tweets(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get list tweets
+ List tweets from an X List
Args:
cursor: Pagination cursor for list tweets
diff --git a/src/x_twitter_scraper/resources/x/media.py b/src/x_twitter_scraper/resources/x/media.py
index cb084bd..18c2db5 100644
--- a/src/x_twitter_scraper/resources/x/media.py
+++ b/src/x_twitter_scraper/resources/x/media.py
@@ -6,6 +6,7 @@
import httpx
+from ..._files import deepcopy_with_paths
from ..._types import (
Body,
Omit,
@@ -17,7 +18,7 @@
omit,
not_given,
)
-from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
+from ..._utils import extract_files, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ...types.x import media_upload_params, media_download_params
from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -35,7 +36,7 @@
class MediaResource(SyncAPIResource):
- """Media upload & download"""
+ """Media upload and download"""
@cached_property
def with_raw_response(self) -> MediaResourceWithRawResponse:
@@ -69,7 +70,7 @@ def download(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MediaDownloadResponse:
"""
- Download tweet media
+ Download images and videos from tweets
Args:
tweet_ids: Array of tweet URLs or IDs (bulk, max 50)
@@ -128,12 +129,13 @@ def upload(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
"is_long_video": is_long_video,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
@@ -152,7 +154,7 @@ def upload(
class AsyncMediaResource(AsyncAPIResource):
- """Media upload & download"""
+ """Media upload and download"""
@cached_property
def with_raw_response(self) -> AsyncMediaResourceWithRawResponse:
@@ -186,7 +188,7 @@ async def download(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MediaDownloadResponse:
"""
- Download tweet media
+ Download images and videos from tweets
Args:
tweet_ids: Array of tweet URLs or IDs (bulk, max 50)
@@ -245,12 +247,13 @@ async def upload(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
"is_long_video": is_long_video,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
diff --git a/src/x_twitter_scraper/resources/x/profile.py b/src/x_twitter_scraper/resources/x/profile.py
index bdabc5e..375cc98 100644
--- a/src/x_twitter_scraper/resources/x/profile.py
+++ b/src/x_twitter_scraper/resources/x/profile.py
@@ -6,8 +6,9 @@
import httpx
+from ..._files import deepcopy_with_paths
from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
-from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
+from ..._utils import extract_files, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ...types.x import profile_update_params, profile_update_avatar_params, profile_update_banner_params
from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -128,11 +129,12 @@ def update_avatar(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
@@ -177,11 +179,12 @@ def update_banner(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
@@ -302,11 +305,12 @@ async def update_avatar(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
@@ -351,11 +355,12 @@ async def update_banner(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = deepcopy_minimal(
+ body = deepcopy_with_paths(
{
"account": account,
"file": file,
- }
+ },
+ [["file"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
# It should be noted that the actual Content-Type header that will be
diff --git a/src/x_twitter_scraper/resources/x/tweets/tweets.py b/src/x_twitter_scraper/resources/x/tweets/tweets.py
index 9c10dbe..33e62f0 100644
--- a/src/x_twitter_scraper/resources/x/tweets/tweets.py
+++ b/src/x_twitter_scraper/resources/x/tweets/tweets.py
@@ -87,12 +87,13 @@ def create(
self,
*,
account: str,
- text: str,
attachment_url: str | Omit = omit,
community_id: str | Omit = omit,
is_note_tweet: bool | Omit = omit,
+ media: SequenceNotStr[str] | Omit = omit,
media_ids: SequenceNotStr[str] | Omit = omit,
reply_to_tweet_id: str | Omit = omit,
+ text: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -106,6 +107,12 @@ def create(
Args:
account: X account (@username or account ID)
+ media: Array of media URLs to attach (mutually exclusive with media_ids)
+
+ media_ids: Array of media IDs to attach (mutually exclusive with media)
+
+ text: Tweet text (optional when media is provided)
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -119,12 +126,13 @@ def create(
body=maybe_transform(
{
"account": account,
- "text": text,
"attachment_url": attachment_url,
"community_id": community_id,
"is_note_tweet": is_note_tweet,
+ "media": media,
"media_ids": media_ids,
"reply_to_tweet_id": reply_to_tweet_id,
+ "text": text,
},
tweet_create_params.TweetCreateParams,
),
@@ -146,7 +154,7 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TweetRetrieveResponse:
"""
- Look up tweet
+ Get tweet with full text, author, metrics and media
Args:
extra_headers: Send extra headers
@@ -254,7 +262,7 @@ def get_favoriters(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users who liked a tweet
+ List users who liked a tweet
Args:
cursor: Pagination cursor for favoriters
@@ -297,7 +305,7 @@ def get_quotes(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get quote tweets of a tweet
+ List quote tweets of a tweet
Args:
cursor: Pagination cursor for quote tweets
@@ -353,7 +361,7 @@ def get_replies(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get replies to a tweet
+ List replies to a tweet
Args:
cursor: Pagination cursor for tweet replies
@@ -404,7 +412,7 @@ def get_retweeters(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users who retweeted a tweet
+ List users who retweeted a tweet
Args:
cursor: Pagination cursor for retweeters
@@ -444,7 +452,7 @@ def get_thread(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get thread context for a tweet
+ Get full conversation thread for a tweet
Args:
cursor: Pagination cursor for thread tweets
@@ -488,7 +496,7 @@ def search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets
+ Search tweets with X query operators and pagination
Args:
q: Search query (keywords,
@@ -568,12 +576,13 @@ async def create(
self,
*,
account: str,
- text: str,
attachment_url: str | Omit = omit,
community_id: str | Omit = omit,
is_note_tweet: bool | Omit = omit,
+ media: SequenceNotStr[str] | Omit = omit,
media_ids: SequenceNotStr[str] | Omit = omit,
reply_to_tweet_id: str | Omit = omit,
+ text: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -587,6 +596,12 @@ async def create(
Args:
account: X account (@username or account ID)
+ media: Array of media URLs to attach (mutually exclusive with media_ids)
+
+ media_ids: Array of media IDs to attach (mutually exclusive with media)
+
+ text: Tweet text (optional when media is provided)
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -600,12 +615,13 @@ async def create(
body=await async_maybe_transform(
{
"account": account,
- "text": text,
"attachment_url": attachment_url,
"community_id": community_id,
"is_note_tweet": is_note_tweet,
+ "media": media,
"media_ids": media_ids,
"reply_to_tweet_id": reply_to_tweet_id,
+ "text": text,
},
tweet_create_params.TweetCreateParams,
),
@@ -627,7 +643,7 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TweetRetrieveResponse:
"""
- Look up tweet
+ Get tweet with full text, author, metrics and media
Args:
extra_headers: Send extra headers
@@ -735,7 +751,7 @@ async def get_favoriters(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users who liked a tweet
+ List users who liked a tweet
Args:
cursor: Pagination cursor for favoriters
@@ -780,7 +796,7 @@ async def get_quotes(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get quote tweets of a tweet
+ List quote tweets of a tweet
Args:
cursor: Pagination cursor for quote tweets
@@ -836,7 +852,7 @@ async def get_replies(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get replies to a tweet
+ List replies to a tweet
Args:
cursor: Pagination cursor for tweet replies
@@ -887,7 +903,7 @@ async def get_retweeters(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users who retweeted a tweet
+ List users who retweeted a tweet
Args:
cursor: Pagination cursor for retweeters
@@ -929,7 +945,7 @@ async def get_thread(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get thread context for a tweet
+ Get full conversation thread for a tweet
Args:
cursor: Pagination cursor for thread tweets
@@ -973,7 +989,7 @@ async def search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Search tweets
+ Search tweets with X query operators and pagination
Args:
q: Search query (keywords,
diff --git a/src/x_twitter_scraper/resources/x/users/users.py b/src/x_twitter_scraper/resources/x/users/users.py
index 3fa63e2..5dfd741 100644
--- a/src/x_twitter_scraper/resources/x/users/users.py
+++ b/src/x_twitter_scraper/resources/x/users/users.py
@@ -35,7 +35,7 @@
async_to_streamed_response_wrapper,
)
from ...._base_client import make_request_options
-from ....types.x.user_profile import UserProfile
+from ....types.shared.user_profile import UserProfile
from ....types.shared.paginated_users import PaginatedUsers
from ....types.shared.paginated_tweets import PaginatedTweets
@@ -43,7 +43,7 @@
class UsersResource(SyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
@cached_property
def follow(self) -> FollowResource:
@@ -81,7 +81,7 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> UserProfile:
"""
- Look up X user
+ Get user profile with follower counts and verification
Args:
extra_headers: Send extra headers
@@ -114,7 +114,7 @@ def retrieve_batch(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get multiple users by IDs
+ Look up multiple users by IDs in one call
Args:
ids: Comma-separated user IDs (max 100)
@@ -153,7 +153,7 @@ def retrieve_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get user followers
+ List followers of a user
Args:
cursor: Pagination cursor for followers list
@@ -201,7 +201,7 @@ def retrieve_followers_you_know(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get followers you know for a user
+ List mutual followers between you and a user
Args:
cursor: Pagination cursor for followers-you-know
@@ -244,7 +244,7 @@ def retrieve_following(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users this user follows
+ List accounts a user follows
Args:
cursor: Pagination cursor for following list
@@ -292,7 +292,7 @@ def retrieve_likes(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get tweets liked by a user
+ List tweets liked by a user
Args:
cursor: Pagination cursor for liked tweets
@@ -332,7 +332,7 @@ def retrieve_media(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get media tweets by a user
+ List media tweets posted by a user
Args:
cursor: Pagination cursor for media tweets
@@ -374,7 +374,7 @@ def retrieve_mentions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get tweets mentioning a user
+ List tweets mentioning a user
Args:
cursor: Pagination cursor for mentions
@@ -473,7 +473,7 @@ def retrieve_tweets(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get recent tweets by a user
+ List recent tweets posted by a user
Args:
cursor: Pagination cursor for user tweets
@@ -524,7 +524,7 @@ def retrieve_verified_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get verified followers
+ List verified followers of a user
Args:
cursor: Pagination cursor for verified followers
@@ -555,7 +555,7 @@ def retrieve_verified_followers(
class AsyncUsersResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
@cached_property
def follow(self) -> AsyncFollowResource:
@@ -593,7 +593,7 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> UserProfile:
"""
- Look up X user
+ Get user profile with follower counts and verification
Args:
extra_headers: Send extra headers
@@ -626,7 +626,7 @@ async def retrieve_batch(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get multiple users by IDs
+ Look up multiple users by IDs in one call
Args:
ids: Comma-separated user IDs (max 100)
@@ -665,7 +665,7 @@ async def retrieve_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get user followers
+ List followers of a user
Args:
cursor: Pagination cursor for followers list
@@ -713,7 +713,7 @@ async def retrieve_followers_you_know(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get followers you know for a user
+ List mutual followers between you and a user
Args:
cursor: Pagination cursor for followers-you-know
@@ -756,7 +756,7 @@ async def retrieve_following(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get users this user follows
+ List accounts a user follows
Args:
cursor: Pagination cursor for following list
@@ -804,7 +804,7 @@ async def retrieve_likes(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get tweets liked by a user
+ List tweets liked by a user
Args:
cursor: Pagination cursor for liked tweets
@@ -846,7 +846,7 @@ async def retrieve_media(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get media tweets by a user
+ List media tweets posted by a user
Args:
cursor: Pagination cursor for media tweets
@@ -890,7 +890,7 @@ async def retrieve_mentions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get tweets mentioning a user
+ List tweets mentioning a user
Args:
cursor: Pagination cursor for mentions
@@ -989,7 +989,7 @@ async def retrieve_tweets(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedTweets:
"""
- Get recent tweets by a user
+ List recent tweets posted by a user
Args:
cursor: Pagination cursor for user tweets
@@ -1040,7 +1040,7 @@ async def retrieve_verified_followers(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PaginatedUsers:
"""
- Get verified followers
+ List verified followers of a user
Args:
cursor: Pagination cursor for verified followers
diff --git a/src/x_twitter_scraper/resources/x/x.py b/src/x_twitter_scraper/resources/x/x.py
index 51c5014..eca4791 100644
--- a/src/x_twitter_scraper/resources/x/x.py
+++ b/src/x_twitter_scraper/resources/x/x.py
@@ -30,7 +30,7 @@
MediaResourceWithStreamingResponse,
AsyncMediaResourceWithStreamingResponse,
)
-from ...types import x_get_home_timeline_params, x_get_notifications_params
+from ...types import x_get_trends_params, x_get_home_timeline_params, x_get_notifications_params
from .profile import (
ProfileResource,
AsyncProfileResource,
@@ -107,20 +107,18 @@
class XResource(SyncAPIResource):
- """X data lookups (subscription required)"""
-
@cached_property
def tweets(self) -> TweetsResource:
return TweetsResource(self._client)
@cached_property
def users(self) -> UsersResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return UsersResource(self._client)
@cached_property
def followers(self) -> FollowersResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return FollowersResource(self._client)
@cached_property
@@ -129,7 +127,7 @@ def dm(self) -> DmResource:
@cached_property
def media(self) -> MediaResource:
- """Media upload & download"""
+ """Media upload and download"""
return MediaResource(self._client)
@cached_property
@@ -148,12 +146,12 @@ def accounts(self) -> AccountsResource:
@cached_property
def bookmarks(self) -> BookmarksResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return BookmarksResource(self._client)
@cached_property
def lists(self) -> ListsResource:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return ListsResource(self._client)
@cached_property
@@ -303,6 +301,8 @@ def get_notifications(
def get_trends(
self,
*,
+ count: int | Omit = omit,
+ woeid: int | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -310,31 +310,54 @@ def get_trends(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> XGetTrendsResponse:
- """Get trending topics"""
+ """
+ Get trending hashtags and topics from X by region
+
+ Args:
+ count: Number of trending topics to return (1-50, default 30)
+
+ woeid: Region WOEID (1=Worldwide, 23424977=US, 23424975=UK, 23424969=Turkey)
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
return self._get(
"/x/trends",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "count": count,
+ "woeid": woeid,
+ },
+ x_get_trends_params.XGetTrendsParams,
+ ),
),
cast_to=XGetTrendsResponse,
)
class AsyncXResource(AsyncAPIResource):
- """X data lookups (subscription required)"""
-
@cached_property
def tweets(self) -> AsyncTweetsResource:
return AsyncTweetsResource(self._client)
@cached_property
def users(self) -> AsyncUsersResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncUsersResource(self._client)
@cached_property
def followers(self) -> AsyncFollowersResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncFollowersResource(self._client)
@cached_property
@@ -343,7 +366,7 @@ def dm(self) -> AsyncDmResource:
@cached_property
def media(self) -> AsyncMediaResource:
- """Media upload & download"""
+ """Media upload and download"""
return AsyncMediaResource(self._client)
@cached_property
@@ -362,12 +385,12 @@ def accounts(self) -> AsyncAccountsResource:
@cached_property
def bookmarks(self) -> AsyncBookmarksResource:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return AsyncBookmarksResource(self._client)
@cached_property
def lists(self) -> AsyncListsResource:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return AsyncListsResource(self._client)
@cached_property
@@ -517,6 +540,8 @@ async def get_notifications(
async def get_trends(
self,
*,
+ count: int | Omit = omit,
+ woeid: int | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -524,11 +549,36 @@ async def get_trends(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> XGetTrendsResponse:
- """Get trending topics"""
+ """
+ Get trending hashtags and topics from X by region
+
+ Args:
+ count: Number of trending topics to return (1-50, default 30)
+
+ woeid: Region WOEID (1=Worldwide, 23424977=US, 23424975=UK, 23424969=Turkey)
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
return await self._get(
"/x/trends",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "count": count,
+ "woeid": woeid,
+ },
+ x_get_trends_params.XGetTrendsParams,
+ ),
),
cast_to=XGetTrendsResponse,
)
@@ -557,12 +607,12 @@ def tweets(self) -> TweetsResourceWithRawResponse:
@cached_property
def users(self) -> UsersResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return UsersResourceWithRawResponse(self._x.users)
@cached_property
def followers(self) -> FollowersResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return FollowersResourceWithRawResponse(self._x.followers)
@cached_property
@@ -571,7 +621,7 @@ def dm(self) -> DmResourceWithRawResponse:
@cached_property
def media(self) -> MediaResourceWithRawResponse:
- """Media upload & download"""
+ """Media upload and download"""
return MediaResourceWithRawResponse(self._x.media)
@cached_property
@@ -590,12 +640,12 @@ def accounts(self) -> AccountsResourceWithRawResponse:
@cached_property
def bookmarks(self) -> BookmarksResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return BookmarksResourceWithRawResponse(self._x.bookmarks)
@cached_property
def lists(self) -> ListsResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return ListsResourceWithRawResponse(self._x.lists)
@@ -622,12 +672,12 @@ def tweets(self) -> AsyncTweetsResourceWithRawResponse:
@cached_property
def users(self) -> AsyncUsersResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncUsersResourceWithRawResponse(self._x.users)
@cached_property
def followers(self) -> AsyncFollowersResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncFollowersResourceWithRawResponse(self._x.followers)
@cached_property
@@ -636,7 +686,7 @@ def dm(self) -> AsyncDmResourceWithRawResponse:
@cached_property
def media(self) -> AsyncMediaResourceWithRawResponse:
- """Media upload & download"""
+ """Media upload and download"""
return AsyncMediaResourceWithRawResponse(self._x.media)
@cached_property
@@ -655,12 +705,12 @@ def accounts(self) -> AsyncAccountsResourceWithRawResponse:
@cached_property
def bookmarks(self) -> AsyncBookmarksResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return AsyncBookmarksResourceWithRawResponse(self._x.bookmarks)
@cached_property
def lists(self) -> AsyncListsResourceWithRawResponse:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return AsyncListsResourceWithRawResponse(self._x.lists)
@@ -687,12 +737,12 @@ def tweets(self) -> TweetsResourceWithStreamingResponse:
@cached_property
def users(self) -> UsersResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return UsersResourceWithStreamingResponse(self._x.users)
@cached_property
def followers(self) -> FollowersResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return FollowersResourceWithStreamingResponse(self._x.followers)
@cached_property
@@ -701,7 +751,7 @@ def dm(self) -> DmResourceWithStreamingResponse:
@cached_property
def media(self) -> MediaResourceWithStreamingResponse:
- """Media upload & download"""
+ """Media upload and download"""
return MediaResourceWithStreamingResponse(self._x.media)
@cached_property
@@ -720,12 +770,12 @@ def accounts(self) -> AccountsResourceWithStreamingResponse:
@cached_property
def bookmarks(self) -> BookmarksResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return BookmarksResourceWithStreamingResponse(self._x.bookmarks)
@cached_property
def lists(self) -> ListsResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return ListsResourceWithStreamingResponse(self._x.lists)
@@ -752,12 +802,12 @@ def tweets(self) -> AsyncTweetsResourceWithStreamingResponse:
@cached_property
def users(self) -> AsyncUsersResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncUsersResourceWithStreamingResponse(self._x.users)
@cached_property
def followers(self) -> AsyncFollowersResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and explore user profiles and relationships"""
return AsyncFollowersResourceWithStreamingResponse(self._x.followers)
@cached_property
@@ -766,7 +816,7 @@ def dm(self) -> AsyncDmResourceWithStreamingResponse:
@cached_property
def media(self) -> AsyncMediaResourceWithStreamingResponse:
- """Media upload & download"""
+ """Media upload and download"""
return AsyncMediaResourceWithStreamingResponse(self._x.media)
@cached_property
@@ -785,10 +835,10 @@ def accounts(self) -> AsyncAccountsResourceWithStreamingResponse:
@cached_property
def bookmarks(self) -> AsyncBookmarksResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """Look up, search, and analyze individual tweets"""
return AsyncBookmarksResourceWithStreamingResponse(self._x.bookmarks)
@cached_property
def lists(self) -> AsyncListsResourceWithStreamingResponse:
- """X data lookups (subscription required)"""
+ """X List followers, members, and tweets"""
return AsyncListsResourceWithStreamingResponse(self._x.lists)
diff --git a/src/x_twitter_scraper/types/__init__.py b/src/x_twitter_scraper/types/__init__.py
index 8d15985..0713a7c 100644
--- a/src/x_twitter_scraper/types/__init__.py
+++ b/src/x_twitter_scraper/types/__init__.py
@@ -7,6 +7,8 @@
from .shared import (
Error as Error,
EventType as EventType,
+ SearchTweet as SearchTweet,
+ UserProfile as UserProfile,
PaginatedUsers as PaginatedUsers,
PaginatedTweets as PaginatedTweets,
)
@@ -17,7 +19,6 @@
from .delivery import Delivery as Delivery
from .radar_item import RadarItem as RadarItem
from .draw_detail import DrawDetail as DrawDetail
-from .integration import Integration as Integration
from .draft_detail import DraftDetail as DraftDetail
from .event_detail import EventDetail as EventDetail
from .style_profile import StyleProfile as StyleProfile
@@ -37,7 +38,7 @@
from .style_list_response import StyleListResponse as StyleListResponse
from .style_update_params import StyleUpdateParams as StyleUpdateParams
from .trend_list_response import TrendListResponse as TrendListResponse
-from .integration_delivery import IntegrationDelivery as IntegrationDelivery
+from .x_get_trends_params import XGetTrendsParams as XGetTrendsParams
from .style_analyze_params import StyleAnalyzeParams as StyleAnalyzeParams
from .style_compare_params import StyleCompareParams as StyleCompareParams
from .api_key_create_params import APIKeyCreateParams as APIKeyCreateParams
@@ -65,15 +66,11 @@
from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse
from .extraction_list_response import ExtractionListResponse as ExtractionListResponse
from .account_retrieve_response import AccountRetrieveResponse as AccountRetrieveResponse
-from .integration_create_params import IntegrationCreateParams as IntegrationCreateParams
-from .integration_list_response import IntegrationListResponse as IntegrationListResponse
-from .integration_update_params import IntegrationUpdateParams as IntegrationUpdateParams
from .subscribe_create_response import SubscribeCreateResponse as SubscribeCreateResponse
from .extraction_retrieve_params import ExtractionRetrieveParams as ExtractionRetrieveParams
from .x_get_home_timeline_params import XGetHomeTimelineParams as XGetHomeTimelineParams
from .x_get_notifications_params import XGetNotificationsParams as XGetNotificationsParams
from .credit_topup_balance_params import CreditTopupBalanceParams as CreditTopupBalanceParams
-from .integration_delete_response import IntegrationDeleteResponse as IntegrationDeleteResponse
from .monitor_deactivate_response import MonitorDeactivateResponse as MonitorDeactivateResponse
from .webhook_deactivate_response import WebhookDeactivateResponse as WebhookDeactivateResponse
from .account_update_locale_params import AccountUpdateLocaleParams as AccountUpdateLocaleParams
@@ -82,7 +79,6 @@
from .account_set_x_username_params import AccountSetXUsernameParams as AccountSetXUsernameParams
from .credit_topup_balance_response import CreditTopupBalanceResponse as CreditTopupBalanceResponse
from .account_update_locale_response import AccountUpdateLocaleResponse as AccountUpdateLocaleResponse
-from .integration_send_test_response import IntegrationSendTestResponse as IntegrationSendTestResponse
from .style_get_performance_response import StyleGetPerformanceResponse as StyleGetPerformanceResponse
from .account_set_x_username_response import AccountSetXUsernameResponse as AccountSetXUsernameResponse
from .extraction_estimate_cost_params import ExtractionEstimateCostParams as ExtractionEstimateCostParams
@@ -90,8 +86,6 @@
from .extraction_export_results_params import ExtractionExportResultsParams as ExtractionExportResultsParams
from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse
from .extraction_estimate_cost_response import ExtractionEstimateCostResponse as ExtractionEstimateCostResponse
-from .integration_list_deliveries_params import IntegrationListDeliveriesParams as IntegrationListDeliveriesParams
-from .integration_list_deliveries_response import IntegrationListDeliveriesResponse as IntegrationListDeliveriesResponse
from .radar_retrieve_trending_topics_params import (
RadarRetrieveTrendingTopicsParams as RadarRetrieveTrendingTopicsParams,
)
diff --git a/src/x_twitter_scraper/types/account_retrieve_response.py b/src/x_twitter_scraper/types/account_retrieve_response.py
index f43a115..ae6b655 100644
--- a/src/x_twitter_scraper/types/account_retrieve_response.py
+++ b/src/x_twitter_scraper/types/account_retrieve_response.py
@@ -1,22 +1,23 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
-from datetime import datetime
from typing_extensions import Literal
from pydantic import Field as FieldInfo
from .._models import BaseModel
-__all__ = ["AccountRetrieveResponse", "CurrentPeriod"]
+__all__ = ["AccountRetrieveResponse", "CreditInfo"]
-class CurrentPeriod(BaseModel):
- end: datetime
+class CreditInfo(BaseModel):
+ auto_topup_enabled: bool = FieldInfo(alias="autoTopupEnabled")
- start: datetime
+ balance: int
- usage_percent: float = FieldInfo(alias="usagePercent")
+ lifetime_purchased: int = FieldInfo(alias="lifetimePurchased")
+
+ lifetime_used: int = FieldInfo(alias="lifetimeUsed")
class AccountRetrieveResponse(BaseModel):
@@ -26,4 +27,4 @@ class AccountRetrieveResponse(BaseModel):
plan: Literal["active", "inactive"]
- current_period: Optional[CurrentPeriod] = FieldInfo(alias="currentPeriod", default=None)
+ credit_info: Optional[CreditInfo] = FieldInfo(alias="creditInfo", default=None)
diff --git a/src/x_twitter_scraper/types/bot/__init__.py b/src/x_twitter_scraper/types/bot/__init__.py
deleted file mode 100644
index f8ee8b1..0000000
--- a/src/x_twitter_scraper/types/bot/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
diff --git a/src/x_twitter_scraper/types/extraction_estimate_cost_response.py b/src/x_twitter_scraper/types/extraction_estimate_cost_response.py
index 641b731..6893256 100644
--- a/src/x_twitter_scraper/types/extraction_estimate_cost_response.py
+++ b/src/x_twitter_scraper/types/extraction_estimate_cost_response.py
@@ -1,5 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
+
from pydantic import Field as FieldInfo
from .._models import BaseModel
@@ -10,10 +12,12 @@
class ExtractionEstimateCostResponse(BaseModel):
allowed: bool
- estimated_results: int = FieldInfo(alias="estimatedResults")
+ credits_available: str = FieldInfo(alias="creditsAvailable")
- projected_percent: float = FieldInfo(alias="projectedPercent")
+ credits_required: str = FieldInfo(alias="creditsRequired")
+
+ estimated_results: int = FieldInfo(alias="estimatedResults")
source: str
- usage_percent: float = FieldInfo(alias="usagePercent")
+ resolved_x_user_id: Optional[str] = FieldInfo(alias="resolvedXUserId", default=None)
diff --git a/src/x_twitter_scraper/types/integration.py b/src/x_twitter_scraper/types/integration.py
deleted file mode 100644
index 22f6b40..0000000
--- a/src/x_twitter_scraper/types/integration.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Dict, List, Optional
-from datetime import datetime
-from typing_extensions import Literal
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.event_type import EventType
-
-__all__ = ["Integration"]
-
-
-class Integration(BaseModel):
- """Third-party integration (e.g. Telegram) subscribed to monitor events."""
-
- id: str
-
- config: Dict[str, object]
- """Integration config — shape varies by type (JSON)"""
-
- created_at: datetime = FieldInfo(alias="createdAt")
-
- event_types: List[EventType] = FieldInfo(alias="eventTypes")
- """Array of event types to subscribe to."""
-
- is_active: bool = FieldInfo(alias="isActive")
-
- name: str
-
- type: Literal["telegram"]
-
- filters: Optional[Dict[str, object]] = None
- """Event filter rules (JSON)"""
-
- message_template: Optional[str] = FieldInfo(alias="messageTemplate", default=None)
-
- scope_all_monitors: Optional[bool] = FieldInfo(alias="scopeAllMonitors", default=None)
-
- silent_push: Optional[bool] = FieldInfo(alias="silentPush", default=None)
diff --git a/src/x_twitter_scraper/types/integration_create_params.py b/src/x_twitter_scraper/types/integration_create_params.py
deleted file mode 100644
index e77c840..0000000
--- a/src/x_twitter_scraper/types/integration_create_params.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import List
-from typing_extensions import Literal, Required, Annotated, TypedDict
-
-from .._utils import PropertyInfo
-from .shared.event_type import EventType
-
-__all__ = ["IntegrationCreateParams", "Config"]
-
-
-class IntegrationCreateParams(TypedDict, total=False):
- config: Required[Config]
- """Integration config (e.g. Telegram chatId)"""
-
- event_types: Required[Annotated[List[EventType], PropertyInfo(alias="eventTypes")]]
- """Array of event types to subscribe to."""
-
- name: Required[str]
-
- type: Required[Literal["telegram"]]
-
-
-class Config(TypedDict, total=False):
- """Integration config (e.g. Telegram chatId)"""
-
- chat_id: Required[Annotated[str, PropertyInfo(alias="chatId")]]
diff --git a/src/x_twitter_scraper/types/integration_delete_response.py b/src/x_twitter_scraper/types/integration_delete_response.py
deleted file mode 100644
index 76da0b5..0000000
--- a/src/x_twitter_scraper/types/integration_delete_response.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import Literal
-
-from .._models import BaseModel
-
-__all__ = ["IntegrationDeleteResponse"]
-
-
-class IntegrationDeleteResponse(BaseModel):
- success: Literal[True]
diff --git a/src/x_twitter_scraper/types/integration_delivery.py b/src/x_twitter_scraper/types/integration_delivery.py
deleted file mode 100644
index bda8435..0000000
--- a/src/x_twitter_scraper/types/integration_delivery.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-from datetime import datetime
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-
-__all__ = ["IntegrationDelivery"]
-
-
-class IntegrationDelivery(BaseModel):
- """Integration delivery attempt record with status and retry count."""
-
- id: str
-
- attempts: int
-
- created_at: datetime = FieldInfo(alias="createdAt")
-
- event_type: str = FieldInfo(alias="eventType")
-
- status: str
-
- delivered_at: Optional[datetime] = FieldInfo(alias="deliveredAt", default=None)
-
- last_error: Optional[str] = FieldInfo(alias="lastError", default=None)
-
- last_status_code: Optional[int] = FieldInfo(alias="lastStatusCode", default=None)
-
- source_id: Optional[str] = FieldInfo(alias="sourceId", default=None)
-
- source_type: Optional[str] = FieldInfo(alias="sourceType", default=None)
diff --git a/src/x_twitter_scraper/types/integration_list_deliveries_params.py b/src/x_twitter_scraper/types/integration_list_deliveries_params.py
deleted file mode 100644
index 948aefd..0000000
--- a/src/x_twitter_scraper/types/integration_list_deliveries_params.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["IntegrationListDeliveriesParams"]
-
-
-class IntegrationListDeliveriesParams(TypedDict, total=False):
- limit: int
- """Maximum number of items to return (1-100, default 50)"""
diff --git a/src/x_twitter_scraper/types/integration_list_deliveries_response.py b/src/x_twitter_scraper/types/integration_list_deliveries_response.py
deleted file mode 100644
index ec5a8ed..0000000
--- a/src/x_twitter_scraper/types/integration_list_deliveries_response.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .._models import BaseModel
-from .integration_delivery import IntegrationDelivery
-
-__all__ = ["IntegrationListDeliveriesResponse"]
-
-
-class IntegrationListDeliveriesResponse(BaseModel):
- deliveries: List[IntegrationDelivery]
diff --git a/src/x_twitter_scraper/types/integration_list_response.py b/src/x_twitter_scraper/types/integration_list_response.py
deleted file mode 100644
index 95aa13a..0000000
--- a/src/x_twitter_scraper/types/integration_list_response.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .._models import BaseModel
-from .integration import Integration
-
-__all__ = ["IntegrationListResponse"]
-
-
-class IntegrationListResponse(BaseModel):
- integrations: List[Integration]
diff --git a/src/x_twitter_scraper/types/integration_send_test_response.py b/src/x_twitter_scraper/types/integration_send_test_response.py
deleted file mode 100644
index b45b687..0000000
--- a/src/x_twitter_scraper/types/integration_send_test_response.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import Literal
-
-from .._models import BaseModel
-
-__all__ = ["IntegrationSendTestResponse"]
-
-
-class IntegrationSendTestResponse(BaseModel):
- success: Literal[True]
diff --git a/src/x_twitter_scraper/types/integration_update_params.py b/src/x_twitter_scraper/types/integration_update_params.py
deleted file mode 100644
index a09d038..0000000
--- a/src/x_twitter_scraper/types/integration_update_params.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, List
-from typing_extensions import Annotated, TypedDict
-
-from .._utils import PropertyInfo
-from .shared.event_type import EventType
-
-__all__ = ["IntegrationUpdateParams"]
-
-
-class IntegrationUpdateParams(TypedDict, total=False):
- event_types: Annotated[List[EventType], PropertyInfo(alias="eventTypes")]
- """Array of event types to subscribe to."""
-
- filters: Dict[str, object]
- """Event filter rules (JSON)"""
-
- is_active: Annotated[bool, PropertyInfo(alias="isActive")]
-
- message_template: Annotated[Dict[str, object], PropertyInfo(alias="messageTemplate")]
- """Custom message template (JSON)"""
-
- name: str
-
- scope_all_monitors: Annotated[bool, PropertyInfo(alias="scopeAllMonitors")]
-
- silent_push: Annotated[bool, PropertyInfo(alias="silentPush")]
diff --git a/src/x_twitter_scraper/types/radar_item.py b/src/x_twitter_scraper/types/radar_item.py
index ccf954a..958b410 100644
--- a/src/x_twitter_scraper/types/radar_item.py
+++ b/src/x_twitter_scraper/types/radar_item.py
@@ -1,7 +1,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, Optional
from datetime import datetime
+from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -11,9 +12,32 @@
class RadarItem(BaseModel):
- """Trending topic with score, category, source, and region."""
+ """
+ Trending topic with score, category, source, region, language, and source-specific metadata.
+ """
- category: str
+ id: str
+ """Internal numeric identifier (stringified bigint)."""
+
+ category: Literal["general", "tech", "dev", "science", "culture", "politics", "business", "entertainment"]
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+
+ language: str
+
+ metadata: Dict[str, object]
+ """Source-specific fields. Shape varies per source:
+
+ - reddit: { subreddit: string, author: string }
+ - github: { starsToday: number }
+ - hacker_news: { points: number, numberComments: number }
+ - google_trends: { approxTraffic: number }
+ - polymarket: { volume24hr: number }
+ - wikipedia: { views: number }
+ - trustmrr: { mrr, growthPercent, last30Days, total, customers,
+ activeSubscriptions, onSale, xHandle?, category?, askingPrice?, country?,
+ growthMrrPercent?, multiple?, paymentProvider?, rank? }
+ """
published_at: datetime = FieldInfo(alias="publishedAt")
@@ -21,7 +45,10 @@ class RadarItem(BaseModel):
score: float
- source: str
+ source: Literal["github", "google_trends", "hacker_news", "polymarket", "reddit", "trustmrr", "wikipedia"]
+
+ source_id: str = FieldInfo(alias="sourceId")
+ """Source-specific identifier used for deduplication."""
title: str
diff --git a/src/x_twitter_scraper/types/radar_retrieve_trending_topics_params.py b/src/x_twitter_scraper/types/radar_retrieve_trending_topics_params.py
index 3dd5d4c..cda5dc6 100644
--- a/src/x_twitter_scraper/types/radar_retrieve_trending_topics_params.py
+++ b/src/x_twitter_scraper/types/radar_retrieve_trending_topics_params.py
@@ -8,14 +8,17 @@
class RadarRetrieveTrendingTopicsParams(TypedDict, total=False):
- category: str
- """Filter by category (general, tech, dev, etc.)"""
+ after: str
+ """Cursor for pagination (from prior response nextCursor)."""
- count: int
- """Number of items to return"""
+ category: Literal["general", "tech", "dev", "science", "culture", "politics", "business", "entertainment"]
+ """Filter by category."""
hours: int
- """Lookback window in hours"""
+ """Lookback window in hours (1-168, default 24)."""
+
+ limit: int
+ """Number of items to return (1-100, default 50)."""
region: str
"""Region filter (us, global, etc.)"""
diff --git a/src/x_twitter_scraper/types/radar_retrieve_trending_topics_response.py b/src/x_twitter_scraper/types/radar_retrieve_trending_topics_response.py
index a786b57..4c94a3b 100644
--- a/src/x_twitter_scraper/types/radar_retrieve_trending_topics_response.py
+++ b/src/x_twitter_scraper/types/radar_retrieve_trending_topics_response.py
@@ -1,6 +1,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
from .._models import BaseModel
from .radar_item import RadarItem
@@ -9,6 +11,9 @@
class RadarRetrieveTrendingTopicsResponse(BaseModel):
+ has_more: bool = FieldInfo(alias="hasMore")
+
items: List[RadarItem]
- total: int
+ next_cursor: Optional[str] = FieldInfo(alias="nextCursor", default=None)
+ """Opaque cursor for the next page (present only when hasMore is true)."""
diff --git a/src/x_twitter_scraper/types/shared/__init__.py b/src/x_twitter_scraper/types/shared/__init__.py
index a9ea29f..8796775 100644
--- a/src/x_twitter_scraper/types/shared/__init__.py
+++ b/src/x_twitter_scraper/types/shared/__init__.py
@@ -2,5 +2,7 @@
from .error import Error as Error
from .event_type import EventType as EventType
+from .search_tweet import SearchTweet as SearchTweet
+from .user_profile import UserProfile as UserProfile
from .paginated_users import PaginatedUsers as PaginatedUsers
from .paginated_tweets import PaginatedTweets as PaginatedTweets
diff --git a/src/x_twitter_scraper/types/shared/error.py b/src/x_twitter_scraper/types/shared/error.py
index 251e5b6..a4afc39 100644
--- a/src/x_twitter_scraper/types/shared/error.py
+++ b/src/x_twitter_scraper/types/shared/error.py
@@ -20,17 +20,17 @@ class Error(BaseModel):
"invalid_tweet_id",
"invalid_tweet_url",
"invalid_username",
+ "insufficient_credits",
"missing_params",
"missing_query",
"monitor_already_exists",
"monitor_limit_reached",
+ "no_credits",
"no_subscription",
"not_found",
- "stream_registration_failed",
"subscription_inactive",
"tweet_not_found",
"unauthenticated",
- "usage_limit_reached",
"user_not_found",
"webhook_inactive",
"x_api_rate_limited",
diff --git a/src/x_twitter_scraper/types/shared/event_type.py b/src/x_twitter_scraper/types/shared/event_type.py
index ecf46ca..f4bafa6 100644
--- a/src/x_twitter_scraper/types/shared/event_type.py
+++ b/src/x_twitter_scraper/types/shared/event_type.py
@@ -4,6 +4,4 @@
__all__ = ["EventType"]
-EventType: TypeAlias = Literal[
- "tweet.new", "tweet.reply", "tweet.retweet", "tweet.quote", "follower.gained", "follower.lost"
-]
+EventType: TypeAlias = Literal["tweet.new", "tweet.reply", "tweet.retweet", "tweet.quote"]
diff --git a/src/x_twitter_scraper/types/shared/paginated_tweets.py b/src/x_twitter_scraper/types/shared/paginated_tweets.py
index 5852ddd..edd6227 100644
--- a/src/x_twitter_scraper/types/shared/paginated_tweets.py
+++ b/src/x_twitter_scraper/types/shared/paginated_tweets.py
@@ -3,7 +3,7 @@
from typing import List
from ..._models import BaseModel
-from ..x.search_tweet import SearchTweet
+from .search_tweet import SearchTweet
__all__ = ["PaginatedTweets"]
diff --git a/src/x_twitter_scraper/types/shared/paginated_users.py b/src/x_twitter_scraper/types/shared/paginated_users.py
index 4065b4a..0982c4a 100644
--- a/src/x_twitter_scraper/types/shared/paginated_users.py
+++ b/src/x_twitter_scraper/types/shared/paginated_users.py
@@ -3,7 +3,7 @@
from typing import List
from ..._models import BaseModel
-from ..x.user_profile import UserProfile
+from .user_profile import UserProfile
__all__ = ["PaginatedUsers"]
diff --git a/src/x_twitter_scraper/types/x/search_tweet.py b/src/x_twitter_scraper/types/shared/search_tweet.py
similarity index 100%
rename from src/x_twitter_scraper/types/x/search_tweet.py
rename to src/x_twitter_scraper/types/shared/search_tweet.py
diff --git a/src/x_twitter_scraper/types/x/user_profile.py b/src/x_twitter_scraper/types/shared/user_profile.py
similarity index 100%
rename from src/x_twitter_scraper/types/x/user_profile.py
rename to src/x_twitter_scraper/types/shared/user_profile.py
diff --git a/src/x_twitter_scraper/types/shared_params/event_type.py b/src/x_twitter_scraper/types/shared_params/event_type.py
index ed5adaa..aea9e2a 100644
--- a/src/x_twitter_scraper/types/shared_params/event_type.py
+++ b/src/x_twitter_scraper/types/shared_params/event_type.py
@@ -6,6 +6,4 @@
__all__ = ["EventType"]
-EventType: TypeAlias = Literal[
- "tweet.new", "tweet.reply", "tweet.retweet", "tweet.quote", "follower.gained", "follower.lost"
-]
+EventType: TypeAlias = Literal["tweet.new", "tweet.reply", "tweet.retweet", "tweet.quote"]
diff --git a/src/x_twitter_scraper/types/x/__init__.py b/src/x_twitter_scraper/types/x/__init__.py
index d40e6e6..7f79a0c 100644
--- a/src/x_twitter_scraper/types/x/__init__.py
+++ b/src/x_twitter_scraper/types/x/__init__.py
@@ -3,10 +3,8 @@
from __future__ import annotations
from .x_account import XAccount as XAccount
-from .search_tweet import SearchTweet as SearchTweet
from .tweet_author import TweetAuthor as TweetAuthor
from .tweet_detail import TweetDetail as TweetDetail
-from .user_profile import UserProfile as UserProfile
from .dm_send_params import DmSendParams as DmSendParams
from .dm_send_response import DmSendResponse as DmSendResponse
from .x_account_detail import XAccountDetail as XAccountDetail
diff --git a/src/x_twitter_scraper/types/x/account_create_response.py b/src/x_twitter_scraper/types/x/account_create_response.py
index 3c64957..0a83f21 100644
--- a/src/x_twitter_scraper/types/x/account_create_response.py
+++ b/src/x_twitter_scraper/types/x/account_create_response.py
@@ -1,5 +1,9 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
from pydantic import Field as FieldInfo
from ..._models import BaseModel
@@ -8,10 +12,26 @@
class AccountCreateResponse(BaseModel):
+ """Sanitized X account summary returned by connect and reauth.
+
+ Includes an optional `loginCountry` field surfaced only when the declared proxy region had no Driver capacity and the login fell back to a single US consumer device for this one-time action. Future activity continues to use the selected `proxy_country`; the field is omitted on normal logins.
+ """
+
id: str
+ created_at: datetime = FieldInfo(alias="createdAt")
+
+ health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
+
status: str
x_user_id: str = FieldInfo(alias="xUserId")
x_username: str = FieldInfo(alias="xUsername")
+
+ login_country: Optional[str] = FieldInfo(alias="loginCountry", default=None)
+ """
+ ISO-3166-1 alpha-2 country code of the Driver consumer device used for this
+ login. Present only when the US fallback was triggered because Driver had no
+ capacity in the declared region. Omitted otherwise.
+ """
diff --git a/src/x_twitter_scraper/types/x/account_reauth_params.py b/src/x_twitter_scraper/types/x/account_reauth_params.py
index 8263ba1..fcde3a8 100644
--- a/src/x_twitter_scraper/types/x/account_reauth_params.py
+++ b/src/x_twitter_scraper/types/x/account_reauth_params.py
@@ -11,5 +11,11 @@ class AccountReauthParams(TypedDict, total=False):
password: Required[str]
"""Updated account password"""
+ email: str
+ """Email for the X account (updates stored email)"""
+
+ proxy_country: str
+ """Two-letter country code for login proxy region"""
+
totp_secret: str
"""TOTP secret for 2FA re-authentication"""
diff --git a/src/x_twitter_scraper/types/x/account_reauth_response.py b/src/x_twitter_scraper/types/x/account_reauth_response.py
index dbeb483..062ed1f 100644
--- a/src/x_twitter_scraper/types/x/account_reauth_response.py
+++ b/src/x_twitter_scraper/types/x/account_reauth_response.py
@@ -1,5 +1,9 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
from pydantic import Field as FieldInfo
from ..._models import BaseModel
@@ -8,8 +12,26 @@
class AccountReauthResponse(BaseModel):
+ """Sanitized X account summary returned by connect and reauth.
+
+ Includes an optional `loginCountry` field surfaced only when the declared proxy region had no Driver capacity and the login fell back to a single US consumer device for this one-time action. Future activity continues to use the selected `proxy_country`; the field is omitted on normal logins.
+ """
+
id: str
+ created_at: datetime = FieldInfo(alias="createdAt")
+
+ health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
+
status: str
+ x_user_id: str = FieldInfo(alias="xUserId")
+
x_username: str = FieldInfo(alias="xUsername")
+
+ login_country: Optional[str] = FieldInfo(alias="loginCountry", default=None)
+ """
+ ISO-3166-1 alpha-2 country code of the Driver consumer device used for this
+ login. Present only when the US fallback was triggered because Driver had no
+ capacity in the declared region. Omitted otherwise.
+ """
diff --git a/src/x_twitter_scraper/types/x/tweet_create_params.py b/src/x_twitter_scraper/types/x/tweet_create_params.py
index ff4d4d1..ee055bb 100644
--- a/src/x_twitter_scraper/types/x/tweet_create_params.py
+++ b/src/x_twitter_scraper/types/x/tweet_create_params.py
@@ -13,14 +13,19 @@ class TweetCreateParams(TypedDict, total=False):
account: Required[str]
"""X account (@username or account ID)"""
- text: Required[str]
-
attachment_url: str
community_id: str
is_note_tweet: bool
+ media: SequenceNotStr[str]
+ """Array of media URLs to attach (mutually exclusive with media_ids)"""
+
media_ids: SequenceNotStr[str]
+ """Array of media IDs to attach (mutually exclusive with media)"""
reply_to_tweet_id: str
+
+ text: str
+ """Tweet text (optional when media is provided)"""
diff --git a/src/x_twitter_scraper/types/x/x_account.py b/src/x_twitter_scraper/types/x/x_account.py
index 6036370..4f80553 100644
--- a/src/x_twitter_scraper/types/x/x_account.py
+++ b/src/x_twitter_scraper/types/x/x_account.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from datetime import datetime
+from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -16,6 +17,15 @@ class XAccount(BaseModel):
created_at: datetime = FieldInfo(alias="createdAt")
+ health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
+ """Derived login/cookie health.
+
+ `healthy` = cookies valid. `needsReauth` = user must submit fresh credentials.
+ `locked` = X locked the account; unlock on x.com first. `suspended` = X banned
+ the account. `recovering` = past cooldown, will auto-retry on next use.
+ `temporaryIssue` = transient backend problem; retry shortly.
+ """
+
status: str
x_user_id: str = FieldInfo(alias="xUserId")
diff --git a/src/x_twitter_scraper/types/x/x_account_detail.py b/src/x_twitter_scraper/types/x/x_account_detail.py
index 5b339de..f2c2fb5 100644
--- a/src/x_twitter_scraper/types/x/x_account_detail.py
+++ b/src/x_twitter_scraper/types/x/x_account_detail.py
@@ -2,6 +2,7 @@
from typing import Optional
from datetime import datetime
+from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -17,6 +18,8 @@ class XAccountDetail(BaseModel):
created_at: datetime = FieldInfo(alias="createdAt")
+ health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
+
status: str
x_user_id: str = FieldInfo(alias="xUserId")
diff --git a/src/x_twitter_scraper/types/x_get_trends_params.py b/src/x_twitter_scraper/types/x_get_trends_params.py
new file mode 100644
index 0000000..44cabeb
--- /dev/null
+++ b/src/x_twitter_scraper/types/x_get_trends_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["XGetTrendsParams"]
+
+
+class XGetTrendsParams(TypedDict, total=False):
+ count: int
+ """Number of trending topics to return (1-50, default 30)"""
+
+ woeid: int
+ """Region WOEID (1=Worldwide, 23424977=US, 23424975=UK, 23424969=Turkey)"""
diff --git a/tests/api_resources/test_integrations.py b/tests/api_resources/test_integrations.py
deleted file mode 100644
index ede13a0..0000000
--- a/tests/api_resources/test_integrations.py
+++ /dev/null
@@ -1,640 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from tests.utils import assert_matches_type
-from x_twitter_scraper import XTwitterScraper, AsyncXTwitterScraper
-from x_twitter_scraper.types import (
- Integration,
- IntegrationListResponse,
- IntegrationDeleteResponse,
- IntegrationSendTestResponse,
- IntegrationListDeliveriesResponse,
-)
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestIntegrations:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_create(self, client: XTwitterScraper) -> None:
- integration = client.integrations.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_create(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_create(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_retrieve(self, client: XTwitterScraper) -> None:
- integration = client.integrations.retrieve(
- "id",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_retrieve(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_retrieve(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_path_params_retrieve(self, client: XTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.integrations.with_raw_response.retrieve(
- "",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_update(self, client: XTwitterScraper) -> None:
- integration = client.integrations.update(
- id="id",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_update_with_all_params(self, client: XTwitterScraper) -> None:
- integration = client.integrations.update(
- id="id",
- event_types=["tweet.new"],
- filters={},
- is_active=True,
- message_template={},
- name="My Telegram Bot",
- scope_all_monitors=True,
- silent_push=False,
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_update(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_update(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_path_params_update(self, client: XTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.integrations.with_raw_response.update(
- id="",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_list(self, client: XTwitterScraper) -> None:
- integration = client.integrations.list()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_list(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_list(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_delete(self, client: XTwitterScraper) -> None:
- integration = client.integrations.delete(
- "id",
- )
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_delete(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.delete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_delete(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.delete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_path_params_delete(self, client: XTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.integrations.with_raw_response.delete(
- "",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_list_deliveries(self, client: XTwitterScraper) -> None:
- integration = client.integrations.list_deliveries(
- id="id",
- )
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_list_deliveries_with_all_params(self, client: XTwitterScraper) -> None:
- integration = client.integrations.list_deliveries(
- id="id",
- limit=1,
- )
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_list_deliveries(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.list_deliveries(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_list_deliveries(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.list_deliveries(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_path_params_list_deliveries(self, client: XTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.integrations.with_raw_response.list_deliveries(
- id="",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_method_send_test(self, client: XTwitterScraper) -> None:
- integration = client.integrations.send_test(
- "id",
- )
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_send_test(self, client: XTwitterScraper) -> None:
- response = client.integrations.with_raw_response.send_test(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = response.parse()
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_send_test(self, client: XTwitterScraper) -> None:
- with client.integrations.with_streaming_response.send_test(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = response.parse()
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_path_params_send_test(self, client: XTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- client.integrations.with_raw_response.send_test(
- "",
- )
-
-
-class TestAsyncIntegrations:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_create(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.create(
- config={"chat_id": "-1001234567890"},
- event_types=["tweet.new", "follower.gained"],
- name="My Telegram Bot",
- type="telegram",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_retrieve(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.retrieve(
- "id",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_retrieve(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.retrieve(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_retrieve(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.retrieve(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_path_params_retrieve(self, async_client: AsyncXTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.integrations.with_raw_response.retrieve(
- "",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_update(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.update(
- id="id",
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_update_with_all_params(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.update(
- id="id",
- event_types=["tweet.new"],
- filters={},
- is_active=True,
- message_template={},
- name="My Telegram Bot",
- scope_all_monitors=True,
- silent_push=False,
- )
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_update(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.update(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_update(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.update(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(Integration, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_path_params_update(self, async_client: AsyncXTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.integrations.with_raw_response.update(
- id="",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_list(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.list()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(IntegrationListResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_delete(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.delete(
- "id",
- )
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_delete(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.delete(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_delete(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.delete(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(IntegrationDeleteResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_path_params_delete(self, async_client: AsyncXTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.integrations.with_raw_response.delete(
- "",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_list_deliveries(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.list_deliveries(
- id="id",
- )
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_list_deliveries_with_all_params(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.list_deliveries(
- id="id",
- limit=1,
- )
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_list_deliveries(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.list_deliveries(
- id="id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_list_deliveries(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.list_deliveries(
- id="id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(IntegrationListDeliveriesResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_path_params_list_deliveries(self, async_client: AsyncXTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.integrations.with_raw_response.list_deliveries(
- id="",
- )
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_method_send_test(self, async_client: AsyncXTwitterScraper) -> None:
- integration = await async_client.integrations.send_test(
- "id",
- )
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_send_test(self, async_client: AsyncXTwitterScraper) -> None:
- response = await async_client.integrations.with_raw_response.send_test(
- "id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- integration = await response.parse()
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_send_test(self, async_client: AsyncXTwitterScraper) -> None:
- async with async_client.integrations.with_streaming_response.send_test(
- "id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- integration = await response.parse()
- assert_matches_type(IntegrationSendTestResponse, integration, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_path_params_send_test(self, async_client: AsyncXTwitterScraper) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
- await async_client.integrations.with_raw_response.send_test(
- "",
- )
diff --git a/tests/api_resources/test_monitors.py b/tests/api_resources/test_monitors.py
index 7574362..e705257 100644
--- a/tests/api_resources/test_monitors.py
+++ b/tests/api_resources/test_monitors.py
@@ -26,7 +26,7 @@ class TestMonitors:
@parametrize
def test_method_create(self, client: XTwitterScraper) -> None:
monitor = client.monitors.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
)
assert_matches_type(MonitorCreateResponse, monitor, path=["response"])
@@ -35,7 +35,7 @@ def test_method_create(self, client: XTwitterScraper) -> None:
@parametrize
def test_raw_response_create(self, client: XTwitterScraper) -> None:
response = client.monitors.with_raw_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
)
@@ -48,7 +48,7 @@ def test_raw_response_create(self, client: XTwitterScraper) -> None:
@parametrize
def test_streaming_response_create(self, client: XTwitterScraper) -> None:
with client.monitors.with_streaming_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
) as response:
assert not response.is_closed
@@ -233,7 +233,7 @@ class TestAsyncMonitors:
@parametrize
async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
monitor = await async_client.monitors.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
)
assert_matches_type(MonitorCreateResponse, monitor, path=["response"])
@@ -242,7 +242,7 @@ async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
@parametrize
async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) -> None:
response = await async_client.monitors.with_raw_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
)
@@ -255,7 +255,7 @@ async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) ->
@parametrize
async def test_streaming_response_create(self, async_client: AsyncXTwitterScraper) -> None:
async with async_client.monitors.with_streaming_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
username="elonmusk",
) as response:
assert not response.is_closed
diff --git a/tests/api_resources/test_radar.py b/tests/api_resources/test_radar.py
index 341a210..979323c 100644
--- a/tests/api_resources/test_radar.py
+++ b/tests/api_resources/test_radar.py
@@ -27,9 +27,10 @@ def test_method_retrieve_trending_topics(self, client: XTwitterScraper) -> None:
@parametrize
def test_method_retrieve_trending_topics_with_all_params(self, client: XTwitterScraper) -> None:
radar = client.radar.retrieve_trending_topics(
- category="category",
- count=0,
- hours=0,
+ after="after",
+ category="general",
+ hours=1,
+ limit=1,
region="region",
source="github",
)
@@ -73,9 +74,10 @@ async def test_method_retrieve_trending_topics(self, async_client: AsyncXTwitter
@parametrize
async def test_method_retrieve_trending_topics_with_all_params(self, async_client: AsyncXTwitterScraper) -> None:
radar = await async_client.radar.retrieve_trending_topics(
- category="category",
- count=0,
- hours=0,
+ after="after",
+ category="general",
+ hours=1,
+ limit=1,
region="region",
source="github",
)
diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py
index f7f4c0f..07c149b 100644
--- a/tests/api_resources/test_webhooks.py
+++ b/tests/api_resources/test_webhooks.py
@@ -28,7 +28,7 @@ class TestWebhooks:
@parametrize
def test_method_create(self, client: XTwitterScraper) -> None:
webhook = client.webhooks.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
)
assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
@@ -37,7 +37,7 @@ def test_method_create(self, client: XTwitterScraper) -> None:
@parametrize
def test_raw_response_create(self, client: XTwitterScraper) -> None:
response = client.webhooks.with_raw_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
)
@@ -50,7 +50,7 @@ def test_raw_response_create(self, client: XTwitterScraper) -> None:
@parametrize
def test_streaming_response_create(self, client: XTwitterScraper) -> None:
with client.webhooks.with_streaming_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
) as response:
assert not response.is_closed
@@ -278,7 +278,7 @@ class TestAsyncWebhooks:
@parametrize
async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
webhook = await async_client.webhooks.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
)
assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
@@ -287,7 +287,7 @@ async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
@parametrize
async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) -> None:
response = await async_client.webhooks.with_raw_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
)
@@ -300,7 +300,7 @@ async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) ->
@parametrize
async def test_streaming_response_create(self, async_client: AsyncXTwitterScraper) -> None:
async with async_client.webhooks.with_streaming_response.create(
- event_types=["tweet.new", "follower.gained"],
+ event_types=["tweet.new", "tweet.reply"],
url="https://example.com/webhook",
) as response:
assert not response.is_closed
diff --git a/tests/api_resources/test_x.py b/tests/api_resources/test_x.py
index 3ac84a7..781cc84 100644
--- a/tests/api_resources/test_x.py
+++ b/tests/api_resources/test_x.py
@@ -144,6 +144,15 @@ def test_method_get_trends(self, client: XTwitterScraper) -> None:
x = client.x.get_trends()
assert_matches_type(XGetTrendsResponse, x, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_trends_with_all_params(self, client: XTwitterScraper) -> None:
+ x = client.x.get_trends(
+ count=1,
+ woeid=0,
+ )
+ assert_matches_type(XGetTrendsResponse, x, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_get_trends(self, client: XTwitterScraper) -> None:
@@ -294,6 +303,15 @@ async def test_method_get_trends(self, async_client: AsyncXTwitterScraper) -> No
x = await async_client.x.get_trends()
assert_matches_type(XGetTrendsResponse, x, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_trends_with_all_params(self, async_client: AsyncXTwitterScraper) -> None:
+ x = await async_client.x.get_trends(
+ count=1,
+ woeid=0,
+ )
+ assert_matches_type(XGetTrendsResponse, x, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_get_trends(self, async_client: AsyncXTwitterScraper) -> None:
diff --git a/tests/api_resources/x/test_accounts.py b/tests/api_resources/x/test_accounts.py
index 9446fdf..b22fa6e 100644
--- a/tests/api_resources/x/test_accounts.py
+++ b/tests/api_resources/x/test_accounts.py
@@ -231,6 +231,8 @@ def test_method_reauth_with_all_params(self, client: XTwitterScraper) -> None:
account = client.x.accounts.reauth(
id="id",
password="password_value",
+ email="user@example.com",
+ proxy_country="US",
totp_secret="totp_secret_value",
)
assert_matches_type(AccountReauthResponse, account, path=["response"])
@@ -485,6 +487,8 @@ async def test_method_reauth_with_all_params(self, async_client: AsyncXTwitterSc
account = await async_client.x.accounts.reauth(
id="id",
password="password_value",
+ email="user@example.com",
+ proxy_country="US",
totp_secret="totp_secret_value",
)
assert_matches_type(AccountReauthResponse, account, path=["response"])
diff --git a/tests/api_resources/x/test_tweets.py b/tests/api_resources/x/test_tweets.py
index bfb5030..42f4a54 100644
--- a/tests/api_resources/x/test_tweets.py
+++ b/tests/api_resources/x/test_tweets.py
@@ -27,7 +27,6 @@ class TestTweets:
def test_method_create(self, client: XTwitterScraper) -> None:
tweet = client.x.tweets.create(
account="@elonmusk",
- text="Just launched our new feature!",
)
assert_matches_type(TweetCreateResponse, tweet, path=["response"])
@@ -36,12 +35,13 @@ def test_method_create(self, client: XTwitterScraper) -> None:
def test_method_create_with_all_params(self, client: XTwitterScraper) -> None:
tweet = client.x.tweets.create(
account="@elonmusk",
- text="Just launched our new feature!",
attachment_url="https://x.com/elonmusk/status/1234567890",
community_id="1500000000000000000",
is_note_tweet=False,
+ media=["https://example.com/image.jpg"],
media_ids=["1234567890123456789"],
reply_to_tweet_id="1234567890",
+ text="Just launched our new feature!",
)
assert_matches_type(TweetCreateResponse, tweet, path=["response"])
@@ -50,7 +50,6 @@ def test_method_create_with_all_params(self, client: XTwitterScraper) -> None:
def test_raw_response_create(self, client: XTwitterScraper) -> None:
response = client.x.tweets.with_raw_response.create(
account="@elonmusk",
- text="Just launched our new feature!",
)
assert response.is_closed is True
@@ -63,7 +62,6 @@ def test_raw_response_create(self, client: XTwitterScraper) -> None:
def test_streaming_response_create(self, client: XTwitterScraper) -> None:
with client.x.tweets.with_streaming_response.create(
account="@elonmusk",
- text="Just launched our new feature!",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -513,7 +511,6 @@ class TestAsyncTweets:
async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
tweet = await async_client.x.tweets.create(
account="@elonmusk",
- text="Just launched our new feature!",
)
assert_matches_type(TweetCreateResponse, tweet, path=["response"])
@@ -522,12 +519,13 @@ async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncXTwitterScraper) -> None:
tweet = await async_client.x.tweets.create(
account="@elonmusk",
- text="Just launched our new feature!",
attachment_url="https://x.com/elonmusk/status/1234567890",
community_id="1500000000000000000",
is_note_tweet=False,
+ media=["https://example.com/image.jpg"],
media_ids=["1234567890123456789"],
reply_to_tweet_id="1234567890",
+ text="Just launched our new feature!",
)
assert_matches_type(TweetCreateResponse, tweet, path=["response"])
@@ -536,7 +534,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncXTwitterSc
async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) -> None:
response = await async_client.x.tweets.with_raw_response.create(
account="@elonmusk",
- text="Just launched our new feature!",
)
assert response.is_closed is True
@@ -549,7 +546,6 @@ async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) ->
async def test_streaming_response_create(self, async_client: AsyncXTwitterScraper) -> None:
async with async_client.x.tweets.with_streaming_response.create(
account="@elonmusk",
- text="Just launched our new feature!",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/x/test_users.py b/tests/api_resources/x/test_users.py
index 3ed2323..2d17796 100644
--- a/tests/api_resources/x/test_users.py
+++ b/tests/api_resources/x/test_users.py
@@ -9,10 +9,7 @@
from tests.utils import assert_matches_type
from x_twitter_scraper import XTwitterScraper, AsyncXTwitterScraper
-from x_twitter_scraper.types.x import (
- UserProfile,
-)
-from x_twitter_scraper.types.shared import PaginatedUsers, PaginatedTweets
+from x_twitter_scraper.types.shared import UserProfile, PaginatedUsers, PaginatedTweets
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
diff --git a/tests/conftest.py b/tests/conftest.py
index be384ff..4083279 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -46,7 +46,6 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "My API Key"
-bearer_token = "My Bearer Token"
@pytest.fixture(scope="session")
@@ -55,9 +54,7 @@ def client(request: FixtureRequest) -> Iterator[XTwitterScraper]:
if not isinstance(strict, bool):
raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
- with XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=strict
- ) as client:
+ with XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client:
yield client
@@ -82,10 +79,6 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncXTwitterSc
raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
async with AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=strict,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client
) as client:
yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index 0d36937..af6b57a 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -40,7 +40,6 @@
T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "My API Key"
-bearer_token = "My Bearer Token"
def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
@@ -141,10 +140,6 @@ def test_copy(self, client: XTwitterScraper) -> None:
assert copied.api_key == "another My API Key"
assert client.api_key == "My API Key"
- copied = client.copy(bearer_token="another My Bearer Token")
- assert copied.bearer_token == "another My Bearer Token"
- assert client.bearer_token == "My Bearer Token"
-
def test_copy_default_options(self, client: XTwitterScraper) -> None:
# options that have a default are overridden correctly
copied = client.copy(max_retries=7)
@@ -163,11 +158,7 @@ def test_copy_default_options(self, client: XTwitterScraper) -> None:
def test_copy_default_headers(self) -> None:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
assert client.default_headers["X-Foo"] == "bar"
@@ -202,11 +193,7 @@ def test_copy_default_headers(self) -> None:
def test_copy_default_query(self) -> None:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_query={"foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"}
)
assert _get_params(client)["foo"] == "bar"
@@ -332,11 +319,7 @@ def test_request_timeout(self, client: XTwitterScraper) -> None:
def test_client_timeout_option(self) -> None:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- timeout=httpx.Timeout(0),
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -349,11 +332,7 @@ def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
with httpx.Client(timeout=None) as http_client:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -365,11 +344,7 @@ def test_http_client_timeout_option(self) -> None:
# no timeout given to the httpx client should not use the httpx default
with httpx.Client() as http_client:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -381,11 +356,7 @@ def test_http_client_timeout_option(self) -> None:
# explicitly passing the default timeout currently results in it being ignored
with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -400,18 +371,13 @@ async def test_invalid_http_client(self) -> None:
XTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=cast(Any, http_client),
)
def test_default_headers_option(self) -> None:
test_client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
@@ -420,7 +386,6 @@ def test_default_headers_option(self) -> None:
test_client2 = XTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
default_headers={
"X-Foo": "stainless",
@@ -435,21 +400,12 @@ def test_default_headers_option(self) -> None:
test_client2.close()
def test_validate_headers(self) -> None:
- client = XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ client = XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("X-Api-Key") == api_key
- with update_env(
- **{
- "X_TWITTER_SCRAPER_API_KEY": Omit(),
- "X_TWITTER_SCRAPER_BEARER_TOKEN": Omit(),
- }
- ):
- client2 = XTwitterScraper(
- base_url=base_url, api_key=None, bearer_token=None, _strict_response_validation=True
- )
+ with update_env(**{"X_TWITTER_SCRAPER_API_KEY": Omit()}):
+ client2 = XTwitterScraper(base_url=base_url, api_key=None, _strict_response_validation=True)
with pytest.raises(
TypeError,
@@ -462,11 +418,7 @@ def test_validate_headers(self) -> None:
def test_default_query_option(self) -> None:
client = XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_query={"query_param": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
url = httpx.URL(request.url)
@@ -663,7 +615,6 @@ def mock_handler(request: httpx.Request) -> httpx.Response:
with XTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
) as client:
@@ -762,10 +713,7 @@ class Model(BaseModel):
def test_base_url_setter(self) -> None:
client = XTwitterScraper(
- base_url="https://example.com/from_init",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True
)
assert client.base_url == "https://example.com/from_init/"
@@ -777,22 +725,18 @@ def test_base_url_setter(self) -> None:
def test_base_url_env(self) -> None:
with update_env(X_TWITTER_SCRAPER_BASE_URL="http://localhost:5000/from/env"):
- client = XTwitterScraper(api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True)
+ client = XTwitterScraper(api_key=api_key, _strict_response_validation=True)
assert client.base_url == "http://localhost:5000/from/env/"
@pytest.mark.parametrize(
"client",
[
XTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
XTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -814,15 +758,11 @@ def test_base_url_trailing_slash(self, client: XTwitterScraper) -> None:
"client",
[
XTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
XTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -844,15 +784,11 @@ def test_base_url_no_trailing_slash(self, client: XTwitterScraper) -> None:
"client",
[
XTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
XTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -871,9 +807,7 @@ def test_absolute_request_url(self, client: XTwitterScraper) -> None:
client.close()
def test_copied_client_does_not_close_http(self) -> None:
- test_client = XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ test_client = XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert not test_client.is_closed()
copied = test_client.copy()
@@ -884,9 +818,7 @@ def test_copied_client_does_not_close_http(self) -> None:
assert not test_client.is_closed()
def test_client_context_manager(self) -> None:
- test_client = XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ test_client = XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with test_client as c2:
assert c2 is test_client
assert not c2.is_closed()
@@ -908,11 +840,7 @@ class Model(BaseModel):
def test_client_max_retries_validation(self) -> None:
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
XTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- max_retries=cast(Any, None),
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)
)
@pytest.mark.respx(base_url=base_url)
@@ -922,16 +850,12 @@ class Model(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
- strict_client = XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ strict_client = XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with pytest.raises(APIResponseValidationError):
strict_client.get("/foo", cast_to=Model)
- non_strict_client = XTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=False
- )
+ non_strict_client = XTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=False)
response = non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
@@ -1150,10 +1074,6 @@ def test_copy(self, async_client: AsyncXTwitterScraper) -> None:
assert copied.api_key == "another My API Key"
assert async_client.api_key == "My API Key"
- copied = async_client.copy(bearer_token="another My Bearer Token")
- assert copied.bearer_token == "another My Bearer Token"
- assert async_client.bearer_token == "My Bearer Token"
-
def test_copy_default_options(self, async_client: AsyncXTwitterScraper) -> None:
# options that have a default are overridden correctly
copied = async_client.copy(max_retries=7)
@@ -1172,11 +1092,7 @@ def test_copy_default_options(self, async_client: AsyncXTwitterScraper) -> None:
async def test_copy_default_headers(self) -> None:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
assert client.default_headers["X-Foo"] == "bar"
@@ -1211,11 +1127,7 @@ async def test_copy_default_headers(self) -> None:
async def test_copy_default_query(self) -> None:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_query={"foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"}
)
assert _get_params(client)["foo"] == "bar"
@@ -1343,11 +1255,7 @@ async def test_request_timeout(self, async_client: AsyncXTwitterScraper) -> None
async def test_client_timeout_option(self) -> None:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- timeout=httpx.Timeout(0),
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1360,11 +1268,7 @@ async def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
async with httpx.AsyncClient(timeout=None) as http_client:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1376,11 +1280,7 @@ async def test_http_client_timeout_option(self) -> None:
# no timeout given to the httpx client should not use the httpx default
async with httpx.AsyncClient() as http_client:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1392,11 +1292,7 @@ async def test_http_client_timeout_option(self) -> None:
# explicitly passing the default timeout currently results in it being ignored
async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- http_client=http_client,
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1411,18 +1307,13 @@ def test_invalid_http_client(self) -> None:
AsyncXTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=cast(Any, http_client),
)
async def test_default_headers_option(self) -> None:
test_client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
@@ -1431,7 +1322,6 @@ async def test_default_headers_option(self) -> None:
test_client2 = AsyncXTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
default_headers={
"X-Foo": "stainless",
@@ -1446,21 +1336,12 @@ async def test_default_headers_option(self) -> None:
await test_client2.close()
def test_validate_headers(self) -> None:
- client = AsyncXTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ client = AsyncXTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("X-Api-Key") == api_key
- with update_env(
- **{
- "X_TWITTER_SCRAPER_API_KEY": Omit(),
- "X_TWITTER_SCRAPER_BEARER_TOKEN": Omit(),
- }
- ):
- client2 = AsyncXTwitterScraper(
- base_url=base_url, api_key=None, bearer_token=None, _strict_response_validation=True
- )
+ with update_env(**{"X_TWITTER_SCRAPER_API_KEY": Omit()}):
+ client2 = AsyncXTwitterScraper(base_url=base_url, api_key=None, _strict_response_validation=True)
with pytest.raises(
TypeError,
@@ -1473,11 +1354,7 @@ def test_validate_headers(self) -> None:
async def test_default_query_option(self) -> None:
client = AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- default_query={"query_param": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
url = httpx.URL(request.url)
@@ -1674,7 +1551,6 @@ async def mock_handler(request: httpx.Request) -> httpx.Response:
async with AsyncXTwitterScraper(
base_url=base_url,
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
) as client:
@@ -1775,10 +1651,7 @@ class Model(BaseModel):
async def test_base_url_setter(self) -> None:
client = AsyncXTwitterScraper(
- base_url="https://example.com/from_init",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True
)
assert client.base_url == "https://example.com/from_init/"
@@ -1790,22 +1663,18 @@ async def test_base_url_setter(self) -> None:
async def test_base_url_env(self) -> None:
with update_env(X_TWITTER_SCRAPER_BASE_URL="http://localhost:5000/from/env"):
- client = AsyncXTwitterScraper(api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True)
+ client = AsyncXTwitterScraper(api_key=api_key, _strict_response_validation=True)
assert client.base_url == "http://localhost:5000/from/env/"
@pytest.mark.parametrize(
"client",
[
AsyncXTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncXTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1827,15 +1696,11 @@ async def test_base_url_trailing_slash(self, client: AsyncXTwitterScraper) -> No
"client",
[
AsyncXTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncXTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1857,15 +1722,11 @@ async def test_base_url_no_trailing_slash(self, client: AsyncXTwitterScraper) ->
"client",
[
AsyncXTwitterScraper(
- base_url="http://localhost:5000/custom/path/",
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncXTwitterScraper(
base_url="http://localhost:5000/custom/path/",
api_key=api_key,
- bearer_token=bearer_token,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1884,9 +1745,7 @@ async def test_absolute_request_url(self, client: AsyncXTwitterScraper) -> None:
await client.close()
async def test_copied_client_does_not_close_http(self) -> None:
- test_client = AsyncXTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ test_client = AsyncXTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert not test_client.is_closed()
copied = test_client.copy()
@@ -1898,9 +1757,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
assert not test_client.is_closed()
async def test_client_context_manager(self) -> None:
- test_client = AsyncXTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ test_client = AsyncXTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
async with test_client as c2:
assert c2 is test_client
assert not c2.is_closed()
@@ -1924,11 +1781,7 @@ class Model(BaseModel):
async def test_client_max_retries_validation(self) -> None:
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
AsyncXTwitterScraper(
- base_url=base_url,
- api_key=api_key,
- bearer_token=bearer_token,
- _strict_response_validation=True,
- max_retries=cast(Any, None),
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)
)
@pytest.mark.respx(base_url=base_url)
@@ -1938,16 +1791,12 @@ class Model(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
- strict_client = AsyncXTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=True
- )
+ strict_client = AsyncXTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with pytest.raises(APIResponseValidationError):
await strict_client.get("/foo", cast_to=Model)
- non_strict_client = AsyncXTwitterScraper(
- base_url=base_url, api_key=api_key, bearer_token=bearer_token, _strict_response_validation=False
- )
+ non_strict_client = AsyncXTwitterScraper(base_url=base_url, api_key=api_key, _strict_response_validation=False)
response = await non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py
deleted file mode 100644
index 66bd733..0000000
--- a/tests/test_deepcopy.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from x_twitter_scraper._utils import deepcopy_minimal
-
-
-def assert_different_identities(obj1: object, obj2: object) -> None:
- assert obj1 == obj2
- assert id(obj1) != id(obj2)
-
-
-def test_simple_dict() -> None:
- obj1 = {"foo": "bar"}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_dict() -> None:
- obj1 = {"foo": {"bar": True}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
-
-
-def test_complex_nested_dict() -> None:
- obj1 = {"foo": {"bar": [{"hello": "world"}]}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
- assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"])
- assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0])
-
-
-def test_simple_list() -> None:
- obj1 = ["a", "b", "c"]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_list() -> None:
- obj1 = ["a", [1, 2, 3]]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1[1], obj2[1])
-
-
-class MyObject: ...
-
-
-def test_ignores_other_types() -> None:
- # custom classes
- my_obj = MyObject()
- obj1 = {"foo": my_obj}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert obj1["foo"] is my_obj
-
- # tuples
- obj3 = ("a", "b")
- obj4 = deepcopy_minimal(obj3)
- assert obj3 is obj4
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
index dffec1f..7a9bf3b 100644
--- a/tests/test_extract_files.py
+++ b/tests/test_extract_files.py
@@ -35,6 +35,15 @@ def test_multiple_files() -> None:
assert query == {"documents": [{}, {}]}
+def test_top_level_file_array() -> None:
+ query = {"files": [b"file one", b"file two"], "title": "hello"}
+ assert extract_files(query, paths=[["files", ""]]) == [
+ ("files[]", b"file one"),
+ ("files[]", b"file two"),
+ ]
+ assert query == {"title": "hello"}
+
+
@pytest.mark.parametrize(
"query,paths,expected",
[
diff --git a/tests/test_files.py b/tests/test_files.py
index b72b88e..34013ae 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -4,7 +4,8 @@
import pytest
from dirty_equals import IsDict, IsList, IsBytes, IsTuple
-from x_twitter_scraper._files import to_httpx_files, async_to_httpx_files
+from x_twitter_scraper._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files
+from x_twitter_scraper._utils import extract_files
readme_path = Path(__file__).parent.parent.joinpath("README.md")
@@ -49,3 +50,99 @@ def test_string_not_allowed() -> None:
"file": "foo", # type: ignore
}
)
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+ assert obj1 == obj2
+ assert obj1 is not obj2
+
+
+class TestDeepcopyWithPaths:
+ def test_copies_top_level_dict(self) -> None:
+ original = {"file": b"data", "other": "value"}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+
+ def test_file_value_is_same_reference(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+ assert result["file"] is file_bytes
+
+ def test_list_popped_wholesale(self) -> None:
+ files = [b"f1", b"f2"]
+ original = {"files": files, "title": "t"}
+ result = deepcopy_with_paths(original, [["files", ""]])
+ assert_different_identities(result, original)
+ result_files = result["files"]
+ assert isinstance(result_files, list)
+ assert_different_identities(result_files, files)
+
+ def test_nested_array_path_copies_list_and_elements(self) -> None:
+ elem1 = {"file": b"f1", "extra": 1}
+ elem2 = {"file": b"f2", "extra": 2}
+ original = {"items": [elem1, elem2]}
+ result = deepcopy_with_paths(original, [["items", "", "file"]])
+ assert_different_identities(result, original)
+ result_items = result["items"]
+ assert isinstance(result_items, list)
+ assert_different_identities(result_items, original["items"])
+ assert_different_identities(result_items[0], elem1)
+ assert_different_identities(result_items[1], elem2)
+
+ def test_empty_paths_returns_same_object(self) -> None:
+ original = {"foo": "bar"}
+ result = deepcopy_with_paths(original, [])
+ assert result is original
+
+ def test_multiple_paths(self) -> None:
+ f1 = b"file1"
+ f2 = b"file2"
+ original = {"a": f1, "b": f2, "c": "unchanged"}
+ result = deepcopy_with_paths(original, [["a"], ["b"]])
+ assert_different_identities(result, original)
+ assert result["a"] is f1
+ assert result["b"] is f2
+ assert result["c"] is original["c"]
+
+ def test_extract_files_does_not_mutate_original_top_level(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes, "other": "value"}
+
+ copied = deepcopy_with_paths(original, [["file"]])
+ extracted = extract_files(copied, paths=[["file"]])
+
+ assert extracted == [("file", file_bytes)]
+ assert original == {"file": file_bytes, "other": "value"}
+ assert copied == {"other": "value"}
+
+ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None:
+ file1 = b"f1"
+ file2 = b"f2"
+ original = {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+
+ copied = deepcopy_with_paths(original, [["items", "", "file"]])
+ extracted = extract_files(copied, paths=[["items", "", "file"]])
+
+ assert extracted == [("items[][file]", file1), ("items[][file]", file2)]
+ assert original == {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+ assert copied == {
+ "items": [
+ {"extra": 1},
+ {"extra": 2},
+ ],
+ "title": "example",
+ }