zenthrix 0.2.0__tar.gz → 0.2.1__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 (38) hide show
  1. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/workflows/ci.yml +3 -1
  2. {zenthrix-0.2.0 → zenthrix-0.2.1}/CHANGELOG.md +14 -0
  3. {zenthrix-0.2.0 → zenthrix-0.2.1}/PKG-INFO +23 -1
  4. {zenthrix-0.2.0 → zenthrix-0.2.1}/README.md +22 -0
  5. {zenthrix-0.2.0 → zenthrix-0.2.1}/pyproject.toml +2 -2
  6. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/__init__.py +1 -1
  7. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/cli.py +44 -16
  8. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/metadata.py +7 -1
  9. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/settings.py +2 -2
  10. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_cli.py +49 -0
  11. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_metadata.py +2 -0
  12. {zenthrix-0.2.0 → zenthrix-0.2.1}/uv.lock +7 -1
  13. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/CODEOWNERS +0 -0
  14. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  17. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/SECURITY.md +0 -0
  18. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/SUPPORT.md +0 -0
  19. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/dependabot.yml +0 -0
  20. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/pull_request_template.md +0 -0
  21. {zenthrix-0.2.0 → zenthrix-0.2.1}/.github/workflows/release.yml +0 -0
  22. {zenthrix-0.2.0 → zenthrix-0.2.1}/.gitignore +0 -0
  23. {zenthrix-0.2.0 → zenthrix-0.2.1}/CONTRIBUTING.md +0 -0
  24. {zenthrix-0.2.0 → zenthrix-0.2.1}/LICENSE +0 -0
  25. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/adapters/__init__.py +0 -0
  26. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/adapters/gguf_loader.py +0 -0
  27. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/adapters/onnx_loader.py +0 -0
  28. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/adapters/pytorch_loader.py +0 -0
  29. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/adapters/safetensors_loader.py +0 -0
  30. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/config.py +0 -0
  31. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/engine.py +0 -0
  32. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/exceptions.py +0 -0
  33. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/runtime.py +0 -0
  34. {zenthrix-0.2.0 → zenthrix-0.2.1}/python/zenthrix/validation.py +0 -0
  35. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_engine.py +0 -0
  36. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_runtime.py +0 -0
  37. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_settings.py +0 -0
  38. {zenthrix-0.2.0 → zenthrix-0.2.1}/tests/test_validation.py +0 -0
@@ -9,6 +9,7 @@ jobs:
9
9
  test:
10
10
  runs-on: ubuntu-latest
11
11
  strategy:
12
+ fail-fast: false
12
13
  matrix:
13
14
  python-version: ["3.10", "3.11", "3.12", "3.13"]
14
15
  steps:
@@ -17,7 +18,8 @@ jobs:
17
18
  with:
18
19
  python-version: ${{ matrix.python-version }}
19
20
  - run: python -m pip install --upgrade pip
20
- - run: python -m pip install pytest ruff mypy build
21
+ - run: python -m pip install pytest ruff mypy build tomli
22
+ - run: python -m pip install -e .
21
23
  - run: python -m ruff check .
22
24
  - run: python -m mypy python
23
25
  - run: python -m pytest
@@ -5,6 +5,19 @@ All notable changes to Zenthrix are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [0.2.1] - 2026-11-04
9
+
10
+ ### Added
11
+
12
+ - Added JSON output for `zenthrix plugins` with runtime names and adapter paths.
13
+ - Added project configuration documentation and compile defaults from
14
+ `zenthrix.toml`, including clear errors for malformed configuration.
15
+ - GGUF inspection now reports the tensor count stored in the file header.
16
+
17
+ ### Changed
18
+
19
+ - Bumped the package and public API version to `0.2.1`.
20
+
8
21
  ## [0.2.0] - 2026-09-23
9
22
 
10
23
  ### Added
