Skip to content

Conversation

@bakatrouble
Copy link

@bakatrouble bakatrouble commented Sep 11, 2025

Summary by Sourcery

Add support for fetching and modeling profile spotlights via a new GraphQL query.

New Features:

  • Introduce ProfileSpotlights class to encapsulate spotlight attributes
  • Add GRAPHQL PROFILE_SPOTLIGHTS endpoint and GQLClient.profile_spotlights method
  • Implement Client.get_profile_spotlights and User.get_profile_spotlights to expose the query

Summary by CodeRabbit

  • New Features
    • Added support to retrieve profile spotlight information for any account by screen/display name.
    • Available from both the main client and user instances for convenient access.
    • Returns key details such as privacy status, follow/block relationships, verification status, and basic profile identifiers.

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 11, 2025

Reviewer's Guide

This PR adds support for retrieving profile spotlights by defining a new GraphQL query endpoint, extending both the low-level GQL client and the high-level client with fetch methods, introducing a ProfileSpotlights model to parse the API response, and exposing it through a User accessor.

Sequence diagram for fetching profile spotlights via User

sequenceDiagram
    participant U as User
    participant C as Client
    participant G as GQLClient
    participant S as ProfileSpotlights
    U->>C: get_profile_spotlights(screen_name)
    C->>G: profile_spotlights(screen_name)
    G-->>C: GraphQL response
    C->>S: ProfileSpotlights(result)
    C-->>U: ProfileSpotlights instance
Loading

Entity relationship diagram for ProfileSpotlights data structure

erDiagram
    PROFILE_SPOTLIGHTS {
        string rest_id
        string name
        string screen_name
        boolean is_verified_organisation
        boolean blocking
        boolean blocked_by
        boolean following
        boolean followed_by
        object protected
    }
    PROFILE_SPOTLIGHTS ||--o| USER : "belongs to"
    USER {
        string screen_name
        string id
    }
Loading

Class diagram for new ProfileSpotlights model and related methods

classDiagram
    class ProfileSpotlights {
        +__init__(data: dict)
        +protected
        +blocking
        +blocked_by
        +following
        +followed_by
        +is_verified_organisation
        +rest_id
        +name
        +screen_name
    }

    class User {
        +get_profile_spotlights() ProfileSpotlights
    }

    class Client {
        +get_profile_spotlights(display_name: str) ProfileSpotlights
    }

    class GQLClient {
        +profile_spotlights(screen_name: str)
    }

    User --> Client : uses
    Client --> GQLClient : uses
    Client --> ProfileSpotlights : returns
    User --> ProfileSpotlights : returns
Loading

File-Level Changes

Change Details Files
Integrate profile spotlights GraphQL query
  • Add PROFILE_SPOTLIGHTS URL constant
  • Implement GQLClient.profile_spotlights method to call the new endpoint
twikit/client/gql.py
Expose client-level fetch method for spotlights
  • Add Client.get_profile_spotlights to invoke GQLClient and parse response
  • Use find_dict to extract result before model instantiation
twikit/client/client.py
Introduce ProfileSpotlights data model
  • Define ProfileSpotlights class parsing privacy, relationship_perspectives, and core fields
  • Map API fields to attributes like protected, blocking, following, name, and screen_name
twikit/user.py
Add User-level accessor for profile spotlights
  • Implement User.get_profile_spotlights to delegate to the client
  • Return a ProfileSpotlights instance based on screen_name
twikit/user.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds a new Profile Spotlights feature: defines a ProfileSpotlights data model, exposes Client.get_profile_spotlights(display_name) to fetch data via a new GQL endpoint, and adds User.get_profile_spotlights() as a convenience wrapper.

Changes

