moops 0.1.0__tar.gz → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moops
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Write Marimo notebooks that also work as CLI scripts, with unified UI controls
5
5
  Keywords: marimo,notebooks,cli,testing
6
6
  Author: Yair Chuchem
@@ -23,6 +23,9 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  # moops
25
25
 
26
+ [![PyPI](https://img.shields.io/pypi/v/moops.svg)](https://pypi.org/project/moops/)
27
+ [![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/yairchu/moops/blob/main/examples/notebook.py)
28
+
26
29
  Easily write Marimo notebooks that work as CLI scripts (and more!) with minimal boilerplate.
27
30
 
28
31
  Marimo supports notebooks running as CLI scripts,
@@ -76,6 +79,52 @@ Keyword arguments override `moops.Group` inputs by their option names,
76
79
  with leading dashes removed and dashes converted to underscores.
77
80
  If no overrides are provided, `moops.run` uses the notebook defaults.
78
81
 
82
+ ## URL query parameters
83
+
84
+ In browser notebooks, `Group()` lets URL query parameters initialize controls
85
+ and keeps later control changes reflected in the URL.
86
+
87
+ ```python
88
+ args = moops.Group()
89
+ input_text = args.text(value="", help_text="Input text")
90
+ style = args.dropdown(
91
+ ["snake_case", "camel_case"],
92
+ value="snake_case",
93
+ help_text="Output style",
94
+ allow_select_none=False,
95
+ )
96
+ ```
97
+
98
+ Opening the notebook with `?input_text=Hello&style=camel_case` initializes
99
+ those controls from the URL. Query keys use the same names as `moops.run`
100
+ keyword arguments. For subgroups, use dot-separated names such as
101
+ `?casing.style=camel_case`.
102
+
103
+ ## Custom notebook controls
104
+
105
+ Use `args.custom()` when the notebook needs an interactive control that moops
106
+ does not wrap directly, while the CLI should use a supported fallback control.
107
+ The fallback supplies the CLI parser, help text, defaults, and query-parameter
108
+ format.
109
+
110
+ ```python
111
+ plot_selection = mo.ui.matplotlib(ax)
112
+ fallback_slider = args.range_slider(
113
+ start=0,
114
+ stop=100,
115
+ value=[10, 50],
116
+ option="--x-range",
117
+ help_text="X axis range",
118
+ )
119
+ x_range = args.custom(
120
+ plot_selection,
121
+ fallback_slider,
122
+ value=lambda plot:
123
+ [plot.value.x_min, plot.value.x_max]
124
+ if plot.value else fallback_slider.value,
125
+ )
126
+ ```
127
+
79
128
  ## Property-based testing
80
129
 
81
130
  `moops.testing.notebook_interface` returns the notebook's `Interface`, from which `.strategy()` generates a [Hypothesis](https://hypothesis.readthedocs.io/) strategy that produces valid `moops.run` kwargs by introspecting the notebook's interface — dropdowns yield their allowed keys, switches yield booleans, and text fields yield arbitrary strings.
@@ -99,6 +148,7 @@ From the project root:
99
148
 
100
149
  ```sh
101
150
  uv run examples/notebook.py
151
+ uv run --with matplotlib --with numpy examples/custom_control.py --x-range 30,70
102
152
  ```
103
153
 
104
154
  Or `uv run marimo edit` to run as notebooks.
@@ -1,5 +1,8 @@
1
1
  # moops
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/moops.svg)](https://pypi.org/project/moops/)
4
+ [![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/yairchu/moops/blob/main/examples/notebook.py)
5
+
3
6
  Easily write Marimo notebooks that work as CLI scripts (and more!) with minimal boilerplate.
4
7
 
5
8
  Marimo supports notebooks running as CLI scripts,
@@ -53,6 +56,52 @@ Keyword arguments override `moops.Group` inputs by their option names,
53
56
  with leading dashes removed and dashes converted to underscores.
54
57
  If no overrides are provided, `moops.run` uses the notebook defaults.
55
58
 
59
+ ## URL query parameters
60
+
61
+ In browser notebooks, `Group()` lets URL query parameters initialize controls
62
+ and keeps later control changes reflected in the URL.
63
+
64
+ ```python
65
+ args = moops.Group()
66
+ input_text = args.text(value="", help_text="Input text")
67
+ style = args.dropdown(
68
+ ["snake_case", "camel_case"],
69
+ value="snake_case",
70
+ help_text="Output style",
71
+ allow_select_none=False,
72
+ )
73
+ ```
74
+
75
+ Opening the notebook with `?input_text=Hello&style=camel_case` initializes
76
+ those controls from the URL. Query keys use the same names as `moops.run`
77
+ keyword arguments. For subgroups, use dot-separated names such as
78
+ `?casing.style=camel_case`.
79
+
80
+ ## Custom notebook controls
81
+
82
+ Use `args.custom()` when the notebook needs an interactive control that moops
83
+ does not wrap directly, while the CLI should use a supported fallback control.
84
+ The fallback supplies the CLI parser, help text, defaults, and query-parameter
85
+ format.
86
+
87
+ ```python
88
+ plot_selection = mo.ui.matplotlib(ax)
89
+ fallback_slider = args.range_slider(
90
+ start=0,
91
+ stop=100,
92
+ value=[10, 50],
93
+ option="--x-range",
94
+ help_text="X axis range",
95
+ )
96
+ x_range = args.custom(
97
+ plot_selection,
98
+ fallback_slider,
99
+ value=lambda plot:
100
+ [plot.value.x_min, plot.value.x_max]
101
+ if plot.value else fallback_slider.value,
102
+ )
103
+ ```
104
+
56
105
  ## Property-based testing
57
106
 
58
107
  `moops.testing.notebook_interface` returns the notebook's `Interface`, from which `.strategy()` generates a [Hypothesis](https://hypothesis.readthedocs.io/) strategy that produces valid `moops.run` kwargs by introspecting the notebook's interface — dropdowns yield their allowed keys, switches yield booleans, and text fields yield arbitrary strings.
@@ -76,6 +125,7 @@ From the project root:
76
125
 
77
126
  ```sh
78
127
  uv run examples/notebook.py
128
+ uv run --with matplotlib --with numpy examples/custom_control.py --x-range 30,70
79
129
  ```
80
130
 
81
131
  Or `uv run marimo edit` to run as notebooks.
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "moops"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Write Marimo notebooks that also work as CLI scripts, with unified UI controls"
9
9
  license = "MIT"
10
10
  readme = "README.md"
@@ -39,6 +39,7 @@ dev-dependencies = [
39
39
  "pyright>=1.1.409",
40
40
  "pytest>=9.0.3",
41
41
  "vulture>=2.16",
42
+ "pymarkdownlnt>=0.9.37",
42
43
  ]
43
44
 
44
45
  [tool.pytest.ini_options]
@@ -0,0 +1,10 @@
1
+ from importlib.metadata import version
2
+
3
+ from ._run import run
4
+ from .group import Group
5
+ from .interface import Interface
6
+ from .presets import Presets
7
+
8
+ __version__ = version("moops")
9
+
10
+ __all__ = ["Group", "Interface", "Presets", "__version__", "run"]
@@ -0,0 +1,24 @@
1
+ import typing
2
+ import weakref
3
+
4
+ from . import _options
5
+
6
+
7
+ class InputMap:
8
+ """Maps UI controls to their input-channel counterparts."""
9
+
10
+ def __init__(self) -> None:
11
+ self._registered: weakref.WeakValueDictionary[str, _options.InputControl] = (
12
+ weakref.WeakValueDictionary()
13
+ )
14
+
15
+ def register(self, control: typing.Any, cli: _options.InputControl) -> typing.Any:
16
+ self._registered[cli.option] = cli
17
+ control._moops_input = cli
18
+ return control
19
+
20
+ def get(self, control: typing.Any) -> _options.InputControl | None:
21
+ return getattr(control, "_moops_input", None)
22
+
23
+ def registered_options(self) -> typing.Iterator[_options.InputControl]:
24
+ yield from self._registered.values()
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import html
2
3
 
3
4
 
4
5
  @dataclasses.dataclass
@@ -25,3 +26,9 @@ class OptionLabel:
25
26
  if label is None:
26
27
  label = option.lstrip("-").replace("-", " ")
27
28
  return OptionLabel(label=label, option=option)
29
+
30
+ def label_with_tooltip(self, help_text: str) -> str:
31
+ return (
32
+ f'<span title="{html.escape(help_text, quote=True)} ({self.option})">'
33
+ f"{self.label}</span>"
34
+ )