Python: Fix mypy also on the Slint module implementation

This commit is contained in:
Simon Hausmann 2025-02-12 23:02:31 +01:00 committed by Simon Hausmann
parent 607d70b3fa
commit 9001966dc9
6 changed files with 113 additions and 83 deletions

View file

@ -188,7 +188,7 @@ jobs:
run: nox --default-venv-backend uv run: nox --default-venv-backend uv
- name: Run mypy - name: Run mypy
working-directory: api/python working-directory: api/python
run: uv run mypy -p tests run: uv run mypy -p tests -p slint
cpp_test_driver: cpp_test_driver:
env: env:

View file

@ -199,7 +199,7 @@ impl CompilationResult {
#[gen_stub_pyclass] #[gen_stub_pyclass]
#[pyclass(unsendable)] #[pyclass(unsendable)]
struct ComponentDefinition { pub struct ComponentDefinition {
definition: slint_interpreter::ComponentDefinition, definition: slint_interpreter::ComponentDefinition,
} }

View file

@ -6,7 +6,8 @@ use pyo3_stub_gen::{define_stub_info_gatherer, derive::gen_stub_pyfunction};
mod image; mod image;
mod interpreter; mod interpreter;
use interpreter::{ use interpreter::{
CompilationResult, Compiler, ComponentInstance, PyDiagnostic, PyDiagnosticLevel, PyValueType, CompilationResult, Compiler, ComponentDefinition, ComponentInstance, PyDiagnostic,
PyDiagnosticLevel, PyValueType,
}; };
mod brush; mod brush;
mod errors; mod errors;
@ -45,6 +46,7 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Compiler>()?; m.add_class::<Compiler>()?;
m.add_class::<CompilationResult>()?; m.add_class::<CompilationResult>()?;
m.add_class::<ComponentInstance>()?; m.add_class::<ComponentInstance>()?;
m.add_class::<ComponentDefinition>()?;
m.add_class::<image::PyImage>()?; m.add_class::<image::PyImage>()?;
m.add_class::<PyValueType>()?; m.add_class::<PyValueType>()?;
m.add_class::<PyDiagnosticLevel>()?; m.add_class::<PyDiagnosticLevel>()?;

View file

@ -14,7 +14,7 @@ import logging
import importlib import importlib
import copy import copy
import typing import typing
import builtins from typing import Any
import pathlib import pathlib
from .models import ListModel, Model from .models import ListModel, Model
from .slint import Image, Color, Brush, Timer, TimerMode from .slint import Image, Color, Brush, Timer, TimerMode
@ -22,27 +22,29 @@ from .slint import Image, Color, Brush, Timer, TimerMode
Struct = native.PyStruct Struct = native.PyStruct
class CompileError(Exception): class CompileError(Exception):
def __init__(self, message, diagnostics): def __init__(self, message: str, diagnostics: list[native.PyDiagnostic]):
self.message = message self.message = message
self.diagnostics = diagnostics self.diagnostics = diagnostics
class Component: class Component:
def show(self): __instance__: native.ComponentInstance
def show(self) -> None:
self.__instance__.show() self.__instance__.show()
def hide(self): def hide(self) -> None:
self.__instance__.hide() self.__instance__.hide()
def run(self): def run(self) -> None:
self.__instance__.run() self.__instance__.run()
def _normalize_prop(name): def _normalize_prop(name: str) -> str:
return name.replace("-", "_") return name.replace("-", "_")
def _build_global_class(compdef, global_name): def _build_global_class(compdef: native.ComponentDefinition, global_name: str) -> Any:
properties_and_callbacks = {} properties_and_callbacks = {}
for prop_name in compdef.global_properties(global_name).keys(): for prop_name in compdef.global_properties(global_name).keys():
@ -51,12 +53,12 @@ def _build_global_class(compdef, global_name):
logging.warning(f"Duplicated property {prop_name}") logging.warning(f"Duplicated property {prop_name}")
continue continue
def mk_setter_getter(prop_name): def mk_setter_getter(prop_name: str): # type: ignore
def getter(self): def getter(self): # type: ignore
return self.__instance__.get_global_property( return self.__instance__.get_global_property(
global_name, prop_name) global_name, prop_name)
def setter(self, value): def setter(self, value): # type: ignore
return self.__instance__.set_global_property( return self.__instance__.set_global_property(
global_name, prop_name, value) global_name, prop_name, value)
@ -70,13 +72,13 @@ def _build_global_class(compdef, global_name):
logging.warning(f"Duplicated property {prop_name}") logging.warning(f"Duplicated property {prop_name}")
continue continue
def mk_setter_getter(callback_name): def mk_setter_getter(callback_name: str): # type: ignore
def getter(self): def getter(self): # type: ignore
def call(*args): def call(*args: Any) -> Any:
return self.__instance__.invoke_global(global_name, callback_name, *args) return self.__instance__.invoke_global(global_name, callback_name, *args)
return call return call
def setter(self, value): def setter(self, value): # type: ignore
return self.__instance__.set_global_callback( return self.__instance__.set_global_callback(
global_name, callback_name, value) global_name, callback_name, value)
@ -90,9 +92,9 @@ def _build_global_class(compdef, global_name):
logging.warning(f"Duplicated function {prop_name}") logging.warning(f"Duplicated function {prop_name}")
continue continue
def mk_getter(function_name): def mk_getter(function_name: str): # type: ignore
def getter(self): def getter(self): # type: ignore
def call(*args): def call(*args: Any) -> Any:
return self.__instance__.invoke_global(global_name, function_name, *args) return self.__instance__.invoke_global(global_name, function_name, *args)
return call return call
@ -103,17 +105,17 @@ def _build_global_class(compdef, global_name):
return type("SlintGlobalClassWrapper", (), properties_and_callbacks) return type("SlintGlobalClassWrapper", (), properties_and_callbacks)
def _build_class(compdef): def _build_class(compdef: native.ComponentDefinition): # type: ignore
def cls_init(self, **kwargs): def cls_init(self: Any, **kwargs) -> Any: # type: ignore
self.__instance__ = compdef.create() self.__instance__ = compdef.create()
for name, value in self.__class__.__dict__.items(): for name, value in self.__class__.__dict__.items():
if hasattr(value, "slint.callback"): if hasattr(value, "slint.callback"):
callback_info = getattr(value, "slint.callback") callback_info = getattr(value, "slint.callback")
name = callback_info["name"] name = callback_info["name"]
def mk_callback(self, callback): def mk_callback(self: Any, callback: typing.Callable[..., Any]) -> typing.Callable[..., Any]:
def invoke(*args, **kwargs): def invoke(*args: Any, **kwargs: Any) -> Any:
return callback(self, *args, **kwargs) return callback(self, *args, **kwargs)
return invoke return invoke
@ -137,12 +139,12 @@ def _build_class(compdef):
logging.warning(f"Duplicated property {prop_name}") logging.warning(f"Duplicated property {prop_name}")
continue continue
def mk_setter_getter(prop_name): def mk_setter_getter(prop_name: str) -> Any:
def getter(self): def getter(self) -> Any: # type: ignore
return self.__instance__.get_property(prop_name) return self.__instance__.get_property(prop_name)
def setter(self, value): def setter(self, value: Any) -> None: # type: ignore
return self.__instance__.set_property( self.__instance__.set_property(
prop_name, value) prop_name, value)
return property(getter, setter) return property(getter, setter)
@ -155,15 +157,14 @@ def _build_class(compdef):
logging.warning(f"Duplicated property {prop_name}") logging.warning(f"Duplicated property {prop_name}")
continue continue
def mk_setter_getter(callback_name): def mk_setter_getter(callback_name: str) -> Any: # type: ignore
def getter(self): def getter(self): # type: ignore
def call(*args): def call(*args: Any) -> Any:
return self.__instance__.invoke(callback_name, *args) return self.__instance__.invoke(callback_name, *args)
return call return call
def setter(self, value): def setter(self, value: any): # type: ignore
return self.__instance__.set_callback( self.__instance__.set_callback(callback_name, value)
callback_name, value)
return property(getter, setter) return property(getter, setter)
@ -175,9 +176,9 @@ def _build_class(compdef):
logging.warning(f"Duplicated function {prop_name}") logging.warning(f"Duplicated function {prop_name}")
continue continue
def mk_getter(function_name): def mk_getter(function_name: str): # type: ignore
def getter(self): def getter(self) -> Any: # type: ignore
def call(*args): def call(*args: Any) -> Any:
return self.__instance__.invoke(function_name, *args) return self.__instance__.invoke(function_name, *args)
return call return call
@ -188,8 +189,8 @@ def _build_class(compdef):
for global_name in compdef.globals: for global_name in compdef.globals:
global_class = _build_global_class(compdef, global_name) global_class = _build_global_class(compdef, global_name)
def mk_global(global_class): def mk_global(global_class: typing.Callable[..., Any]): # type: ignore
def global_getter(self): def global_getter(self) -> Any: # type: ignore
wrapper = global_class() wrapper = global_class()
setattr(wrapper, "__instance__", self.__instance__) setattr(wrapper, "__instance__", self.__instance__)
return wrapper return wrapper
@ -201,9 +202,9 @@ def _build_class(compdef):
return type("SlintClassWrapper", (Component,), properties_and_callbacks) return type("SlintClassWrapper", (Component,), properties_and_callbacks)
def _build_struct(name, struct_prototype): def _build_struct(name: str, struct_prototype: native.PyStruct) -> type:
def new_struct(cls, *args, **kwargs): def new_struct(cls: Any, *args: Any, **kwargs: Any) -> native.PyStruct:
inst = copy.copy(struct_prototype) inst = copy.copy(struct_prototype)
for prop, val in kwargs.items(): for prop, val in kwargs.items():
@ -218,7 +219,7 @@ def _build_struct(name, struct_prototype):
return type(name, (), type_dict) return type(name, (), type_dict)
def load_file(path: builtins.str | os.PathLike | pathlib.Path, quiet:bool=False, style:typing.Optional[str]=None, include_paths:typing.Optional[typing.List[builtins.str | os.PathLike | pathlib.Path]]=None, library_paths:typing.Optional[typing.List[builtins.str | os.PathLike | pathlib.Path]]=None, translation_domain:typing.Optional[str]=None): def load_file(path: str | os.PathLike[Any] | pathlib.Path, quiet:bool=False, style:typing.Optional[str]=None, include_paths:typing.Optional[typing.List[str | os.PathLike[Any] | pathlib.Path]]=None, library_paths:typing.Optional[typing.List[str | os.PathLike[Any] | pathlib.Path]]=None, translation_domain:typing.Optional[str]=None) -> Any:
compiler = native.Compiler() compiler = native.Compiler()
if style is not None: if style is not None:
@ -264,13 +265,12 @@ def load_file(path: builtins.str | os.PathLike | pathlib.Path, quiet:bool=False,
class SlintAutoLoader: class SlintAutoLoader:
def __init__(self, base_dir=None): def __init__(self, base_dir: str | None=None):
self.local_dirs: typing.List[str] | None = None
if base_dir: if base_dir:
self.local_dirs = [base_dir] self.local_dirs = [base_dir]
else:
self.local_dirs = None def __getattr__(self, name: str) -> Any:
def __getattr__(self, name):
for path in self.local_dirs or sys.path: for path in self.local_dirs or sys.path:
dir_candidate = os.path.join(path, name) dir_candidate = os.path.join(path, name)
if os.path.isdir(dir_candidate): if os.path.isdir(dir_candidate):
@ -297,14 +297,14 @@ class SlintAutoLoader:
loader = SlintAutoLoader() loader = SlintAutoLoader()
def _callback_decorator(callable, info): def _callback_decorator(callable: typing.Callable[..., Any], info: typing.Dict[str, Any]) -> typing.Callable[..., Any]:
if "name" not in info: if "name" not in info:
info["name"] = callable.__name__ info["name"] = callable.__name__
setattr(callable, "slint.callback", info) setattr(callable, "slint.callback", info)
return callable return callable
def callback(global_name=None, name=None) -> typing.Callable[..., typing.Any]: def callback(global_name: str | None=None, name : str | None=None) -> typing.Callable[..., Any]:
if callable(global_name): if callable(global_name):
callback = global_name callback = global_name
return _callback_decorator(callback, {}) return _callback_decorator(callback, {})
@ -316,7 +316,7 @@ def callback(global_name=None, name=None) -> typing.Callable[..., typing.Any]:
info["global_name"] = global_name info["global_name"] = global_name
return lambda callback: _callback_decorator(callback, info) return lambda callback: _callback_decorator(callback, info)
def set_xdg_app_id(app_id: str): def set_xdg_app_id(app_id: str) -> None:
native.set_xdg_app_id(app_id) native.set_xdg_app_id(app_id)
__all__ = ["CompileError", "Component", "load_file", "loader", "Image", "Color", __all__ = ["CompileError", "Component", "load_file", "loader", "Image", "Color",

View file

@ -4,29 +4,41 @@
from . import slint as native from . import slint as native
from collections.abc import Iterable from collections.abc import Iterable
import typing import typing
from typing import Any, cast, Iterator
class Model[T](native.PyModelBase): class Model[T](native.PyModelBase, Iterable[T]):
def __new__(cls, *args): def __new__(cls, *args: Any) -> "Model[T]":
return super().__new__(cls) return super().__new__(cls)
def __init__(self): def __init__(self) -> None:
self.init_self(self) self.init_self(self)
def __len__(self): def __len__(self) -> int:
return self.row_count() return self.row_count()
def __getitem__(self, index): def __getitem__(self, index: int) -> typing.Optional[T]:
return self.row_data(index) return self.row_data(index)
def __setitem__(self, index, value): def __setitem__(self, index: int, value: T) -> None:
self.set_row_data(index, value) self.set_row_data(index, value)
def __iter__(self): def __iter__(self) -> Iterator[T]:
return ModelIterator(self) return ModelIterator(self)
def notify_row_changed(self, row: int) -> None:
super().notify_row_changed(row)
def notify_row_removed(self, row: int, count: int) -> None:
super().notify_row_removed(row, count)
class ListModel[T](Model): def notify_row_added(self, row: int, count: int) -> None:
super().notify_row_added(row, count)
def row_data(self, row: int) -> typing.Optional[T]:
return cast(T, super().row_data(row))
class ListModel[T](Model[T]):
def __init__(self, iterable: typing.Optional[Iterable[T]]=None): def __init__(self, iterable: typing.Optional[Iterable[T]]=None):
super().__init__() super().__init__()
if iterable is not None: if iterable is not None:
@ -40,11 +52,11 @@ class ListModel[T](Model):
def row_data(self, row:int ) -> typing.Optional[T]: def row_data(self, row:int ) -> typing.Optional[T]:
return self.list[row] return self.list[row]
def set_row_data(self, row: int, data: T): def set_row_data(self, row: int, data: T) -> None:
self.list[row] = data self.list[row] = data
super().notify_row_changed(row) super().notify_row_changed(row)
def __delitem__(self, key: int | slice): def __delitem__(self, key: int | slice) -> None:
if isinstance(key, slice): if isinstance(key, slice):
start, stop, step = key.indices(len(self.list)) start, stop, step = key.indices(len(self.list))
del self.list[key] del self.list[key]
@ -54,23 +66,25 @@ class ListModel[T](Model):
del self.list[key] del self.list[key]
super().notify_row_removed(key, 1) super().notify_row_removed(key, 1)
def append(self, value: T): def append(self, value: T) -> None:
index = len(self.list) index = len(self.list)
self.list.append(value) self.list.append(value)
super().notify_row_added(index, 1) super().notify_row_added(index, 1)
class ModelIterator: class ModelIterator[T](Iterator[T]):
def __init__(self, model): def __init__(self, model: Model[T]):
self.model = model self.model = model
self.index = 0 self.index = 0
def __iter__(self): def __iter__(self) -> "ModelIterator[T]":
return self return self
def __next__(self): def __next__(self) -> T:
if self.index >= self.model.row_count(): if self.index >= self.model.row_count():
raise StopIteration() raise StopIteration()
index = self.index index = self.index
self.index += 1 self.index += 1
return self.model.row_data(index) data = self.model.row_data(index)
assert data is not None
return data

View file

@ -31,7 +31,7 @@ class Color:
green: builtins.int green: builtins.int
blue: builtins.int blue: builtins.int
alpha: builtins.int alpha: builtins.int
def __new__(cls,maybe_value:typing.Optional[builtins.str | RgbaColor | RgbColor | typing.Dict[str, int]] = None): ... def __new__(cls,maybe_value:typing.Optional[builtins.str | RgbaColor | RgbColor | typing.Dict[str, int]] = None) -> "Color": ...
def brighter(self, factor:builtins.float) -> "Color": def brighter(self, factor:builtins.float) -> "Color":
... ...
@ -57,7 +57,7 @@ class Color:
class Brush: class Brush:
color: Color color: Color
def __new__(cls,maybe_value:typing.Optional[Color]): ... def __new__(cls,maybe_value:typing.Optional[Color]) -> "Brush": ...
def is_transparent(self) -> builtins.bool: def is_transparent(self) -> builtins.bool:
... ...
@ -89,9 +89,9 @@ class Image:
width: builtins.int width: builtins.int
height: builtins.int height: builtins.int
path: typing.Optional[builtins.str] path: typing.Optional[builtins.str]
def __new__(cls,): ... def __new__(cls,) -> "Image": ...
@staticmethod @staticmethod
def load_from_path(path:builtins.str | os.PathLike | pathlib.Path) -> "Image": def load_from_path(path:builtins.str | os.PathLike[Any] | pathlib.Path) -> "Image":
r""" r"""
Loads the image from the specified path. Returns None if the image can't be loaded. Loads the image from the specified path. Returns None if the image can't be loaded.
""" """
@ -131,7 +131,7 @@ class Timer:
def set_interval(self, interval:datetime.timedelta) -> None: def set_interval(self, interval:datetime.timedelta) -> None:
... ...
def set_xdg_app_id(app_id: str): def set_xdg_app_id(app_id: str) -> None:
... ...
def run_event_loop() -> None: ... def run_event_loop() -> None: ...
@ -139,7 +139,13 @@ def run_event_loop() -> None: ...
def quit_event_loop() -> None: ... def quit_event_loop() -> None: ...
class PyModelBase: class PyModelBase:
... def init_self(self, *args: Any) -> None: ...
def row_count(self) -> int: ...
def row_data(self, row: int) -> typing.Optional[Any]: ...
def set_row_data(self, row: int, value: Any) -> None: ...
def notify_row_changed(self, row: int) -> None: ...
def notify_row_removed(self, row: int, count: int) -> None: ...
def notify_row_added(self, row: int, count: int) -> None: ...
class PyStruct(Any): class PyStruct(Any):
... ...
@ -168,14 +174,17 @@ class PyDiagnostic:
class ComponentInstance: class ComponentInstance:
def invoke(self, callback_name: str, *args): ... def show(self) -> None: ...
def invoke_global(self, global_name: str, callback_name: str, *args): ... def hide(self) -> None: ...
def set_property(self, property_name: str, value: Any): ... def run(self) -> None: ...
def get_property(self, property_name: str): ... def invoke(self, callback_name: str, *args: Any) -> Any: ...
def set_callback(self, callback_name: str, callback: Callable[..., Any]): ... def invoke_global(self, global_name: str, callback_name: str, *args: Any) -> Any: ...
def set_global_callback(self, global_name: str, callback_name: str, callback: Callable[..., Any]): ... def set_property(self, property_name: str, value: Any) -> None: ...
def set_global_property(self, global_name: str, property_name: str, value: Any): ... def get_property(self, property_name: str) -> Any: ...
def get_global_property(self, global_name: str, property_name: str): ... def set_callback(self, callback_name: str, callback: Callable[..., Any]) -> None: ...
def set_global_callback(self, global_name: str, callback_name: str, callback: Callable[..., Any]) -> None: ...
def set_global_property(self, global_name: str, property_name: str, value: Any) -> None: ...
def get_global_property(self, global_name: str, property_name: str) -> Any: ...
class ComponentDefinition: class ComponentDefinition:
@ -193,9 +202,14 @@ class ComponentDefinition:
class CompilationResult: class CompilationResult:
component_names: list[str] component_names: list[str]
diagnostics: list[PyDiagnostic] diagnostics: list[PyDiagnostic]
named_exports: list[typing.Tuple[str, str]]
structs_and_enums: typing.Dict[str, PyStruct]
def component(self, name: str) -> ComponentDefinition: ... def component(self, name: str) -> ComponentDefinition: ...
class Compiler: class Compiler:
include_paths: list[str] include_paths: list[str | os.PathLike[Any] | pathlib.Path]
def build_from_path(self, path:str) -> CompilationResult: ... library_paths: list[str | os.PathLike[Any] | pathlib.Path]
def build_from_source(self, source:str, path: str) -> CompilationResult: ... translation_domain: str
style: str
def build_from_path(self, path:str | os.PathLike[Any] | pathlib.Path) -> CompilationResult: ...
def build_from_source(self, source:str, path: str | os.PathLike[Any] | pathlib.Path) -> CompilationResult: ...