Cohort / File(s) Summary
Client API: Profile Spotlights
twikit/client/client.py
Adds Client.get_profile_spotlights(display_name) that calls self.gql.profile_spotlights(display_name), extracts the first result item, and returns a ProfileSpotlights instance; imports ProfileSpotlights.
GraphQL Endpoint & Method
twikit/client/gql.py
Introduces Endpoint.PROFILE_SPOTLIGHTS (path 1sAf0uU4-B2ZLJGUX5O7LQ/ProfileSpotlightsQuery) and GQLClient.profile_spotlights(screen_name) using gql_get with {'screen_name': screen_name}.
User Model & Convenience Method
twikit/user.py
Adds ProfileSpotlights data class parsing relationship/privacy/core fields; adds User.get_profile_spotlights() delegating to the client with self.screen_name.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Caller
  participant User as User
  participant Client as Client
  participant GQL as GQLClient
  participant API as GraphQL API

  Dev->>User: await user.get_profile_spotlights()
  User->>Client: get_profile_spotlights(screen_name)
  Client->>GQL: profile_spotlights(screen_name)
  GQL->>API: gql_get(PROFILE_SPOTLIGHTS, {screen_name})
  API-->>GQL: JSON { data: { result: [...] } }
  GQL-->>Client: data
  Client->>Client: extract first result
  Client-->>User: ProfileSpotlights(instance)
  User-->>Dev: ProfileSpotlights
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "add profile spotlights query" accurately and concisely describes the primary change in the changeset (adding a profile spotlights GraphQL query and associated client/user methods) and is focused on the main feature rather than unrelated files or vague wording. It is short, relevant, and understandable to a reviewer scanning the PR history.

Poem

I twitch my whiskers, fetch the lights,
New spotlights bloom on profile nights—
Hop to the client, down the GQL lane,
Nibble the JSON, carrots of plain,
Wrap it snug in a dataclass coat,
Return with a thump and a cheerful note. 🥕🐇

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b18105 and 750c09c.

📒 Files selected for processing (3)
  • twikit/client/client.py (2 hunks)
  • twikit/client/gql.py (2 hunks)
  • twikit/user.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
twikit/user.py (1)
twikit/client/client.py (2)
  • get (209-211)
  • get_profile_spotlights (4326-4329)
twikit/client/client.py (3)
twikit/user.py (2)
  • ProfileSpotlights (17-32)
  • get_profile_spotlights (528-529)
twikit/client/gql.py (1)
  • profile_spotlights (686-688)
twikit/utils.py (1)
  • find_dict (111-127)
twikit/client/gql.py (1)
twikit/client/v11.py (1)
  • Endpoint (14-50)
🔇 Additional comments (4)
twikit/client/gql.py (1)

102-102: LGTM: endpoint added correctly.

Constant name and path look consistent with existing endpoints.

twikit/client/client.py (1)

49-49: LGTM: type import is correct.

Public return type wired up properly.

twikit/user.py (2)

528-530: LGTM, but depends on the client rename.

Wrapper is fine; will align once Client.get_profile_spotlights(screen_name) is renamed.

After renaming in Client, ensure this call site still type-checks and runs.


17-33: Fix protected default type; add org spelling fallback.

  • protected should default to a bool, not {}.
  • Keep the is_verified_organisation attribute but fall back to the US spelling key.

Apply:

 class ProfileSpotlights:
     def __init__(self, data: dict):
         self._data = data
         relationship_perspectives = data.get('relationship_perspectives', {})
         privacy = data.get('privacy', {})
         core = data.get('core', {})

-        self.protected = privacy.get('protected', {})
+        self.protected = bool(privacy.get('protected', False))
         self.blocking = relationship_perspectives.get('blocking', False)
         self.blocked_by = relationship_perspectives.get('blocked_by', False)
         self.following = relationship_perspectives.get('following', False)
         self.followed_by = relationship_perspectives.get('followed_by', False)
-        self.is_verified_organisation = data.get('is_verified_organisation', False)
+        self.is_verified_organisation = data.get(
+            'is_verified_organisation',
+            data.get('is_verified_organization', False)
+        )
         self.rest_id = data.get('rest_id', None)
         self.name = core.get('name', None)
         self.screen_name = core.get('screen_name', None)

If you have a sample profile_spotlights payload, confirm whether name/screen_name live under core (as used here) or under legacy so mappings are correct.

