spotregen 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.
- spotregen-0.1.0/PKG-INFO +77 -0
- spotregen-0.1.0/README.md +60 -0
- spotregen-0.1.0/pyproject.toml +79 -0
- spotregen-0.1.0/pyproject.toml.orig +81 -0
- spotregen-0.1.0/src/spotregen/__init__.py +82 -0
- spotregen-0.1.0/src/spotregen/__main__.py +9 -0
- spotregen-0.1.0/src/spotregen/_tools.py +115 -0
- spotregen-0.1.0/src/spotregen/checks.py +625 -0
- spotregen-0.1.0/src/spotregen/cli.py +211 -0
- spotregen-0.1.0/src/spotregen/compact.py +49 -0
- spotregen-0.1.0/src/spotregen/context.py +203 -0
- spotregen-0.1.0/src/spotregen/drivers/__init__.py +11 -0
- spotregen-0.1.0/src/spotregen/drivers/_common.py +429 -0
- spotregen-0.1.0/src/spotregen/drivers/blind.py +668 -0
- spotregen-0.1.0/src/spotregen/drivers/local.py +723 -0
- spotregen-0.1.0/src/spotregen/reports.py +136 -0
- spotregen-0.1.0/src/spotregen/sample.py +155 -0
- spotregen-0.1.0/src/spotregen/scaffold.py +212 -0
- spotregen-0.1.0/src/spotregen/scan.py +231 -0
- spotregen-0.1.0/src/spotregen/spec.py +682 -0
spotregen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: spotregen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Regenerative Software tooling: check intent, evaluations and provenance of regenerable units, and drive blind regeneration
|
|
5
|
+
Author: bartzbeielstein
|
|
6
|
+
Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
|
|
7
|
+
License: AGPL-3.0-or-later
|
|
8
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Project-URL: Documentation, https://advm1.gm.fh-koeln.de/~bartz/spotregen/
|
|
14
|
+
Project-URL: Repository, https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen
|
|
15
|
+
Project-URL: Issues, https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen/-/issues
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# spotregen
|
|
19
|
+
|
|
20
|
+
Tooling for **Regenerative Software** in the sense of Chad Fowler: code is
|
|
21
|
+
disposable, the knowledge around it is the asset. A module is safely replaceable
|
|
22
|
+
when it can be deleted and rebuilt from knowledge that lives outside its
|
|
23
|
+
implementation. `spotregen` reads that knowledge from a tracked `regen/` tree and
|
|
24
|
+
checks it against the source tree.
|
|
25
|
+
|
|
26
|
+
* Standard library only: zero runtime dependencies, Python 3.13 or later.
|
|
27
|
+
* It never imports the modules it describes; it reads the source with `ast`.
|
|
28
|
+
* A deterministic gate (`spotregen check`) for `make guard`, plus reports
|
|
29
|
+
(`status`, `deletion-test`, `context`, `compact`, `new-unit`) and two
|
|
30
|
+
non-deterministic drivers (`blind`, `local`) that regenerate a unit with an LLM.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv add --dev spotregen
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Use
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
spotregen check [--strict] # the gate: exit 1 on errors
|
|
42
|
+
spotregen status # units by pace and coverage
|
|
43
|
+
spotregen deletion-test UNIT # blast radius and knowledge gaps
|
|
44
|
+
spotregen context UNIT [-o FILE] # regeneration bundle, no bodies
|
|
45
|
+
spotregen compact # compaction candidates
|
|
46
|
+
spotregen new-unit MODULE --pace P --risk R [--dry-run]
|
|
47
|
+
spotregen blind UNIT [...] # regenerate with `claude -p` (needs claude)
|
|
48
|
+
spotregen local UNIT --model M [...] # regenerate with a local LM Studio model
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`--root DIR` selects the repository (default: search upwards for
|
|
52
|
+
`regen/architecture.toml`); `python -m spotregen` works as well. A repository
|
|
53
|
+
carries:
|
|
54
|
+
|
|
55
|
+
* `regen/architecture.toml`: the compilation target (allowed imports, accepted
|
|
56
|
+
cycles, pace policy, conventions, scan directories).
|
|
57
|
+
* `regen/units/<unit>.toml`: one regenerable unit with intent, evaluations and
|
|
58
|
+
consumers.
|
|
59
|
+
* `regen/decisions/<id>.toml`: provenance records.
|
|
60
|
+
|
|
61
|
+
The schema, the finding codes and the tutorials are in the documentation,
|
|
62
|
+
<https://advm1.gm.fh-koeln.de/~bartz/spotregen/>. A minimal repository for trying
|
|
63
|
+
it out is built by `spotregen.make_sample_repo`.
|
|
64
|
+
|
|
65
|
+
## Develop
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
make sync && make test-fast # environment and fast tests
|
|
69
|
+
make verify # guard + lint + test + doc, the gate before a push
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [RELEASING.md](RELEASING.md). The design
|
|
73
|
+
is recorded in [ADR 0001](docs/adr/0001-spotregen-package.md).
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
AGPL-3.0-or-later.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# spotregen
|
|
2
|
+
|
|
3
|
+
Tooling for **Regenerative Software** in the sense of Chad Fowler: code is
|
|
4
|
+
disposable, the knowledge around it is the asset. A module is safely replaceable
|
|
5
|
+
when it can be deleted and rebuilt from knowledge that lives outside its
|
|
6
|
+
implementation. `spotregen` reads that knowledge from a tracked `regen/` tree and
|
|
7
|
+
checks it against the source tree.
|
|
8
|
+
|
|
9
|
+
* Standard library only: zero runtime dependencies, Python 3.13 or later.
|
|
10
|
+
* It never imports the modules it describes; it reads the source with `ast`.
|
|
11
|
+
* A deterministic gate (`spotregen check`) for `make guard`, plus reports
|
|
12
|
+
(`status`, `deletion-test`, `context`, `compact`, `new-unit`) and two
|
|
13
|
+
non-deterministic drivers (`blind`, `local`) that regenerate a unit with an LLM.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add --dev spotregen
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
spotregen check [--strict] # the gate: exit 1 on errors
|
|
25
|
+
spotregen status # units by pace and coverage
|
|
26
|
+
spotregen deletion-test UNIT # blast radius and knowledge gaps
|
|
27
|
+
spotregen context UNIT [-o FILE] # regeneration bundle, no bodies
|
|
28
|
+
spotregen compact # compaction candidates
|
|
29
|
+
spotregen new-unit MODULE --pace P --risk R [--dry-run]
|
|
30
|
+
spotregen blind UNIT [...] # regenerate with `claude -p` (needs claude)
|
|
31
|
+
spotregen local UNIT --model M [...] # regenerate with a local LM Studio model
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`--root DIR` selects the repository (default: search upwards for
|
|
35
|
+
`regen/architecture.toml`); `python -m spotregen` works as well. A repository
|
|
36
|
+
carries:
|
|
37
|
+
|
|
38
|
+
* `regen/architecture.toml`: the compilation target (allowed imports, accepted
|
|
39
|
+
cycles, pace policy, conventions, scan directories).
|
|
40
|
+
* `regen/units/<unit>.toml`: one regenerable unit with intent, evaluations and
|
|
41
|
+
consumers.
|
|
42
|
+
* `regen/decisions/<id>.toml`: provenance records.
|
|
43
|
+
|
|
44
|
+
The schema, the finding codes and the tutorials are in the documentation,
|
|
45
|
+
<https://advm1.gm.fh-koeln.de/~bartz/spotregen/>. A minimal repository for trying
|
|
46
|
+
it out is built by `spotregen.make_sample_repo`.
|
|
47
|
+
|
|
48
|
+
## Develop
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
make sync && make test-fast # environment and fast tests
|
|
52
|
+
make verify # guard + lint + test + doc, the gate before a push
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [RELEASING.md](RELEASING.md). The design
|
|
56
|
+
is recorded in [ADR 0001](docs/adr/0001-spotregen-package.md).
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
AGPL-3.0-or-later.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spotregen"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Regenerative Software tooling: check intent, evaluations and provenance of regenerable units, and drive blind regeneration"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
classifiers = [
|
|
7
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"Programming Language :: Python :: 3.13",
|
|
10
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.13"
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.license]
|
|
16
|
+
text = "AGPL-3.0-or-later"
|
|
17
|
+
|
|
18
|
+
[[project.authors]]
|
|
19
|
+
name = "bartzbeielstein"
|
|
20
|
+
email = "32470350+bartzbeielstein@users.noreply.github.com"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Documentation = "https://advm1.gm.fh-koeln.de/~bartz/spotregen/"
|
|
24
|
+
Repository = "https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen"
|
|
25
|
+
Issues = "https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen/-/issues"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
spotregen = "spotregen.cli:main"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv_build>=0.9.18"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=9.0.2",
|
|
37
|
+
"pytest-cov>=6.0.0",
|
|
38
|
+
"black>=24.1.0",
|
|
39
|
+
"isort>=5.13.0",
|
|
40
|
+
"ruff>=0.3.0",
|
|
41
|
+
"ty>=0.0.29",
|
|
42
|
+
"reuse>=5.0.0",
|
|
43
|
+
"pre-commit>=4.0.0",
|
|
44
|
+
"quartodoc>=0.9.1",
|
|
45
|
+
"ipykernel>=7.1.0",
|
|
46
|
+
"jupyter>=1.1.1",
|
|
47
|
+
"nbformat>=5.10.4",
|
|
48
|
+
"pyyaml>=6.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
python_files = ["test_*.py"]
|
|
54
|
+
python_classes = ["Test*"]
|
|
55
|
+
python_functions = ["test_*"]
|
|
56
|
+
markers = [
|
|
57
|
+
"slow: end-to-end tests with fake claude / fake endpoint, and the parity test (deselect with -m 'not slow')",
|
|
58
|
+
"regen: durable evaluations of a regenerable unit (regen/units/*.toml); they must survive a rewrite of the implementation",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.black]
|
|
62
|
+
target-version = ["py313"]
|
|
63
|
+
|
|
64
|
+
[tool.isort]
|
|
65
|
+
profile = "black"
|
|
66
|
+
|
|
67
|
+
[tool.ruff]
|
|
68
|
+
line-length = 88
|
|
69
|
+
target-version = "py313"
|
|
70
|
+
|
|
71
|
+
[tool.coverage.run]
|
|
72
|
+
branch = true
|
|
73
|
+
source = ["src/spotregen"]
|
|
74
|
+
|
|
75
|
+
[tool.coverage.report]
|
|
76
|
+
fail_under = 85
|
|
77
|
+
|
|
78
|
+
[tool.ty.environment]
|
|
79
|
+
python-version = "3.13"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spotregen"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Regenerative Software tooling: check intent, evaluations and provenance of regenerable units, and drive blind regeneration"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "AGPL-3.0-or-later" }
|
|
7
|
+
classifiers = [
|
|
8
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
9
|
+
"Programming Language :: Python :: 3",
|
|
10
|
+
"Programming Language :: Python :: 3.13",
|
|
11
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
12
|
+
]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "bartzbeielstein", email = "32470350+bartzbeielstein@users.noreply.github.com" }
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.13"
|
|
17
|
+
# Zero runtime dependencies is a contract (ADR 0001): `make guard` fails if this
|
|
18
|
+
# list is ever non-empty. The tool uses the standard library only.
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Documentation = "https://advm1.gm.fh-koeln.de/~bartz/spotregen/"
|
|
23
|
+
Repository = "https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen"
|
|
24
|
+
Issues = "https://git-ce.rwth-aachen.de/thomas.bartz-beielstein/spotregen/-/issues"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
spotregen = "spotregen.cli:main"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.9.18"]
|
|
31
|
+
build-backend = "uv_build"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
# Testing
|
|
36
|
+
"pytest>=9.0.2",
|
|
37
|
+
"pytest-cov>=6.0.0",
|
|
38
|
+
# Linting / formatting / types / licences
|
|
39
|
+
"black>=24.1.0",
|
|
40
|
+
"isort>=5.13.0",
|
|
41
|
+
"ruff>=0.3.0",
|
|
42
|
+
"ty>=0.0.29",
|
|
43
|
+
"reuse>=5.0.0",
|
|
44
|
+
"pre-commit>=4.0.0",
|
|
45
|
+
# Documentation (Quarto + quartodoc)
|
|
46
|
+
"quartodoc>=0.9.1",
|
|
47
|
+
"ipykernel>=7.1.0",
|
|
48
|
+
"jupyter>=1.1.1",
|
|
49
|
+
"nbformat>=5.10.4",
|
|
50
|
+
"pyyaml>=6.0",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = ["test_*.py"]
|
|
56
|
+
python_classes = ["Test*"]
|
|
57
|
+
python_functions = ["test_*"]
|
|
58
|
+
markers = [
|
|
59
|
+
"slow: end-to-end tests with fake claude / fake endpoint, and the parity test (deselect with -m 'not slow')",
|
|
60
|
+
"regen: durable evaluations of a regenerable unit (regen/units/*.toml); they must survive a rewrite of the implementation",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.black]
|
|
64
|
+
target-version = ["py313"]
|
|
65
|
+
|
|
66
|
+
[tool.isort]
|
|
67
|
+
profile = "black"
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 88
|
|
71
|
+
target-version = "py313"
|
|
72
|
+
|
|
73
|
+
[tool.coverage.run]
|
|
74
|
+
branch = true
|
|
75
|
+
source = ["src/spotregen"]
|
|
76
|
+
|
|
77
|
+
[tool.coverage.report]
|
|
78
|
+
fail_under = 85
|
|
79
|
+
|
|
80
|
+
[tool.ty.environment]
|
|
81
|
+
python-version = "3.13"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 bartzbeielstein
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
"""spotregen: Regenerative Software tooling (stdlib only).
|
|
5
|
+
|
|
6
|
+
Chad Fowler's *Regenerative Software* treats code as disposable and the system's
|
|
7
|
+
knowledge as the asset. This package reads that knowledge from the tracked
|
|
8
|
+
``regen/`` tree of a repository and checks it against the source tree. It uses the
|
|
9
|
+
standard library only and never imports the modules it describes.
|
|
10
|
+
|
|
11
|
+
The command line is ``spotregen [--root DIR] <command>`` (see `spotregen.cli.main`);
|
|
12
|
+
the functions below are the programmatic interface. The drivers that regenerate a
|
|
13
|
+
unit with an LLM live in `spotregen.drivers` and are not imported here.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
17
|
+
|
|
18
|
+
from spotregen.checks import (
|
|
19
|
+
Finding,
|
|
20
|
+
check_architecture,
|
|
21
|
+
check_decisions,
|
|
22
|
+
check_unit,
|
|
23
|
+
run_checks,
|
|
24
|
+
)
|
|
25
|
+
from spotregen.compact import compaction_report
|
|
26
|
+
from spotregen.context import build_context, public_contract
|
|
27
|
+
from spotregen.reports import deletion_report, status_table
|
|
28
|
+
from spotregen.sample import make_sample_repo
|
|
29
|
+
from spotregen.scaffold import scaffold_unit
|
|
30
|
+
from spotregen.scan import find_importers
|
|
31
|
+
from spotregen.spec import (
|
|
32
|
+
Alternative,
|
|
33
|
+
Architecture,
|
|
34
|
+
Consumer,
|
|
35
|
+
Decision,
|
|
36
|
+
IntentItem,
|
|
37
|
+
RegenSpec,
|
|
38
|
+
SpecError,
|
|
39
|
+
Unit,
|
|
40
|
+
find_repo_root,
|
|
41
|
+
load_architecture,
|
|
42
|
+
load_decision,
|
|
43
|
+
load_spec,
|
|
44
|
+
load_unit,
|
|
45
|
+
resolve_unit,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
__version__ = version("spotregen")
|
|
50
|
+
except PackageNotFoundError: # pragma: no cover - running from an unbuilt tree
|
|
51
|
+
__version__ = "0+unknown"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"Alternative",
|
|
55
|
+
"Architecture",
|
|
56
|
+
"Consumer",
|
|
57
|
+
"Decision",
|
|
58
|
+
"Finding",
|
|
59
|
+
"IntentItem",
|
|
60
|
+
"RegenSpec",
|
|
61
|
+
"SpecError",
|
|
62
|
+
"Unit",
|
|
63
|
+
"__version__",
|
|
64
|
+
"build_context",
|
|
65
|
+
"check_architecture",
|
|
66
|
+
"check_decisions",
|
|
67
|
+
"check_unit",
|
|
68
|
+
"compaction_report",
|
|
69
|
+
"deletion_report",
|
|
70
|
+
"find_importers",
|
|
71
|
+
"find_repo_root",
|
|
72
|
+
"load_architecture",
|
|
73
|
+
"load_decision",
|
|
74
|
+
"load_spec",
|
|
75
|
+
"load_unit",
|
|
76
|
+
"make_sample_repo",
|
|
77
|
+
"public_contract",
|
|
78
|
+
"resolve_unit",
|
|
79
|
+
"run_checks",
|
|
80
|
+
"scaffold_unit",
|
|
81
|
+
"status_table",
|
|
82
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 bartzbeielstein
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
"""Checks for the external tools the drivers depend on (standard library only).
|
|
5
|
+
|
|
6
|
+
The drivers call ``claude``, ``git`` and ``lsof``, or talk to a local model server.
|
|
7
|
+
They fail early and clearly when one of these is missing: `ExternalToolError` for a
|
|
8
|
+
missing executable or an unreachable endpoint, `ConfigurationError` for an option the
|
|
9
|
+
drivers refuse. The command line maps both to exit code 2.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import shutil
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ConfigurationError",
|
|
19
|
+
"ExternalToolError",
|
|
20
|
+
"probe_endpoint",
|
|
21
|
+
"require_executable",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExternalToolError(RuntimeError):
|
|
26
|
+
"""A required executable is not installed or an endpoint is not reachable.
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
```{python}
|
|
30
|
+
from spotregen._tools import ExternalToolError, require_executable
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
require_executable("no-such-tool-xyz", "install it first")
|
|
34
|
+
except ExternalToolError as exc:
|
|
35
|
+
print(exc)
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfigurationError(ValueError):
|
|
41
|
+
"""A driver option is refused, for example a file outside the repository.
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
```{python}
|
|
45
|
+
from spotregen._tools import ConfigurationError
|
|
46
|
+
|
|
47
|
+
print(issubclass(ConfigurationError, ValueError))
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def require_executable(name: str, hint: str = "") -> str:
|
|
53
|
+
"""Find an executable on ``PATH`` or fail with a clear message.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: Executable name, for example ``"claude"``.
|
|
57
|
+
hint: How to get it, appended to the error message. Defaults to ``""``.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
str: The absolute path of the executable.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ExternalToolError: If the executable is not on ``PATH``.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
```{python}
|
|
67
|
+
from spotregen._tools import require_executable
|
|
68
|
+
|
|
69
|
+
print(require_executable("sh").endswith("sh"))
|
|
70
|
+
```
|
|
71
|
+
"""
|
|
72
|
+
found = shutil.which(name)
|
|
73
|
+
if found is None:
|
|
74
|
+
suffix = f" ({hint})" if hint else ""
|
|
75
|
+
raise ExternalToolError(f"'{name}' was not found on PATH{suffix}")
|
|
76
|
+
return found
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def probe_endpoint(url: str, timeout: float = 5.0) -> list[str]:
|
|
80
|
+
"""Ask an OpenAI-compatible server which models it serves (``GET /v1/models``).
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
url: Base URL of the server, for example ``"http://127.0.0.1:1234"``.
|
|
84
|
+
timeout: Seconds to wait. Defaults to 5.0.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
list[str]: The model identifiers the server lists.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ExternalToolError: If the server cannot be reached or does not answer with the
|
|
91
|
+
expected JSON.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
```{python}
|
|
95
|
+
from spotregen._tools import ExternalToolError, probe_endpoint
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
probe_endpoint("http://127.0.0.1:9", timeout=1.0)
|
|
99
|
+
except ExternalToolError as exc:
|
|
100
|
+
print("unreachable:", "127.0.0.1:9" in str(exc))
|
|
101
|
+
```
|
|
102
|
+
"""
|
|
103
|
+
import urllib.error # imported here: the command line imports this module
|
|
104
|
+
import urllib.request # for its exceptions and must not pay for urllib
|
|
105
|
+
|
|
106
|
+
target = url.rstrip("/") + "/v1/models"
|
|
107
|
+
try:
|
|
108
|
+
with urllib.request.urlopen(target, timeout=timeout) as response:
|
|
109
|
+
payload = json.load(response)
|
|
110
|
+
return [str(item["id"]) for item in payload["data"]]
|
|
111
|
+
except (OSError, urllib.error.URLError, ValueError, KeyError, TypeError) as exc:
|
|
112
|
+
raise ExternalToolError(
|
|
113
|
+
f"cannot reach a model server at {target}: {exc}. "
|
|
114
|
+
"Start LM Studio's local server (or pass --endpoint)."
|
|
115
|
+
) from exc
|