moops 0.13.0__tar.gz → 0.13.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moops-0.13.0 → moops-0.13.2}/PKG-INFO +1 -1
- {moops-0.13.0 → moops-0.13.2}/pyproject.toml +1 -1
- {moops-0.13.0 → moops-0.13.2}/src/moops/_control_mirroring.py +1 -1
- {moops-0.13.0 → moops-0.13.2}/src/moops/_options.py +4 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_variant.py +6 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/group.py +1 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/interface.py +49 -24
- {moops-0.13.0 → moops-0.13.2}/README.md +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/__init__.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_choice_options.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_custom_element.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_embed.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_input_map.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_interface_utils.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_list_controls.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_list_options.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_marimo_controls.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_markdown.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_naming.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_parse.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_preset_state.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_presets_ui.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_query_params.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_run.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_run_button.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_subgroup_registry.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_terminal_graphics.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_text_wrap.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_ui_workarounds.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/_value_resolution.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/presets.py +0 -0
- {moops-0.13.0 → moops-0.13.2}/src/moops/workarounds.py +0 -0
|
@@ -188,7 +188,7 @@ def _create_control(
|
|
|
188
188
|
) -> typing.Any:
|
|
189
189
|
"""Create a marimo element from an existing InputControl."""
|
|
190
190
|
display_option = _interface_utils.unprefixed_option(iface, input_control.option)
|
|
191
|
-
opt = group._make_opt(label=
|
|
191
|
+
opt = group._make_opt(label=input_control.label, option=display_option)
|
|
192
192
|
cloned = input_control.with_option(opt.option)
|
|
193
193
|
return group._register_control(opt, cloned, cloned.help_text, None)
|
|
194
194
|
|
|
@@ -52,6 +52,10 @@ class InputControl(abc.ABC):
|
|
|
52
52
|
option: str
|
|
53
53
|
help_text: str
|
|
54
54
|
default: typing.Any
|
|
55
|
+
# Resolved display label, stashed at registration so controls_from can
|
|
56
|
+
# mirror it. None until registered; the mirror falls back to the option
|
|
57
|
+
# name. Travels through with_option's dataclasses.replace.
|
|
58
|
+
label: str | None = dataclasses.field(default=None, kw_only=True)
|
|
55
59
|
extra_kwargs: dict[str, typing.Any] = dataclasses.field(
|
|
56
60
|
default_factory=dict[str, typing.Any], kw_only=True
|
|
57
61
|
)
|
|
@@ -26,6 +26,12 @@ class VariantContext:
|
|
|
26
26
|
def selected_key(selector: typing.Any) -> typing.Any:
|
|
27
27
|
if hasattr(selector, "_selected_key"):
|
|
28
28
|
return selector._selected_key
|
|
29
|
+
# A switch/checkbox used as a variant selector has no `_selected_key`. In
|
|
30
|
+
# the controls_from mirroring path the selector is a freshly-cloned widget,
|
|
31
|
+
# so reading `.value` trips marimo's "value accessed in its creating cell"
|
|
32
|
+
# guard; the raw cached `_value` holds the same value without the guard.
|
|
33
|
+
if hasattr(selector, "_value"):
|
|
34
|
+
return selector._value
|
|
29
35
|
return selector.value
|
|
30
36
|
|
|
31
37
|
|
|
@@ -972,6 +972,7 @@ class Group:
|
|
|
972
972
|
help_text: str,
|
|
973
973
|
on_change: typing.Callable[[typing.Any], None] | None,
|
|
974
974
|
) -> typing.Any:
|
|
975
|
+
input_control.label = opt.label
|
|
975
976
|
reset_state = self._value_resolver.query_on_change(input_control, on_change)
|
|
976
977
|
control = input_control.make_element(
|
|
977
978
|
self._value_resolver.get_value(input_control, input_control.default),
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
import html
|
|
5
|
+
import os
|
|
5
6
|
import pathlib
|
|
6
7
|
import shlex
|
|
7
8
|
import sys
|
|
@@ -179,6 +180,8 @@ class Interface:
|
|
|
179
180
|
tokens = shlex.split(text)
|
|
180
181
|
except ValueError as exc:
|
|
181
182
|
return (f"Could not parse arguments: {exc}",)
|
|
183
|
+
if tokens[:2] == ["uv", "run"]:
|
|
184
|
+
tokens = tokens[2:]
|
|
182
185
|
commands = {self.command, pathlib.PurePath(self.command).name}
|
|
183
186
|
if tokens and tokens[0] in commands:
|
|
184
187
|
tokens = tokens[1:]
|
|
@@ -443,9 +446,7 @@ class Interface:
|
|
|
443
446
|
# Non-empty: the parent injected defs beyond args, so no CLI
|
|
444
447
|
# command can reproduce this setup.
|
|
445
448
|
return link
|
|
446
|
-
command =
|
|
447
|
-
self.notebook_file, self._standalone_arg_groups()
|
|
448
|
-
)
|
|
449
|
+
command = _wrap_command(self.notebook_file, self._standalone_arg_groups())
|
|
449
450
|
return mo.vstack(
|
|
450
451
|
[link, mo.md(f"To run as a standalone script:\n```\n{command}\n```")]
|
|
451
452
|
)
|
|
@@ -455,48 +456,57 @@ class Interface:
|
|
|
455
456
|
query = urllib.parse.urlencode({"file": self.notebook_file, **values})
|
|
456
457
|
return f"/?{query}"
|
|
457
458
|
|
|
458
|
-
def _standalone_query_values(self) -> dict[str, str]:
|
|
459
|
-
|
|
460
|
-
for ctrl in self.controls:
|
|
461
|
-
if isinstance(ctrl, Interface):
|
|
462
|
-
values.update(ctrl._standalone_query_values())
|
|
463
|
-
elif (sub_iface := attached_interface(ctrl)) is not None:
|
|
464
|
-
values.update(self._controls_from_query_values(sub_iface))
|
|
465
|
-
else:
|
|
466
|
-
self._add_query_value(values, ctrl, self.input_map)
|
|
467
|
-
return values
|
|
468
|
-
|
|
469
|
-
def _controls_from_query_values(self, sub_iface: Interface) -> dict[str, str]:
|
|
470
|
-
"""Collect standalone query values for a controls_from mirror.
|
|
459
|
+
def _standalone_query_values(self, prefix: str = "") -> dict[str, str]:
|
|
460
|
+
"""Query params reproducing this notebook's state when run standalone.
|
|
471
461
|
|
|
472
|
-
|
|
473
|
-
|
|
462
|
+
``prefix`` is the dotted query-param path of this interface relative to
|
|
463
|
+
the standalone root (empty at the root). A real subgroup keeps its
|
|
464
|
+
segment so its controls round-trip under the same dotted key the
|
|
465
|
+
subgroup reads back (e.g. ``sub.mode``); the embedded-summary link calls
|
|
466
|
+
this on the subgroup itself, so there ``prefix`` is empty and its
|
|
467
|
+
controls go flat, matching the embedded notebook running as root.
|
|
474
468
|
"""
|
|
475
469
|
values: dict[str, str] = {}
|
|
476
|
-
for ctrl in
|
|
477
|
-
if (
|
|
478
|
-
|
|
470
|
+
for ctrl in self.controls:
|
|
471
|
+
if (sub_iface := attached_interface(ctrl)) is not None:
|
|
472
|
+
segment = sub_iface._query_segment_below(self.query_params.prefix)
|
|
473
|
+
child = (
|
|
474
|
+
f"{prefix}.{segment}" if prefix and segment else segment or prefix
|
|
475
|
+
)
|
|
476
|
+
values.update(sub_iface._standalone_query_values(child))
|
|
479
477
|
else:
|
|
480
|
-
self._add_query_value(values, ctrl,
|
|
478
|
+
self._add_query_value(values, ctrl, self.input_map, prefix)
|
|
481
479
|
return values
|
|
482
480
|
|
|
481
|
+
def _query_segment_below(self, parent_prefix: str) -> str:
|
|
482
|
+
"""This interface's dotted query path relative to ``parent_prefix``."""
|
|
483
|
+
prefix = self.query_params.prefix
|
|
484
|
+
if parent_prefix and prefix.startswith(f"{parent_prefix}."):
|
|
485
|
+
return prefix[len(parent_prefix) + 1 :]
|
|
486
|
+
if parent_prefix and prefix == parent_prefix:
|
|
487
|
+
return ""
|
|
488
|
+
return prefix
|
|
489
|
+
|
|
483
490
|
def _add_query_value(
|
|
484
491
|
self,
|
|
485
492
|
values: dict[str, str],
|
|
486
493
|
ctrl: typing.Any,
|
|
487
494
|
input_map: _input_map.InputMap,
|
|
495
|
+
prefix: str = "",
|
|
488
496
|
) -> None:
|
|
489
497
|
input_control = input_map.get(ctrl)
|
|
490
498
|
if input_control is None:
|
|
491
499
|
return
|
|
492
500
|
value = input_control.format_query_value(_marimo_controls.ctrl_value(ctrl))
|
|
493
501
|
if value is not None:
|
|
494
|
-
|
|
502
|
+
leaf = self._key(input_control)
|
|
503
|
+
full = f"{prefix}.{leaf}" if prefix else leaf
|
|
504
|
+
values[_query_params.escape_url_key(full)] = value
|
|
495
505
|
|
|
496
506
|
def _root_panel(self) -> mo.Html:
|
|
497
507
|
args = self._current_args()
|
|
498
508
|
body_items: list[typing.Any] = [mo.md("This notebook also works as a script:")]
|
|
499
|
-
command =
|
|
509
|
+
command = _wrap_command(self.command, self._arg_groups())
|
|
500
510
|
missing_options = self.missing_options()
|
|
501
511
|
kind = "warn" if missing_options else "info"
|
|
502
512
|
if self._presets_ui is not None:
|
|
@@ -621,6 +631,21 @@ def attached_interface(ctrl: typing.Any) -> Interface | None:
|
|
|
621
631
|
return iface
|
|
622
632
|
|
|
623
633
|
|
|
634
|
+
def _wrap_command(command: str, groups: list[str]) -> str:
|
|
635
|
+
if _should_use_uv_run(command):
|
|
636
|
+
return _text_wrap.wrap_command("uv", [f"run {shlex.quote(command)}", *groups])
|
|
637
|
+
return _text_wrap.wrap_command(command, groups)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _should_use_uv_run(command: str) -> bool:
|
|
641
|
+
path = pathlib.Path(command)
|
|
642
|
+
return (
|
|
643
|
+
"UV_RUN_RECURSION_DEPTH" in os.environ
|
|
644
|
+
and path.is_file()
|
|
645
|
+
and not os.access(path, os.X_OK)
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
624
649
|
def _reset_value(
|
|
625
650
|
input_control: _options.InputControl,
|
|
626
651
|
args: _parse.ParsedArgs,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|