Skip to content

Improve sprite stubs #3525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Improve sprite stubs #3525

wants to merge 12 commits into from

Conversation

aatle
Copy link
Contributor

@aatle aatle commented Jul 1, 2025

Typing fixes, simplifications, improvements, and renames for the sprite module.
Also see #3488, which had other fixes.

In the future, the Optional might get removed from image and rect, possibly with runtime changes. This would fix the last sprite typing issue and make it as comfortable to use static typing with as other pygame modules.

Summary by CodeRabbit

  • New Features

    • Add/remove methods now accept nested iterables of sprites or groups for more flexible usage.
  • Refactor

    • Unified, consistent generic typing across sprite and group APIs for clearer, broader compatibility.
    • Group and sprite APIs now return consistent list/dict types for queries and iteration.
    • Collision helpers standardized to a single rect-based interface for improved interoperability.
  • Bug Fixes

    • Draw methods accept optional special flags where applicable.
  • Documentation

    • Clarified collision docstrings and noted a deprecated alias.

@aatle aatle requested a review from a team as a code owner July 1, 2025 22:14
@aatle aatle added type hints Type hint checking related tasks sprite pygame.sprite labels Jul 2, 2025
Copy link
Member

@ankith26 ankith26 left a comment

Choose a reason for hiding this comment

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

LGTM (though I havent rigorously tested this).

Left a few minor observations but nothing that should block a merge

@aatle
Copy link
Contributor Author

aatle commented Aug 18, 2025

@coderabbitai full review please

Copy link

coderabbitai bot commented Aug 18, 2025

@aatle I'll conduct a full review of the sprite module improvements in this PR.

✅ Actions performed

Full review triggered.

Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

Refactors pygame.sprite type stubs to a single generic Sprite parameterization (_SpriteT), replaces legacy protocols/typevars, introduces _GroupOrGroups/_SpriteOrSprites, updates class bases and method signatures across Sprite, groups, layered groups, and collision helpers, and makes _HasMask mask a method.

Changes

Cohort / File(s) Summary of edits
Core generics & aliases
buildconfig/stubs/pygame/sprite.pyi
Replace _TSprite/_TSprite2/_TDirtySprite with _SpriteT/_SpriteT2/_DirtySpriteT; add _GroupOrGroups, _SpriteOrSprites; rename _THasRect_HasRectT; update spritedict and iterator generics.
Sprite & DirtySprite
buildconfig/stubs/pygame/sprite.pyi
Sprite now extends _HasImageAndRect; constructor/add/remove/add_internal/remove_internal/groups signatures accept/return new generic types (_GroupOrGroups/AbstractGroup[_SpriteT]); DirtySprite no longer mixes _SupportsDirtySprite.
AbstractGroup / Group / basic collections
buildconfig/stubs/pygame/sprite.pyi
AbstractGroup parameterized by _SpriteT; add/remove/has and internal methods use _SpriteOrSprites[_SpriteT]; Group, RenderPlain, RenderClear, RenderUpdates, OrderedUpdates updated to _SpriteT generics and matching constructors/returns.
Layered groups (LayeredUpdates / LayeredDirty / GroupSingle)
buildconfig/stubs/pygame/sprite.pyi
LayeredUpdates genericized to _SpriteT with constructors/add/remove/has and layer APIs returning/accepting _SpriteT; LayeredDirty bound to _DirtySpriteT, draw uses special_flags: Optional[int], clear kept with type-ignore; GroupSingle.sprite typed Optional[_SpriteT].
Collision & proximity helpers
buildconfig/stubs/pygame/sprite.pyi
spritecollide, groupcollide, spritecollideany signatures use _HasRectT, _SpriteT/_SpriteT2 and return lists/dicts of _SpriteT types; _SupportsCollideCircle simplified to _HasRect.
Protocols & docs
buildconfig/stubs/pygame/sprite.pyi
_HasMaskAndRect.mask changed from attribute to method def mask(self) -> Mask: ...; removed/merged legacy protocols/aliases; minor collision docstring grammar tweaks and deprecation note for set_timing_treshold.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I thump my paw on type-lined ground,
One generic sprite in every bound.
Masks now call and layers dance,
Groups align in tidy stance.
A rabbit noses stubs to rhyme—swift, neat, profound. 🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

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

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 1

