mirror of
https://github.com/slint-ui/slint.git
synced 2025-12-23 09:19:32 +00:00
slint-compiler: Add support for generating Python stubs (#10123)
The generated file offers the following two pieces of functionality: - Convenient front-end to slint.load_file() by loading the .slint file (replaces use of auto-loader) - More importantly: Type information for exported properties, callbacks, functions, and globals On loading, the previously generated API is compared to what's in the .slint file and if the .slint file was changed in an incompatible way, an exception is thrown. A Python test driver is added which performs three steps with each test case: 1. Generate python stubs and checks that they can be loaded with the python interpreter and that `uv ty check` passes. 2. Extract expected python API out of pyi sections and compares them. 3. Appends any python code from python sections and runs them (this is combined with step 1) Fixes #4136
This commit is contained in:
parent
65fb303b4b
commit
16ceb115a1
53 changed files with 2286 additions and 33 deletions
|
|
@ -44,8 +44,8 @@ accessibility = ["slint-interpreter/accessibility"]
|
|||
[dependencies]
|
||||
i-slint-backend-selector = { workspace = true }
|
||||
i-slint-core = { workspace = true, features = ["tr"] }
|
||||
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] }
|
||||
i-slint-compiler = { workspace = true }
|
||||
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal", "internal-highlight"] }
|
||||
i-slint-compiler = { workspace = true, features = ["python"] }
|
||||
pyo3 = { version = "0.26", features = ["extension-module", "indexmap", "chrono", "abi3-py311"] }
|
||||
indexmap = { version = "2.1.0" }
|
||||
chrono = "0.4"
|
||||
|
|
|
|||
|
|
@ -340,6 +340,60 @@ For the common use case of interacting with REST APIs, we recommend the [`aiohtt
|
|||
|
||||
- Pipes and sub-processes are only supported on Unix-like platforms.
|
||||
|
||||
## Type Hints
|
||||
|
||||
[PEP 484](https://peps.python.org/pep-0484/) introduces a standard syntax for type annotations to Python, enabling static analysis for
|
||||
type checking, refactoring, and code completion. Popular type checkers include [mypy](http://mypy-lang.org/), [Pyre](https://pyre-check.org),
|
||||
and Astral's [ty](https://docs.astral.sh/ty/).
|
||||
|
||||
Use Slint's [slint-compiler](https://pypi.org/project/slint-compiler/) to generate stub `.py` files for `.slint` files, which are annotated with
|
||||
type information. These replace the need to call `load_file` or any use of `slint.loader`.
|
||||
|
||||
1. Create a new project with `uv init`.
|
||||
2. Add the Slint Python package to your Python project: `uv add slint`
|
||||
3. Create a file called `app-window.slint`:
|
||||
|
||||
```slint
|
||||
import { Button, VerticalBox } from "std-widgets.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
in-out property<int> counter: 42;
|
||||
callback request-increase-value();
|
||||
VerticalBox {
|
||||
Text {
|
||||
text: "Counter: \{root.counter}";
|
||||
}
|
||||
Button {
|
||||
text: "Increase value";
|
||||
clicked => {
|
||||
root.request-increase-value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Run the [slint-compiler](https://pypi.org/project/slint-compiler/) to generate `app_window.py`:
|
||||
`uvx slint-compiler -f python -o app_window.py app-window.slint`
|
||||
|
||||
5. Create a file called `main.py`:
|
||||
|
||||
```python
|
||||
import slint
|
||||
import app_window
|
||||
|
||||
class App(app_window.AppWindow):
|
||||
@slint.callback
|
||||
def request_increase_value(self):
|
||||
self.counter = self.counter + 1
|
||||
|
||||
app = App()
|
||||
app.run()
|
||||
```
|
||||
|
||||
5. Run it with `uv run main.py`
|
||||
|
||||
|
||||
## Third-Party Licenses
|
||||
|
||||
For a list of the third-party licenses of all dependencies, see the separate [Third-Party Licenses page](thirdparty.html).
|
||||
|
|
|
|||
63
api/python/slint/api_match.rs
Normal file
63
api/python/slint/api_match.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// 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
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods};
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(name = "GeneratedAPI", unsendable)]
|
||||
pub struct PyGeneratedAPI {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) module: i_slint_compiler::generator::python::PyModule,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyGeneratedAPI {
|
||||
#[new]
|
||||
fn new(path: PathBuf, json: &str) -> PyResult<Self> {
|
||||
let module = i_slint_compiler::generator::python::PyModule::load_from_json(json)
|
||||
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
|
||||
Ok(Self { path, module })
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn compare_generated_vs_actual(generated: &Self, actual: &Self) -> PyResult<()> {
|
||||
let changed_globals = generated.module.changed_globals(&actual.module);
|
||||
let changed_components = generated.module.changed_components(&actual.module);
|
||||
let changed_structs_or_enums = generated.module.changed_structs_or_enums(&actual.module);
|
||||
|
||||
let diff = changed_globals.is_some()
|
||||
|| changed_components.is_some()
|
||||
|| changed_structs_or_enums.is_some();
|
||||
|
||||
let incompatible_changes =
|
||||
changed_globals.as_ref().map_or(false, |c| c.incompatible_changes())
|
||||
|| changed_components.as_ref().map_or(false, |c| c.incompatible_changes())
|
||||
|| changed_structs_or_enums.as_ref().map_or(false, |c| c.incompatible_changes());
|
||||
|
||||
if diff {
|
||||
let slint_file = actual.path.display();
|
||||
let python_file = generated.path.display();
|
||||
eprintln!(
|
||||
r#"Changes detected between {slint_file} and {python_file}
|
||||
Re-run the slint compiler to re-generate the file, for example:
|
||||
|
||||
uxv slint-compiler -f python -o {slint_file} {python_file}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
if incompatible_changes {
|
||||
Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
|
||||
"Incompatible API changes detected between {} and {}",
|
||||
generated.path.display(),
|
||||
actual.path.display()
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use std::collections::HashMap;
|
|||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use i_slint_compiler::generator::python::ident;
|
||||
use pyo3::IntoPyObjectExt;
|
||||
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyclass_enum, gen_stub_pymethods};
|
||||
use slint_interpreter::{ComponentHandle, Value};
|
||||
|
|
@ -19,6 +20,7 @@ use pyo3::prelude::*;
|
|||
use pyo3::types::PyTuple;
|
||||
use pyo3::PyTraverseError;
|
||||
|
||||
use crate::api_match::PyGeneratedAPI;
|
||||
use crate::errors::{
|
||||
PyGetPropertyError, PyInvokeError, PyPlatformError, PySetCallbackError, PySetPropertyError,
|
||||
};
|
||||
|
|
@ -74,7 +76,8 @@ impl Compiler {
|
|||
}
|
||||
|
||||
fn build_from_path(&mut self, py: Python<'_>, path: PathBuf) -> CompilationResult {
|
||||
CompilationResult::new(spin_on::spin_on(self.compiler.build_from_path(path)), py)
|
||||
let result = spin_on::spin_on(self.compiler.build_from_path(&path));
|
||||
CompilationResult::new(result, path, py)
|
||||
}
|
||||
|
||||
fn build_from_source(
|
||||
|
|
@ -83,10 +86,8 @@ impl Compiler {
|
|||
source_code: String,
|
||||
path: PathBuf,
|
||||
) -> CompilationResult {
|
||||
CompilationResult::new(
|
||||
spin_on::spin_on(self.compiler.build_from_source(source_code, path)),
|
||||
py,
|
||||
)
|
||||
let result = spin_on::spin_on(self.compiler.build_from_source(source_code, path.clone()));
|
||||
CompilationResult::new(result, path, py)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,12 +146,13 @@ pub enum PyDiagnosticLevel {
|
|||
pub struct CompilationResult {
|
||||
result: slint_interpreter::CompilationResult,
|
||||
type_collection: TypeCollection,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl CompilationResult {
|
||||
fn new(result: slint_interpreter::CompilationResult, py: Python<'_>) -> Self {
|
||||
fn new(result: slint_interpreter::CompilationResult, path: PathBuf, py: Python<'_>) -> Self {
|
||||
let type_collection = TypeCollection::new(&result, py);
|
||||
Self { result, type_collection }
|
||||
Self { result, type_collection, path }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,14 +190,14 @@ impl CompilationResult {
|
|||
self.type_collection.struct_to_py(slint_interpreter::Struct::from_iter(
|
||||
s.fields.iter().map(|(name, field_type)| {
|
||||
(
|
||||
name.to_string(),
|
||||
ident(&name).into(),
|
||||
slint_interpreter::default_value_for_type(field_type),
|
||||
)
|
||||
}),
|
||||
));
|
||||
|
||||
structs.insert(
|
||||
s.name.slint_name().unwrap().to_string(),
|
||||
ident(&s.name.slint_name().unwrap()).into(),
|
||||
struct_instance.into_bound_py_any(py).unwrap(),
|
||||
);
|
||||
}
|
||||
|
|
@ -216,6 +218,35 @@ impl CompilationResult {
|
|||
fn named_exports(&self) -> Vec<(String, String)> {
|
||||
self.result.named_exports(i_slint_core::InternalToken {}).cloned().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn generated_api(&self) -> PyResult<PyGeneratedAPI> {
|
||||
let type_loader = self
|
||||
.result
|
||||
.components()
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(
|
||||
"Cannot generated API for empty slint file",
|
||||
)
|
||||
})?
|
||||
.type_loader();
|
||||
let doc = type_loader.get_document(&self.path).ok_or_else(|| {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(
|
||||
"Failed to load document from cache for API generation",
|
||||
)
|
||||
})?;
|
||||
i_slint_compiler::generator::python::generate_py_module(
|
||||
doc,
|
||||
&i_slint_compiler::CompilerConfiguration::new(
|
||||
i_slint_compiler::generator::OutputFormat::Python,
|
||||
),
|
||||
)
|
||||
.map_err(|e| {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(format!("Error generating pymodule: {}", e))
|
||||
})
|
||||
.map(|module| PyGeneratedAPI { path: self.path.clone(), module })
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
|
|
@ -344,6 +375,7 @@ impl From<i_slint_compiler::langtype::Type> for PyValueType {
|
|||
| i_slint_compiler::langtype::Type::PhysicalLength
|
||||
| i_slint_compiler::langtype::Type::LogicalLength
|
||||
| i_slint_compiler::langtype::Type::Percent
|
||||
| i_slint_compiler::langtype::Type::Rem
|
||||
| i_slint_compiler::langtype::Type::UnitProduct(_) => PyValueType::Number,
|
||||
i_slint_compiler::langtype::Type::String => PyValueType::String,
|
||||
i_slint_compiler::langtype::Type::Array(..) => PyValueType::Model,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use interpreter::{
|
|||
CompilationResult, Compiler, ComponentDefinition, ComponentInstance, PyDiagnostic,
|
||||
PyDiagnosticLevel, PyValueType,
|
||||
};
|
||||
mod api_match;
|
||||
mod async_adapter;
|
||||
mod brush;
|
||||
mod errors;
|
||||
|
|
@ -183,6 +184,7 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
m.add_class::<models::PyModelBase>()?;
|
||||
m.add_class::<value::PyStruct>()?;
|
||||
m.add_class::<async_adapter::AsyncAdapter>()?;
|
||||
m.add_class::<api_match::PyGeneratedAPI>()?;
|
||||
m.add_function(wrap_pyfunction!(run_event_loop, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_xdg_app_id, m)?)?;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ from pathlib import Path
|
|||
from collections.abc import Coroutine
|
||||
import asyncio
|
||||
import gettext
|
||||
import gzip
|
||||
import base64
|
||||
|
||||
Struct = native.PyStruct
|
||||
|
||||
|
|
@ -268,7 +270,7 @@ def _build_struct(name: str, struct_prototype: native.PyStruct) -> type:
|
|||
return type(name, (), type_dict)
|
||||
|
||||
|
||||
def load_file(
|
||||
def _load_file(
|
||||
path: str | os.PathLike[Any] | pathlib.Path,
|
||||
quiet: bool = False,
|
||||
style: typing.Optional[str] = None,
|
||||
|
|
@ -277,7 +279,7 @@ def load_file(
|
|||
typing.Dict[str, os.PathLike[Any] | pathlib.Path]
|
||||
] = None,
|
||||
translation_domain: typing.Optional[str] = None,
|
||||
) -> types.SimpleNamespace:
|
||||
) -> typing.Tuple[types.SimpleNamespace, native.CompilationResult]:
|
||||
"""This function is the low-level entry point into Slint for instantiating components. It loads the `.slint` file at
|
||||
the specified `path` and returns a namespace with all exported components as Python classes, as well as enums, and structs.
|
||||
|
||||
|
|
@ -339,6 +341,57 @@ def load_file(
|
|||
new_name = _normalize_prop(new_name)
|
||||
setattr(module, new_name, getattr(module, orig_name))
|
||||
|
||||
return (module, result)
|
||||
|
||||
|
||||
def load_file(
|
||||
path: str | os.PathLike[Any] | pathlib.Path,
|
||||
quiet: bool = False,
|
||||
style: typing.Optional[str] = None,
|
||||
include_paths: typing.Optional[typing.List[os.PathLike[Any] | pathlib.Path]] = None,
|
||||
library_paths: typing.Optional[
|
||||
typing.Dict[str, os.PathLike[Any] | pathlib.Path]
|
||||
] = None,
|
||||
translation_domain: typing.Optional[str] = None,
|
||||
) -> types.SimpleNamespace:
|
||||
"""This function is the low-level entry point into Slint for instantiating components. It loads the `.slint` file at
|
||||
the specified `path` and returns a namespace with all exported components as Python classes, as well as enums, and structs.
|
||||
|
||||
* `quiet`: Set to true to prevent any warnings during compilation from being printed to stderr.
|
||||
* `style`: Specify a widget style.
|
||||
* `include_paths`: Additional include paths used to look up `.slint` files imported from other `.slint` files.
|
||||
* `library_paths`: A dictionary that maps library names to their location in the file system. This is then used to look up
|
||||
library imports, such as `import { MyButton } from "@mylibrary";`.
|
||||
* `translation_domain`: The domain to use for looking up the catalogue run-time translations. This must match the
|
||||
translation domain used when extracting translations with `slint-tr-extractor`.
|
||||
|
||||
"""
|
||||
|
||||
return _load_file(
|
||||
path, quiet, style, include_paths, library_paths, translation_domain
|
||||
)[0]
|
||||
|
||||
|
||||
def _load_file_checked(
|
||||
path: str | os.PathLike[Any] | pathlib.Path,
|
||||
expected_api_base64_compressed: str,
|
||||
generated_file: str | os.PathLike[Any] | pathlib.Path,
|
||||
) -> types.SimpleNamespace:
|
||||
"""@private"""
|
||||
|
||||
module, compilation_result = _load_file(path)
|
||||
|
||||
expected_api = gzip.decompress(
|
||||
base64.standard_b64decode(expected_api_base64_compressed)
|
||||
).decode("utf-8")
|
||||
|
||||
generated_api_module = native.GeneratedAPI(path=generated_file, json=expected_api)
|
||||
actual_api_module = compilation_result.generated_api
|
||||
|
||||
generated_api_module.compare_generated_vs_actual(
|
||||
generated=generated_api_module, actual=actual_api_module
|
||||
)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
|
|
@ -420,7 +473,7 @@ def _callback_decorator(
|
|||
|
||||
|
||||
def callback(
|
||||
global_name: str | None = None, name: str | None = None
|
||||
global_name: typing.Callable[..., Any] | str | None = None, name: str | None = None
|
||||
) -> typing.Callable[..., Any]:
|
||||
"""Use the callback decorator to mark a method as a callback that can be invoked from the Slint component.
|
||||
|
||||
|
|
@ -553,6 +606,7 @@ __all__ = [
|
|||
"CompileError",
|
||||
"Component",
|
||||
"load_file",
|
||||
"_load_file_checked",
|
||||
"loader",
|
||||
"Image",
|
||||
"Color",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ class CompilationResult:
|
|||
diagnostics: list[PyDiagnostic]
|
||||
named_exports: list[typing.Tuple[str, str]]
|
||||
structs_and_enums: typing.Tuple[typing.Dict[str, PyStruct], typing.Dict[str, Enum]]
|
||||
generated_api: GeneratedAPI
|
||||
def component(self, name: str) -> ComponentDefinition: ...
|
||||
|
||||
class Compiler:
|
||||
|
|
@ -244,3 +245,12 @@ class AsyncAdapter:
|
|||
) -> "AsyncAdapter": ...
|
||||
def wait_for_readable(self, callback: typing.Callable[[int], None]) -> None: ...
|
||||
def wait_for_writable(self, callback: typing.Callable[[int], None]) -> None: ...
|
||||
|
||||
class GeneratedAPI:
|
||||
def __new__(
|
||||
cls, path: str | os.PathLike[Any] | pathlib.Path, json: str
|
||||
) -> "GeneratedAPI": ...
|
||||
@staticmethod
|
||||
def compare_generated_vs_actual(
|
||||
generated: "GeneratedAPI", actual: "GeneratedAPI"
|
||||
) -> None: ...
|
||||
|
|
|
|||
6
api/python/slint/tests/api-match.slint
Normal file
6
api/python/slint/tests/api-match.slint
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// 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
|
||||
|
||||
export component Test inherits Window {
|
||||
in-out property <string> name;
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ export { Secret-Struct as Public-Struct }
|
|||
export enum TestEnum {
|
||||
Variant1,
|
||||
Variant2,
|
||||
Variant-three,
|
||||
}
|
||||
|
||||
export component App inherits Window {
|
||||
|
|
|
|||
81
api/python/slint/tests/test_api_match.py
Normal file
81
api/python/slint/tests/test_api_match.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 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
|
||||
|
||||
import pytest
|
||||
from slint import _load_file_checked
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import gzip
|
||||
|
||||
|
||||
def base_dir() -> Path:
|
||||
origin = __spec__.origin
|
||||
assert origin is not None
|
||||
base_dir = Path(origin).parent
|
||||
assert base_dir is not None
|
||||
return base_dir
|
||||
|
||||
|
||||
def compress_and_encode(json: str) -> str:
|
||||
return base64.standard_b64encode(gzip.compress(json.encode("utf-8"))).decode(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
|
||||
def test_no_change() -> None:
|
||||
_load_file_checked(
|
||||
base_dir() / "api-match.slint",
|
||||
expected_api_base64_compressed=compress_and_encode(r"""
|
||||
{
|
||||
"version":"1.0",
|
||||
"globals":[],
|
||||
"components":[
|
||||
{
|
||||
"name":"Test",
|
||||
"properties":[
|
||||
{
|
||||
"name": "name",
|
||||
"ty": "str"
|
||||
}
|
||||
],
|
||||
"aliases":[]
|
||||
}
|
||||
],
|
||||
"structs_and_enums":[]
|
||||
}"""),
|
||||
generated_file="/some/path.py",
|
||||
)
|
||||
|
||||
|
||||
def test_incompatible_changes() -> None:
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
_load_file_checked(
|
||||
base_dir() / "api-match.slint",
|
||||
expected_api_base64_compressed=compress_and_encode(r"""
|
||||
{
|
||||
"version":"1.0",
|
||||
"globals":[],
|
||||
"components":[
|
||||
{
|
||||
"name":"Test",
|
||||
"properties":[
|
||||
{
|
||||
"name": "name",
|
||||
"ty": "str"
|
||||
},
|
||||
{
|
||||
"name": "not_there_anymore",
|
||||
"ty": "str"
|
||||
}
|
||||
],
|
||||
"aliases":[]
|
||||
}
|
||||
],
|
||||
"structs_and_enums":[]
|
||||
}"""),
|
||||
generated_file="/some/path.py",
|
||||
)
|
||||
assert (
|
||||
f"Incompatible API changes detected between /some/path.py and {base_dir() / 'api-match.slint'}"
|
||||
== str(excinfo.value)
|
||||
)
|
||||
|
|
@ -34,6 +34,9 @@ def test_enums() -> None:
|
|||
instance.enum_property = TestEnum.Variant1
|
||||
assert instance.enum_property == TestEnum.Variant1
|
||||
assert instance.enum_property.__class__ is TestEnum
|
||||
instance.enum_property = TestEnum.Variant_three
|
||||
assert instance.enum_property == TestEnum.Variant_three
|
||||
assert instance.enum_property.__class__ is TestEnum
|
||||
|
||||
model_with_enums = instance.model_with_enums
|
||||
assert len(model_with_enums) == 2
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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
|
||||
|
||||
use i_slint_compiler::generator::python::ident;
|
||||
use pyo3::types::PyDict;
|
||||
use pyo3::{prelude::*, PyVisit};
|
||||
use pyo3::{IntoPyObjectExt, PyTraverseError};
|
||||
|
|
@ -216,17 +217,14 @@ impl TypeCollection {
|
|||
en.name.to_string(),
|
||||
en.values
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let val = val.to_string();
|
||||
(val.clone(), val)
|
||||
})
|
||||
.map(|val| (ident(&val).to_string(), val.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
enum_classes.insert(en.name.to_string(), enum_type);
|
||||
enum_classes.insert(ident(&en.name).into(), enum_type);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -250,7 +248,7 @@ impl TypeCollection {
|
|||
enum_value: &str,
|
||||
py: Python<'_>,
|
||||
) -> Result<Py<PyAny>, PyErr> {
|
||||
let enum_cls = self.enum_classes.get(enum_name).ok_or_else(|| {
|
||||
let enum_cls = self.enum_classes.get(ident(enum_name).as_str()).ok_or_else(|| {
|
||||
PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
|
||||
"Slint provided enum {enum_name} is unknown"
|
||||
))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue