Python: run mypy on the tests

This commit is contained in:
Simon Hausmann 2025-02-12 16:28:26 +01:00 committed by Simon Hausmann
parent c111098e49
commit 607d70b3fa
21 changed files with 169 additions and 69 deletions

View file

@ -6,7 +6,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use pyo3_stub_gen::derive::gen_stub_pyclass_enum;
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyclass_enum, gen_stub_pymethods};
use slint_interpreter::{ComponentHandle, Value};
use i_slint_compiler::langtype::Type;
@ -22,11 +22,13 @@ use crate::errors::{
};
use crate::value::{PyStruct, PyValue};
#[gen_stub_pyclass]
#[pyclass(unsendable)]
pub struct Compiler {
compiler: slint_interpreter::Compiler,
}
#[gen_stub_pymethods]
#[pymethods]
impl Compiler {
#[new]
@ -81,9 +83,11 @@ impl Compiler {
}
#[derive(Debug, Clone)]
#[gen_stub_pyclass]
#[pyclass(unsendable)]
pub struct PyDiagnostic(slint_interpreter::Diagnostic);
#[gen_stub_pymethods]
#[pymethods]
impl PyDiagnostic {
#[getter]
@ -120,17 +124,20 @@ impl PyDiagnostic {
}
}
#[gen_stub_pyclass_enum]
#[pyclass(name = "DiagnosticLevel")]
pub enum PyDiagnosticLevel {
Error,
Warning,
}
#[gen_stub_pyclass]
#[pyclass(unsendable)]
pub struct CompilationResult {
result: slint_interpreter::CompilationResult,
}
#[gen_stub_pymethods]
#[pymethods]
impl CompilationResult {
#[getter]
@ -190,6 +197,7 @@ impl CompilationResult {
}
}
#[gen_stub_pyclass]
#[pyclass(unsendable)]
struct ComponentDefinition {
definition: slint_interpreter::ComponentDefinition,
@ -274,8 +282,9 @@ impl From<slint_interpreter::ValueType> for PyValueType {
}
}
#[gen_stub_pyclass]
#[pyclass(unsendable, weakref)]
struct ComponentInstance {
pub struct ComponentInstance {
instance: slint_interpreter::ComponentInstance,
callbacks: GcVisibleCallbacks,
global_callbacks: HashMap<String, GcVisibleCallbacks>,

View file

@ -5,18 +5,22 @@ use pyo3_stub_gen::{define_stub_info_gatherer, derive::gen_stub_pyfunction};
mod image;
mod interpreter;
use interpreter::{CompilationResult, Compiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
use interpreter::{
CompilationResult, Compiler, ComponentInstance, PyDiagnostic, PyDiagnosticLevel, PyValueType,
};
mod brush;
mod errors;
mod models;
mod timer;
mod value;
#[gen_stub_pyfunction]
#[pyfunction]
fn run_event_loop() -> Result<(), errors::PyPlatformError> {
slint_interpreter::run_event_loop().map_err(|e| e.into())
}
#[gen_stub_pyfunction]
#[pyfunction]
fn quit_event_loop() -> Result<(), errors::PyEventLoopError> {
slint_interpreter::quit_event_loop().map_err(|e| e.into())
@ -40,6 +44,7 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Compiler>()?;
m.add_class::<CompilationResult>()?;
m.add_class::<ComponentInstance>()?;
m.add_class::<image::PyImage>()?;
m.add_class::<PyValueType>()?;
m.add_class::<PyDiagnosticLevel>()?;

View file

@ -3,7 +3,7 @@
import nox
@nox.session(python="3.11")
@nox.session(python="3.12")
def python(session: nox.Session):
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
session.install(".[dev]")

View file

@ -8,7 +8,7 @@ build-backend = "maturin"
[project]
name = "slint"
version = "1.10.0a1"
requires-python = ">= 3.11"
requires-python = ">= 3.12"
authors = [
{name = "Slint Team", email = "info@slint.dev"},
]
@ -46,6 +46,7 @@ dev = ["pytest"]
[dependency-groups]
dev = [
"mypy>=1.15.0",
"nox>=2024.10.9",
"pdoc>=15.0.1",
"pytest>=8.3.4",
@ -56,3 +57,7 @@ dev = [
cache-keys = [{file = "pyproject.toml"}, {file = "Cargo.toml"}, {file = "**/*.rs"}]
# Uncomment to build rust code in development mode
# config-settings = { build-args = '--profile=dev' }
[tool.mypy]
strict=true
disallow_subclassing_any=false

View file

@ -13,6 +13,9 @@ import types
import logging
import importlib
import copy
import typing
import builtins
import pathlib
from .models import ListModel, Model
from .slint import Image, Color, Brush, Timer, TimerMode
@ -215,7 +218,7 @@ def _build_struct(name, struct_prototype):
return type(name, (), type_dict)
def load_file(path, quiet=False, style=None, include_paths=None, library_paths=None, translation_domain=None):
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):
compiler = native.Compiler()
if style is not None:
@ -301,7 +304,7 @@ def _callback_decorator(callable, info):
return callable
def callback(global_name=None, name=None):
def callback(global_name=None, name=None) -> typing.Callable[..., typing.Any]:
if callable(global_name):
callback = global_name
return _callback_decorator(callback, {})

View file

@ -2,13 +2,15 @@
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
from . import slint as native
from collections.abc import Iterable
import typing
class Model(native.PyModelBase):
class Model[T](native.PyModelBase):
def __new__(cls, *args):
return super().__new__(cls)
def __init__(self, lst=None):
def __init__(self):
self.init_self(self)
def __len__(self):
@ -24,25 +26,25 @@ class Model(native.PyModelBase):
return ModelIterator(self)
class ListModel(Model):
def __init__(self, iterable=None):
class ListModel[T](Model):
def __init__(self, iterable: typing.Optional[Iterable[T]]=None):
super().__init__()
if iterable is not None:
self.list = list(iterable)
else:
self.list = []
def row_count(self):
def row_count(self) -> int:
return len(self.list)
def row_data(self, row):
def row_data(self, row:int ) -> typing.Optional[T]:
return self.list[row]
def set_row_data(self, row, data):
def set_row_data(self, row: int, data: T):
self.list[row] = data
super().notify_row_changed(row)
def __delitem__(self, key):
def __delitem__(self, key: int | slice):
if isinstance(key, slice):
start, stop, step = key.indices(len(self.list))
del self.list[key]
@ -52,7 +54,7 @@ class ListModel(Model):
del self.list[key]
super().notify_row_removed(key, 1)
def append(self, value):
def append(self, value: T):
index = len(self.list)
self.list.append(value)
super().notify_row_added(index, 1)

View file

@ -0,0 +1,4 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
# This file is just a marker

View file

@ -9,7 +9,7 @@ import datetime
import os
import pathlib
import typing
from typing import Any, overload
from typing import Any, List
from collections.abc import Callable
from enum import Enum, auto
@ -31,7 +31,7 @@ class Color:
green: builtins.int
blue: builtins.int
alpha: builtins.int
def __new__(cls,maybe_value:typing.Optional[builtins.str | RgbaColor | RgbColor]): ...
def __new__(cls,maybe_value:typing.Optional[builtins.str | RgbaColor | RgbColor | typing.Dict[str, int]] = None): ...
def brighter(self, factor:builtins.float) -> "Color":
...
@ -111,7 +111,7 @@ class TimerMode(Enum):
class Timer:
def __new__(cls,): ...
def __new__(cls,) -> "Timer": ...
def start(self, mode:TimerMode, interval:datetime.timedelta, callback:typing.Any) -> None:
...
@ -134,10 +134,14 @@ class Timer:
def set_xdg_app_id(app_id: str):
...
def run_event_loop() -> None: ...
def quit_event_loop() -> None: ...
class PyModelBase:
...
class PyStruct:
class PyStruct(Any):
...
@ -150,3 +154,48 @@ class ValueType(Enum):
Struct = auto()
Brush = auto()
Image = auto()
class DiagnosticLevel(Enum):
Error = auto()
Warning = auto()
class PyDiagnostic:
level: DiagnosticLevel
message: str
line_number: int
column_number: int
source_file: typing.Optional[str]
class ComponentInstance:
def invoke(self, callback_name: str, *args): ...
def invoke_global(self, global_name: str, callback_name: str, *args): ...
def set_property(self, property_name: str, value: Any): ...
def get_property(self, property_name: str): ...
def set_callback(self, callback_name: str, callback: Callable[..., Any]): ...
def set_global_callback(self, global_name: str, callback_name: str, callback: Callable[..., Any]): ...
def set_global_property(self, global_name: str, property_name: str, value: Any): ...
def get_global_property(self, global_name: str, property_name: str): ...
class ComponentDefinition:
def create(self) -> ComponentInstance: ...
name: str
globals: list[str]
functions: list[str]
callbacks: list[str]
properties: dict[str, ValueType]
def global_functions(self, global_name: str) -> list[str]: ...
def global_callbacks(self, global_name: str) -> list[str]: ...
def global_properties(self, global_name: str) -> typing.Dict[str, ValueType]: ...
class CompilationResult:
component_names: list[str]
diagnostics: list[PyDiagnostic]
def component(self, name: str) -> ComponentDefinition: ...
class Compiler:
include_paths: list[str]
def build_from_path(self, path:str) -> CompilationResult: ...
def build_from_source(self, source:str, path: str) -> CompilationResult: ...

View file

@ -6,7 +6,7 @@ from slint import Color, Brush
def test_col_default():
def test_col_default() -> None:
col = Color()
assert col.red == 0
assert col.green == 0
@ -14,7 +14,7 @@ def test_col_default():
assert col.alpha == 0
def test_col_from_str():
def test_col_from_str() -> None:
col = Color("#123456")
assert col.red == 0x12
assert col.green == 0x34
@ -23,7 +23,7 @@ def test_col_from_str():
assert str(col) == "argb(255, 18, 52, 86)"
def test_col_from_rgb_dict():
def test_col_from_rgb_dict() -> None:
coldict = {'red': 0x12, 'green': 0x34, 'blue': 0x56}
col = Color(coldict)
assert col.red == 0x12
@ -32,7 +32,7 @@ def test_col_from_rgb_dict():
assert col.alpha == 255
def test_col_from_rgba_dict():
def test_col_from_rgba_dict() -> None:
coldict = {'red': 0x12, 'green': 0x34, 'blue': 0x56, 'alpha': 128}
col = Color(coldict)
assert col.red == 0x12
@ -41,7 +41,7 @@ def test_col_from_rgba_dict():
assert col.alpha == 128
def test_from_col():
def test_from_col() -> None:
col = Color("#123456")
brush = Brush(col)
assert brush.color == col

View file

@ -4,23 +4,32 @@
from slint import load_file, CompileError
import slint
import os
import pytest
import typing
def test_callback_decorators(caplog):
module = load_file(os.path.join(os.path.dirname(
__spec__.origin), "test-load-file.slint"), quiet=False)
def base_dir() -> str:
origin = __spec__.origin
assert origin is not None
base_dir = os.path.dirname(origin)
assert base_dir is not None
return base_dir
class SubClass(module.App):
def test_callback_decorators(caplog: pytest.LogCaptureFixture) -> None:
module = load_file(os.path.join(base_dir(), "test-load-file.slint"), quiet=False)
class SubClass(module.App): # type: ignore
@slint.callback()
def say_hello_again(self, arg):
def say_hello_again(self, arg: str) -> str:
return "say_hello_again:" + arg
@slint.callback(name="say-hello")
def renamed(self, arg):
def renamed(self, arg: str) -> str:
return "renamed:" + arg
@slint.callback(global_name="MyGlobal", name="global-callback")
def global_callback(self, arg):
def global_callback(self, arg: str) -> str:
return "global:" + arg
instance = SubClass()

View file

@ -6,7 +6,7 @@ from slint import slint as native
from slint.slint import ValueType
def test_basic_compiler():
def test_basic_compiler() -> None:
compiler = native.Compiler()
assert compiler.include_paths == []
@ -66,7 +66,7 @@ def test_basic_compiler():
assert instance != None
def test_compiler_build_from_path():
def test_compiler_build_from_path() -> None:
compiler = native.Compiler()
result = compiler.build_from_path("Nonexistent.slint")

View file

@ -4,9 +4,10 @@
from slint import slint as native
import weakref
import gc
import typing
def test_callback_gc():
def test_callback_gc() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""
@ -17,17 +18,18 @@ def test_callback_gc():
""", "").component("Test")
assert compdef != None
instance = compdef.create()
instance: native.ComponentInstance | None = compdef.create()
assert instance != None
class Handler:
def __init__(self, instance):
def __init__(self, instance: native.ComponentInstance) -> None:
self.instance = instance
def python_callback(self, input):
return input + instance.get_property("test-value")
def python_callback(self, input: str) -> str:
return input + typing.cast(str, self.instance.get_property("test-value"))
handler = Handler(instance)
handler: Handler | None = Handler(instance)
assert handler is not None
instance.set_callback(
"test-callback", handler.python_callback)
handler = None

View file

@ -8,7 +8,7 @@ import os
def test_property_access():
def test_property_access() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""
@ -125,7 +125,7 @@ def test_property_access():
assert instance.get_global_property("TestGlobal", "theglobalprop") == "Ok"
def test_callbacks():
def test_callbacks() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""

View file

@ -4,11 +4,18 @@
import pytest
from slint import load_file, CompileError
import os
from typing import cast
def test_load_file(caplog):
module = load_file(os.path.join(os.path.dirname(
__spec__.origin), "test-load-file.slint"), quiet=False)
def base_dir() -> str:
origin = __spec__.origin
assert origin is not None
base_dir = os.path.dirname(origin)
assert base_dir is not None
return base_dir
def test_load_file(caplog: pytest.LogCaptureFixture) -> None:
module = load_file(os.path.join(base_dir(), "test-load-file.slint"), quiet=False)
assert "The property 'color' has been deprecated. Please use 'background' instead" in caplog.text
@ -35,14 +42,13 @@ def test_load_file(caplog):
assert module.MyDiag is module.Diag
def test_load_file_fail():
def test_load_file_fail() -> None:
with pytest.raises(CompileError, match="Could not compile non-existent.slint"):
load_file("non-existent.slint")
def test_load_file_wrapper():
module = load_file(os.path.join(os.path.dirname(
__spec__.origin), "test-load-file.slint"), quiet=False)
def test_load_file_wrapper() -> None:
module = load_file(os.path.join(base_dir(), "test-load-file.slint"), quiet=False)
instance = module.App()
@ -64,11 +70,10 @@ def test_load_file_wrapper():
del instance
def test_constructor_kwargs():
module = load_file(os.path.join(os.path.dirname(
__spec__.origin), "test-load-file.slint"), quiet=False)
def test_constructor_kwargs() -> None:
module = load_file(os.path.join(base_dir(), "test-load-file.slint"), quiet=False)
def early_say_hello(arg):
def early_say_hello(arg: str) -> str:
return "early:" + arg
instance = module.App(hello="Set early", say_hello=early_say_hello)

View file

@ -8,12 +8,12 @@ import sys
import os
def test_magic_import():
def test_magic_import() -> None:
instance = loader.test_load_file.App()
del instance
def test_magic_import_path():
def test_magic_import_path() -> None:
oldsyspath = sys.path
assert loader.printerdemo == None
try:

View file

@ -3,9 +3,10 @@
from slint import slint as native
from slint import models as models
import typing
def test_model_notify():
def test_model_notify() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""
@ -51,7 +52,7 @@ def test_model_notify():
"fixed-height-model"), models.ListModel)
def test_model_from_list():
def test_model_from_list() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""
@ -73,7 +74,7 @@ def test_model_from_list():
assert list(instance.get_property("data")) == [1, 2, 3, 4]
def test_python_model_sequence():
def test_python_model_sequence() -> None:
model = models.ListModel([1, 2, 3, 4, 5])
assert len(model) == 5
@ -83,8 +84,8 @@ def test_python_model_sequence():
assert model[2] == 3
def test_python_model_iterable():
def test_generator(max):
def test_python_model_iterable() -> None:
def test_generator(max: int) -> typing.Iterator[int]:
i = 0
while i < max:
yield i
@ -96,7 +97,7 @@ def test_python_model_iterable():
assert list(model) == [0, 1, 2, 3, 4]
def test_rust_model_sequence():
def test_rust_model_sequence() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""
@ -116,7 +117,7 @@ def test_rust_model_sequence():
assert model[2] == 3
def test_model_writeback():
def test_model_writeback() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source("""

View file

@ -6,10 +6,13 @@ from slint import slint as native
from slint.slint import ValueType;
from datetime import timedelta
def test_timer():
counter: int
def test_timer() -> None:
global counter
counter = 0
def quit_after_two_invocations():
def quit_after_two_invocations() -> None:
global counter
counter = min(counter + 1, 2)
if counter == 2:
@ -21,6 +24,6 @@ def test_timer():
test_timer.stop()
assert(counter == 2)
def test_single_shot():
def test_single_shot() -> None:
native.Timer.single_shot(timedelta(milliseconds=100), native.quit_event_loop)
native.run_event_loop()