nicegui-autoform 0.1.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.
Files changed (43) hide show
  1. nicegui_autoform-0.1.0/.github/workflows/release.yml +53 -0
  2. nicegui_autoform-0.1.0/.github/workflows/tests.yml +43 -0
  3. nicegui_autoform-0.1.0/.gitignore +9 -0
  4. nicegui_autoform-0.1.0/LICENSE +21 -0
  5. nicegui_autoform-0.1.0/PKG-INFO +206 -0
  6. nicegui_autoform-0.1.0/README.md +176 -0
  7. nicegui_autoform-0.1.0/examples/argparse_demo.py +51 -0
  8. nicegui_autoform-0.1.0/examples/click_demo.py +48 -0
  9. nicegui_autoform-0.1.0/examples/cyclopts_demo.py +88 -0
  10. nicegui_autoform-0.1.0/examples/data/README.md +13 -0
  11. nicegui_autoform-0.1.0/examples/data/measurements.csv +13 -0
  12. nicegui_autoform-0.1.0/examples/data/notes.txt +4 -0
  13. nicegui_autoform-0.1.0/examples/typer_demo.py +58 -0
  14. nicegui_autoform-0.1.0/pyproject.toml +69 -0
  15. nicegui_autoform-0.1.0/src/nicegui_autoform/__init__.py +48 -0
  16. nicegui_autoform-0.1.0/src/nicegui_autoform/_compat.py +51 -0
  17. nicegui_autoform-0.1.0/src/nicegui_autoform/_introspect.py +99 -0
  18. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/__init__.py +60 -0
  19. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/_argparse.py +133 -0
  20. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/_click.py +215 -0
  21. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/_cyclopts.py +158 -0
  22. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/_typer.py +28 -0
  23. nicegui_autoform-0.1.0/src/nicegui_autoform/adapters/plain.py +150 -0
  24. nicegui_autoform-0.1.0/src/nicegui_autoform/form.py +251 -0
  25. nicegui_autoform-0.1.0/src/nicegui_autoform/spec.py +192 -0
  26. nicegui_autoform-0.1.0/src/nicegui_autoform/validate.py +79 -0
  27. nicegui_autoform-0.1.0/src/nicegui_autoform/values.py +103 -0
  28. nicegui_autoform-0.1.0/src/nicegui_autoform/widgets.py +242 -0
  29. nicegui_autoform-0.1.0/tests/conftest.py +9 -0
  30. nicegui_autoform-0.1.0/tests/fixtures_cli.py +156 -0
  31. nicegui_autoform-0.1.0/tests/test_adapter_argparse.py +130 -0
  32. nicegui_autoform-0.1.0/tests/test_adapter_click.py +196 -0
  33. nicegui_autoform-0.1.0/tests/test_adapter_cyclopts.py +178 -0
  34. nicegui_autoform-0.1.0/tests/test_adapter_plain.py +132 -0
  35. nicegui_autoform-0.1.0/tests/test_adapter_typer.py +115 -0
  36. nicegui_autoform-0.1.0/tests/test_examples.py +66 -0
  37. nicegui_autoform-0.1.0/tests/test_form.py +381 -0
  38. nicegui_autoform-0.1.0/tests/test_parity.py +71 -0
  39. nicegui_autoform-0.1.0/tests/test_spec.py +123 -0
  40. nicegui_autoform-0.1.0/tests/test_validate.py +78 -0
  41. nicegui_autoform-0.1.0/tests/test_values.py +87 -0
  42. nicegui_autoform-0.1.0/tests/test_widgets.py +56 -0
  43. nicegui_autoform-0.1.0/uv.lock +1538 -0
@@ -0,0 +1,53 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v7
12
+ - uses: astral-sh/setup-uv@v10.2.0 # no floating v10 major tag published yet
13
+ with:
14
+ enable-cache: true
15
+ cache-dependency-glob: uv.lock
16
+ cache-suffix: release
17
+ # A tag that disagrees with pyproject.toml would publish a version nobody
18
+ # can reproduce from the tag, so fail before anything is built.
19
+ - name: Check tag matches project version
20
+ run: |
21
+ tag="${GITHUB_REF_NAME#v}"
22
+ version="$(uv version --short)"
23
+ if [ "$tag" != "$version" ]; then
24
+ echo "tag $GITHUB_REF_NAME does not match project version $version" >&2
25
+ exit 1
26
+ fi
27
+ - run: uv build
28
+ - uses: actions/upload-artifact@v7
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ # Trusted publishing: PyPI mints a short-lived token from this job's OIDC
37
+ # identity, so there is no API token to store. Configure the publisher at
38
+ # https://pypi.org/manage/account/publishing/ with workflow "release.yml"
39
+ # and environment "pypi".
40
+ environment:
41
+ name: pypi
42
+ url: https://pypi.org/p/nicegui-autoform
43
+ permissions:
44
+ id-token: write
45
+ steps:
46
+ - uses: actions/download-artifact@v8
47
+ with:
48
+ name: dist
49
+ path: dist/
50
+ - uses: astral-sh/setup-uv@v10.2.0 # no floating v10 major tag published yet
51
+ # --check-url skips files PyPI already has, so re-running the job after a
52
+ # partial failure is not an error.
53
+ - run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/
@@ -0,0 +1,43 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ jobs:
12
+ test:
13
+ # Every version the README claims support for. 3.15 is still a release
14
+ # candidate, so uv installs it rather than actions/setup-python.
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version: ["3.13", "3.14", "3.15"]
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+ - uses: astral-sh/setup-uv@v10.2.0 # no floating v10 major tag published yet
23
+ with:
24
+ enable-cache: true
25
+ cache-dependency-glob: uv.lock
26
+ # Without this the three matrix jobs race for one cache key and two
27
+ # of them fail to save.
28
+ cache-suffix: py${{ matrix.python-version }}
29
+ - run: uv sync --frozen --python ${{ matrix.python-version }}
30
+ - run: uv run --frozen --python ${{ matrix.python-version }} pytest -q
31
+
32
+ lint:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v7
36
+ - uses: astral-sh/setup-uv@v10.2.0 # no floating v10 major tag published yet
37
+ with:
38
+ enable-cache: true
39
+ cache-dependency-glob: uv.lock
40
+ cache-suffix: lint
41
+ - run: uv run --frozen ruff check .
42
+ - run: uv run --frozen ruff format --check .
43
+ - run: uv run --frozen ty check src tests examples
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .coverage
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Owen Solberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.5
2
+ Name: nicegui-autoform
3
+ Version: 0.1.0
4
+ Summary: Render a NiceGUI web form from a command line interface.
5
+ Project-URL: Homepage, https://github.com/odoublewen/nicegui-autoform
6
+ Project-URL: Issues, https://github.com/odoublewen/nicegui-autoform/issues
7
+ Author: Owen Solberg
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: argparse,cli,click,cyclopts,forms,nicegui,typer
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Software Development :: User Interfaces
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: nicegui>=3.12
19
+ Provides-Extra: all
20
+ Requires-Dist: click>=8.1; extra == 'all'
21
+ Requires-Dist: cyclopts>=4.0; extra == 'all'
22
+ Requires-Dist: typer>=0.13; extra == 'all'
23
+ Provides-Extra: click
24
+ Requires-Dist: click>=8.1; extra == 'click'
25
+ Provides-Extra: cyclopts
26
+ Requires-Dist: cyclopts>=4.0; extra == 'cyclopts'
27
+ Provides-Extra: typer
28
+ Requires-Dist: typer>=0.13; extra == 'typer'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # nicegui-autoform
32
+
33
+ [![tests](https://github.com/odoublewen/nicegui-autoform/actions/workflows/tests.yml/badge.svg)](https://github.com/odoublewen/nicegui-autoform/actions/workflows/tests.yml)
34
+
35
+ Render a [NiceGUI](https://nicegui.io) web form from a command line interface.
36
+
37
+ If you already have a function wired up as a CLI, `AutoForm` reads that CLI's own
38
+ parameter metadata -- types, defaults, help text, choices, flags -- and builds a form
39
+ that calls the same function.
40
+
41
+ ```python
42
+ from nicegui import ui
43
+ from nicegui_autoform import AutoForm
44
+
45
+ AutoForm(app, command="train") # a cyclopts App, click Command, Typer app, ...
46
+ ui.run()
47
+ ```
48
+
49
+ ## Supported frameworks
50
+
51
+ | Framework | Install | Notes |
52
+ |---|---|---|
53
+ | [cyclopts](https://cyclopts.readthedocs.io) | `nicegui-autoform[cyclopts]` | Including CLIs defined with a `@dataclass` |
54
+ | [click](https://click.palletsprojects.com) | `nicegui-autoform[click]` | |
55
+ | [Typer](https://typer.tiangolo.com) | `nicegui-autoform[typer]` | |
56
+ | `argparse` | built in | No callback exists, so `on_submit` is required |
57
+ | Plain `@dataclass` or annotated function | built in | No CLI framework needed |
58
+
59
+ The core package depends only on NiceGUI. CLI frameworks are optional extras and are
60
+ imported lazily, so nothing is imported that you do not already use.
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ uv add "nicegui-autoform[all]"
66
+ ```
67
+
68
+ ## Supported versions
69
+
70
+ Python 3.13, 3.14 and 3.15. The dependency floors below were measured by running
71
+ the test suite against each version rather than guessed:
72
+
73
+ | Dependency | Floor | Why not lower |
74
+ |---|---|---|
75
+ | NiceGUI | 3.12 | NiceGUI's own `user` test fixture only works from 3.12, so earlier versions cannot be verified end to end |
76
+ | cyclopts | 4.0 | 3.x has a different `ArgumentCollection` API |
77
+ | click | 8.1 | 8.0 predates the parameter metadata the adapter reads |
78
+ | Typer | 0.13 | 0.12 cannot build its commands against current click |
79
+
80
+ One caveat on click 8.1 and 8.2: they report an option with no default the same
81
+ way as `default=None`, so those two cases are indistinguishable. From click 8.3
82
+ onwards an unset default is a distinct `UNSET` sentinel and the form can tell
83
+ "no default" from "defaults to None".
84
+
85
+ ## How it works
86
+
87
+ Each framework has an adapter that produces a `CommandSpec` -- a flat list of
88
+ `ParamSpec` leaves plus the containers needed to rebuild nested objects. Everything
89
+ downstream (widget selection, validation, rendering) works on that spec alone, so all
90
+ frameworks get identical behaviour and a new framework only needs a new adapter.
91
+
92
+ On submit the form validates its values against the spec and then calls the command's
93
+ own function directly. It does not shell out or re-parse a command line.
94
+
95
+ ## Usage
96
+
97
+ ### Choosing a command
98
+
99
+ ```python
100
+ AutoForm(cyclopts_app, command="train")
101
+ AutoForm(typer_app, command="train")
102
+ AutoForm(click_group, command="train")
103
+ ```
104
+
105
+ A single-command CLI needs no `command=`.
106
+
107
+ ### Overriding the callback
108
+
109
+ By default the form calls the command's own function. Pass `on_submit` to intercept it:
110
+
111
+ ```python
112
+ async def run(*args, **kwargs):
113
+ ui.notify(f"running with {kwargs}")
114
+
115
+
116
+ AutoForm(app, command="train", on_submit=run)
117
+ ```
118
+
119
+ `on_submit` may be sync or async. For an `argparse.ArgumentParser` there is no callback
120
+ to default to, so `on_submit` is required and receives an `argparse.Namespace`.
121
+
122
+ ### Nested dataclasses
123
+
124
+ A cyclopts command taking a dataclass renders its fields as a labelled section, and the
125
+ dataclass is rebuilt before the callback is called:
126
+
127
+ ```python
128
+ @dataclass
129
+ class Config:
130
+ """Training configuration.
131
+
132
+ Parameters
133
+ ----------
134
+ epochs: int
135
+ number of epochs
136
+ """
137
+
138
+ data: Path
139
+ epochs: int = 10
140
+
141
+
142
+ @app.command
143
+ def train(config: Config, seed: int = 0): ...
144
+ ```
145
+
146
+ Help text comes from the dataclass's own numpydoc docstring, exactly as cyclopts'
147
+ `--help` renders it.
148
+
149
+ ### Widgets and exclusions
150
+
151
+ ```python
152
+ from nicegui_autoform import WidgetKind
153
+
154
+ AutoForm(
155
+ app,
156
+ command="train",
157
+ exclude=["debug", "config.seed"], # bare name or dotted path
158
+ widgets={"notes": WidgetKind.TEXTAREA},
159
+ initial={"epochs": 50},
160
+ )
161
+ ```
162
+
163
+ `Path` parameters render as a file upload by default. The upload is written to a private
164
+ temporary directory under its **original** name, so the callback receives a real, readable
165
+ `Path` whose `.name` is the file the user picked -- useful when the function derives an
166
+ output name or switches on the extension. Only the basename of the client-supplied filename
167
+ is ever used, so an uploaded name cannot escape that directory.
168
+
169
+ Use `widgets={"out": WidgetKind.PATH_TEXT}` for an output path that should be typed rather
170
+ than uploaded.
171
+
172
+ ## Examples
173
+
174
+ Four runnable demos, one per framework:
175
+
176
+ ```bash
177
+ uv run python examples/cyclopts_demo.py # nested dataclass as a section
178
+ uv run python examples/click_demo.py # IntRange, Choice, repeatable option
179
+ uv run python examples/typer_demo.py # rich_help_panel as a section
180
+ uv run python examples/argparse_demo.py # on_submit receives a Namespace
181
+ ```
182
+
183
+ Each has a required `data` upload. `examples/data/measurements.csv` (12 rows of
184
+ dose/response data) is there to drop into it, and `examples/data/notes.txt` shows that a
185
+ non-CSV extension survives the round trip. On submit each demo reads the uploaded file and
186
+ reports its name and line count, so you can see the real file reached the function.
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ uv sync
192
+ uv run pytest
193
+ uv run ruff check
194
+ uv run ruff format
195
+ uv run ty check src tests examples
196
+ ```
197
+
198
+ To check a dependency floor, run the suite against that exact version:
199
+
200
+ ```bash
201
+ uv run --isolated --with "click==8.1.8" pytest
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,176 @@
1
+ # nicegui-autoform
2
+
3
+ [![tests](https://github.com/odoublewen/nicegui-autoform/actions/workflows/tests.yml/badge.svg)](https://github.com/odoublewen/nicegui-autoform/actions/workflows/tests.yml)
4
+
5
+ Render a [NiceGUI](https://nicegui.io) web form from a command line interface.
6
+
7
+ If you already have a function wired up as a CLI, `AutoForm` reads that CLI's own
8
+ parameter metadata -- types, defaults, help text, choices, flags -- and builds a form
9
+ that calls the same function.
10
+
11
+ ```python
12
+ from nicegui import ui
13
+ from nicegui_autoform import AutoForm
14
+
15
+ AutoForm(app, command="train") # a cyclopts App, click Command, Typer app, ...
16
+ ui.run()
17
+ ```
18
+
19
+ ## Supported frameworks
20
+
21
+ | Framework | Install | Notes |
22
+ |---|---|---|
23
+ | [cyclopts](https://cyclopts.readthedocs.io) | `nicegui-autoform[cyclopts]` | Including CLIs defined with a `@dataclass` |
24
+ | [click](https://click.palletsprojects.com) | `nicegui-autoform[click]` | |
25
+ | [Typer](https://typer.tiangolo.com) | `nicegui-autoform[typer]` | |
26
+ | `argparse` | built in | No callback exists, so `on_submit` is required |
27
+ | Plain `@dataclass` or annotated function | built in | No CLI framework needed |
28
+
29
+ The core package depends only on NiceGUI. CLI frameworks are optional extras and are
30
+ imported lazily, so nothing is imported that you do not already use.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ uv add "nicegui-autoform[all]"
36
+ ```
37
+
38
+ ## Supported versions
39
+
40
+ Python 3.13, 3.14 and 3.15. The dependency floors below were measured by running
41
+ the test suite against each version rather than guessed:
42
+
43
+ | Dependency | Floor | Why not lower |
44
+ |---|---|---|
45
+ | NiceGUI | 3.12 | NiceGUI's own `user` test fixture only works from 3.12, so earlier versions cannot be verified end to end |
46
+ | cyclopts | 4.0 | 3.x has a different `ArgumentCollection` API |
47
+ | click | 8.1 | 8.0 predates the parameter metadata the adapter reads |
48
+ | Typer | 0.13 | 0.12 cannot build its commands against current click |
49
+
50
+ One caveat on click 8.1 and 8.2: they report an option with no default the same
51
+ way as `default=None`, so those two cases are indistinguishable. From click 8.3
52
+ onwards an unset default is a distinct `UNSET` sentinel and the form can tell
53
+ "no default" from "defaults to None".
54
+
55
+ ## How it works
56
+
57
+ Each framework has an adapter that produces a `CommandSpec` -- a flat list of
58
+ `ParamSpec` leaves plus the containers needed to rebuild nested objects. Everything
59
+ downstream (widget selection, validation, rendering) works on that spec alone, so all
60
+ frameworks get identical behaviour and a new framework only needs a new adapter.
61
+
62
+ On submit the form validates its values against the spec and then calls the command's
63
+ own function directly. It does not shell out or re-parse a command line.
64
+
65
+ ## Usage
66
+
67
+ ### Choosing a command
68
+
69
+ ```python
70
+ AutoForm(cyclopts_app, command="train")
71
+ AutoForm(typer_app, command="train")
72
+ AutoForm(click_group, command="train")
73
+ ```
74
+
75
+ A single-command CLI needs no `command=`.
76
+
77
+ ### Overriding the callback
78
+
79
+ By default the form calls the command's own function. Pass `on_submit` to intercept it:
80
+
81
+ ```python
82
+ async def run(*args, **kwargs):
83
+ ui.notify(f"running with {kwargs}")
84
+
85
+
86
+ AutoForm(app, command="train", on_submit=run)
87
+ ```
88
+
89
+ `on_submit` may be sync or async. For an `argparse.ArgumentParser` there is no callback
90
+ to default to, so `on_submit` is required and receives an `argparse.Namespace`.
91
+
92
+ ### Nested dataclasses
93
+
94
+ A cyclopts command taking a dataclass renders its fields as a labelled section, and the
95
+ dataclass is rebuilt before the callback is called:
96
+
97
+ ```python
98
+ @dataclass
99
+ class Config:
100
+ """Training configuration.
101
+
102
+ Parameters
103
+ ----------
104
+ epochs: int
105
+ number of epochs
106
+ """
107
+
108
+ data: Path
109
+ epochs: int = 10
110
+
111
+
112
+ @app.command
113
+ def train(config: Config, seed: int = 0): ...
114
+ ```
115
+
116
+ Help text comes from the dataclass's own numpydoc docstring, exactly as cyclopts'
117
+ `--help` renders it.
118
+
119
+ ### Widgets and exclusions
120
+
121
+ ```python
122
+ from nicegui_autoform import WidgetKind
123
+
124
+ AutoForm(
125
+ app,
126
+ command="train",
127
+ exclude=["debug", "config.seed"], # bare name or dotted path
128
+ widgets={"notes": WidgetKind.TEXTAREA},
129
+ initial={"epochs": 50},
130
+ )
131
+ ```
132
+
133
+ `Path` parameters render as a file upload by default. The upload is written to a private
134
+ temporary directory under its **original** name, so the callback receives a real, readable
135
+ `Path` whose `.name` is the file the user picked -- useful when the function derives an
136
+ output name or switches on the extension. Only the basename of the client-supplied filename
137
+ is ever used, so an uploaded name cannot escape that directory.
138
+
139
+ Use `widgets={"out": WidgetKind.PATH_TEXT}` for an output path that should be typed rather
140
+ than uploaded.
141
+
142
+ ## Examples
143
+
144
+ Four runnable demos, one per framework:
145
+
146
+ ```bash
147
+ uv run python examples/cyclopts_demo.py # nested dataclass as a section
148
+ uv run python examples/click_demo.py # IntRange, Choice, repeatable option
149
+ uv run python examples/typer_demo.py # rich_help_panel as a section
150
+ uv run python examples/argparse_demo.py # on_submit receives a Namespace
151
+ ```
152
+
153
+ Each has a required `data` upload. `examples/data/measurements.csv` (12 rows of
154
+ dose/response data) is there to drop into it, and `examples/data/notes.txt` shows that a
155
+ non-CSV extension survives the round trip. On submit each demo reads the uploaded file and
156
+ reports its name and line count, so you can see the real file reached the function.
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ uv sync
162
+ uv run pytest
163
+ uv run ruff check
164
+ uv run ruff format
165
+ uv run ty check src tests examples
166
+ ```
167
+
168
+ To check a dependency floor, run the suite against that exact version:
169
+
170
+ ```bash
171
+ uv run --isolated --with "click==8.1.8" pytest
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,51 @@
1
+ """Argparse.
2
+
3
+ A parser has no callback, so ``on_submit`` is required and receives the
4
+ ``Namespace`` the form assembled.
5
+
6
+ uv run python examples/argparse_demo.py
7
+
8
+ Upload examples/data/measurements.csv into the Data field to submit.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ from pathlib import Path
15
+
16
+ from nicegui import ui
17
+
18
+ from nicegui_autoform import AutoForm
19
+
20
+ parser = argparse.ArgumentParser(prog="train", description="Train a model.")
21
+ parser.add_argument("data", type=Path, help="the input data file")
22
+ parser.add_argument("--epochs", type=int, default=10, help="number of epochs")
23
+ parser.add_argument("--mode", choices=["fast", "slow"], default="fast")
24
+ parser.add_argument("--verbose", action="store_true", help="log every step")
25
+ parser.add_argument("--tag", dest="tags", action="append", default=[], help="repeatable label")
26
+
27
+ advanced = parser.add_argument_group("Advanced")
28
+ advanced.add_argument("--workers", type=int, default=4, help="parallel workers")
29
+ advanced.add_argument("--seed", type=int, default=0)
30
+
31
+
32
+ def run(args: argparse.Namespace) -> None:
33
+ """A parser has no callback of its own, so this receives the Namespace."""
34
+ lines = args.data.read_text().splitlines()
35
+ ui.notify(
36
+ f"{args.data.name} ({len(lines)} lines) with "
37
+ f"epochs={args.epochs}, mode={args.mode}, verbose={args.verbose}, "
38
+ f"tags={args.tags}, workers={args.workers}, seed={args.seed}",
39
+ type="positive",
40
+ multi_line=True,
41
+ close_button=True,
42
+ )
43
+
44
+
45
+ @ui.page("/")
46
+ def index() -> None:
47
+ ui.label("argparse").classes("text-h5")
48
+ AutoForm(parser, on_submit=run)
49
+
50
+
51
+ ui.run(title="argparse demo", reload=False)
@@ -0,0 +1,48 @@
1
+ """Click.
2
+
3
+ uv run python examples/click_demo.py
4
+
5
+ Upload examples/data/measurements.csv into the Data field to submit.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import click
13
+ from nicegui import ui
14
+
15
+ from nicegui_autoform import AutoForm
16
+
17
+
18
+ def summarise(data: Path) -> str:
19
+ """Prove the upload arrived: the callback gets a real, readable file."""
20
+ lines = data.read_text().splitlines()
21
+ return f"{data.name} ({len(lines)} lines, {data.stat().st_size} bytes)"
22
+
23
+
24
+ @click.command("train")
25
+ @click.argument("data", type=click.Path(path_type=Path))
26
+ @click.option("--epochs", type=int, default=10, help="number of epochs")
27
+ @click.option("--workers", type=click.IntRange(1, 64), default=4, help="parallel workers")
28
+ @click.option("--mode", type=click.Choice(["fast", "slow"]), default="fast")
29
+ @click.option("--verbose/--no-verbose", default=False, help="log every step")
30
+ @click.option("--tag", "tags", multiple=True, help="repeatable label")
31
+ def train(data, epochs, workers, mode, verbose, tags):
32
+ """Train a model."""
33
+ ui.notify(
34
+ f"train(data={summarise(data)}, epochs={epochs}, workers={workers}, "
35
+ f"mode={mode}, verbose={verbose}, tags={list(tags)})",
36
+ type="positive",
37
+ multi_line=True,
38
+ close_button=True,
39
+ )
40
+
41
+
42
+ @ui.page("/")
43
+ def index() -> None:
44
+ ui.label("click").classes("text-h5")
45
+ AutoForm(train)
46
+
47
+
48
+ ui.run(title="click demo", reload=False)
@@ -0,0 +1,88 @@
1
+ """Cyclopts, including a nested dataclass rendered as a section.
2
+
3
+ uv run python examples/cyclopts_demo.py
4
+
5
+ Upload examples/data/measurements.csv into the Data field to submit.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import enum
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Annotated
14
+
15
+ import cyclopts
16
+ from nicegui import ui
17
+
18
+ from nicegui_autoform import AutoForm
19
+
20
+ app = cyclopts.App(name="trainer")
21
+
22
+
23
+ def summarise(data: Path) -> str:
24
+ """Prove the upload arrived: the callback gets a real, readable file."""
25
+ lines = data.read_text().splitlines()
26
+ return f"{data.name} ({len(lines)} lines, {data.stat().st_size} bytes)"
27
+
28
+
29
+ class Mode(enum.StrEnum):
30
+ fast = "fast"
31
+ slow = "slow"
32
+
33
+
34
+ @dataclass
35
+ class Tuning:
36
+ """Tuning.
37
+
38
+ Parameters
39
+ ----------
40
+ lr: float
41
+ learning rate
42
+ workers: int
43
+ parallel worker processes
44
+ """
45
+
46
+ lr: float = 0.001
47
+ workers: int = 4
48
+
49
+
50
+ DEFAULT_TUNING = Tuning()
51
+
52
+
53
+ @app.command
54
+ def train(
55
+ data: Path,
56
+ epochs: int = 10,
57
+ mode: Mode = Mode.fast,
58
+ verbose: Annotated[bool, cyclopts.Parameter(help="log every step")] = False,
59
+ tags: list[str] = [], # noqa: B006 - cyclopts reads the annotation, not the object
60
+ tuning: Tuning = DEFAULT_TUNING,
61
+ ):
62
+ """Train a model.
63
+
64
+ Parameters
65
+ ----------
66
+ data: Path
67
+ the input data file
68
+ epochs: int
69
+ number of epochs
70
+ mode: Mode
71
+ scheduling mode
72
+ """
73
+ ui.notify(
74
+ f"train(data={summarise(data)}, epochs={epochs}, mode={mode}, "
75
+ f"verbose={verbose}, tags={tags}, tuning={tuning})",
76
+ type="positive",
77
+ multi_line=True,
78
+ close_button=True,
79
+ )
80
+
81
+
82
+ @ui.page("/")
83
+ def index() -> None:
84
+ ui.label("cyclopts").classes("text-h5")
85
+ AutoForm(app, command="train")
86
+
87
+
88
+ ui.run(title="cyclopts demo", reload=False)