@@ -83,3 +96,4 @@ and this project follows [Semantic Versioning](https://semver.org/).
83
96
  [0.1.2]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.2
84
97
  [0.1.3]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.1.3
85
98
  [0.2.0]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.2.0
99
+ [0.2.1]: https://github.com/withbrian-technologies/zenthrix/releases/tag/v0.2.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: zenthrix
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Hardware-adaptive edge neural graph compiler frontend
5
5
  Author: WithBrian Technologies
6
6
  License: Apache License
@@ -353,6 +353,28 @@ failures are emitted as a JSON object with `error` and `message` fields:
353
353
  ```
354
354
 
355
355
  Successful inference uses `text`, `ttft_ms`, and `tokens_per_second` fields.
356
+ The `plugins --json` command emits a JSON array containing each installed
357
+ runtime's `name` and importable `adapter` path.
358
+
359
+ ### Project Configuration
360
+
361
+ Create `zenthrix.toml` in the project directory to provide compile and run
362
+ defaults. CLI arguments override values from the file:
363
+
364
+ ```toml
365
+ [compile]
366
+ model_format = "onnx"
367
+ target = "auto"
368
+ quantization = "int4"
369
+ output = "./build/model.zx"
370
+
371
+ [run]
372
+ max_tokens = 256
373
+ temperature = 0.2
374
+ ```
375
+
376
+ The configuration file is discovered in the current directory or its parents.
377
+ Malformed configuration is reported as a CLI error.
356
378
 
357
379
  ## Supported Target Architectures
358
380
 
@@ -138,6 +138,28 @@ failures are emitted as a JSON object with `error` and `message` fields:
138
138
  ```
139
139
 
140
140
  Successful inference uses `text`, `ttft_ms`, and `tokens_per_second` fields.
141
+ The `plugins --json` command emits a JSON array containing each installed
142
+ runtime's `name` and importable `adapter` path.
143
+
144
+ ### Project Configuration
145
+
146
+ Create `zenthrix.toml` in the project directory to provide compile and run
147
+ defaults. CLI arguments override values from the file:
148
+
149
+ ```toml
150
+ [compile]
151
+ model_format = "onnx"
152
+ target = "auto"
153
+ quantization = "int4"
154
+ output = "./build/model.zx"
155
+
156
+ [run]
157
+ max_tokens = 256
158
+ temperature = 0.2
159
+ ```
160
+
161
+ The configuration file is discovered in the current directory or its parents.
162
+ Malformed configuration is reported as a CLI error.
141
163
 
142
164
  ## Supported Target Architectures
143
165
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "zenthrix"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Hardware-adaptive edge neural graph compiler frontend"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -26,6 +26,7 @@ dev = [
26
26
  "mypy",
27
27
  "pytest",
28
28
  "ruff",
29
+ "tomli",
29
30
  ]
30
31
 
31
32
  [project.scripts]
@@ -47,6 +48,5 @@ target-version = "py310"
47
48
  select = ["E", "F", "I", "UP"]
48
49
 
49
50
  [tool.mypy]
50
- python_version = "3.10"
51
51
  strict = true
52
52
  mypy_path = "python"
@@ -4,7 +4,7 @@ from .engine import Engine, InferenceResult
4
4
  from .exceptions import EngineUnavailableError, InputValidationError, ZenthrixError
5
5
  from .metadata import ModelMetadata, TensorInfo
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.2.1"
8
8
 
