laco-typer 1.0.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.
- laco_typer-1.0.0/PKG-INFO +29 -0
- laco_typer-1.0.0/README.md +19 -0
- laco_typer-1.0.0/pyproject.toml +19 -0
- laco_typer-1.0.0/setup.cfg +4 -0
- laco_typer-1.0.0/sources/laco/integrations/typer/__init__.py +21 -0
- laco_typer-1.0.0/sources/laco/integrations/typer/_core.py +147 -0
- laco_typer-1.0.0/sources/laco_typer.egg-info/PKG-INFO +29 -0
- laco_typer-1.0.0/sources/laco_typer.egg-info/SOURCES.txt +10 -0
- laco_typer-1.0.0/sources/laco_typer.egg-info/dependency_links.txt +1 -0
- laco_typer-1.0.0/sources/laco_typer.egg-info/requires.txt +2 -0
- laco_typer-1.0.0/sources/laco_typer.egg-info/top_level.txt +1 -0
- laco_typer-1.0.0/tests/test_laco_typer.py +77 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: laco-typer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Typer CLI generation from @L.params blocks.
|
|
5
|
+
Author-email: Kurt Stolle <kurt@khws.io>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: laco>=1.0.0
|
|
9
|
+
Requires-Dist: typer>=0.12
|
|
10
|
+
|
|
11
|
+
# Laco-Typer
|
|
12
|
+
|
|
13
|
+
Typer CLI generation from `@L.params` blocks.
|
|
14
|
+
|
|
15
|
+
Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install laco-typer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
`make_app()`, `register()`
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
See [`docs/index.md`](docs/index.md) for the full guide.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Laco-Typer
|
|
2
|
+
|
|
3
|
+
Typer CLI generation from `@L.params` blocks.
|
|
4
|
+
|
|
5
|
+
Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install laco-typer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
`make_app()`, `register()`
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
See [`docs/index.md`](docs/index.md) for the full guide.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "laco-typer"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Typer CLI generation from @L.params blocks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
authors = [{ name = "Kurt Stolle", email = "kurt@khws.io" }]
|
|
12
|
+
dependencies = ["laco>=1.0.0", "typer>=0.12"]
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.packages.find]
|
|
15
|
+
where = ["sources"]
|
|
16
|
+
namespaces = true
|
|
17
|
+
|
|
18
|
+
[tool.uv.sources]
|
|
19
|
+
laco = { workspace = true }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Typer CLI generation from @L.params blocks for laco.
|
|
2
|
+
|
|
3
|
+
Examples
|
|
4
|
+
--------
|
|
5
|
+
::
|
|
6
|
+
|
|
7
|
+
import laco.integrations.typer as laco_typer
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = laco_typer.make_app("configs/train.py", callback=train)
|
|
11
|
+
app()
|
|
12
|
+
|
|
13
|
+
# Or register onto an existing Typer app:
|
|
14
|
+
root = typer.Typer()
|
|
15
|
+
laco_typer.register(root, "configs/train.py", callback=train)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from laco.integrations.typer._core import make_app as make_app
|
|
19
|
+
from laco.integrations.typer._core import register as register
|
|
20
|
+
|
|
21
|
+
__all__ = ["make_app", "register"]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Implementation for laco-typer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
import typer
|
|
9
|
+
from omegaconf import DictConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _build_typer_command(
|
|
13
|
+
config_path: str,
|
|
14
|
+
callback: typing.Callable[[DictConfig], typing.Any],
|
|
15
|
+
) -> typing.Callable[..., None]:
|
|
16
|
+
"""Return a Typer-compatible command function with typed Option parameters."""
|
|
17
|
+
import inspect
|
|
18
|
+
|
|
19
|
+
import typer as _typer
|
|
20
|
+
from laco._io import _load_with_namespace
|
|
21
|
+
from laco._overrides import apply_overrides
|
|
22
|
+
from laco.language import LacoParam, ParamsWrapper
|
|
23
|
+
|
|
24
|
+
cfg_base, nsp = _load_with_namespace(config_path)
|
|
25
|
+
|
|
26
|
+
params_blocks: list[tuple[str, ParamsWrapper]] = [
|
|
27
|
+
(k, v)
|
|
28
|
+
for k, v in nsp.items()
|
|
29
|
+
if isinstance(v, ParamsWrapper) and not v._exclude and not k.startswith("_")
|
|
30
|
+
]
|
|
31
|
+
module_params: list[tuple[str, LacoParam]] = [
|
|
32
|
+
(k, v)
|
|
33
|
+
for k, v in nsp.items()
|
|
34
|
+
if isinstance(v, LacoParam) and not k.startswith("_")
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# Build (cli_name, config_key, default, annotation, help) tuples
|
|
38
|
+
params_spec: list[tuple[str, str, typing.Any, type, str | None]] = []
|
|
39
|
+
|
|
40
|
+
for var_name, wrapper in params_blocks:
|
|
41
|
+
prefix = wrapper._prefix
|
|
42
|
+
if prefix is None:
|
|
43
|
+
flag_prefix = var_name + "_"
|
|
44
|
+
elif prefix == "":
|
|
45
|
+
flag_prefix = ""
|
|
46
|
+
else:
|
|
47
|
+
flag_prefix = prefix + "_"
|
|
48
|
+
annotations = (
|
|
49
|
+
typing.get_type_hints(wrapper._cls) if hasattr(wrapper, "_cls") else {}
|
|
50
|
+
)
|
|
51
|
+
for field_name, raw_val in wrapper._dict.items():
|
|
52
|
+
if field_name in wrapper._hidden:
|
|
53
|
+
continue
|
|
54
|
+
laco_p = raw_val if isinstance(raw_val, LacoParam) else None
|
|
55
|
+
default = laco_p.default if laco_p else raw_val
|
|
56
|
+
ann = annotations.get(
|
|
57
|
+
field_name, type(default) if default is not None else str
|
|
58
|
+
)
|
|
59
|
+
help_text = laco_p.help if laco_p else None
|
|
60
|
+
cli_name = f"{flag_prefix}{field_name}"
|
|
61
|
+
config_key = f"{var_name}.{field_name}"
|
|
62
|
+
params_spec.append((cli_name, config_key, default, ann, help_text))
|
|
63
|
+
|
|
64
|
+
for field_name, laco_p in module_params:
|
|
65
|
+
default = laco_p.default
|
|
66
|
+
ann = type(default) if default is not None else str
|
|
67
|
+
params_spec.append((field_name, field_name, default, ann, laco_p.help))
|
|
68
|
+
|
|
69
|
+
# Dynamically build the function signature using inspect.Parameter
|
|
70
|
+
sig_params = [
|
|
71
|
+
inspect.Parameter(
|
|
72
|
+
name=cli_name,
|
|
73
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
74
|
+
default=_typer.Option(default, help=help_text or ""),
|
|
75
|
+
annotation=ann,
|
|
76
|
+
)
|
|
77
|
+
for cli_name, _config_key, default, ann, help_text in params_spec
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
def _command(**kwargs: typing.Any) -> None:
|
|
81
|
+
overrides: list[str] = []
|
|
82
|
+
for cli_name, config_key, default, _ann, _help in params_spec:
|
|
83
|
+
val = kwargs[cli_name]
|
|
84
|
+
if val != default:
|
|
85
|
+
v = repr(val) if isinstance(val, str) else str(val)
|
|
86
|
+
overrides.append(f"{config_key}={v}")
|
|
87
|
+
cfg = apply_overrides(cfg_base, overrides) if overrides else cfg_base
|
|
88
|
+
callback(cfg)
|
|
89
|
+
|
|
90
|
+
_command.__signature__ = inspect.Signature(sig_params) # type: ignore[attr-defined]
|
|
91
|
+
_command.__name__ = callback.__name__
|
|
92
|
+
_command.__doc__ = callback.__doc__ or ""
|
|
93
|
+
return _command
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def make_app(
|
|
97
|
+
config_path: str,
|
|
98
|
+
callback: typing.Callable[[DictConfig], typing.Any],
|
|
99
|
+
*,
|
|
100
|
+
name: str | None = None,
|
|
101
|
+
) -> typer.Typer:
|
|
102
|
+
"""Create a Typer application from a laco config file.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
config_path : str
|
|
107
|
+
Path to the laco config file.
|
|
108
|
+
callback : Callable
|
|
109
|
+
Function called with the resolved DictConfig.
|
|
110
|
+
name : str | None
|
|
111
|
+
Optional Typer app name.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
typer.Typer
|
|
116
|
+
App ready to be invoked.
|
|
117
|
+
"""
|
|
118
|
+
import typer as _typer
|
|
119
|
+
|
|
120
|
+
app = _typer.Typer(name=name)
|
|
121
|
+
cmd = _build_typer_command(config_path, callback)
|
|
122
|
+
app.command()(cmd)
|
|
123
|
+
return app
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def register(
|
|
127
|
+
app: typer.Typer,
|
|
128
|
+
config_path: str,
|
|
129
|
+
callback: typing.Callable[[DictConfig], typing.Any],
|
|
130
|
+
*,
|
|
131
|
+
name: str | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Register a laco config as a command on an existing Typer app.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
app : typer.Typer
|
|
138
|
+
Existing Typer application.
|
|
139
|
+
config_path : str
|
|
140
|
+
Path to the laco config file.
|
|
141
|
+
callback : Callable
|
|
142
|
+
Function called with the resolved DictConfig.
|
|
143
|
+
name : str | None
|
|
144
|
+
Command name override. Defaults to ``callback.__name__``.
|
|
145
|
+
"""
|
|
146
|
+
cmd = _build_typer_command(config_path, callback)
|
|
147
|
+
app.command(name=name)(cmd)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: laco-typer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Typer CLI generation from @L.params blocks.
|
|
5
|
+
Author-email: Kurt Stolle <kurt@khws.io>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: laco>=1.0.0
|
|
9
|
+
Requires-Dist: typer>=0.12
|
|
10
|
+
|
|
11
|
+
# Laco-Typer
|
|
12
|
+
|
|
13
|
+
Typer CLI generation from `@L.params` blocks.
|
|
14
|
+
|
|
15
|
+
Part of the [laco](https://github.com/khwstolle/laco) project — see the [root README](../../README.md) for an overview.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install laco-typer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
`make_app()`, `register()`
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
See [`docs/index.md`](docs/index.md) for the full guide.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
sources/laco/integrations/typer/__init__.py
|
|
4
|
+
sources/laco/integrations/typer/_core.py
|
|
5
|
+
sources/laco_typer.egg-info/PKG-INFO
|
|
6
|
+
sources/laco_typer.egg-info/SOURCES.txt
|
|
7
|
+
sources/laco_typer.egg-info/dependency_links.txt
|
|
8
|
+
sources/laco_typer.egg-info/requires.txt
|
|
9
|
+
sources/laco_typer.egg-info/top_level.txt
|
|
10
|
+
tests/test_laco_typer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
laco
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Tests for laco-typer."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_import():
|
|
7
|
+
import laco.integrations.typer # noqa: F401
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_public_api():
|
|
11
|
+
import laco.integrations.typer as laco_typer
|
|
12
|
+
|
|
13
|
+
assert set(laco_typer.__all__) == {"make_app", "register"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_make_app_signature():
|
|
17
|
+
import laco.integrations.typer as laco_typer
|
|
18
|
+
|
|
19
|
+
sig = inspect.signature(laco_typer.make_app)
|
|
20
|
+
params = list(sig.parameters)
|
|
21
|
+
|
|
22
|
+
assert "config_path" in params
|
|
23
|
+
assert "callback" in params
|
|
24
|
+
assert "name" in params
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_register_signature():
|
|
28
|
+
import laco.integrations.typer as laco_typer
|
|
29
|
+
|
|
30
|
+
sig = inspect.signature(laco_typer.register)
|
|
31
|
+
params = list(sig.parameters)
|
|
32
|
+
|
|
33
|
+
assert "app" in params
|
|
34
|
+
assert "config_path" in params
|
|
35
|
+
assert "callback" in params
|
|
36
|
+
assert "name" in params
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_make_app_with_mock_laco(tmp_path):
|
|
40
|
+
from unittest.mock import MagicMock, patch
|
|
41
|
+
|
|
42
|
+
import laco.integrations.typer as laco_typer
|
|
43
|
+
|
|
44
|
+
config_file = tmp_path / "config.py"
|
|
45
|
+
config_file.write_text("lr = 1e-3\n")
|
|
46
|
+
callback = MagicMock()
|
|
47
|
+
|
|
48
|
+
mock_cfg = MagicMock()
|
|
49
|
+
mock_nsp = {}
|
|
50
|
+
|
|
51
|
+
with patch("laco._io._load_with_namespace", return_value=(mock_cfg, mock_nsp)):
|
|
52
|
+
import typer
|
|
53
|
+
|
|
54
|
+
app = laco_typer.make_app(str(config_file), callback)
|
|
55
|
+
assert isinstance(app, typer.Typer)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_register_with_mock_laco(tmp_path):
|
|
59
|
+
from unittest.mock import MagicMock, patch
|
|
60
|
+
|
|
61
|
+
import laco.integrations.typer as laco_typer
|
|
62
|
+
import typer
|
|
63
|
+
|
|
64
|
+
config_file = tmp_path / "config.py"
|
|
65
|
+
config_file.write_text("lr = 1e-3\n")
|
|
66
|
+
callback = MagicMock()
|
|
67
|
+
callback.__name__ = "train"
|
|
68
|
+
callback.__doc__ = "Train the model."
|
|
69
|
+
|
|
70
|
+
root = typer.Typer()
|
|
71
|
+
mock_cfg = MagicMock()
|
|
72
|
+
mock_nsp = {}
|
|
73
|
+
|
|
74
|
+
with patch("laco._io._load_with_namespace", return_value=(mock_cfg, mock_nsp)):
|
|
75
|
+
laco_typer.register(root, str(config_file), callback)
|
|
76
|
+
|
|
77
|
+
assert len(root.registered_commands) == 1
|