♻️ Duplicate comments (1)
buildconfig/stubs/pygame/sprite.pyi (1)

171-173: Simplifying _SupportsCollideCircle to _HasRect is fine

The union was redundant; code only requires .rect, with .radius optional per docs. The simplification avoids confusion.

🧹 Nitpick comments (2)
buildconfig/stubs/pygame/sprite.pyi (2)

66-66: Loosen Sprite.groups() return type to avoid generic invariance friction

AbstractGroup is invariant in its type parameter. Returning list[AbstractGroup[Sprite]] will reject groups parameterized with a Sprite subclass (e.g., AbstractGroup[DirtySprite]). list[AbstractGroup[Any]] avoids this.

Apply this diff:

-    def groups(self) -> list[AbstractGroup[Sprite]]: ...
+    def groups(self) -> list[AbstractGroup[Any]]: ...

195-212: Tighten collided callback return type to bool

The documentation and implementations treat collided callbacks as boolean predicates. Using Any obscures misuse.

Apply this diff:

 def spritecollide(
-    sprite: _HasRectT,
-    group: AbstractGroup[_SpriteT],
-    dokill: bool,
-    collided: Optional[Callable[[_HasRectT, _SpriteT], Any]] = None,
-) -> list[_SpriteT]: ...
+    sprite: _HasRectT,
+    group: AbstractGroup[_SpriteT],
+    dokill: bool,
+    collided: Optional[Callable[[_HasRectT, _SpriteT], bool]] = None,
+) -> list[_SpriteT]: ...

 def groupcollide(
-    groupa: AbstractGroup[_SpriteT],
-    groupb: AbstractGroup[_SpriteT2],
+    groupa: AbstractGroup[_SpriteT],
+    groupb: AbstractGroup[_SpriteT2],
     dokilla: bool,
     dokillb: bool,
-    collided: Optional[Callable[[_SpriteT, _SpriteT2], Any]] = None,
+    collided: Optional[Callable[[_SpriteT, _SpriteT2], bool]] = None,
 ) -> dict[_SpriteT, list[_SpriteT2]]: ...

 def spritecollideany(
-    sprite: _HasRectT,
-    group: AbstractGroup[_SpriteT],
-    collided: Optional[Callable[[_HasRectT, _SpriteT], Any]] = None,
+    sprite: _HasRectT,
+    group: AbstractGroup[_SpriteT],
+    collided: Optional[Callable[[_HasRectT, _SpriteT], bool]] = None,
 ) -> Optional[_SpriteT]: ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 501a59e and c31a63c.