9
9
  __all__ = [
10
10
  "Engine",
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
 
13
13
  from . import __version__
14
14
  from .engine import Engine
15
- from .exceptions import EngineUnavailableError, ZenthrixError
15
+ from .exceptions import EngineUnavailableError, InputValidationError, ZenthrixError
16
16
  from .validation import (
17
17
  validate_compile_options,
18
18
  validate_compiled_model_path,
@@ -28,10 +28,10 @@ def build_parser() -> argparse.ArgumentParser:
28
28
 
29
29
  compile_parser = commands.add_parser("compile", help="Compile a model")
30
30
  compile_parser.add_argument("--model", required=True)
31
- compile_parser.add_argument("--format", required=True, dest="model_format")
32
- compile_parser.add_argument("--target", default="auto")
31
+ compile_parser.add_argument("--format", default=None, dest="model_format")
32
+ compile_parser.add_argument("--target", default=None)
33
33
  compile_parser.add_argument("--quantization", default=None)
34
- compile_parser.add_argument("--output", required=True)
34
+ compile_parser.add_argument("--output", default=None)
35
35
  compile_parser.add_argument("--json", action="store_true")
36
36
 
37
37
  inspect_parser = commands.add_parser(
@@ -48,14 +48,29 @@ def build_parser() -> argparse.ArgumentParser:
48
48
  run_parser.add_argument("--temperature", type=float, default=None)
49
49
  run_parser.add_argument("--json", action="store_true")
50
50
 
51
- commands.add_parser("plugins", help="List installed runtime plugins")
51
+ plugins_parser = commands.add_parser(
52
+ "plugins", help="List installed runtime plugins"
53
+ )
54
+ plugins_parser.add_argument("--json", action="store_true")
52
55
 
53
56
  return parser
54
57
 
55
58
 
56
59
  def _compile(args: argparse.Namespace) -> int:
60
+ if args.model_format is None:
61
+ raise InputValidationError(
62
+ "compile requires --format or model_format in zenthrix.toml"
63
+ )
64
+ if args.output is None:
65
+ raise InputValidationError(
66
+ "compile requires --output or output in zenthrix.toml"
67
+ )
57
68
  validate_model_input(args.model, args.model_format)
58
- validate_compile_options(args.target, args.quantization, args.output)
69
+ validate_compile_options(
70
+ args.target if args.target is not None else "auto",
71
+ args.quantization,
72
+ args.output,
73
+ )
59
74
  raise ZenthrixError(
60
75
  "The native compiler engine is not installed. "
61
76
  "The public frontend cannot produce a .zx binary yet."
@@ -101,8 +116,8 @@ def _print_metadata(meta: ModelMetadata) -> None:
101
116
  lines.append(f" Est. memory: {meta.estimated_memory_mb:.1f} MB")
102
117
  if meta.architecture:
103
118
  lines.append(f" Architecture: {meta.architecture}")
104
- if meta.tensors:
105
- lines.append(f" Tensors: {len(meta.tensors)}")
119
+ if meta.tensor_count is not None:
120
+ lines.append(f" Tensors: {meta.tensor_count}")
106
121
  if meta.extra:
107
122
  for key, value in sorted(meta.extra.items()):
108
123
  lines.append(f" {key}: {value}")
@@ -142,10 +157,26 @@ def _run(args: argparse.Namespace) -> int:
142
157
  return 0
143
158
 
144
159
 
145
- def _plugins(_args: argparse.Namespace) -> int:
160
+ def _plugins(args: argparse.Namespace) -> int:
146
161
  from .runtime import discover_runtimes
147
162
 
148
163
  runtimes = discover_runtimes()
164
+ if args.json:
165
+ print(
166
+ json.dumps(
167
+ [
168
+ {
169
+ "name": name,
170
+ "adapter": (
171
+ f"{adapter_cls.__module__}:{adapter_cls.__qualname__}"
172
+ ),
173
+ }
174
+ for name, adapter_cls in runtimes
175
+ ]
176
+ )
177
+ )
178
+ return 0
179
+
149
180
  if not runtimes:
150
181
  print("No runtime plugins installed.")
151
182
  print()
@@ -164,10 +195,10 @@ def main(argv: list[str] | None = None) -> int:
164
195
  """Run the CLI and return a process exit code."""
165
196
  args = build_parser().parse_args(argv)
166
197
 
167
- # Merge config-file defaults for compile/run when available
168
- _apply_config_defaults(args)
169
-
170
198
  try:
199
+ # Merge config-file defaults for compile/run when available
200
+ _apply_config_defaults(args)
201
+
171
202
  if args.command == "compile":
172
203
  return _compile(args)
173
204
  if args.command == "inspect":
@@ -203,10 +234,7 @@ def _apply_config_defaults(args: argparse.Namespace) -> None:
203
234
  if config_path is None:
204
235
  return
205
236
 
206
- try:
207
- config = load_config(config_path)
208
- except Exception:
209
- return # silently skip broken config during CLI execution
237
+ config = load_config(config_path)
210
238
 
211
239
  command = getattr(args, "command", None)
212
240
  section = config.get(command, {}) if command else {}
@@ -41,6 +41,7 @@ class ModelMetadata:
41
41
  estimated_memory_mb: float = 0.0
42
42
  architecture: str = ""
43
43
  extra: dict[str, str] = field(default_factory=dict)
44
+ tensor_count: int | None = None
44
45
 
45
46
  def to_dict(self) -> dict[str, Any]:
46
47
  """Serialize to a JSON-compatible dictionary."""
@@ -51,7 +52,11 @@ class ModelMetadata:
51
52
  "parameter_count": self.parameter_count,
52
53
  "estimated_memory_mb": round(self.estimated_memory_mb, 2),
53
54
  "architecture": self.architecture,
54
- "tensor_count": len(self.tensors),
55
+ "tensor_count": (
56
+ len(self.tensors)
57
+ if self.tensor_count is None
58
+ else self.tensor_count
59
+ ),
55
60
  "extra": dict(self.extra),
56
61
  }
57
62
 
@@ -246,6 +251,7 @@ def _parse_gguf_header(path: Path) -> ModelMetadata:
246
251
  architecture=architecture,
247
252
  extra=extra,
248
253
  tensors=(),
254
+ tensor_count=tensor_count,
249
255
  )
250
256
 
251
257
 
@@ -19,7 +19,7 @@ if sys.version_info >= (3, 11):
19
19
  import tomllib
20
20
  else:
21
21
  try:
22
- import tomli as tomllib # type: ignore[import-not-found]
22
+ import tomli as tomllib
23
23
  except ModuleNotFoundError as exc:
24
24
  raise ImportError(
25
25
  "zenthrix requires the 'tomli' package on Python < 3.11. "
@@ -62,7 +62,7 @@ def load_config(path: Path) -> dict[str, Any]:
62
62
  """
63
63
  try:
64
64
  with open(path, "rb") as fh:
65
- return tomllib.load(fh) # type: ignore[no-any-return]
65
+ return tomllib.load(fh)
66
66
  except Exception as exc:
67
67
  raise InputValidationError(
68
68
  f"Failed to parse {path}: {exc}"
@@ -81,6 +81,17 @@ def test_inspect_source_model_json_output(capsys, tmp_path: Path) -> None:
81
81
  assert payload["parameter_count"] == 12
82
82
 
83
83
 
84
+ def test_inspect_gguf_reports_tensor_count(capsys, tmp_path: Path) -> None:
85
+ import struct
86
+
87
+ model = tmp_path / "model.gguf"
88
+ model.write_bytes(struct.pack("<IIQQ", 0x46475547, 3, 7, 0))
89
+
90
+ assert main(["inspect", str(model), "--json"]) == 0
91
+ payload = json.loads(capsys.readouterr().out)
92
+ assert payload["tensor_count"] == 7
93
+
94
+
84
95
  def test_compile_reports_missing_native_engine(tmp_path: Path, capsys) -> None:
85
96
  model = tmp_path / "model.onnx"
86
97
  model.write_bytes(b"placeholder")
@@ -102,6 +113,39 @@ def test_compile_reports_missing_native_engine(tmp_path: Path, capsys) -> None:
102
113
  assert "native compiler engine" in capsys.readouterr().err
103
114
 
104
115
 
116
+ def test_compile_uses_config_defaults(
117
+ tmp_path: Path, monkeypatch, capsys
118
+ ) -> None:
119
+ model = tmp_path / "model.onnx"
120
+ model.write_bytes(b"placeholder")
121
+ (tmp_path / "zenthrix.toml").write_text(
122
+ "[compile]\n"
123
+ "model_format = 'onnx'\n"
124
+ "target = 'apple'\n"
125
+ "output = 'model.zx'\n"
126
+ )
127
+ monkeypatch.chdir(tmp_path)
128
+
129
+ assert main(["compile", "--model", str(model)]) == 2
130
+ assert "native compiler engine" in capsys.readouterr().err
131
+
132
+
133
+ def test_compile_requires_config_or_cli_values(tmp_path: Path, capsys) -> None:
134
+ model = tmp_path / "model.onnx"
135
+ model.write_bytes(b"placeholder")
136
+
137
+ assert main(["compile", "--model", str(model)]) == 2
138
+ assert "--format" in capsys.readouterr().err
139
+
140
+
141
+ def test_malformed_config_is_reported(tmp_path: Path, monkeypatch, capsys) -> None:
142
+ (tmp_path / "zenthrix.toml").write_text("[[[[invalid toml")
143
+ monkeypatch.chdir(tmp_path)
144
+
145
+ assert main(["plugins"]) == 2
146
+ assert "Failed to parse" in capsys.readouterr().err
147
+
148
+
105
149
  def test_compile_rejects_invalid_output_extension(tmp_path: Path, capsys) -> None:
106
150
  model = tmp_path / "model.onnx"
107
151
  model.write_bytes(b"placeholder")
@@ -127,3 +171,8 @@ def test_plugins_shows_no_plugins(capsys) -> None:
127
171
  assert main(["plugins"]) == 0
128
172
  output = capsys.readouterr().out
129
173
  assert "No runtime plugins installed" in output
174
+
175
+
176
+ def test_plugins_json_output(capsys) -> None:
177
+ assert main(["plugins", "--json"]) == 0
178
+ assert json.loads(capsys.readouterr().out) == []
@@ -120,12 +120,14 @@ class TestExtractGgufMetadata:
120
120
 
121
121
  assert meta.format == "gguf"
122
122
  assert meta.architecture == "llama"
123
+ assert meta.tensor_count == 5
123
124
  assert meta.file_size_bytes > 0
124
125
 
125
126
  def test_version_in_extra(self, tmp_path):
126
127
  model = _write_gguf(tmp_path / "model.gguf", version=3)
127
128
  meta = extract_metadata(model)
128
129
  assert meta.extra.get("version") == "3"
130
+ assert meta.to_dict()["tensor_count"] == 5
129
131
 
130
132
  def test_bad_magic_rejected(self, tmp_path):
131
133
  model = tmp_path / "bad.gguf"
@@ -487,8 +487,11 @@ wheels = [
487
487
 
488
488
  [[package]]
489
489
  name = "zenthrix"
490
- version = "0.1.3"
490
+ version = "0.2.0"
491
491
  source = { editable = "." }
492
+ dependencies = [
493
+ { name = "tomli", marker = "python_full_version < '3.11'" },
494
+ ]
492
495
 
493
496
  [package.dev-dependencies]
494
497
  dev = [
@@ -496,9 +499,11 @@ dev = [
496
499
  { name = "mypy" },
497
500
  { name = "pytest" },
498
501
  { name = "ruff" },
502
+ { name = "tomli" },
499
503
  ]
500
504
 
501
505
  [package.metadata]
506
+ requires-dist = [{ name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }]
502
507
 
503
508
  [package.metadata.requires-dev]
504
509
  dev = [
@@ -506,6 +511,7 @@ dev = [
506
511
  { name = "mypy" },
507
512
  { name = "pytest" },
508
513
  { name = "ruff" },
514
+ { name = "tomli" },
509
515
  ]
510
516
 
511
517
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes