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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .ci/requirements-mypy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ arro3-core
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
numpy==2.4.6
numpy==2.4.6;python_version=="3.11"
numpy;python_version>="3.12"
packaging
pyarrow-stubs
pybind11
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,23 @@ jobs:
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
- name: Lint
run: uvx --with tox-uv tox -e lint
mypy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [
"3.14",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note to self - it appears apache/arrow#48172 is blocking 3.15 support

"3.13",
"3.12",
"3.11",
]
name: Mypy
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
- name: Mypy
run: uvx --with tox-uv tox -e mypy
run: uvx --python=${{ matrix.python-version }} --with tox-uv tox -e mypy
4 changes: 2 additions & 2 deletions Tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

gil_enabled_at_start = True
if FREE_THREADED_BUILD:
gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined]
gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined,unused-ignore]


def pytest_report_header(config: pytest.Config) -> str:
Expand All @@ -28,7 +28,7 @@ def pytest_terminal_summary(terminalreporter: pytest.TerminalReporter) -> None:
if (
FREE_THREADED_BUILD
and not gil_enabled_at_start
and sys._is_gil_enabled() # type: ignore[attr-defined]
and sys._is_gil_enabled() # type: ignore[attr-defined,unused-ignore]
):
tr = terminalreporter
tr.ensure_newline()
Expand Down
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,14 @@ exclude = [
"^Tests/images/picins/",
"^Tests/images/sunraster/",
]
follow_imports = "silent"
python_version = "3.11"
disallow_any_generics = true
disallow_untyped_defs = true
warn_redundant_casts = true
warn_unused_ignores = true
disallow_subclassing_any = false
disallow_untyped_calls = false
warn_return_any = false
warn_unreachable = true
enable_error_code = "ignore-without-code"
extra_checks = true
strict = true
pretty = true
no_implicit_reexport = false
num_workers = 4

[tool.pytest]
Expand Down
4 changes: 3 additions & 1 deletion src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ def _open(self) -> None:
imagedata_size: tuple[int, int] | None = None

byte_arr = bytearray(255)
bytes_mv = memoryview(byte_arr)
# the extra `bytes` annotation here works around several false positive
# `comparison-overlap` mypy errors
bytes_mv: bytes | memoryview = memoryview(byte_arr)
bytes_read = 0
reading_header_comments = True
reading_trailer_comments = False
Expand Down
5 changes: 3 additions & 2 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,8 +1195,9 @@ class Collector(BytesIO):
data = []

def write(self, data: Buffer) -> int:
self.data.append(data)
return len(data)
data_bytes = bytes(data)
self.data.append(data_bytes)
return len(data_bytes)

im.load() # make sure raster data is available

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/ImageGrab.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def grab(
scale = 1 if scale_down else 2
im_cropped = im.resize(
((right - left) * scale, (bottom - top) * scale),
box=tuple(coord * 2 for coord in bbox),
box=(left * 2, top * 2, right * 2, bottom * 2),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would you like to either reduce the PR to just this, or split this bit to another PR? I'm happy to merge this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ImageGrab.py fix is needed for macOS with the current config.

Could you expand on what this means?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Split out: #9935

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ImageGrab.py fix is needed for macOS with the current config.

Could you expand on what this means?

Yep, on main I get:

tox -e mypy
mypy: commands[0]> mypy conftest.py selftest.py setup.py checks docs src winbuild Tests
src/PIL/ImageGrab.py:82: error: Argument "box" to "resize" of "Image" has incompatible
type "tuple[int, ...]"; expected "tuple[float, float, float, float] | None"  [arg-type]
                                box=tuple(coord * 2 for coord in bbox),
                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 1 error in 1 file (checked 302 source files)
mypy: exit 1 (2.58 seconds) /Users/hugo/github/Pillow> mypy conftest.py selftest.py setup.py checks docs src winbuild Tests pid=84969
  mypy: FAIL code 1 (2.58=setup[0.00]+cmd[2.58] seconds)
  evaluation failed :( (2.63 seconds)

And with #9935:

tox -e mypy
mypy: commands[0]> mypy conftest.py selftest.py setup.py checks docs src winbuild Tests
Success: no issues found in 302 source files
  mypy: OK (2.22=setup[0.00]+cmd[2.22] seconds)
  congratulations :) (2.27 seconds)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So why isn't that error appearing in our CI?

@hugovk hugovk Sep 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CI runs on Ubuntu and the code in question is macOS-only, gated by if sys.platform == "darwin".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, ok, I see.

)
else:
im_cropped = im.crop(bbox)
Expand Down
Loading