-
Notifications
You must be signed in to change notification settings - Fork 54
Add type hints support (#355) #564
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
base: master
Are you sure you want to change the base?
Conversation
Adds PEP 561 type stubs for pyvips, enabling IDE autocomplete and type checking with mypy. - Add pyvips/__init__.pyi with type stubs for 300+ Image operations - Add generate_type_stubs.py script using introspection (follows existing enum/doc pattern) - Add test_type_hints.py with mypy validation - Update README with type checking documentation - Add mypy check to CI workflow Type stubs are generated to handle libvips's 300+ operations and frequent updates. Zero runtime overhead, full type coverage.
|
This is great @JoshCLWren! I'm on holiday right now, but I'll read this carefully when I get home again. |
jcupitt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other:
- we need a line in the changelog, and please credit yourself for doing this nice work
generate_type_stubs.pyneeds to be set executable- does the type stub generator need to be in
pyvips/? it's something for maintainers, so I'd be inclined to put it inexamples/. The doc generator is inpyvips/because it's used forhelp()output - perhaps we could run mypy on the demo scripts in
examples/as part of CI? but it'll need eg. a hint forimage * [1, 2, 3]first
pyvips/generate_type_stubs.py
Outdated
| stub += """ | ||
| # Operator overloads | ||
| def __add__(self, other: Union[Image, float, int]) -> Image: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can have arrays of float and int too, eg. a + [1, 2, 3] adds 1 to band 0, 2 to band 1, 3 to band 2.
| def __call__(self, x: int, y: int) -> List[float]: ... | ||
| def __repr__(self) -> str: ... | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a set of hand-written bindings too, perhaps some of them could have type hints?
Easy: image.floor() etc.
Medium: bandjoin, bandsplit, composite etc.
Horrible: ifthenelse.
…erator overloads - Add type hints for ifthenelse, composite, floor, ceil, rint, bandsplit, bandjoin, bandrank, hasalpha, get_page_height - Enhance operator overloads to support List[float] and List[int] operands - Move generate_type_stubs.py from pyvips/ to examples/ for better maintainability - Add CI mypy checks for examples/affine.py and examples/convolve.py - Update documentation references for type stub generation path
pyvips/__init__.pyi
Outdated
| @staticmethod | ||
| def new_from_list(array: List[List[float]], scale: float =1.0, offset: float = 0.0) -> Image: ... | ||
| def new_from_list( | ||
| array: List[List[float]], scale: float = 1.0, offset: float = 0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can also be List[List[int]]
| enum_classes = "\n".join([f"class {name}: ..." for name in enum_names]) | ||
|
|
||
| stub = f'''"""Type stubs for pyvips. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add:
# flake8: noqa: E501to stop flake8 moaning about long lines in the generated output.
| """ | ||
|
|
||
| import typing | ||
| from typing import Any, Optional, Union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't use any of these (I think).
| type_map, | ||
| type_from_name, | ||
| nickname_find, | ||
| at_least_libvips, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
examples/generate_type_stubs.py
Outdated
| nickname_find, | ||
| at_least_libvips, | ||
| ) | ||
| from pyvips import ffi, Error, _to_bytes, _to_string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_to_byes, _to_string unused
| import os | ||
|
|
||
| # Script is in examples/, need to go up one level to find pyvips/ | ||
| stub_file = os.path.join(os.path.dirname(__file__), "..", "pyvips", "__init__.pyi") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we need this -- just generate __init__.pyi in the current directory and let the user know. Or just print to stdout (the safest).
examples/generate_type_stubs.py
Outdated
| """Generate type signature for an operation method.""" | ||
| intro = Introspect.get(operation_name) | ||
|
|
||
| if (intro.flags & 4) != 0: # _OPERATION_DEPRECATED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? VipsOperationFlags uses 8 for VipsOperationFlags. I think you're removed all uncacheable operations instead.
|
|
||
| # Return type | ||
| output_types = [ | ||
| gtype_to_python_type(intro.details[name]["type"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no newline before this
|
|
||
| # Optional output dicts can contain any metadata value type | ||
| if len(intro.doc_optional_output) > 0: | ||
| dict_value_type = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need the brackets
| return "\n".join([sig for _, sig in all_names]) | ||
|
|
||
|
|
||
| def get_all_enum_names() -> list[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we should handle flags as well as enums, but perhaps that can wait for another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gen-enums has this code as well, of course, perhaps it should be moved into Introspect
|
Sorry, there are a lot of comments. I don't know how much time you have -- we could merge and share the work of fixups in later PRs, if you like. |
|
Oh, I just saw cdisplayagain, nice! You have:
Another possible solution is: $ pip install pyvips[binary]And it should download and use a libvips binary appropriate for your system. It might be a little easier for your users. |
No worries! Thanks for the feedback. I'll get on it this afternoon/evening. |
Good call, I think that'll help a lot. |
…precated operations Changed hardcoded value 4 (NOCACHE) to use _OPERATION_DEPRECATED constant (8). This fixes incorrect filtering that was excluding uncacheable operations instead of deprecated ones.
…, add flake8 noqa
- Add SourceCustom class with on_read() and on_seek() methods - Add Source.new_from_descriptor() and Target.new_to_descriptor() static methods - Add pyvips.call() function for calling libvips operations - Add erode() and dilate() methods to Image class stub - Add explicit enum attributes for Direction and Align (HORIZONTAL, VERTICAL, LOW, CENTRE, HIGH) - Fix try7.py to use .format instead of non-existent .bandfmt attribute - Add type: ignore comments for Union type narrowing issues in 7 example files - All 35 example files now pass mypy type checking - Type stub file passes mypy with 0 errors
- Update version.py to 3.2.0 - Update CHANGELOG.rst with version 3.2.0 entry - Update doc/conf.py to version 3.2.0 - Expand CI mypy checks to include more example scripts
Match operator overload signatures which already support int and List[int] types. Ensures type consistency across the Image API.
Closes #355
This PR adds PEP 561 type stubs for pyvips, enabling IDE autocomplete and type checking with mypy.
Changes
pyvips/__init__.pyi- Type stubs for all Image operations (300+ methods)pyvips/generate_type_stubs.py- Script to regenerate stubs from libvips introspectiontests/test_type_hints.py- Test verifying type stubs work correctlyDesign Decisions
Type stubs are generated rather than handwritten because:
This follows the existing pattern used for:
examples/gen-enums.py)pyvips.Operation.generate_sphinx_all())Features
Any- specific Union types insteadin_forinparameter)Usage
Users can now type check pyvips code:
Regenerate stubs after libvips updates:
Testing
test_type_hints.pyadded