diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2854cac..aa3cf8f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,4 +23,4 @@ jobs: with: python-version-path: .python-version runs-on: windows-latest - run-coverage: true \ No newline at end of file + run-coverage: true diff --git a/pyproject.toml b/pyproject.toml index eee3ae1c..458f7b15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,11 +40,17 @@ aind-services = [ "aind-data-schema<2" ] +magicgui = [ + "magicgui[pyqt6]" +] + dev = [ "ruff", "codespell", "coverage", "aind-behavior-experiment-launcher[aind-services]", + "aind-behavior-experiment-launcher[magicgui]", + ] docs = [ diff --git a/src/aind_behavior_experiment_launcher/data_transfer/aind_watchdog.py b/src/aind_behavior_experiment_launcher/data_transfer/aind_watchdog.py index ce71d62e..718f381c 100644 --- a/src/aind_behavior_experiment_launcher/data_transfer/aind_watchdog.py +++ b/src/aind_behavior_experiment_launcher/data_transfer/aind_watchdog.py @@ -1,12 +1,12 @@ -try: - import aind_watchdog_service # noqa: F401 -except ImportError as e: - e.add_note( - "The 'aind-watchdog-service' package is required to use this module. \ +import importlib.util + +if importlib.util.find_spec("aind_watchdog_service") is None: + raise ImportError( + "The 'aind_watchdog_service' package is required to use this module. \ Install the optional dependencies defined in `project.toml' \ by running `pip install .[aind-services]`" ) - raise + import datetime import json diff --git a/src/aind_behavior_experiment_launcher/launcher/behavior_launcher.py b/src/aind_behavior_experiment_launcher/launcher/behavior_launcher.py index d29a1f83..be972985 100644 --- a/src/aind_behavior_experiment_launcher/launcher/behavior_launcher.py +++ b/src/aind_behavior_experiment_launcher/launcher/behavior_launcher.py @@ -521,7 +521,7 @@ def pick_rig(self) -> TRig: else: while True: try: - path = self.prompt_pick_file_from_list(available_rigs, prompt="Choose a rig:", zero_label=None) + path = self.ui_helper.prompt_pick_from_list(available_rigs, prompt="Choose a rig:") if not isinstance(path, str): raise ValueError("Invalid choice.") rig = model_from_json_file(path, self.launcher.rig_schema_model) @@ -606,7 +606,7 @@ def pick_task_logic(self) -> TTaskLogic: available_files = glob.glob(os.path.join(_path, "*.json")) if len(available_files) == 0: break - path = self.prompt_pick_file_from_list(available_files, prompt="Choose a task logic:", zero_label=None) + path = self.ui_helper.prompt_pick_from_list(available_files, prompt="Choose a task logic:") if not isinstance(path, str): raise ValueError("Invalid choice.") if not os.path.isfile(path): @@ -622,51 +622,6 @@ def pick_task_logic(self) -> TTaskLogic: raise ValueError("No task logic file found.") return task_logic - def prompt_pick_file_from_list( - self, - available_files: list[str], - prompt: str = "Choose a file:", - zero_label: Optional[str] = None, - zero_value: Optional[_T] = None, - zero_as_input: bool = True, - zero_as_input_label: str = "Enter manually", - ) -> Optional[str | _T]: - """ - Prompts the user to pick a file from a list of available files. - - Args: - available_files (list[str]): List of file paths to choose from. - prompt (str): The prompt message to display. - zero_label (Optional[str]): Label for the "zero" option. - zero_value (Optional[_T]): Value to return for the "zero" option. - zero_as_input (bool): Whether to allow manual input for the "zero" option. - zero_as_input_label (str): Label for manual input prompt. - - Returns: - Optional[str | _T]: The selected file path or the zero value. - - Raises: - ValueError: If an invalid choice is made. - """ - self.ui_helper.print(prompt) - if zero_label is not None: - self.ui_helper.print(f"0: {zero_label}") - for i, file in enumerate(available_files): - self.ui_helper.print(f"{i + 1}: {os.path.split(file)[1]}") - choice = int(input("Choice: ")) - if choice < 0 or choice >= len(available_files) + 1: - raise ValueError - if choice == 0: - if zero_label is None: - raise ValueError - else: - if zero_as_input: - return str(input(zero_as_input_label)) - else: - return zero_value - else: - return available_files[choice - 1] - def choose_subject(self, directory: str | os.PathLike) -> str: """ Prompts the user to select or manually enter a subject name. diff --git a/src/aind_behavior_experiment_launcher/ui/_magicgui.py b/src/aind_behavior_experiment_launcher/ui/_magicgui.py new file mode 100644 index 00000000..11756553 --- /dev/null +++ b/src/aind_behavior_experiment_launcher/ui/_magicgui.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +import importlib.util + +if importlib.util.find_spec("magicgui") is None: + raise ImportError( + "The 'magicgui' package is required to use this module. " + "Install the optional dependencies defined in `project.toml` " + "by running `pip install .[magicgui]`" + ) + + +from functools import partial +from typing import Any, Iterable, Optional, Type + +from magicgui import event_loop +from magicgui.types import Undefined +from magicgui.widgets import ( + Button, + CheckBox, + Container, + Textarea, + create_widget, +) +from magicgui.widgets.bases import ValueWidget +from pydantic import BaseModel, TypeAdapter, ValidationError +from pydantic.fields import FieldInfo, PydanticUndefined + + +class _ValidationErrorSink: + def __init__(self, widget: Optional[ValueWidget] = None): + self._widget = widget + self._error: Optional[Exception] = None + self.clear() + + def __call__(self, e: Exception | None): + self.update(e) + + @property + def widget(self) -> Optional[ValueWidget]: + return self._widget + + @property + def error(self) -> Optional[Exception]: + return self._error + + def update(self, e: Exception | None): + self._error = e + if self._widget is not None: + self._widget.value = f"{self.error}" + + def clear(self): + self.update(None) + + def register_widget(self, widget: ValueWidget): + self._widget = widget + + +def _container_updater(container, validator, error_sink: Optional[_ValidationErrorSink] = None): + # https://icon-sets.iconify.design/material-symbols/ + + null_checkbox: CheckBox = container[0] + input_widget: ValueWidget = container[1] + validation_button: Button = container[2] + if error_sink is None: + error_sink = _ValidationErrorSink() + + is_null = null_checkbox.value + with input_widget.changed.blocked(): + input_widget.enabled = not is_null + try: + if is_null: + value = None + else: + value = input_widget.value + + value = validator(value) + if value is not None: + # in case the type adapter does something fancy + input_widget.set_value(value) + + validation_button.set_icon("material-symbols:check-circle-outline-rounded", "#0b7500") + error_sink.clear() + except ValidationError as e: + validation_button.set_icon("material-symbols:cancel", "#fd0000") + error_sink(e) + + +def _make_widget_from_field( + field: FieldInfo, + name: str, + default: Any = Undefined, + *, + override_type: Optional[dict[str, Type[ValueWidget]]] = None, + validation_error_sink: Optional[_ValidationErrorSink] = None, +) -> Container: + # Each field will be rendered as: + # - Left button for nullability + # - Value input + # - Right button for validation + + if override_type is None: + override_type = {} + widget_type = override_type.get(name, None) + + if default is Undefined: + default = field.get_default(call_default_factory=True) + if default is PydanticUndefined or default is None: + default = Undefined + + container: Container = Container(layout="horizontal") + + container.append( + CheckBox( + value=(default is Undefined), + label="N", + ) + ) + widget = create_widget(default, field.annotation, name=name, widget_type=widget_type) + + container.append(widget) + container.append(Button(icon="material-symbols:check-circle-outline-rounded", icon_color="#0b7500", enabled=True)) + container.tooltip = field.description + validator = TypeAdapter(field.rebuild_annotation()).validate_python + updater = partial(_container_updater, validator=validator, error_sink=validation_error_sink) + container.changed.connect(updater) + updater(container) + return container + + +def create_container_from_model( + model: Type[BaseModel] | BaseModel, + *, + include_fields: Optional[Iterable[str]] = None, + exclude_fields: Optional[Iterable[str]] = None, + override_type: Optional[dict[str, Type[ValueWidget]]] = None, + validation_error_sink: Optional[_ValidationErrorSink] = None, + populate_with_instance: bool = False, +) -> "Container": + if include_fields is None: + include_fields = model.model_fields.keys() + if exclude_fields is None: + exclude_fields = [] + + if populate_with_instance: + if not isinstance(model, BaseModel): + raise ValueError("Cannot populate with instance if model is not an instance") + + widgets = [ + _make_widget_from_field( + field, + name, + getattr(model, name) if populate_with_instance else Undefined, + override_type=override_type, + validation_error_sink=validation_error_sink, + ) + for name, field in model.model_fields.items() + if (name in include_fields) and (name not in exclude_fields) + ] + + container = Container(widgets=widgets) + return container + + +def create_form( + model: BaseModel | Type[BaseModel], + *, + include_fields: Optional[Iterable[str]] = None, + exclude_fields: Optional[Iterable[str]] = None, + populate_with_instance: bool = False, + allow_errors: bool = False, +) -> dict[str, Any]: + submit_button = Button(text="Submit") + error_stack = Textarea(enabled=False) + error_sink = _ValidationErrorSink(error_stack) + model_widget = create_container_from_model( + model, + include_fields=include_fields, + exclude_fields=exclude_fields, + validation_error_sink=error_sink, + populate_with_instance=populate_with_instance, + ) + container = Container( + widgets=[ + submit_button, + model_widget, + error_stack, + ] + ) + submit_button.changed.connect(container.close) + if isinstance(model, BaseModel): + container.native.setWindowTitle(model.__class__.__name__) + else: + container.native.setWindowTitle(model.__name__) + with event_loop(): + container.show() + + # TODO + # This is a big hack, but seems like investing + # too much time into an event driven solution is not worth it atm + if not allow_errors and error_sink.error is not None: + raise error_sink.error + + return {getattr(widget[1], "name"): getattr(widget[1], "value") for widget in model_widget} diff --git a/src/aind_behavior_experiment_launcher/ui/gui_helper.py b/src/aind_behavior_experiment_launcher/ui/gui_helper.py new file mode 100644 index 00000000..454048b6 --- /dev/null +++ b/src/aind_behavior_experiment_launcher/ui/gui_helper.py @@ -0,0 +1,127 @@ +import logging +from typing import List, Optional, TypeVar + +import magicgui +import magicgui.types +from magicgui import widgets as widgets +from qtpy.QtCore import Qt +from typing_extensions import override + +from aind_behavior_experiment_launcher.ui import UiHelper + +logger = logging.getLogger(__name__) + +T = TypeVar("T") + + +class GuiHelper(UiHelper): + def prompt_pick_from_list( + self, value: List[str], prompt: str, allow_0_as_none: bool = True, **kwargs + ) -> Optional[str]: + container: widgets.Container = widgets.Container(layout="vertical") + + # Add button to submit + submit_button = widgets.Button(text="Submit") + submit_button.changed.connect(container.close) + container.append(submit_button) + + # Make the picker + files = widgets.ComboBox( + value=None if allow_0_as_none else value[0], + choices=[None] + value if allow_0_as_none else value, + label=prompt, + ) + container.append(files) + file_picker = widgets.FileEdit( + mode=magicgui.types.FileDialogMode.EXISTING_FILE, + label="Or provide a file path:", + ) + container.append(file_picker) + + container.native.setWindowTitle(prompt) + with magicgui.event_loop(): + container.show() + + return files.get_value() + + @override + def prompt_yes_no_question(self, prompt: str) -> bool: + _container_outer: widgets.Container = widgets.Container(layout="vertical") + _container_inner: widgets.Container = widgets.Container(layout="horizontal") + _p = widgets.Label(value=prompt) + + _w_yes = widgets.Button(value=True, text="Yes") + _w_no = widgets.Button(value=False, text="No") + + _result: List[Optional[bool]] = [None] + _w_yes.changed.connect(lambda _: _result.__setitem__(0, True)) + _w_no.changed.connect(lambda _: _result.__setitem__(0, False)) + + _w_yes.changed.connect(_container_outer.close) + _w_no.changed.connect(_container_outer.close) + + _container_inner.append(_w_yes) + _container_inner.append(_w_no) + _container_outer.append(_p) + _container_outer.append(_container_inner) + + _container_outer.native.setWindowTitle(prompt) + with magicgui.event_loop(): + _container_outer.show() + + if _result[0] is None: + return False + + return _result[0] + + def prompt_text(self, prompt: str) -> str: + container: widgets.Container = widgets.Container(layout="vertical") + + # Add button to submit + submit_button = widgets.Button(text="Submit") + submit_button.changed.connect(container.close) + container.append(submit_button) + + # Make the picker + notes_ui = widgets.TextEdit() + container.append(notes_ui) + container.native.setWindowTitle(f"{prompt}") + + with magicgui.event_loop(): + container.show() + + return notes_ui.get_value() + + +def make_header() -> None: + _HEADER = """\n + ██████╗██╗ █████╗ ██████╗ ███████╗ + ██╔════╝██║ ██╔══██╗██╔══██╗██╔════╝ + ██║ ██║ ███████║██████╔╝█████╗ + ██║ ██║ ██╔══██║██╔══██╗██╔══╝ + ╚██████╗███████╗██║ ██║██████╔╝███████╗ + ╚═════╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝ + """ + + sub_text = """ + Command-line-interface Launcher for AIND Behavior Experiments + Press Control+C to exit at any time. + """ + + container = widgets.Container() + text_ui = widgets.Label(value=_HEADER) + text_ui.native.setTextFormat(Qt.TextFormat.MarkdownText) + font = text_ui.native.font() + font.setPointSize(15) + font.setFamily("Courier New") + text_ui.native.setFont(font) + sub_text_ui = widgets.Label(value=sub_text) + ok_ui = widgets.Button(text="Close") + container.append(text_ui) + container.append(sub_text_ui) + container.append(ok_ui) + ok_ui.clicked.connect(container.close) + + container.native.setWindowTitle("CLABE") + with magicgui.event_loop(): + container.show() diff --git a/tests/test_ui.py b/tests/test_ui.py index 9d0ce74c..0bde7f94 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -34,18 +34,6 @@ def setUp(self): ) self.picker = self.launcher.picker - @patch("builtins.input", side_effect=["1"]) - def test_prompt_pick_file_from_list(self, mock_input): - files = ["file1.txt", "file2.txt"] - result = self.picker.prompt_pick_file_from_list(files) - self.assertEqual(result, "file1.txt") - - @patch("builtins.input", side_effect=["0", "manual_entry"]) - def test_prompt_pick_file_from_list_manual_entry(self, mock_input): - files = ["file1.txt", "file2.txt"] - result = self.picker.prompt_pick_file_from_list(files, zero_label="Manual Entry", zero_as_input=True) - self.assertEqual(result, "manual_entry") - @patch("os.path.isdir", return_value=True) @patch("os.listdir", return_value=["subjects/subject1", "subjects/subject2"]) @patch("builtins.input", side_effect=["1"]) diff --git a/uv.lock b/uv.lock index ef8733fd..2d87338f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.12'", @@ -43,6 +44,7 @@ dev = [ { name = "aind-watchdog-service" }, { name = "codespell" }, { name = "coverage" }, + { name = "magicgui", extra = ["pyqt6"] }, { name = "ruff" }, ] docs = [ @@ -57,11 +59,15 @@ docs = [ { name = "sphinx-jsonschema" }, { name = "sphinx-mdinclude" }, ] +magicgui = [ + { name = "magicgui", extra = ["pyqt6"] }, +] [package.metadata] requires-dist = [ { name = "aind-behavior-experiment-launcher", extras = ["aind-services"], marker = "extra == 'dev'" }, { name = "aind-behavior-experiment-launcher", extras = ["aind-services"], marker = "extra == 'docs'" }, + { name = "aind-behavior-experiment-launcher", extras = ["magicgui"], marker = "extra == 'dev'" }, { name = "aind-behavior-services", specifier = "<1" }, { name = "aind-behavior-video-transformation", marker = "extra == 'aind-services'" }, { name = "aind-data-schema", marker = "extra == 'aind-services'", specifier = "<2" }, @@ -72,6 +78,7 @@ requires-dist = [ { name = "coverage", marker = "extra == 'dev'" }, { name = "furo", marker = "extra == 'docs'" }, { name = "gitpython" }, + { name = "magicgui", extras = ["pyqt6"], marker = "extra == 'magicgui'" }, { name = "pydantic", specifier = ">=2.7,<3.0" }, { name = "rich" }, { name = "ruff", marker = "extra == 'dev'" }, @@ -82,6 +89,7 @@ requires-dist = [ { name = "sphinx-jsonschema", marker = "extra == 'docs'" }, { name = "sphinx-mdinclude", marker = "extra == 'docs'" }, ] +provides-extras = ["aind-services", "magicgui", "dev", "docs"] [[package]] name = "aind-behavior-services" @@ -102,14 +110,14 @@ wheels = [ [[package]] name = "aind-behavior-video-transformation" -version = "0.1.6" +version = "0.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aind-data-transformation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/dd/8b377119685163d3ffc4704c9e403cc60f6bca42420b91b09f2a22a0cdc4/aind_behavior_video_transformation-0.1.6.tar.gz", hash = "sha256:1d1d01ade2ffa4c4993931c89ad6c688bcbd330e9a68113f2192ed4d8bab6640", size = 590008 } +sdist = { url = "https://files.pythonhosted.org/packages/21/48/c6dccbeaafa363aa54077d678b2c479d9264203eb043265835e8c3f97cc3/aind_behavior_video_transformation-0.1.7.tar.gz", hash = "sha256:23044acf06398408b22ebfbddacffa96b815a55724ea6af08f9c1fba920e2ea0", size = 589474 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/64/abba18b73f359fe254644b6e27209250f82bbaca01e1aa17523f4acea5b6/aind_behavior_video_transformation-0.1.6-py3-none-any.whl", hash = "sha256:c67726b1944d665007742a4a500c7b2b6e3af7c67a56087ac85004347ab257ba", size = 12430 }, + { url = "https://files.pythonhosted.org/packages/9c/9b/a372ef07f6ed7407e20b48519c86706b0fe5d9b8c47097695a6483c548e3/aind_behavior_video_transformation-0.1.7-py3-none-any.whl", hash = "sha256:fb9de926129a69b7ab3fe2a43353982b720b3b60c3ec72c0926db0eea6f8226e", size = 12433 }, ] [[package]] @@ -487,6 +495,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, ] +[[package]] +name = "docstring-parser" +version = "0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/12/9c22a58c0b1e29271051222d8906257616da84135af9ed167c9e28f85cb3/docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e", size = 26565 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533 }, +] + [[package]] name = "docutils" version = "0.20.1" @@ -643,6 +660,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, ] +[[package]] +name = "magicgui" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docstring-parser" }, + { name = "psygnal" }, + { name = "qtpy" }, + { name = "superqt", extra = ["iconify"] }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/44/405f7028b00d94e29ddbaff00f2674e548d3bff8d343fbf7500bd77aa071/magicgui-0.10.0.tar.gz", hash = "sha256:56dbe28afc526809e09932cd6caad8fc1a8305fe66c8feca16f797a04b5aee7c", size = 20942460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/42/7e4f03201dfc10b4a8d4d94d183c878d7d0d4f2eee173e95294c71828014/magicgui-0.10.0-py3-none-any.whl", hash = "sha256:836276d61b0d9752eb8a215ff9f140c9c07ed5659b6e2a3c78df1cc96399aecd", size = 126758 }, +] + +[package.optional-dependencies] +pyqt6 = [ + { name = "pyqt6" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -849,6 +887,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, ] +[[package]] +name = "psygnal" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/7f/ef01fa880529b0cbdf33a02e690cbca7868ee0ee291bcb2ebce53f3b3043/psygnal-0.12.0.tar.gz", hash = "sha256:8d2a99803f3152c469d3642d36c04d680213a20e114245558e026695adf9a9c2", size = 104400 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/57/d6b488dac03f65e731843c984e4677a30c48dd4c5dae3c04df7993ce3168/psygnal-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a9ee1e6c441074fe71765b0a96c75b19d72c8198ec5bdea7e97e06a6fe9bd41", size = 458923 }, + { url = "https://files.pythonhosted.org/packages/7a/fa/bab2170fc8b47a4c591e7ab821fc1fe3d4b10292753f47acba7323eb3d66/psygnal-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cdb387d1d6f00649c970a8084e4ae3fcd3e38ac12b5c51d086fc9e01d8f7530", size = 430113 }, + { url = "https://files.pythonhosted.org/packages/2e/58/91359b72fe0413626be8857122897bb9238fa7b1dd53a3ed299183a17cb6/psygnal-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b410dab639353320044856cef68bd9aa940f8e1399da2f57522356b42bc4cf5d", size = 765465 }, + { url = "https://files.pythonhosted.org/packages/9c/ee/869a2d6741ba3848d6cadf35a1f08535115eab67b7b1c41b2d45f467da7a/psygnal-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd075d7bbe82f0615cfef953ed19ca54feba08f1686b42655c02d6ade0b0beb5", size = 751927 }, + { url = "https://files.pythonhosted.org/packages/68/eb/c59c13a6da8263f3119a3d9faa7790e58d4fe541458197de4b2370927d52/psygnal-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc763dbab05fb75f4517c8bd31ede6a4f27e68c59adca55b81a9d7bc875156e0", size = 377665 }, + { url = "https://files.pythonhosted.org/packages/9b/2e/6cff528f8f5dc7f60221fddca85ed52131a90b606a72c5a4085606d5217d/psygnal-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dac134c8890e3d0e413ab701fcb56a882f9b151a6a9d625080736c36833b26ed", size = 469384 }, + { url = "https://files.pythonhosted.org/packages/55/9d/774d547ed1fcb079de7fc41b1e3103b261eebae7f34da5cf05909a040470/psygnal-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc2324cef7ba3f4d30d32895f8cb7d5cf9ad7bcfdb7955aa92a0fbfe7537ec3f", size = 426617 }, + { url = "https://files.pythonhosted.org/packages/b4/89/300991108d86c00e6aa84ea85e9c998e27eed59fdcb1abd3e69b9f4d62f5/psygnal-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae2bd6edcf911fbff34ed75150e8f8dfb246ebf203514c7c1e4397cabbb1368a", size = 787401 }, + { url = "https://files.pythonhosted.org/packages/9e/ae/8cc64c0f1eebbc4be74a953e76e431431c770a30f28b71b48edc7f6b8288/psygnal-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d6fbeee192beab90ca23d9d3ee3bf1eb7ef5f00e815fa53e23e402feee617242", size = 781293 }, + { url = "https://files.pythonhosted.org/packages/84/09/f00841834b7ae543bd232c22e557914d63d0d0430d32980883421d5981bb/psygnal-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:25a9f2db710a6cd2566b3e0e03cf6e04d56276f36ac86b42fa22d81f9a4ac0f2", size = 381816 }, + { url = "https://files.pythonhosted.org/packages/cb/b4/64a06b1d9b7628c84c9ea68a6cdc9d54378bae04695e7173addb9cf46607/psygnal-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2f4c1fed9337f57778109c397b6b9591961123ce4bbeb068115c0468964fc2b4", size = 468346 }, + { url = "https://files.pythonhosted.org/packages/78/be/b3df7dac845f5f6b9897e60d19c3eaed27b56b024099588db92c3b76bb21/psygnal-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d5a953a50fc8263bb23bc558b926cf691f70c9c781c68c64c983fb8cbead910", size = 426482 }, + { url = "https://files.pythonhosted.org/packages/df/36/0017e838d3c63081a64e6d2252c8dda368a6d0898c7ecf689ba678fe4127/psygnal-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a67ec8e0c8a6553dd56ed653f87c46ef652b0c512bb8c8f8c5adcff3907751f", size = 785239 }, + { url = "https://files.pythonhosted.org/packages/67/d0/7057151debcd5c7d8ce7789d276e18681d5c141c9222c9cf99ce3a418680/psygnal-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:742abb2d0e230521b208161eeab06abb682a19239e734e543a269214c84a54d2", size = 779690 }, + { url = "https://files.pythonhosted.org/packages/5e/ae/a3d6815db583b6d05878b3647ea0e2aa21ce6941d03c9d2c6caad1afbcf6/psygnal-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:d779f20c6977ec9d5b9fece23b4b28bbcf0a7773539a4a176b5527aea5da27c7", size = 382622 }, + { url = "https://files.pythonhosted.org/packages/eb/fa/84fc30ad391081cb119099aae5491ba4a9ebd34ce5139bf05b31813abb84/psygnal-0.12.0-py3-none-any.whl", hash = "sha256:15f39abd8bee2926e79da76bec31a258d03dbe3e61d22d6251f65caefbae5d54", size = 78492 }, +] + +[[package]] +name = "pyconify" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/7f/94d424dc756a6287271cf40cf1b2a44c10e3f137bf3246a2b4a7416ca3d3/pyconify-0.2.1.tar.gz", hash = "sha256:8dd53757d9fbed41711434460932b2b5dbc25da720cd9f9a44af0187b2dfc07d", size = 22478 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/40/50dd2e8bfec81676e4619903bd452c10dc0d8efac1533e79e67cc76759b5/pyconify-0.2.1-py3-none-any.whl", hash = "sha256:d3b53eee1f8a2d60c1d135610f42e789774dbe71c6d8af68af0a21d3b3ec9eb7", size = 19459 }, +] + [[package]] name = "pydantic" version = "2.10.6" @@ -952,6 +1026,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] +[[package]] +name = "pyqt6" +version = "6.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyqt6-qt6" }, + { name = "pyqt6-sip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/bf/ff284a136b39cb1873c18e4fca4a40a8847c84a1910c5fb38c6a77868968/pyqt6-6.8.1.tar.gz", hash = "sha256:91d937d6166274fafd70f4dee11a8da6dbfdb0da53de05f5d62361ddf775e256", size = 1064723 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/da/70971b3d7f53a68644ea32544d3786dfbbb162d18572ac1defcf5a6481d5/PyQt6-6.8.1-cp39-abi3-macosx_10_14_universal2.whl", hash = "sha256:0425f9eebdd5d4e57ab36424c9382f2ea06670c3c550fa0028c2b19bd0a1d7bd", size = 12213924 }, + { url = "https://files.pythonhosted.org/packages/be/25/a4392c323a0fb97eb5f449b7594f37e93d9794b900756b43cd65772def77/PyQt6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:36bf48e3df3a6ff536e703315d155480ef4e260396eb5469eb7a875bc5bb7ab4", size = 8238120 }, + { url = "https://files.pythonhosted.org/packages/de/a3/e528b4cc3394f2ae15b531c17f27b53de756a8c0404dfa9c184502367c48/PyQt6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:2eac2267a34828b8db7660dd3cc3b3b5fd76a92e61ad45471565b01221cb558b", size = 8173996 }, + { url = "https://files.pythonhosted.org/packages/f2/69/11404cfcb916bd7207805c21432ecab0401779361d48b67f28ae9337f70d/PyQt6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:70bad7b890a8f9e9e5fb9598c544b832d9d9d99a9519e0009cb29c1e15e96632", size = 6723466 }, + { url = "https://files.pythonhosted.org/packages/00/2a/21a555aea9bc8abc4f09017b922dbdf509c421f70506d4c83d2e8f4315b2/PyQt6-6.8.1-cp39-abi3-win_arm64.whl", hash = "sha256:a40f878e8e5eeeb0bba995152d07eeef9375ea0116df0f4aad0a6b97c8ad1175", size = 5463379 }, +] + +[[package]] +name = "pyqt6-qt6" +version = "6.8.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/a4/3d764e05955382b3dc7227cbfde090700edd63431147f1c66d428ccac45c/PyQt6_Qt6-6.8.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:470dd4211fe5a67b0565e0202e7aa67816e5dcf7d713528b88327adaebd0934e", size = 66121240 }, + { url = "https://files.pythonhosted.org/packages/d6/b3/6d4f8257b46554fb2c89b33a6773a3f05ed961b3cd83828caee5dc79899f/PyQt6_Qt6-6.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40cda901a3e1617e79225c354fe9d89b80249f0a6c6aaa18b40938e05bbf7d1f", size = 60286219 }, + { url = "https://files.pythonhosted.org/packages/92/95/0036435b9e2cbd22e08f14eec2362c32fc641660c6e4aea6f59d165cb5fc/PyQt6_Qt6-6.8.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fb6d0acdd7d43c33fb8b9d2dd7922d381cdedd00da316049fbe01fc1973e6f05", size = 81263397 }, + { url = "https://files.pythonhosted.org/packages/6e/fb/c01dde044eca1542d88cac72fc99369af76a981cc2f52790236efa566e01/PyQt6_Qt6-6.8.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:5970c85d22cbe5c476418994549161b23ed938e25b04fc4ca8fabf6dcac7b03f", size = 79832921 }, + { url = "https://files.pythonhosted.org/packages/1a/f7/31f03a9f5e6c7cc23ceb2bd0d9c2df0518837f7af0e693e15b6e0881b8b0/PyQt6_Qt6-6.8.2-py3-none-win_amd64.whl", hash = "sha256:28e2bb641f05b01e498503c3ef01c8a919d6e0e96b50230301c0baac2b7d1433", size = 71934164 }, + { url = "https://files.pythonhosted.org/packages/00/c9/102c9537795ca11c12120ec9d5f554d9437787f52d8e23fbc8269e6a2699/PyQt6_Qt6-6.8.2-py3-none-win_arm64.whl", hash = "sha256:912afdddd0dfc666ce1c16bc4695e2acd680db72343e4f7a2b7c053a0146b4bc", size = 48120018 }, +] + +[[package]] +name = "pyqt6-sip" +version = "13.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/18/0405c54acba0c8e276dd6f0601890e6e735198218d031a6646104870fe22/pyqt6_sip-13.10.0.tar.gz", hash = "sha256:d6daa95a0bd315d9ec523b549e0ce97455f61ded65d5eafecd83ed2aa4ae5350", size = 92464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/c4/97446339ff086ad6530af0970b6905a6930d8868214c182a0ca0e0e9638e/PyQt6_sip-13.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e78fb8036b18f6258a1af0956c5a3cec1dd3d8dd5196ecd89a31b529bf40e82", size = 110770 }, + { url = "https://files.pythonhosted.org/packages/2b/4b/d41d9d1a5496948426b1a30b778df1d87625245d4ba5b7aa55810f2899c5/PyQt6_sip-13.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e19d5887fa3003a635419644dfed3158cb15eb566fc27b1ed56913a5767a71dc", size = 316994 }, + { url = "https://files.pythonhosted.org/packages/ff/81/62e36cddb4641c7ce6e2779e0783635555a3ecfdb50f46f9200f61af24be/PyQt6_sip-13.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079bb946edc3960f08d92b3a8eebff55d3abb51bc2a0583b6683dfd9f77a616a", size = 293804 }, + { url = "https://files.pythonhosted.org/packages/2f/4d/23e9e23a331d5a608217349c931b1d9b5cf9419640033e73ae9895e7f5bd/PyQt6_sip-13.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:90974f5dbba1f5d1d2ca9b1cfdfd5258e5e3cfacead03f0df674d54c69973ea7", size = 53444 }, + { url = "https://files.pythonhosted.org/packages/2a/0c/2caff1607ddf7dcb8d53f1657160d3b334a8e9d9a9bf700e79971677ab89/PyQt6_sip-13.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:bbefd5539eeda4dec37e8b6dfc362ba240ec31279060336bcceaff572807dac8", size = 45057 }, + { url = "https://files.pythonhosted.org/packages/69/81/66d9bdacb790592a0641378749a047f12e3b254cdc2cb51f7ed636cf01d2/PyQt6_sip-13.10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:48791db2914fc39c3218519a02d2a5fd3fcd354a1be3141a57bf2880701486f2", size = 112334 }, + { url = "https://files.pythonhosted.org/packages/26/2c/4796c209009a018e0d4a5c406d5a519234c5a378f370dc679d0ad5f455b2/PyQt6_sip-13.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:466d6b4791973c9fcbdc2e0087ed194b9ea802a8c3948867a849498f0841c70c", size = 322334 }, + { url = "https://files.pythonhosted.org/packages/99/34/2ec54bd475f0a811df1d32be485f2344cf9e8b388ce7adb26b46ce5552d4/PyQt6_sip-13.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ae15358941f127cd3d1ab09c1ebd45c4dabb0b2e91587b9eebde0279d0039c54", size = 303798 }, + { url = "https://files.pythonhosted.org/packages/0c/e4/82099bb4ab8bc152b5718541e93c0b3adf7566c0f307c9e58e2368b8c517/PyQt6_sip-13.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad573184fa8b00041944e5a17d150ab0d08db2d2189e39c9373574ebab3f2e58", size = 53569 }, + { url = "https://files.pythonhosted.org/packages/e3/09/90e0378887a3cb9664da77061229cf8e97e6ec25a5611b7dbc9cc3e02c78/PyQt6_sip-13.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:2d579d810d0047d40bde9c6aef281d6ed218db93c9496ebc9e55b9e6f27a229d", size = 45430 }, + { url = "https://files.pythonhosted.org/packages/6b/0c/8d1de48b45b565a46bf4757341f13f9b1853a7d2e6b023700f0af2c213ab/PyQt6_sip-13.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7b6e250c2e7c14702a623f2cc1479d7fb8db2b6eee9697cac10d06fe79c281bb", size = 112343 }, + { url = "https://files.pythonhosted.org/packages/af/13/e2cc2b667a9f5d44c2d0e18fa6e1066fca3f4521dcb301f4b5374caeb33e/PyQt6_sip-13.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fcb30756568f8cd59290f9ef2ae5ee3e72ff9cdd61a6f80c9e3d3b95ae676be", size = 322527 }, + { url = "https://files.pythonhosted.org/packages/20/1a/5c6fcae85edb65cf236c9dc6d23b279b5316e94cdca1abdee6d0a217ddbb/PyQt6_sip-13.10.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:757ac52c92b2ef0b56ecc7cd763b55a62d3c14271d7ea8d03315af85a70090ff", size = 303407 }, + { url = "https://files.pythonhosted.org/packages/b9/db/6924ec985be7d746772806b96ab81d24263ef72f0249f0573a82adaed75e/PyQt6_sip-13.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:571900c44a3e38738d696234d94fe2043972b9de0633505451c99e2922cb6a34", size = 53580 }, + { url = "https://files.pythonhosted.org/packages/77/c3/9e44729b582ee7f1d45160e8c292723156889f3e38ce6574f88d5ab8fa02/PyQt6_sip-13.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:39cba2cc71cf80a99b4dc8147b43508d4716e128f9fb99f5eb5860a37f082282", size = 45446 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1017,6 +1144,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "qtpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -1119,27 +1258,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/c3/418441a8170e8d53d05c0b9dad69760dbc7b8a12c10dbe6db1e1205d2377/ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933", size = 3717448 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/c3/2c4afa9ba467555d074b146d9aed0633a56ccdb900839fb008295d037b89/ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367", size = 10027252 }, - { url = "https://files.pythonhosted.org/packages/33/d1/439e58487cf9eac26378332e25e7d5ade4b800ce1eec7dc2cfc9b0d7ca96/ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7", size = 10840721 }, - { url = "https://files.pythonhosted.org/packages/50/44/fead822c38281ba0122f1b76b460488a175a9bd48b130650a6fb6dbcbcf9/ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d", size = 10161439 }, - { url = "https://files.pythonhosted.org/packages/11/ae/d404a2ab8e61ddf6342e09cc6b7f7846cce6b243e45c2007dbe0ca928a5d/ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a", size = 10336264 }, - { url = "https://files.pythonhosted.org/packages/6a/4e/7c268aa7d84cd709fb6f046b8972313142cffb40dfff1d2515c5e6288d54/ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe", size = 9908774 }, - { url = "https://files.pythonhosted.org/packages/cc/26/c618a878367ef1b76270fd027ca93692657d3f6122b84ba48911ef5f2edc/ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c", size = 11428127 }, - { url = "https://files.pythonhosted.org/packages/d7/9a/c5588a93d9bfed29f565baf193fe802fa676a0c837938137ea6cf0576d8c/ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be", size = 12133187 }, - { url = "https://files.pythonhosted.org/packages/3e/ff/e7980a7704a60905ed7e156a8d73f604c846d9bd87deda9cabfa6cba073a/ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590", size = 11602937 }, - { url = "https://files.pythonhosted.org/packages/24/78/3690444ad9e3cab5c11abe56554c35f005b51d1d118b429765249095269f/ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb", size = 13771698 }, - { url = "https://files.pythonhosted.org/packages/6e/bf/e477c2faf86abe3988e0b5fd22a7f3520e820b2ee335131aca2e16120038/ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0", size = 11249026 }, - { url = "https://files.pythonhosted.org/packages/f7/82/cdaffd59e5a8cb5b14c408c73d7a555a577cf6645faaf83e52fe99521715/ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17", size = 10220432 }, - { url = "https://files.pythonhosted.org/packages/fe/a4/2507d0026225efa5d4412b6e294dfe54725a78652a5c7e29e6bd0fc492f3/ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1", size = 9874602 }, - { url = "https://files.pythonhosted.org/packages/d5/be/f3aab1813846b476c4bcffe052d232244979c3cd99d751c17afb530ca8e4/ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57", size = 10851212 }, - { url = "https://files.pythonhosted.org/packages/8b/45/8e5fd559bea0d2f57c4e12bf197a2fade2fac465aa518284f157dfbca92b/ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e", size = 11327490 }, - { url = "https://files.pythonhosted.org/packages/42/55/e6c90f13880aeef327746052907e7e930681f26a164fe130ddac28b08269/ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1", size = 10227912 }, - { url = "https://files.pythonhosted.org/packages/35/b2/da925693cb82a1208aa34966c0f36cb222baca94e729dd22a587bc22d0f3/ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1", size = 11355632 }, - { url = "https://files.pythonhosted.org/packages/31/d8/de873d1c1b020d668d8ec9855d390764cb90cf8f6486c0983da52be8b7b7/ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf", size = 10435860 }, +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, + { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, + { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, + { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, + { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, + { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, + { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, + { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, + { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, + { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, + { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, + { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, + { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, + { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, + { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, + { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, + { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, ] [[package]] @@ -1242,7 +1381,7 @@ wheels = [ [[package]] name = "slims-python-api" -version = "6.9.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecation" }, @@ -1250,9 +1389,9 @@ dependencies = [ { name = "requests" }, { name = "requests-oauthlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/a0/c3e4894d833692f8693351d77a82631d907f26b5ea2bf64a72186141fba1/slims-python-api-6.9.0.tar.gz", hash = "sha256:195c6b30520425d75dea7ab8d326e59a2125efcd8d14cc966d5cfade93712265", size = 21087 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/21/62fb94273fa5708681de873f0b91bf1dfedde59700e2259052403dc66e62/slims_python_api-7.2.0.tar.gz", hash = "sha256:2dc23bbf7d20ddf0d712a622f34fc4341b3df8b099d0ced111b532b1f4694634", size = 21349 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/6a/223b7fec5f5a9552c894d9600db5df78589be5d7639dd9cb815a05863009/slims_python_api-6.9.0-py2.py3-none-any.whl", hash = "sha256:15ddc0fc5da62329d2a4fd8cae2f5349202cd09aead90593a67e54d3c86e4798", size = 17005 }, + { url = "https://files.pythonhosted.org/packages/77/1f/938934b209e2a09f9c7a31015218449d2e3eb8c0c29d713c858822c1352f/slims_python_api-7.2.0-py2.py3-none-any.whl", hash = "sha256:88e13e24e0612a55964cdc1f11ba56c47e8befc3ea3ab526e4fc19d622bcab2e", size = 17144 }, ] [[package]] @@ -1428,13 +1567,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[package]] +name = "superqt" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, + { name = "qtpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/28/3b3afeb6e1efd9095d18a43a60d851dede470e95297ff694d3d75e704926/superqt-0.7.1.tar.gz", hash = "sha256:dade2953916e9adff912e08a337e322b5f4603c780ed92505515489598d73960", size = 101188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/43/4f2393a5203b53e94dfd9f7e408bc4caeed5ead51868b091578c6a5aaaf5/superqt-0.7.1-py3-none-any.whl", hash = "sha256:c40df8d63bc06e0b7a3ec01c2c41c901fee03e2177c17eafabd6cfd157aa99ed", size = 95107 }, +] + +[package.optional-dependencies] +iconify = [ + { name = "pyconify" }, +] + [[package]] name = "threadpoolctl" -version = "3.5.0" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, ] [[package]]