Comment on lines +4326 to +4329
async def get_profile_spotlights(self, display_name: str) -> ProfileSpotlights:
response, _ = await self.gql.profile_spotlights(display_name)
result = find_dict(response, 'result', find_one=True)[0]
return ProfileSpotlights(result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Rename param to screen_name and handle empty/error responses.

“display_name” is misleading (API expects screen_name). Also avoid IndexError when no result is present and surface API errors consistently.

Apply:

-async def get_profile_spotlights(self, display_name: str) -> ProfileSpotlights:
-    response, _ = await self.gql.profile_spotlights(display_name)
-    result = find_dict(response, 'result', find_one=True)[0]
-    return ProfileSpotlights(result)
+async def get_profile_spotlights(self, screen_name: str) -> ProfileSpotlights:
+    response, _ = await self.gql.profile_spotlights(screen_name)
+    if isinstance(response, dict) and 'errors' in response:
+        raise_exceptions_from_response(response['errors'])
+    result_ = find_dict(response, 'result', find_one=True)
+    if not result_:
+        raise TwitterException(f'Profile spotlights not found for screen_name: {screen_name}')
+    return ProfileSpotlights(result_[0])

Run to ensure no callers rely on the old param name:


🏁 Script executed:

#!/bin/bash
rg -nC2 '\bget_profile_spotlights\s*\('

Length of output: 781


Rename parameter to screen_name and handle empty/error responses

Change the arg name and add explicit API-error / empty-result handling to avoid IndexError. Callers: twikit/user.py:528 calls self._client.get_profile_spotlights(self.screen_name) (positional) — safe to rename.

-async def get_profile_spotlights(self, display_name: str) -> ProfileSpotlights:
-    response, _ = await self.gql.profile_spotlights(display_name)
-    result = find_dict(response, 'result', find_one=True)[0]
-    return ProfileSpotlights(result)
+async def get_profile_spotlights(self, screen_name: str) -> ProfileSpotlights:
+    response, _ = await self.gql.profile_spotlights(screen_name)
+    if isinstance(response, dict) and 'errors' in response:
+        raise_exceptions_from_response(response['errors'])
+    result_ = find_dict(response, 'result', find_one=True)
+    if not result_:
+        raise TwitterException(f'Profile spotlights not found for screen_name: {screen_name}')
+    return ProfileSpotlights(result_[0])

Location: twikit/client/client.py:4326-4329; caller: twikit/user.py:528.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def get_profile_spotlights(self, display_name: str) -> ProfileSpotlights:
response, _ = await self.gql.profile_spotlights(display_name)
result = find_dict(response, 'result', find_one=True)[0]
return ProfileSpotlights(result)
async def get_profile_spotlights(self, screen_name: str) -> ProfileSpotlights:
response, _ = await self.gql.profile_spotlights(screen_name)
if isinstance(response, dict) and 'errors' in response:
raise_exceptions_from_response(response['errors'])
result_ = find_dict(response, 'result', find_one=True)
if not result_:
raise TwitterException(f'Profile spotlights not found for screen_name: {screen_name}')
return ProfileSpotlights(result_[0])
🤖 Prompt for AI Agents
In twikit/client/client.py around lines 4326-4329, rename the method parameter
from display_name to screen_name (callers use positional so this is safe) and
add explicit handling for API errors and empty results: call
self.gql.profile_spotlights(screen_name), check for an error condition in the
response and raise a descriptive exception (or return None) if present, then use
find_dict and verify the returned list is non-empty before indexing to avoid
IndexError; if the list is empty raise/return a clear API error indicating "no
profile spotlights found for {screen_name}" so callers get a deterministic
failure instead of an IndexError.

Comment on lines +686 to +689
async def profile_spotlights(self, screen_name):
variables = {'screen_name': screen_name}
return await self.gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass USER_FEATURES and add a type hint for robustness.

Some GQL endpoints require feature gates; align with user queries and add typing.

Apply:

-async def profile_spotlights(self, screen_name):
-    variables = {'screen_name': screen_name}
-    return await self.gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables)
+async def profile_spotlights(self, screen_name: str):
+    variables = {'screen_name': screen_name}
+    return await self.gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables, USER_FEATURES)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def profile_spotlights(self, screen_name):
variables = {'screen_name': screen_name}
return await self.gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables)
async def profile_spotlights(self, screen_name: str):
variables = {'screen_name': screen_name}
return await self.gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables, USER_FEATURES)
🤖 Prompt for AI Agents
In twikit/client/gql.py around lines 686-689, update the profile_spotlights
method to accept and pass feature gates and add typing: change the signature to
include a user_features parameter (typed Optional[dict | Any] or appropriate
feature type) with a default of USER_FEATURES and add a return type hint (e.g.
-> Any or -> dict); then include user_features when calling self.gql_get (so
gql_get(Endpoint.PROFILE_SPOTLIGHTS, variables, user_features=user_features)).
Ensure any necessary imports for typing (Optional, Any) and the USER_FEATURES
symbol are present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant