Skip to content
Merged
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
248 changes: 80 additions & 168 deletions examples/generate_type_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def generate_enums() -> str:
)
enum_defs.append(enum_def)

return "\n\n".join(enum_defs)
return "\n\n".join(enum_defs) + "\n"


def generate_stub() -> str:
Expand All @@ -205,32 +205,98 @@ def generate_stub() -> str:
enum_names = sorted(n for n in dir(pyvips.enums) if not n.startswith("__"))
enums = ", ".join(f"{n} as {n}" for n in enum_names)

stub = f"""import logging
from collections.abc import Mapping
from typing import Any, Callable

from .base import leak_set as leak_set, shutdown as shutdown, version as version, get_suffixes as get_suffixes, at_least_libvips as at_least_libvips, type_find as type_find, type_name as type_name, nickname_find as nickname_find, type_from_name as type_from_name, type_map as type_map, values_for_enum as values_for_enum, values_for_flag as values_for_flag, enum_dict as enum_dict, flags_dict as flags_dict
from .enums import {enums}
from .error import Error as Error, _to_bytes as _to_bytes, _to_string as _to_string, _to_string_copy as _to_string_copy
from .gobject import GObject as GObject
from .gvalue import GValue as GValue
from .vconnection import Connection as Connection
from .vimage import Image as Image
from .vinterpolate import Interpolate as Interpolate
from .vobject import VipsObject as VipsObject
from .voperation import Introspect as Introspect, Operation as Operation, cache_set_max as cache_set_max, cache_set_max_mem as cache_set_max_mem, cache_set_max_files as cache_set_max_files, cache_set_trace as cache_set_trace, cache_get_max as cache_get_max, cache_get_size as cache_get_size, cache_get_max_mem as cache_get_max_mem, cache_get_max_files as cache_get_max_files, block_untrusted_set as block_untrusted_set, operation_block_set as operation_block_set
from .vregion import Region as Region
from .vsource import Source as Source
from .vsourcecustom import SourceCustom as SourceCustom
from .vtarget import Target as Target
from .vtargetcustom import TargetCustom as TargetCustom

_MetadataValue = bool | int | float | str | Image | list[int] | list[float] | list[Image]
_MetadataDict = dict[str, _MetadataValue]

logger: logging.Logger

API_mode: bool

__version__: str

ffi: Any
glib_lib: Any
vips_lib: Any
gobject_lib: Any

_log_handler_id: int | None
_remove_handler: Callable[[bytes, int], Any]

def library_name(name: str, abi_number: int, lib_prefix: str = "lib") -> str: ...
def _log_handler_callback(
domain: Any, level: int, message: Any, user_data: Any
) -> None: ...
def _remove_log_handler() -> None: ...

def call(operation_name: str, *args: Any, **kwargs: Any) -> Image | tuple[Image, _MetadataDict]: ...

class GLogLevelFlags:
FLAG_RECURSION: int
FLAG_FATAL: int

LEVEL_ERROR: int
LEVEL_CRITICAL: int
LEVEL_WARNING: int
LEVEL_MESSAGE: int
LEVEL_INFO: int
LEVEL_DEBUG: int

LEVEL_TO_LOGGER: Mapping[int, int]
"""

return stub


def generate_vimage() -> str:
"""Generate the ``vimage.py`` stub."""

enum_names = sorted(n for n in dir(pyvips.enums) if not n.startswith("__"))
enums = ", ".join(f"{n} as {n}" for n in enum_names)

stub = f'''"""Type stubs for pyvips.

# flake8: noqa: E501

This file is automatically generated by examples/generate_type_stubs.py.

RATIONALE FOR GENERATION:
- pyvips is a binding around libvips C library
- libvips has 300+ operations that change frequently
- Manual stub maintenance would be unmaintainable
- Generation uses same introspection as docs/ enums
- Ensures stubs stay synchronized with available operations

To regenerate after libvips updates:
python examples/generate_type_stubs.py
"""

from pathlib import Path
from types import TracebackType
from typing import Any, Callable, Protocol, TypeAlias
from typing import Any, Protocol, TypeAlias

import numpy as np # type: ignore[import-not-found]

from PIL.Image import Image as PILImage # type: ignore

from .enums import {enums}
from .error import Error as Error
from .vinterpolate import Interpolate
from .vobject import VipsObject
from .vsource import Source
from .vtarget import Target

class _ArrayInterface(Protocol):
@property
Expand All @@ -254,95 +320,6 @@ def __array__(self, dtype: np.dtype | None = None, copy: bool | None = None) ->
_BufferLike = bytes | bytearray | memoryview
_ImageOperand = _ImageAlias | _NumberLike | _NumberLikeList

# Low-level handles and helpers
ffi: Any
glib_lib: Any
vips_lib: Any
gobject_lib: Any


# Exception classes
class Error(Exception): ...

# GObject base classes
class GObject(object):
def signal_connect(self, name: str, callback: Any) -> None: ...

class VipsObject(GObject): ...

class GValue(object):
gbool_type: int
gint_type: int
guint64_type: int
gdouble_type: int
gstr_type: int
genum_type: int
gflags_type: int
gobject_type: int
image_type: int
array_int_type: int
array_double_type: int
array_image_type: int
refstr_type: int
blob_type: int
source_type: int
target_type: int
format_type: int
blend_mode_type: int

# mapping from GType to textual Python type description
_gtype_to_python: dict[int, str]

# underlying C data
pointer: Any
gvalue: Any

@staticmethod
def gtype_to_python(gtype: int) -> str: ...
@staticmethod
def to_enum(gtype: int, value: str | int) -> int: ...
@staticmethod
def from_enum(gtype: int, enum_value: int) -> str: ...
@staticmethod
def to_flag(gtype: int, value: str | int) -> int: ...

def __init__(self) -> None: ...

def set_type(self, gtype: int) -> None: ...
def set(self, value: _MetadataValue) -> None: ...
def get(self) -> _MetadataValue: ...

# Connection classes
class Connection(VipsObject): ...

class Source(Connection):
@staticmethod
def new_from_descriptor(descriptor: int) -> Source: ...

class SourceCustom(Source):
def on_read(self, handler: Any) -> None: ...
def on_seek(self, handler: Any) -> None: ...

class Target(Connection):
@staticmethod
def new_to_descriptor(descriptor: int) -> Target: ...

@staticmethod
def new_to_memory() -> Target: ...

class TargetCustom(Target):
def __init__(self) -> None: ...
def on_write(self, handler: Callable[[_BufferLike], int]) -> None: ...
def on_read(self, handler: Callable[[int], _BufferLike | None]) -> None: ...
def on_seek(self, handler: Callable[[int, int], int]) -> None: ...
def on_end(self, handler: Callable[..., int]) -> None: ...
def on_finish(self, handler: Callable[..., int]) -> None: ...

# Interpolator class
class Interpolate(VipsObject):
@staticmethod
def new(name: str) -> Interpolate: ...

class Image(VipsObject):
"""Wrap a VipsImage object."""

Expand Down Expand Up @@ -513,76 +490,6 @@ def get_value(self, name: str) -> _MetadataValue: ...
def set_value(self, name: str, value: _MetadataValue) -> None: ...
def get_scale(self) -> float: ...
def get_offset(self) -> float: ...

class Operation(VipsObject):
@staticmethod
def new_from_name(operation_name: str) -> "Operation": ...
def set(self, name: str, flags: int, match_image: Image | None, value: Any) -> None: ...
@staticmethod
def call(operation_name: str, *args: Any, **kwargs: Any) -> Any: ...
@staticmethod
def generate_docstring(operation_name: str) -> str: ...
@staticmethod
def generate_sphinx(operation_name: str) -> str: ...
@staticmethod
def generate_sphinx_all() -> None: ...

class Introspect(object):
description: str
flags: int
details: dict[str, dict[str, Any]]

required_input: list[str]
optional_input: list[str]
required_output: list[str]
optional_output: list[str]

doc_optional_input: list[str]
doc_optional_output: list[str]

member_x: str | None
method_args: list[str]

def __init__(self, operation_name: str) -> None: ...
@classmethod
def get(cls: type["Introspect"], operation_name: str) -> "Introspect": ...

# Global functions

# base.py
def _to_string(cdata: Any) -> str: ...
def _to_bytes(value: object) -> bytes: ...
def leak_set(leak: bool) -> None: ...
def shutdown() -> None: ...
def version(flag: int) -> int: ...
def get_suffixes() -> list[str]: ...
def at_least_libvips(x: int, y: int) -> bool: ...
def type_find(basename: str, nickname: str) -> int: ...
def type_name(gtype: int) -> str: ...
def nickname_find(gtype: int) -> str: ...
def type_from_name(name: str) -> int: ...
def type_map(gtype: int, fn: Callable[..., int]) -> int: ...
def values_for_enum(gtype: int) -> list[str]: ...
def values_for_flag(gtype: int) -> list[str]: ...
def enum_dict(gtype: int) -> dict[str, int]: ...
def flags_dict(gtype: int) -> dict[str, int]: ...

# voperation.py
def cache_set_max(mx: int) -> None: ...
def cache_set_max_mem(mx: int) -> None: ...
def cache_set_max_files(mx: int) -> None: ...
def cache_set_trace(trace: bool) -> None: ...
def cache_get_max() -> int: ...
def cache_get_size() -> int: ...
def cache_get_max_mem() -> int: ...
def cache_get_max_files() -> int: ...
def block_untrusted_set(state: bool) -> None: ...
def operation_block_set(name: str, state: bool) -> None: ...

def call(operation_name: str, *args: Any, **kwargs: Any) -> Image | tuple[Image, _MetadataDict]: ...

# Module-level constants
API_mode: bool
'''

return stub
Expand All @@ -600,3 +507,8 @@ def call(operation_name: str, *args: Any, **kwargs: Any) -> Image | tuple[Image,
stub_file = stub_base / "enums.pyi"
stub_file.write_text(generate_enums())
print(f"Generated type stub at {stub_file}")

# Write to pyvips/vimage.pyi
stub_file = stub_base / "vimage.pyi"
stub_file.write_text(generate_vimage())
print(f"Generated type stub at {stub_file}")
Loading
Loading