heinzel-example 0.4.0__py3-none-any.whl
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.
- heinzel_example/__init__.py +71 -0
- heinzel_example/_models.py +25 -0
- heinzel_example/_service.py +35 -0
- heinzel_example/hello/__init__.py +20 -0
- heinzel_example/py.typed +0 -0
- heinzel_example/views/index.js +10 -0
- heinzel_example-0.4.0.dist-info/METADATA +59 -0
- heinzel_example-0.4.0.dist-info/RECORD +11 -0
- heinzel_example-0.4.0.dist-info/WHEEL +4 -0
- heinzel_example-0.4.0.dist-info/entry_points.txt +2 -0
- heinzel_example-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Show how a Heinzelmännchen plugin is built.
|
|
2
|
+
|
|
3
|
+
A Heinzelmännchen plugin, discovered through the `heinzel.modules` entry point in pyproject.toml. Every package
|
|
4
|
+
below it that declares `module = Module(...)` becomes a submodule (see `hello/`).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from heinzel import MetricWidget, Module, Navigate, NoticeKind, Notifications, StatusItem
|
|
10
|
+
|
|
11
|
+
from heinzel_example._models import ExampleAbout, ExampleSettings
|
|
12
|
+
from heinzel_example._service import ExampleService
|
|
13
|
+
|
|
14
|
+
module = Module("example", "Show how a Heinzelmännchen plugin is built.")
|
|
15
|
+
module.settings(ExampleSettings)
|
|
16
|
+
module.service(ExampleService)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@module.operation
|
|
20
|
+
def info(service: ExampleService) -> ExampleAbout:
|
|
21
|
+
"""Show this plugin's version, the Heinzelmännchen it runs on and the modules it brings."""
|
|
22
|
+
return service.about()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@module.info
|
|
26
|
+
def system_info(service: ExampleService) -> ExampleAbout:
|
|
27
|
+
"""Contribute this plugin's details to `hz system info`."""
|
|
28
|
+
return service.about()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["ExampleAbout", "ExampleService", "ExampleSettings", "module"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@module.operation
|
|
35
|
+
def sample() -> int:
|
|
36
|
+
"""Read a tiny value used by every example representation."""
|
|
37
|
+
return 42
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@module.operation(read_only=False)
|
|
41
|
+
def notify(notifications: Notifications) -> str:
|
|
42
|
+
"""Send an example notice after the ordinary operation confirmation."""
|
|
43
|
+
notice = notifications.emit(
|
|
44
|
+
NoticeKind.PLUGIN, "Example is ready", route="/", command="hz example sample", key="example:ready"
|
|
45
|
+
)
|
|
46
|
+
return notice.title if notice else "Notifications are disabled"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@module.status(source="sample", order=700, slots={"footer": False, "sidebar": True})
|
|
50
|
+
def status(value: int) -> StatusItem:
|
|
51
|
+
"""Render checked data through the same host slots as built-in modules."""
|
|
52
|
+
return StatusItem(text=f"Example: {value}", icon="star", effect=Navigate(path="/?panel=example:inspector"))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@module.widget(source="sample", order=700)
|
|
56
|
+
def today(value: int) -> MetricWidget:
|
|
57
|
+
"""Contribute a Today widget without a core change."""
|
|
58
|
+
return MetricWidget(label="Example value", value=str(value))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
module.action("inspect", label="Example", icon="star", effect=Navigate(path="/?panel=example:inspector"), order=700)
|
|
62
|
+
module.nav(title="Example", icon="star", sidebar=Navigate(path="/?panel=example:inspector"), order=700)
|
|
63
|
+
module.view(
|
|
64
|
+
Path(__file__).parent / "views",
|
|
65
|
+
abi=1,
|
|
66
|
+
name="inspector",
|
|
67
|
+
source="sample",
|
|
68
|
+
title="Example",
|
|
69
|
+
fallback="Run hz example sample to read the value.",
|
|
70
|
+
order=700,
|
|
71
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Models returned by the plugin's operations."""
|
|
2
|
+
|
|
3
|
+
from heinzel import ModuleSettings
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExampleAbout(BaseModel):
|
|
8
|
+
"""What this plugin is and where it runs."""
|
|
9
|
+
|
|
10
|
+
plugin: str = Field(description="Distribution name of the plugin.")
|
|
11
|
+
version: str = Field(description="Installed version of the plugin.")
|
|
12
|
+
heinzel_version: str = Field(description="Version of the Heinzelmännchen it runs in.")
|
|
13
|
+
modules: list[str] = Field(description="Qualified names of the modules the plugin provides.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExampleGreeting(BaseModel):
|
|
17
|
+
"""A greeting."""
|
|
18
|
+
|
|
19
|
+
text: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExampleSettings(ModuleSettings):
|
|
23
|
+
"""Settings of the plugin: its table in the `hz` config files, or `HZ_<PLUGIN>__<FIELD>` variables."""
|
|
24
|
+
|
|
25
|
+
greeting: str = Field(default="Hello", description="How `hello` greets.")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""The plugin's logic, independent of any head."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from heinzel import Kernel, __version__
|
|
6
|
+
|
|
7
|
+
from heinzel_example._models import ExampleAbout, ExampleGreeting, ExampleSettings
|
|
8
|
+
|
|
9
|
+
DISTRIBUTION = "heinzel-example"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExampleService:
|
|
13
|
+
"""Everything the example modules do; operations stay thin and call into it."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, kernel: Kernel, settings: ExampleSettings) -> None:
|
|
16
|
+
"""Create the service; the kernel and the plugin's settings are injected."""
|
|
17
|
+
self._kernel = kernel
|
|
18
|
+
self._settings = settings
|
|
19
|
+
|
|
20
|
+
def about(self) -> ExampleAbout:
|
|
21
|
+
"""Describe the plugin and the Heinzelmännchen installation it runs in."""
|
|
22
|
+
try:
|
|
23
|
+
installed = version(DISTRIBUTION)
|
|
24
|
+
except PackageNotFoundError: # pragma: no cover - only when running from an uninstalled source tree
|
|
25
|
+
installed = "0.0.0"
|
|
26
|
+
return ExampleAbout(
|
|
27
|
+
plugin=DISTRIBUTION,
|
|
28
|
+
version=installed,
|
|
29
|
+
heinzel_version=__version__,
|
|
30
|
+
modules=[r.qualname for r in self._kernel.modules if r.origin.distribution == DISTRIBUTION],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def greet(self, name: str) -> ExampleGreeting:
|
|
34
|
+
"""Greet someone by name, the way the settings say."""
|
|
35
|
+
return ExampleGreeting(text=f"{self._settings.greeting}, {name}!")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""A submodule. Its only operation shares its name, so the CLI collapses it to `hz example hello`."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from heinzel import Module
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from heinzel_example._models import ExampleGreeting
|
|
9
|
+
from heinzel_example._service import ExampleService
|
|
10
|
+
|
|
11
|
+
module = Module("hello", "Greet someone.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@module.operation
|
|
15
|
+
def hello(
|
|
16
|
+
service: ExampleService,
|
|
17
|
+
name: Annotated[str, Field(description="Who to greet.")] = "world",
|
|
18
|
+
) -> ExampleGreeting:
|
|
19
|
+
"""Greet someone by name."""
|
|
20
|
+
return service.greet(name)
|
heinzel_example/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// This small view needs no bundler. A specialist view can use the same versioned SDK with its own build.
|
|
2
|
+
export default function createView(sdk) {
|
|
3
|
+
if (sdk.abi !== 1) throw new Error("Example requires plugin SDK 1");
|
|
4
|
+
return function Inspector({data, loading, onClose}) {
|
|
5
|
+
return sdk.React.createElement("section", {"aria-label": "Example", className: "p-4"},
|
|
6
|
+
sdk.React.createElement("h2", null, "Example"),
|
|
7
|
+
sdk.React.createElement("p", {role: "status"}, loading ? "Loading…" : `The value is ${data}.`),
|
|
8
|
+
sdk.React.createElement(sdk.Button, {onClick: onClose}, "Close example"));
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: heinzel-example
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Show how a Heinzelmännchen plugin is built.
|
|
5
|
+
Project-URL: Source, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/tree/main/plugins/heinzel-example
|
|
6
|
+
Project-URL: Changelog, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/blob/main/plugins/heinzel-example/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/issues
|
|
8
|
+
Author-email: Helmut Hoffer von Ankershoffen <helmuthva@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: heinzel,heinzel-plugin,heinzelmaennchen
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: heinzel<0.10,>=0.9.1
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# heinzel-example
|
|
23
|
+
|
|
24
|
+
Show how a Heinzelmännchen plugin is built: one module with an operation, settings, a health check and a submodule.
|
|
25
|
+
|
|
26
|
+
A plugin for [Heinzelmännchen](https://github.com/helmut-hoffer-von-ankershoffen/heinzel), the open-source, AI-driven
|
|
27
|
+
operating system for product development teams. It lives in
|
|
28
|
+
[heinzel-plugins](https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins). Its earlier history is preserved
|
|
29
|
+
in heinzel-contrib and the archived heinzel-example repository.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
With Heinzelmännchen installed (`uv tool install git+https://github.com/helmut-hoffer-von-ankershoffen/heinzel`):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
hz system plugin add "heinzel-example @ git+https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins#subdirectory=plugins/heinzel-example"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Running `hz system plugin add` again upgrades; `hz system plugin remove heinzel-example` removes it.
|
|
40
|
+
|
|
41
|
+
## Use
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
hz example info # this plugin, its version and the Heinzelmännchen it runs on
|
|
45
|
+
hz example hello --name Ada # a submodule collapsed into a command
|
|
46
|
+
hz example info --json # every command speaks JSON, for scripts and agents
|
|
47
|
+
hz system info # includes a section contributed by this plugin
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Develop
|
|
51
|
+
|
|
52
|
+
From the repository root: `make check-fast NAME=example` runs this plugin's edit loop; `make check NAME=example`
|
|
53
|
+
runs its full checks. `make build NAME=example` and `make acceptance NAME=example` verify its installed artifact.
|
|
54
|
+
The modules live in
|
|
55
|
+
`src/heinzel_example/`; read the root [AGENTS.md](../../AGENTS.md) before changing them.
|
|
56
|
+
|
|
57
|
+
Development and release commands: [repository guide](../../README.md).
|
|
58
|
+
|
|
59
|
+
Migration provenance: heinzel-contrib, before the 0.9 plugin-boundary migration.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
heinzel_example/__init__.py,sha256=Jj_OvwKJ2pfhwXefD5O3EGAFMFfLQwQ0hJlPGexeX04,2502
|
|
2
|
+
heinzel_example/_models.py,sha256=IM1y8LwSGu1iAZir55mol1VhkNXbDHB53ty3Tt-8cUQ,842
|
|
3
|
+
heinzel_example/_service.py,sha256=jmqDTiVgOAiZJVANCGwdqW4dRFz_4_-qFHll1pYdDu4,1366
|
|
4
|
+
heinzel_example/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
heinzel_example/hello/__init__.py,sha256=SIOmKrv6S5FXIjboGMMI8V4uFfBI74zhZrz2UqnsU0g,554
|
|
6
|
+
heinzel_example/views/index.js,sha256=frB2T8gchfvbxf37bRqkNaS7HFP343UcKiowzZhb8zc,612
|
|
7
|
+
heinzel_example-0.4.0.dist-info/METADATA,sha256=RTI-T2GMIKVDlC5n236dkICPSsJtwCa2l2nLiro0lzg,2713
|
|
8
|
+
heinzel_example-0.4.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
9
|
+
heinzel_example-0.4.0.dist-info/entry_points.txt,sha256=tOa-l6Z7Nu1zrehG7e_K5i8DtHCdPkz0yZ-Fie4vqfw,44
|
|
10
|
+
heinzel_example-0.4.0.dist-info/licenses/LICENSE,sha256=uAnloDezQUp6s7yo2vDvfNU_f6qLKss3jcU01jGz1cU,1087
|
|
11
|
+
heinzel_example-0.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Helmut Hoffer von Ankershoffen
|
|
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.
|