📒 Files selected for processing (1)
  • buildconfig/stubs/pygame/sprite.pyi (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
buildconfig/stubs/pygame/sprite.pyi (4)
buildconfig/stubs/pygame/mask.pyi (2)
  • Mask (18-60)
  • copy (21-21)
src_py/sprite.py (45)
  • Sprite (97-264)
  • groups (216-224)
  • add_internal (171-177)
  • add_internal (396-407)
  • add_internal (759-794)
  • add_internal (1115-1135)
  • add_internal (1424-1428)
  • AbstractGroup (362-637)
  • remove_internal (179-185)
  • remove_internal (409-417)
  • remove_internal (839-854)
  • remove_internal (1454-1458)
  • update (187-199)
  • update (546-556)
  • add (135-151)
  • add (446-479)
  • add (796-837)
  • remove (153-169)
  • remove (481-511)
  • kill (201-214)
  • alive (226-233)
  • visible (322-327)
  • visible (330-331)
  • sprite (1442-1448)
  • sprite (1451-1452)
  • layer (239-252)
  • layer (255-264)
  • layer (334-342)
  • layer (345-354)
  • has_internal (419-425)
  • has_internal (1460-1461)
  • copy (427-438)
  • copy (1416-1417)
  • sprites (383-394)
  • sprites (856-862)
  • sprites (1419-1422)
  • has (513-544)
  • Group (640-662)
  • get_sprite (903-911)
  • change_layer (932-966)
  • change_layer (1345-1356)
  • GroupSingle (1398-1465)
  • spritecollide (1690-1724)
  • groupcollide (1727-1756)
  • spritecollideany (1759-1789)
buildconfig/stubs/pygame/rect.pyi (8)
  • update (179-181)
  • update (183-183)
  • update (185-185)
  • FRect (304-304)
  • Rect (303-303)
  • copy (152-152)
  • left (51-51)
  • left (53-53)
buildconfig/stubs/pygame/surface.pyi (1)
  • copy (335-346)
🔇 Additional comments (7)
buildconfig/stubs/pygame/sprite.pyi (7)

41-44: Mask as a property remains attribute-compatible for collide_mask

Using a read-only property for mask keeps structural compatibility with code that uses obj.mask as an attribute. No behavioral or typing regressions here.


68-74: DirtySprite deriving directly from Sprite simplifies the surface

Removing the auxiliary _SupportsDirtySprite protocol reduces indirection without losing expressiveness. Looks good.


75-77: TypeVar renames align with PEP 8/typevar naming conventions

Suffix-T naming (_SpriteT, _SpriteT2, _DirtySpriteT) is the convention used by typeshed and linters. Good change.


110-124: Generic propagation across Group/Render classes is consistent*

The shift to Group[_SpriteT] and related aliases unifies the API and keeps variance consistent throughout. No issues spotted.


185-188: collide_mask support via either image or mask remains accurate

Protocol union covers both access paths; comment tweak improves clarity. No further changes needed.


142-148: Confirm Optional[int] for special_flags matches runtime semantics

The stub in buildconfig/stubs/pygame/sprite.pyi widens special_flags from int to Optional[int] = None (contravariance is type-safe), but you need to verify that LayeredDirty.draw actually accepts None at runtime (e.g., treats it as 0). If it doesn’t, revert the stub to special_flags: int = 0 to match the real behavior.

  • File: buildconfig/stubs/pygame/sprite.pyi, lines 142–148
  • Stub definition:
    def draw(
        self,
        surface: Surface,
        bgd: Optional[Surface] = None,
        special_flags: Optional[int] = None,
    ) -> list[Union[FRect, Rect]]: ...
  • Action: inspect LayeredDirty.draw in the installed pygame/sprite.py (or its source) to confirm None is handled; otherwise update the stub.

79-81: Apply TypeAlias + string forward refs for recursive aliases

Some type checkers (e.g. mypy in strict modes) reject self-referential type aliases without a TypeAlias annotation and quoted forward references. Please update:

• In buildconfig/stubs/pygame/sprite.pyi (around lines 79–81):

-_GroupOrGroups = Union[AbstractGroup[_SpriteT], Iterable[_GroupOrGroups[_SpriteT]]]
-_SpriteOrSprites = Union[_SpriteT, Iterable[_SpriteOrSprites[_SpriteT]]]
+_GroupOrGroups: TypeAlias = "Union[AbstractGroup[_SpriteT], Iterable[_GroupOrGroups[_SpriteT]]]"
+_SpriteOrSprites: TypeAlias = "Union[_SpriteT, Iterable[_SpriteOrSprites[_SpriteT]]]"

• Add the import (near the existing Self/deprecated imports):

if sys.version_info >= (3, 11):
    from typing import Self, TypeAlias
else:
    from typing_extensions import Self, TypeAlias

Finally, verify that mypy (v1.10.0) accepts the new recursive aliases (e.g. with --python-version 3.9 and appropriate MYPYPATH or --explicit-package-bases).

Copy link

@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: 0

🔭 Outside diff range comments (1)
buildconfig/stubs/pygame/sprite.pyi (1)

45-67: Fix return type of Sprite.groups() to avoid generic invariance break

AbstractGroup is invariant in its type parameter, so returning list[AbstractGroup[Sprite]] is too narrow. E.g., a DirtySprite can belong to an AbstractGroup[DirtySprite], which is not a subtype of AbstractGroup[Sprite]. Use Any (or a covariant param if you ever change variance) to reflect reality and prevent false-positive type errors.

Proposed diff:

-    def groups(self) -> list[AbstractGroup[Sprite]]: ...
+    def groups(self) -> list[AbstractGroup[Any]]: ...
♻️ Duplicate comments (2)
buildconfig/stubs/pygame/sprite.pyi (2)

82-99: Layer argument in add_internal correctly widened to Optional[int]

This resolves the earlier concern about forbidding layer integers. Thanks for addressing it.


171-173: Comment mentions optional radius but the type alias no longer models it — update the comment for clarity

The simplification to _SupportsCollideCircle = _HasRect is fine, but the comment still suggests a radius attribute might be present. Consider clarifying that radius is a runtime convention not modeled in types, to avoid confusing users.

Proposed diff:

-# Must have rect attribute, may optionally have radius attribute
+# Must have a rect attribute. A 'radius' attribute, if present at runtime,
+# may be used by collide_circle, but it is intentionally not modeled here.
 _SupportsCollideCircle = _HasRect
🧹 Nitpick comments (2)
buildconfig/stubs/pygame/sprite.pyi (2)

75-81: Recursive type aliases look good; consider TypeAlias for clarity (optional)

Most type checkers understand self-referential aliases in .pyi files. If you want to be extra explicit, you could mark them as TypeAlias, but it’s not required.

Example (optional, not strictly necessary in .pyi):

-from typing import (
+from typing import (
     Any,
     Generic,
     Optional,
     Protocol,
     SupportsFloat,
     TypeVar,
     Union,
+    TypeAlias,
 )
@@
-_GroupOrGroups = Union[AbstractGroup[_SpriteT], Iterable[_GroupOrGroups[_SpriteT]]]
-_SpriteOrSprites = Union[_SpriteT, Iterable[_SpriteOrSprites[_SpriteT]]]
+_GroupOrGroups: TypeAlias = Union[AbstractGroup[_SpriteT], Iterable[_GroupOrGroups[_SpriteT]]]
+_SpriteOrSprites: TypeAlias = Union[_SpriteT, Iterable[_SpriteOrSprites[_SpriteT]]]

185-187: Polish the collide_mask comment grammar

Minor wording clean-up to read naturally.

Proposed diff:

-# Arguments to collide_mask must either have mask or have image attribute, in
-# addition to mandatorily having a rect attribute
+# Arguments to collide_mask must have either a mask or an image attribute,
+# in addition to a mandatory rect attribute.
 _SupportsCollideMask = Union[_HasImageAndRect, _HasMaskAndRect]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c31a63c and 286e5ad.

📒 Files selected for processing (1)
  • buildconfig/stubs/pygame/sprite.pyi (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: AMD64
  • GitHub Check: x86
  • GitHub Check: i686
  • GitHub Check: x86_64
  • GitHub Check: aarch64
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: dev-check
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (ubuntu-24.04)
🔇 Additional comments (10)
buildconfig/stubs/pygame/sprite.pyi (10)

43-44: Good change: express mask as a property in the protocol

Defining mask via a @Property keeps compatibility with objects that expose a simple attribute while remaining precise for structural typing.


58-63: Constructor and add/remove now accept nested groups via recursive alias — nice usability improvement

The _GroupOrGroups[Any] recursion captures nested iterables of groups well. This aligns with how users actually pass groups to Sprite.


68-74: Dropping the extra dirty-sprite protocol from DirtySprite is a welcome simplification

Keeping DirtySprite as a plain Sprite subtype reduces protocol clutter without losing typing precision.


110-124: Deprecation wrappers and aliases are consistent with the runtime API

The deprecation annotations and aliases for RenderPlain, RenderClear, RenderUpdates, and OrderedUpdates match user expectations and keep the surface area tidy.


125-140: LayeredUpdates generics and return types are coherent

Signatures now consistently use _SpriteT across add/get/change/move accessors. This should improve type inference in user code.


142-148: LayeredDirty.draw: special_flags made Optional — good alignment with runtime behavior

This matches the semantics where None means “use default blit flags,” and reads better than sentinel integers.


160-162: GroupSingle typing tightened

Using Optional[_SpriteT] for the sprite attribute and constructor matches real usage and avoids unnecessary Any.


193-201: Collision helpers adopt the new generics cleanly

Switching to _HasRectT/_SpriteT keeps type relationships precise without over-constraining the APIs.


202-207: groupcollide generic parameters and return map are correctly parameterized

The dict[_SpriteT, list[_SpriteT2]] return type aligns with the function semantics.


209-212: spritecollideany return type Optional[_SpriteT] is correct

Reflects None when no collision is found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sprite pygame.sprite type hints Type hint checking related tasks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants