From 7f2dc1e3401bc9fc39db3913fa57044db77ed4fc Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Wed, 19 Aug 2026 10:03:42 +0200 Subject: [PATCH 1/6] adding SdkSettings utility class --- README.md | 36 ++++++ cuvis/General.py | 37 ++++--- cuvis/__init__.py | 1 + cuvis/sdk_settings.py | 168 ++++++++++++++++++++++++++++ tests/test_sdk_settings.py | 220 +++++++++++++++++++++++++++++++++++++ 5 files changed, 449 insertions(+), 13 deletions(-) create mode 100644 cuvis/sdk_settings.py create mode 100644 tests/test_sdk_settings.py diff --git a/README.md b/README.md index 1cd90e6..dda2342 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,42 @@ covering some basic applications. Further, we provide a set of example measurements to explore [here](https://cloud.cubert-gmbh.de/s/SrkSRja5FKGS2Tw). These measurements are also used by the examples mentioned above. +### Configuring the SDK from code + +The SDK reads its configuration from a `cuvis.settings` file in the directory passed to `cuvis.init()`. +`SdkSettings` lets you write that configuration in Python instead of maintaining the file by hand. + +```python +import cuvis + +settings = cuvis.SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) +cuvis.init(settings) +``` + +It behaves like a dict, so you can inspect and edit it before applying it: + +```python +settings["verbose"] = True # booleans become "true" / "false" +del settings["force_gpu_mode"] +print(dict(settings)) # {'processing_thread_count': '8', 'verbose': 'true'} +print(settings.xml_str) # the exact file that will be written +``` + +An existing configuration can be loaded from a settings file or from the directory containing one, and keyword arguments override what was loaded: + +```python +settings = cuvis.SdkSettings("/path/to/settings_dir", processing_thread_count=4) +``` + +Used as a context manager it serializes itself into a temporary directory, which is useful for pointing other tools at the same configuration: + +```python +with settings as settings_dir: + cuvis.init(settings_dir) +``` + +Use `settings.save(path)` to write the file to a permanent location instead. + ### Getting involved cuvis.hub welcomes your enthusiasm and expertise! diff --git a/cuvis/General.py b/cuvis/General.py index b499a4d..f04ca90 100644 --- a/cuvis/General.py +++ b/cuvis/General.py @@ -1,9 +1,12 @@ import logging import os +import platform +from contextlib import ExitStack from importlib.metadata import version as imp_version from ._cuvis_il import cuvis_il from .cuvis_aux import SDKException +from .sdk_settings import SdkSettings from pathlib import Path import cuvis.cuvis_types as internal @@ -12,22 +15,30 @@ def init( - settings_path: str = ".", + settings_path: Union[str, Path, SdkSettings] = ".", global_loglevel: Union[int, str] = logging.DEBUG, logfile_name: Optional[str] = None, ): - if "CUVIS_SETTINGS" in os.environ and settings_path == ".": - # env variable is set and settings path is default kwarg - settings_path = os.environ["CUVIS_SETTINGS"] - - if isinstance(global_loglevel, str): - # also support string as input argument - global_loglevel = internal.__strToLogLevel__[global_loglevel] - - if cuvis_il.status_ok != cuvis_il.cuvis_init( - settings_path, internal.__CuvisLoglevel__[global_loglevel], logfile_name - ): - raise SDKException() + with ExitStack() as stack: + if isinstance(settings_path, SdkSettings): + # The SDK reads the settings once here, so the temporary directory + # only has to exist for the duration of the cuvis_init call. + settings_path = stack.enter_context(settings_path) + elif isinstance(settings_path, Path): + settings_path = str(settings_path) + + if "CUVIS_SETTINGS" in os.environ and settings_path == ".": + # env variable is set and settings path is default kwarg + settings_path = os.environ["CUVIS_SETTINGS"] + + if isinstance(global_loglevel, str): + # also support string as input argument + global_loglevel = internal.__strToLogLevel__[global_loglevel] + + if cuvis_il.status_ok != cuvis_il.cuvis_init( + settings_path, internal.__CuvisLoglevel__[global_loglevel], logfile_name + ): + raise SDKException() def shutdown(): diff --git a/cuvis/__init__.py b/cuvis/__init__.py index 0bcaa2c..065de6c 100644 --- a/cuvis/__init__.py +++ b/cuvis/__init__.py @@ -25,6 +25,7 @@ from .ProcessingContext import ProcessingContext from .Measurement import Measurement from .General import init, shutdown, version, set_log_level +from .sdk_settings import SdkSettings from .FileWriteSettings import ( GeneralExportSettings, SaveArgs, diff --git a/cuvis/sdk_settings.py b/cuvis/sdk_settings.py new file mode 100644 index 0000000..0b9ba07 --- /dev/null +++ b/cuvis/sdk_settings.py @@ -0,0 +1,168 @@ +import io +import tempfile +from collections.abc import Iterator, MutableMapping +from enum import Enum +from pathlib import Path +from typing import Any, Optional, Union +from xml.etree import ElementTree as ET + +SETTINGS_NAMESPACE = "http://cubert-gmbh.de/core/settings.xsd" +SETTINGS_VERSION = "1.0.0" +SETTINGS_FILENAME = "cuvis.settings" + +_SETTINGS_TAG = "{{{}}}settings".format(SETTINGS_NAMESPACE) +_PROPERTY_TAG = "{{{}}}property".format(SETTINGS_NAMESPACE) + + +def _resolve_source(source: Union[str, Path]) -> Path: + source = Path(source) + if not source.is_dir(): + return source + settings_path = source / SETTINGS_FILENAME + if not settings_path.is_file(): + raise FileNotFoundError( + "No '{}' found in directory '{}'.".format(SETTINGS_FILENAME, source) + ) + return settings_path + + +def _load(path: Path) -> dict[str, str]: + try: + root = ET.parse(str(path)).getroot() + except ET.ParseError as e: + raise ValueError("Settings file '{}' is not valid XML: {}".format(path, e)) + + if root.tag != _SETTINGS_TAG: + raise ValueError( + "Settings file '{}' has root element '{}', expected '{}'.".format( + path, root.tag, _SETTINGS_TAG + ) + ) + if root.get("version") is None: + raise ValueError( + "Settings file '{}' is missing the required 'version' attribute.".format( + path + ) + ) + + data = {} + for prop in root: + if prop.tag != _PROPERTY_TAG: + raise ValueError( + "Settings file '{}' contains unexpected element '{}', " + "expected '{}'.".format(path, prop.tag, _PROPERTY_TAG) + ) + key = prop.get("id") + if key is None: + raise ValueError( + "Settings file '{}' contains a property without an 'id' " + "attribute.".format(path) + ) + if key in data: + raise ValueError( + "Settings file '{}' contains duplicate property id '{}'.".format( + path, key + ) + ) + data[key] = prop.get("value", "") + return data + + +class SdkSettings(MutableMapping): + """Dict-like builder for the SDK's ``cuvis.settings`` file. + + Behaves like a ``dict`` of setting id to value, can be loaded from an + existing settings file or from a directory containing one, and serializes + itself into a temporary directory when used as a context manager. + + Example:: + + settings = SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) + settings["verbose"] = True + print(dict(settings)) + + with settings as settings_dir: + cuvis.init(settings_dir) + """ + + def __init__(self, source: Optional[Union[str, Path]] = None, /, **kwargs): + self._tmpdir = None + ET.register_namespace("", SETTINGS_NAMESPACE) + + loaded = _load(_resolve_source(source)) if source is not None else {} + overrides = { + self._check_key(k): self._coerce_value(v) + for k, v in kwargs.items() + if v is not None + } + self._data = dict(loaded, **overrides) + self._rebuild_tree() + + @staticmethod + def _check_key(key: str) -> str: + if not isinstance(key, str) or not key or key.split() != [key]: + raise ValueError( + "Invalid setting id {!r}: must be a non-empty string without " + "whitespace.".format(key) + ) + return key + + @staticmethod + def _coerce_value(value: Any) -> str: + if isinstance(value, bool): + return "true" if value else "false" + if isinstance(value, Enum): + return str(value.value) + return str(value) + + def __getitem__(self, key: str) -> str: + return self._data[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._data[self._check_key(key)] = self._coerce_value(value) + self._rebuild_tree() + + def __delitem__(self, key: str) -> None: + del self._data[key] + self._rebuild_tree() + + def __iter__(self) -> Iterator[str]: + return iter(self._data) + + def __len__(self) -> int: + return len(self._data) + + def __repr__(self) -> str: + return "{}({!r})".format(type(self).__name__, self._data) + + def _rebuild_tree(self) -> None: + root = ET.Element(_SETTINGS_TAG) + root.set("version", SETTINGS_VERSION) + for key, value in self._data.items(): + prop = ET.SubElement(root, _PROPERTY_TAG) + prop.set("id", key) + prop.set("value", value) + self._tree = ET.ElementTree(root) + ET.indent(self._tree) + + @property + def xml_str(self) -> str: + buf = io.BytesIO() + self._tree.write(buf, xml_declaration=True, encoding="utf-8") + return buf.getvalue().decode("utf-8") + + def save(self, path: Union[str, Path]) -> None: + path = Path(path) + if path.is_dir(): + path = path / SETTINGS_FILENAME + self._tree.write(str(path), xml_declaration=True, encoding="utf-8") + + def __enter__(self) -> str: + self._tmpdir = tempfile.TemporaryDirectory() + self.save(self._tmpdir.name) + return self._tmpdir.name + + def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + self._tmpdir.cleanup() + self._tmpdir = None + return False diff --git a/tests/test_sdk_settings.py b/tests/test_sdk_settings.py new file mode 100644 index 0000000..98de701 --- /dev/null +++ b/tests/test_sdk_settings.py @@ -0,0 +1,220 @@ +""" +Tests for the SdkSettings utility. + +Covers building settings in code, inspecting them like a dict, loading them +back from a file or directory, and serializing them to a temporary directory. +""" + +import os +from pathlib import Path + +import pytest + +import cuvis +from cuvis.sdk_settings import SETTINGS_FILENAME, SETTINGS_NAMESPACE, SdkSettings + + +def _write(path, body): + """Write a raw settings XML document and return its path.""" + path.write_text(body, encoding="utf-8") + return path + + +VALID_XML = ( + '\n' + '\n' + ' \n' + ' \n' + "\n".format(SETTINGS_NAMESPACE) +) + + +def test_kwargs_become_properties(): + """Keyword arguments are stored as string values.""" + settings = SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) + assert dict(settings) == { + "force_gpu_mode": "cuda", + "processing_thread_count": "8", + } + + +def test_none_values_are_dropped(): + """A None value means 'not set' and never reaches the file.""" + settings = SdkSettings(il_pos_min=None, il_pos_max=4) + assert "il_pos_min" not in settings + assert settings["il_pos_max"] == "4" + + +def test_value_coercion(): + """Booleans render as true/false, everything else via str().""" + settings = SdkSettings(a=True, b=False, c=3, d=1.5, e="text") + assert settings["a"] == "true" + assert settings["b"] == "false" + assert settings["c"] == "3" + assert settings["d"] == "1.5" + assert settings["e"] == "text" + + +def test_enum_value_coercion(): + """Enums are written as their value, so SDK enums can be passed directly.""" + settings = SdkSettings(processing_mode=cuvis.ProcessingMode.Raw) + assert settings["processing_mode"] == str(cuvis.ProcessingMode.Raw.value) + + +def test_mapping_interface(): + """The full MutableMapping surface is available for inspection.""" + settings = SdkSettings(force_gpu_mode="cuda") + + settings["verbose"] = True + assert settings["verbose"] == "true" + assert len(settings) == 2 + assert "verbose" in settings + assert sorted(settings) == ["force_gpu_mode", "verbose"] + assert sorted(settings.keys()) == ["force_gpu_mode", "verbose"] + assert dict(settings.items())["force_gpu_mode"] == "cuda" + assert settings.get("missing") is None + assert settings.get("missing", "fallback") == "fallback" + + settings.update({"file_compression": "9"}) + assert settings["file_compression"] == "9" + + del settings["verbose"] + assert "verbose" not in settings + + +def test_repr_contains_keys(): + """repr shows the class name and the current mapping.""" + text = repr(SdkSettings(force_gpu_mode="cuda")) + assert text.startswith("SdkSettings(") + assert "force_gpu_mode" in text + assert "cuda" in text + + +def test_xml_str_structure(): + """The serialized XML carries the declaration, namespace and version.""" + xml = SdkSettings(force_gpu_mode="cuda").xml_str + assert xml.startswith("", + "missing_version": ''.format(SETTINGS_NAMESPACE), + "property_without_id": ( + '' + '' + "".format(SETTINGS_NAMESPACE) + ), + "duplicate_ids": ( + '' + '' + "".format(SETTINGS_NAMESPACE) + ), + "unexpected_element": ( + ''.format( + SETTINGS_NAMESPACE + ) + ), + "malformed_xml": " Date: Wed, 19 Aug 2026 10:27:33 +0200 Subject: [PATCH 2/6] docs: move settings doc to docstring --- README.md | 36 ------------------------- cuvis/sdk_settings.py | 61 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index dda2342..1cd90e6 100644 --- a/README.md +++ b/README.md @@ -84,42 +84,6 @@ covering some basic applications. Further, we provide a set of example measurements to explore [here](https://cloud.cubert-gmbh.de/s/SrkSRja5FKGS2Tw). These measurements are also used by the examples mentioned above. -### Configuring the SDK from code - -The SDK reads its configuration from a `cuvis.settings` file in the directory passed to `cuvis.init()`. -`SdkSettings` lets you write that configuration in Python instead of maintaining the file by hand. - -```python -import cuvis - -settings = cuvis.SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) -cuvis.init(settings) -``` - -It behaves like a dict, so you can inspect and edit it before applying it: - -```python -settings["verbose"] = True # booleans become "true" / "false" -del settings["force_gpu_mode"] -print(dict(settings)) # {'processing_thread_count': '8', 'verbose': 'true'} -print(settings.xml_str) # the exact file that will be written -``` - -An existing configuration can be loaded from a settings file or from the directory containing one, and keyword arguments override what was loaded: - -```python -settings = cuvis.SdkSettings("/path/to/settings_dir", processing_thread_count=4) -``` - -Used as a context manager it serializes itself into a temporary directory, which is useful for pointing other tools at the same configuration: - -```python -with settings as settings_dir: - cuvis.init(settings_dir) -``` - -Use `settings.save(path)` to write the file to a permanent location instead. - ### Getting involved cuvis.hub welcomes your enthusiasm and expertise! diff --git a/cuvis/sdk_settings.py b/cuvis/sdk_settings.py index 0b9ba07..a02ba98 100644 --- a/cuvis/sdk_settings.py +++ b/cuvis/sdk_settings.py @@ -69,20 +69,67 @@ def _load(path: Path) -> dict[str, str]: class SdkSettings(MutableMapping): - """Dict-like builder for the SDK's ``cuvis.settings`` file. + """ + Dict-like builder for the SDK's ``cuvis.settings`` file. + + The SDK reads its configuration from a ``cuvis.settings`` file in the + directory passed to :func:`cuvis.init`. This class lets that configuration + be written in Python instead of maintaining the file by hand:: + + import cuvis + + settings = cuvis.SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) + cuvis.init(settings) - Behaves like a ``dict`` of setting id to value, can be loaded from an - existing settings file or from a directory containing one, and serializes - itself into a temporary directory when used as a context manager. + Settings are a flat mapping of setting id to value. Values are stored as + strings, because that is what the XML carries and what the SDK parses. + Booleans become ``"true"`` / ``"false"``, enums are written as their value, + and anything else goes through ``str()``. A value of ``None`` means "not + set" and is dropped rather than written. - Example:: + Inspecting and editing + ---------------------- + The full ``MutableMapping`` interface is available, so an instance can be + read and edited like a ``dict``:: - settings = SdkSettings(force_gpu_mode="cuda", processing_thread_count=8) settings["verbose"] = True - print(dict(settings)) + del settings["force_gpu_mode"] + print(dict(settings)) # {'processing_thread_count': '8', 'verbose': 'true'} + print(settings.xml_str) # the exact file that will be written + + Loading + ------- + An existing configuration can be loaded from a settings file, or from the + directory containing one. Keyword arguments override what was loaded:: + + settings = cuvis.SdkSettings("/path/to/settings_dir", processing_thread_count=4) + + Writing + ------- + Used as a context manager, the settings are serialized into a temporary + directory that exists for the duration of the block. This is useful for + pointing other consumers at the same configuration:: with settings as settings_dir: cuvis.init(settings_dir) + + Use :meth:`save` to write the file to a permanent location instead. + + Parameters + ---------- + source : str or Path, optional + A ``cuvis.settings`` file, or a directory containing one, to use as the + starting point. Positional only. + **kwargs + Settings to set, overriding any value read from ``source``. + + Raises + ------ + FileNotFoundError + If ``source`` is a directory that contains no ``cuvis.settings``. + ValueError + If ``source`` is not a well-formed settings document, or if a setting + id is empty or contains whitespace. """ def __init__(self, source: Optional[Union[str, Path]] = None, /, **kwargs): From 49a45fceebd6be7698102a919b002ec4d8234583 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Wed, 19 Aug 2026 14:03:39 +0200 Subject: [PATCH 3/6] chore: run ruff format --- tests/test_sdk_settings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_sdk_settings.py b/tests/test_sdk_settings.py index 98de701..706471f 100644 --- a/tests/test_sdk_settings.py +++ b/tests/test_sdk_settings.py @@ -140,7 +140,9 @@ def test_round_trip_via_directory(tmp_path): def test_load_from_directory_matches_load_from_file(tmp_path): """A directory and the file inside it load to the same mapping.""" _write(tmp_path / SETTINGS_FILENAME, VALID_XML) - assert dict(SdkSettings(tmp_path)) == dict(SdkSettings(tmp_path / SETTINGS_FILENAME)) + assert dict(SdkSettings(tmp_path)) == dict( + SdkSettings(tmp_path / SETTINGS_FILENAME) + ) def test_load_accepts_str_path(tmp_path): @@ -169,9 +171,9 @@ def test_missing_settings_file_in_directory(tmp_path): "wrong_root": "", "missing_version": ''.format(SETTINGS_NAMESPACE), "property_without_id": ( - '' - '' - "".format(SETTINGS_NAMESPACE) + ''.format( + SETTINGS_NAMESPACE + ) ), "duplicate_ids": ( '' From af0d10a07afad27046d280e7e63b34a4db5b8888 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Wed, 19 Aug 2026 15:22:54 +0200 Subject: [PATCH 4/6] updating changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ec078..0a162a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ Pre-releases (`b*`, `rc*`) are not listed. - `CI` - `scripts/check_changelog.py` validates this file's structure (header format, allowed section names, descending versions) and the tag/version/changelog agreement at release time. - `CONTRIBUTING.md` - documents the branch model, the version scheme, the changelog conventions and the release checklist. - `cuvis.BindingInfo` - new frozen dataclass with the fields `built_against: str`, `library_version: str`, `library_path: str` and `missing_symbols: Tuple[str, ...]`, the read-only property `is_complete: bool`, and a `__str__` rendering a report fit for a bug report. +- `cuvis.SdkSettings` - new class, a `MutableMapping` of setting id to value that writes the SDK's `cuvis.settings` file, so the SDK configuration can be built in Python instead of maintained by hand. + Values are stored as strings: `bool` becomes `true`/`false`, an `Enum` becomes its value, anything else goes through `str()`, and `None` drops the entry. +- `cuvis.SdkSettings.__enter__`, `cuvis.SdkSettings.__exit__` - new methods; entering the context serializes the settings into a temporary directory and returns its path as `str`, leaving the context removes the directory. +- `cuvis.SdkSettings.save` - new method, writes the settings to a file, or into a directory as `cuvis.settings`. +- `cuvis.SdkSettings.xml_str` - new read-only property, returns the serialized settings document as `str`. - `cuvis.UnavailableSDKFunction` - new exception deriving from both `cuvis.cuvis_aux.SDKException` and `RuntimeError`, with the field `names: Tuple[str, ...]`. - `cuvis.binding` - new module reporting the compiled binding, the cuvis library loaded beside it, and the functions that library does not provide. Nothing in it needs the SDK to be initialised, so it can be called before `cuvis.init`. @@ -27,6 +32,10 @@ Pre-releases (`b*`, `rc*`) are not listed. - `cuvis.binding.info` - new function, returns `BindingInfo`. - `cuvis.binding.missing_symbols` - new function, returns `FrozenSet[str]`. - `cuvis.binding.require` - new function, raises `UnavailableSDKFunction` naming whichever of the given functions the installed cuvis library does not provide. +- `cuvis.sdk_settings` - new module implementing `SdkSettings`, using only the standard library, so no dependency was added. +- `cuvis.sdk_settings.SETTINGS_FILENAME` - new constant, `str`, the `cuvis.settings` file name the SDK looks for. +- `cuvis.sdk_settings.SETTINGS_NAMESPACE` - new constant, `str`, the XML namespace of the settings document. +- `cuvis.sdk_settings.SETTINGS_VERSION` - new constant, `str`, the settings document version written to the file. - `pyproject.toml` - `dev` extra pinning `ruff==0.16.3`, plus `[tool.ruff]` configuration selecting the `E4`, `E7`, `E9` and `F` rule sets. ### Changed @@ -34,6 +43,8 @@ Pre-releases (`b*`, `rc*`) are not listed. - Whole tree reformatted with `ruff format`; no behaviour change. - `README.md` - documents the version scheme, and lists Python 3.14 among the supported interpreters as `pyproject.toml` already did. - `prebuild.py` - writes `cuvis/git-hash.txt` instead of `git-hash.txt` at the repository root, so the file lands inside the package that declares it as package data. +- `cuvis.General.init` - parameter `settings_path` type changed from `str` to `Union[str, Path, SdkSettings]`. + An `SdkSettings` is written to a temporary directory that exists only for the duration of the call, since the SDK reads the settings once during initialisation. - `cuvis.FileWriteSettings.GeneralExportSettings.__repr__`, `cuvis.FileWriteSettings.ViewerSettings.__repr__` - the docstring that sat below the nested helper, where it was a dead expression rather than a docstring, moved to the top of the method. ### Removed From 892ad8276ac8d813285b254bbc33576f0da411ea Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Wed, 19 Aug 2026 15:26:06 +0200 Subject: [PATCH 5/6] updating changelog --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a162a4..ed0390f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,11 +32,6 @@ Pre-releases (`b*`, `rc*`) are not listed. - `cuvis.binding.info` - new function, returns `BindingInfo`. - `cuvis.binding.missing_symbols` - new function, returns `FrozenSet[str]`. - `cuvis.binding.require` - new function, raises `UnavailableSDKFunction` naming whichever of the given functions the installed cuvis library does not provide. -- `cuvis.sdk_settings` - new module implementing `SdkSettings`, using only the standard library, so no dependency was added. -- `cuvis.sdk_settings.SETTINGS_FILENAME` - new constant, `str`, the `cuvis.settings` file name the SDK looks for. -- `cuvis.sdk_settings.SETTINGS_NAMESPACE` - new constant, `str`, the XML namespace of the settings document. -- `cuvis.sdk_settings.SETTINGS_VERSION` - new constant, `str`, the settings document version written to the file. -- `pyproject.toml` - `dev` extra pinning `ruff==0.16.3`, plus `[tool.ruff]` configuration selecting the `E4`, `E7`, `E9` and `F` rule sets. ### Changed From 792738cd3cee51c07e501e62e9b15679e1027a66 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Wed, 19 Aug 2026 15:40:54 +0200 Subject: [PATCH 6/6] chore: run ruff format --check --- cuvis/General.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cuvis/General.py b/cuvis/General.py index f04ca90..983250d 100644 --- a/cuvis/General.py +++ b/cuvis/General.py @@ -1,6 +1,5 @@ import logging import os -import platform from contextlib import ExitStack from importlib.metadata import version as imp_version