process-bigraph-lang 0.1.0__py3-none-win_amd64.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.
File without changes
@@ -0,0 +1,4 @@
1
+ # Ignore everything in this directory
2
+ *
3
+ # Except this file
4
+ !.gitignore
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: process_bigraph_lang/bin/cli-native.exe
3
+ size: 82848027 bytes
4
+ sha256: 12cb10ac6a84072036b1578eb07a35dcb175cdd61e71ca004afd1e7adeadb4a4
File without changes
@@ -0,0 +1,56 @@
1
+ """
2
+ The `.pblang` v2 language, Python side.
3
+
4
+ The v2 language is a tree-shaped, verifiable projection of a process-bigraph composite document
5
+ (see `docs/v2/design.md`). The grammar, the checker, the compiler and the decompiler are TypeScript (`dsl/`),
6
+ run here through `cli-native`; this package is the Python entry to them, plus what has to be Python:
7
+
8
+ - `compile_text` / `compile_file`: `.pblang` text -> parsed, checked -> a `{"schema", "state"}` document or a
9
+ CompositeSpec mapping (`CompileError` when the checker finds errors)
10
+ - `decompile`: a document or a CompositeSpec mapping -> canonical `.pblang` text plus sidecar assets
11
+ - `check_file` / `check_text`: the checker's findings (`Diagnostic`s)
12
+ - `describe_module` / `InterfaceRegistry`: interface declarations ("headers") from a package's classes, as the
13
+ JSON manifest the CLI reads; `render_interfaces` has the CLI print the `.pblang` header file
14
+ - `normalize_document` / `equivalent` / `diff_documents`: equivalence of two documents modulo representation
15
+
16
+ There is no Python model of a composite: a Python tool that builds composites targets the DSL text.
17
+ """
18
+
19
+ from process_bigraph_lang.v2.api import (
20
+ CompileError,
21
+ Compiled,
22
+ Printed,
23
+ compile_file,
24
+ compile_text,
25
+ decompile,
26
+ dumps,
27
+ loads,
28
+ read_json,
29
+ write_json,
30
+ )
31
+ from process_bigraph_lang.v2.check import Diagnostic, check_file, check_text
32
+ from process_bigraph_lang.v2.interfaces import InterfaceRegistry, describe_module, render_interfaces
33
+ from process_bigraph_lang.v2.normalize import deep_equal, diff_documents, equivalent, normalize_document
34
+
35
+ __all__ = [
36
+ "CompileError",
37
+ "Compiled",
38
+ "Diagnostic",
39
+ "InterfaceRegistry",
40
+ "Printed",
41
+ "check_file",
42
+ "check_text",
43
+ "compile_file",
44
+ "compile_text",
45
+ "decompile",
46
+ "deep_equal",
47
+ "describe_module",
48
+ "diff_documents",
49
+ "dumps",
50
+ "equivalent",
51
+ "loads",
52
+ "normalize_document",
53
+ "read_json",
54
+ "render_interfaces",
55
+ "write_json",
56
+ ]
@@ -0,0 +1,204 @@
1
+ """
2
+ `python -m process_bigraph_lang.v2 compile <file.pblang> [--interfaces m.json]... [--to document|spec] [-o out]`:
3
+ parse and check the text, then write the document (JSON) or the CompositeSpec (yaml); errors stop it.
4
+ `python -m process_bigraph_lang.v2 decompile <doc.json | spec.composite.yaml>`: a document as `.pblang` text.
5
+ `python -m process_bigraph_lang.v2 check <file.pblang | doc.json> [--interfaces m.json]...`: the checker's findings.
6
+ `python -m process_bigraph_lang.v2 diff <a.json> <b.json>`: explain how two documents differ after normalization.
7
+ `python -m process_bigraph_lang.v2 describe <module>...` / `describe-model <sbml>`: headers from code or SBML.
8
+
9
+ Documents are JSON (`bigraph_schema.json_codec` encoding, so numpy arrays survive) or CompositeSpec yaml.
10
+ Sidecar assets of `decompile` are written next to the output when `--out` is given, else listed on stderr.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from process_bigraph_lang.v2 import (
21
+ CompileError,
22
+ InterfaceRegistry,
23
+ compile_file,
24
+ decompile,
25
+ diff_documents,
26
+ equivalent,
27
+ loads,
28
+ render_interfaces,
29
+ )
30
+
31
+
32
+ def _load(path: Path) -> dict[str, Any]:
33
+ text = path.read_text(encoding="utf-8")
34
+ if path.suffix in (".yaml", ".yml"):
35
+ import yaml
36
+
37
+ loaded = yaml.safe_load(text)
38
+ else:
39
+ loaded = loads(text)
40
+ if not isinstance(loaded, dict):
41
+ raise SystemExit(f"{path}: not a document")
42
+ return loaded
43
+
44
+
45
+ def _manifests(args: argparse.Namespace) -> list[Path]:
46
+ return [Path(m) for m in getattr(args, "interfaces", None) or []]
47
+
48
+
49
+ def _decompile(args: argparse.Namespace) -> int:
50
+ path = Path(args.document)
51
+ printed = decompile(_load(path), name=path.stem.replace(".composite", ""), manifests=_manifests(args))
52
+ if args.out:
53
+ out = Path(args.out)
54
+ out.write_text(printed.text, encoding="utf-8")
55
+ for asset_name, content in printed.assets.items():
56
+ target = out.parent / asset_name
57
+ target.parent.mkdir(parents=True, exist_ok=True)
58
+ target.write_bytes(content)
59
+ print(f"wrote {out} and {len(printed.assets)} asset(s)", file=sys.stderr)
60
+ else:
61
+ sys.stdout.write(printed.text)
62
+ for asset_name in printed.assets:
63
+ print(f"asset (not written): {asset_name}", file=sys.stderr)
64
+ return 0
65
+
66
+
67
+ def _compile(args: argparse.Namespace) -> int:
68
+ try:
69
+ compiled = compile_file(args.document, manifests=_manifests(args), to=args.to, strict=args.strict)
70
+ except CompileError as error:
71
+ for diagnostic in error.diagnostics:
72
+ print(diagnostic, file=sys.stderr)
73
+ print("compile failed", file=sys.stderr)
74
+ return 1
75
+ for diagnostic in compiled.diagnostics:
76
+ print(diagnostic, file=sys.stderr)
77
+ if args.to == "spec":
78
+ import yaml
79
+
80
+ text = yaml.safe_dump(compiled.document, sort_keys=False)
81
+ else:
82
+ from process_bigraph_lang.v2 import dumps
83
+
84
+ text = dumps(compiled.document) + "\n"
85
+ if args.out:
86
+ Path(args.out).write_text(text, encoding="utf-8")
87
+ print(f"wrote {args.out}", file=sys.stderr)
88
+ else:
89
+ sys.stdout.write(text)
90
+ return 0
91
+
92
+
93
+ def _diff(args: argparse.Namespace) -> int:
94
+ import process_bigraph as pg # type: ignore[import-untyped]
95
+
96
+ core = pg.allocate_core()
97
+ a, b = _load(Path(args.a)), _load(Path(args.b))
98
+ if equivalent(a, b, core):
99
+ return 0
100
+ print(diff_documents(a, b, core))
101
+ return 1
102
+
103
+
104
+ def _describe(args: argparse.Namespace) -> int:
105
+ registry = InterfaceRegistry.from_modules(*args.modules)
106
+ if args.manifest:
107
+ registry.write_manifest(Path(args.manifest))
108
+ files = {
109
+ module: render_interfaces([d for d in registry.decls if d.module == module], module) for module in args.modules
110
+ }
111
+ if args.out and (len(files) > 1 or Path(args.out).is_dir()):
112
+ out_dir = Path(args.out)
113
+ out_dir.mkdir(parents=True, exist_ok=True)
114
+ for module, text in files.items():
115
+ (out_dir / f"{module}.pblang").write_text(text, encoding="utf-8")
116
+ print(f"wrote {len(files)} interface file(s) to {out_dir}", file=sys.stderr)
117
+ elif args.out:
118
+ Path(args.out).write_text(next(iter(files.values())), encoding="utf-8")
119
+ else:
120
+ sys.stdout.write("".join(files.values()))
121
+ return 0
122
+
123
+
124
+ def _check(args: argparse.Namespace) -> int:
125
+ from process_bigraph_lang.v2.check import check_file, check_text
126
+
127
+ source = Path(args.document)
128
+ manifests = _manifests(args)
129
+ if source.suffix in (".json", ".yaml", ".yml"): # a document: decompile it, then check the text
130
+ printed = decompile(_load(source), name=source.stem, manifests=manifests)
131
+ diagnostics = check_text(printed.text, manifests)
132
+ else:
133
+ diagnostics = check_file(source, manifests)
134
+ for diagnostic in diagnostics:
135
+ print(diagnostic)
136
+ errors = sum(1 for d in diagnostics if d.severity == "error")
137
+ print(f"{errors} error(s), {sum(1 for d in diagnostics if d.severity == 'warning')} warning(s)", file=sys.stderr)
138
+ return 1 if errors else 0
139
+
140
+
141
+ def _describe_model(args: argparse.Namespace) -> int:
142
+ import json
143
+
144
+ from process_bigraph_lang.v2.sbml import describe_sbml, print_model_header
145
+
146
+ header = describe_sbml(args.model)
147
+ if args.manifest:
148
+ Path(args.manifest).write_text(json.dumps(header.to_dict(), indent=2) + "\n", encoding="utf-8")
149
+ text = print_model_header(header)
150
+ if args.out:
151
+ Path(args.out).write_text(text, encoding="utf-8")
152
+ else:
153
+ sys.stdout.write(text)
154
+ return 0
155
+
156
+
157
+ def main(argv: list[str] | None = None) -> int:
158
+ parser = argparse.ArgumentParser(prog="python -m process_bigraph_lang.v2")
159
+ commands = parser.add_subparsers(dest="command", required=True)
160
+ compile_cmd = commands.add_parser("compile", help="parse and check a .pblang file, then write the document")
161
+ compile_cmd.add_argument("document", help="a .pblang file (file(...) references resolve next to it)")
162
+ compile_cmd.add_argument("--interfaces", action="append", help="a manifest.json (repeatable)")
163
+ compile_cmd.add_argument("--to", choices=["document", "spec"], default="document")
164
+ compile_cmd.add_argument("--strict", action="store_true", help="warnings fail the compile too")
165
+ compile_cmd.add_argument("-o", "--out", help="write the document (JSON) or CompositeSpec (yaml) here")
166
+ compile_cmd.set_defaults(run=_compile)
167
+ decompile_cmd = commands.add_parser(
168
+ "decompile", aliases=["print"], help="print a document or CompositeSpec as .pblang text"
169
+ )
170
+ decompile_cmd.add_argument("document")
171
+ decompile_cmd.add_argument("--out", help="write the text here (assets go next to it)")
172
+ decompile_cmd.add_argument(
173
+ "--interfaces", action="append", help="a manifest.json (repeatable); matching addresses print as imports"
174
+ )
175
+ decompile_cmd.set_defaults(run=_decompile)
176
+ describe_cmd = commands.add_parser("describe", help="generate interface declarations from Python modules")
177
+ describe_cmd.add_argument("modules", nargs="+", help="importable module names (run in the package's env)")
178
+ describe_cmd.add_argument("--manifest", help="write the JSON manifest here")
179
+ describe_cmd.add_argument(
180
+ "--out", help="the .pblang interface file (one module) or a directory (one file per module)"
181
+ )
182
+ describe_cmd.set_defaults(run=_describe)
183
+ model_cmd = commands.add_parser("describe-model", help="generate a model header from an SBML file")
184
+ model_cmd.add_argument("model", help="an SBML file")
185
+ model_cmd.add_argument("--manifest", help="write the JSON form here")
186
+ model_cmd.add_argument("--out", help="write the .pblang model header here")
187
+ model_cmd.set_defaults(run=_describe_model)
188
+ check_cmd = commands.add_parser("check", help="statically check a .pblang file or a document against manifests")
189
+ check_cmd.add_argument(
190
+ "document", help="a .pblang file, or a document (.json) / CompositeSpec (.yaml) to decompile and check"
191
+ )
192
+ check_cmd.add_argument("--interfaces", action="append", help="a manifest.json (repeatable)")
193
+ check_cmd.set_defaults(run=_check)
194
+ diff_cmd = commands.add_parser("diff", help="compare two documents after normalization (exit 1 if they differ)")
195
+ diff_cmd.add_argument("a")
196
+ diff_cmd.add_argument("b")
197
+ diff_cmd.set_defaults(run=_diff)
198
+ args = parser.parse_args(argv)
199
+ result: int = args.run(args)
200
+ return result
201
+
202
+
203
+ if __name__ == "__main__":
204
+ sys.exit(main())
@@ -0,0 +1,192 @@
1
+ """
2
+ The public Python API of v2: text in, checked documents out; documents in, text out. All of it runs in the DSL
3
+ CLI (`cli-native`, TypeScript): parsing, checking, compiling and decompiling. There is no Python model of a
4
+ composite, so a Python tool that builds composites targets the DSL text, where the result is checked, reviewed
5
+ and documented (docs/v2/design.md, section 5.3).
6
+
7
+ - `compile_text` / `compile_file`: `.pblang` text -> parse -> check -> a process-bigraph `{"schema", "state"}`
8
+ document or a CompositeSpec mapping. An error-level finding stops the compile (`CompileError` carries every
9
+ finding); warnings and infos come back on the result.
10
+ - `decompile`: a document or a CompositeSpec mapping -> canonical `.pblang` text plus sidecar assets.
11
+
12
+ Documents cross the CLI boundary as JSON in `bigraph_schema.json_codec`'s encoding (numpy arrays tagged), with
13
+ tuples tagged as `{"__tuple__": true, "data": [...]}` so that they survive too.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import tempfile
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+ from typing import Any, Literal
23
+
24
+ from bigraph_schema import json_codec # type: ignore[import-untyped]
25
+
26
+ from process_bigraph_lang.v2.check import Diagnostic, check_file, registry_manifests
27
+ from process_bigraph_lang.v2.interfaces import InterfaceRegistry
28
+ from process_bigraph_lang.v2.langium import run_cli
29
+
30
+ Target = Literal["document", "spec"]
31
+
32
+
33
+ @dataclass
34
+ class Compiled:
35
+ """What `compile_text` / `compile_file` return: the artifact and the findings that did not stop the compile."""
36
+
37
+ document: dict[str, Any]
38
+ diagnostics: list[Diagnostic] = field(default_factory=list)
39
+
40
+ @property
41
+ def warnings(self) -> list[Diagnostic]:
42
+ return [d for d in self.diagnostics if d.severity == "warning"]
43
+
44
+
45
+ @dataclass
46
+ class Printed:
47
+ """What `decompile` returns: the `.pblang` text and its sidecar assets (`<name>.assets/...`), by path."""
48
+
49
+ text: str
50
+ assets: dict[str, bytes] = field(default_factory=dict)
51
+
52
+
53
+ class CompileError(Exception):
54
+ """The text did not pass the checker; `diagnostics` holds every finding, errors first."""
55
+
56
+ def __init__(self, diagnostics: list[Diagnostic]):
57
+ self.diagnostics = diagnostics
58
+ errors = [d for d in diagnostics if d.severity == "error"]
59
+ super().__init__("\n".join(str(d) for d in errors or diagnostics))
60
+
61
+
62
+ def compile_text(
63
+ text: str,
64
+ *,
65
+ registry: InterfaceRegistry | None = None,
66
+ manifests: list[Path] | None = None,
67
+ to: Target = "document",
68
+ strict: bool = False,
69
+ force: bool = False,
70
+ assets: dict[str, bytes] | None = None,
71
+ ) -> Compiled:
72
+ """Compile v2 text. `assets` holds the sidecar files `file(...)` references name, by path."""
73
+ with tempfile.TemporaryDirectory() as tmp:
74
+ source = Path(tmp) / "document.pblang"
75
+ source.write_text(text, encoding="utf-8")
76
+ for name, content in (assets or {}).items():
77
+ target = Path(tmp) / name
78
+ target.parent.mkdir(parents=True, exist_ok=True)
79
+ target.write_bytes(content)
80
+ return compile_file(source, registry=registry, manifests=manifests, to=to, strict=strict, force=force)
81
+
82
+
83
+ def compile_file(
84
+ path: str | os.PathLike[str],
85
+ *,
86
+ registry: InterfaceRegistry | None = None,
87
+ manifests: list[Path] | None = None,
88
+ to: Target = "document",
89
+ strict: bool = False,
90
+ force: bool = False,
91
+ ) -> Compiled:
92
+ """
93
+ Compile a `.pblang` file; `file(...)` references resolve next to it. `force` compiles despite the checker's
94
+ errors (parse errors still stop it): the escape for documents that rely on defaults the checker flags.
95
+ """
96
+ source = Path(path).resolve()
97
+ with tempfile.TemporaryDirectory() as tmp:
98
+ manifest_paths = [*(manifests or []), *registry_manifests(registry, Path(tmp))]
99
+ diagnostics = check_file(source, manifest_paths)
100
+ fatal = set() if force else {"error", "warning"} if strict else {"error"}
101
+ if any(d.severity in fatal for d in diagnostics) or any(d.code == "P001" for d in diagnostics):
102
+ raise CompileError(diagnostics)
103
+ out = Path(tmp) / ("compiled.yaml" if to == "spec" else "compiled.json")
104
+ args = ["compile", str(source), "--to", to, "--out", str(out), *(["--force"] if force else [])]
105
+ if manifest_paths:
106
+ args += ["--interfaces", *[str(Path(m).resolve()) for m in manifest_paths]]
107
+ run_cli(*args)
108
+ text = out.read_text(encoding="utf-8")
109
+ if to == "spec":
110
+ import yaml
111
+
112
+ return Compiled(yaml.safe_load(text), diagnostics)
113
+ return Compiled(loads(text), diagnostics)
114
+
115
+
116
+ def decompile(
117
+ mapping: dict[str, Any],
118
+ *,
119
+ name: str = "document",
120
+ registry: InterfaceRegistry | None = None,
121
+ manifests: list[Path] | None = None,
122
+ ) -> Printed:
123
+ """Canonical `.pblang` text (and sidecar assets) for a document or a CompositeSpec mapping (told apart by keys)."""
124
+ with tempfile.TemporaryDirectory() as tmp:
125
+ source = Path(tmp) / f"{name}.json"
126
+ source.write_text(dumps(mapping), encoding="utf-8")
127
+ out = Path(tmp) / f"{name}.pblang"
128
+ args = ["decompile", str(source), "--name", name, "--out", str(out)]
129
+ manifest_paths = [*(manifests or []), *registry_manifests(registry, Path(tmp))]
130
+ if manifest_paths:
131
+ args += ["--interfaces", *[str(Path(m).resolve()) for m in manifest_paths]]
132
+ run_cli(*args)
133
+ printed = Printed(out.read_text(encoding="utf-8"))
134
+ assets_dir = Path(tmp) / f"{name}.assets"
135
+ if assets_dir.is_dir():
136
+ for asset in sorted(assets_dir.iterdir()):
137
+ printed.assets[f"{name}.assets/{asset.name}"] = asset.read_bytes()
138
+ return printed
139
+
140
+
141
+ # --- the JSON that crosses the CLI boundary ----------------------------------------------------------------------
142
+
143
+
144
+ def dumps(document: Any) -> str:
145
+ """`bigraph_schema.json_codec` encoding, plus tuples tagged so that they come back as tuples."""
146
+ return str(json_codec.dumps(_tag_tuples(document)))
147
+
148
+
149
+ def loads(text: str) -> Any:
150
+ return _untag_tuples(json_codec.loads(text))
151
+
152
+
153
+ def _tag_tuples(value: Any) -> Any:
154
+ if isinstance(value, tuple):
155
+ return {"__tuple__": True, "data": [_tag_tuples(v) for v in value]}
156
+ if isinstance(value, dict):
157
+ return {k: _tag_tuples(v) for k, v in value.items()}
158
+ if isinstance(value, list):
159
+ return [_tag_tuples(v) for v in value]
160
+ return value
161
+
162
+
163
+ def _untag_tuples(value: Any) -> Any:
164
+ if isinstance(value, dict):
165
+ if value.get("__tuple__") is True and isinstance(value.get("data"), list):
166
+ return tuple(_untag_tuples(v) for v in value["data"])
167
+ return {k: _untag_tuples(v) for k, v in value.items()}
168
+ if isinstance(value, list):
169
+ return [_untag_tuples(v) for v in value]
170
+ return value
171
+
172
+
173
+ def write_json(document: Any, path: str | os.PathLike[str]) -> None:
174
+ Path(path).write_text(dumps(document) + "\n", encoding="utf-8")
175
+
176
+
177
+ def read_json(path: str | os.PathLike[str]) -> Any:
178
+ return loads(Path(path).read_text(encoding="utf-8"))
179
+
180
+
181
+ __all__ = [
182
+ "CompileError",
183
+ "Compiled",
184
+ "Printed",
185
+ "compile_file",
186
+ "compile_text",
187
+ "decompile",
188
+ "dumps",
189
+ "loads",
190
+ "read_json",
191
+ "write_json",
192
+ ]
@@ -0,0 +1,73 @@
1
+ """
2
+ Static checks of a composite, run by the DSL CLI's `check` command (the checker lives in TypeScript,
3
+ `dsl/src/language/v2/check.ts`, so the editor, the browser and the CLI share one implementation).
4
+
5
+ `check_file` runs `cli-native check --json` on a `.pblang` file with header manifests; `check_text` does the same
6
+ for text. Both return the findings of the `pblang-diagnostics/1` envelope as `Diagnostic`s.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import tempfile
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from process_bigraph_lang.v2.interfaces import InterfaceRegistry
19
+ from process_bigraph_lang.v2.langium import run_cli
20
+
21
+
22
+ @dataclass
23
+ class Diagnostic:
24
+ code: str
25
+ slug: str
26
+ severity: str # error | warning | info | hint
27
+ path: str # document path, slash-joined (as `fill_sites` spells it)
28
+ message: str
29
+ line: int | None = None
30
+
31
+ def __str__(self) -> str:
32
+ return f"{self.severity:7} {self.code} {self.slug:24} {self.path}: {self.message}"
33
+
34
+
35
+ def check_text(
36
+ text: str, manifests: list[Path] | None = None, registry: InterfaceRegistry | None = None
37
+ ) -> list[Diagnostic]:
38
+ """Check v2 text against header manifests and/or a registry (written as a manifest)."""
39
+ with tempfile.TemporaryDirectory() as tmp:
40
+ source = Path(tmp) / "document.pblang"
41
+ source.write_text(text, encoding="utf-8")
42
+ return check_file(source, [*(manifests or []), *registry_manifests(registry, Path(tmp))])
43
+
44
+
45
+ def check_file(path: str | os.PathLike[str], manifests: list[Path] | None = None) -> list[Diagnostic]:
46
+ """Check a `.pblang` file against header manifests through the CLI."""
47
+ args = ["check", str(Path(path).resolve()), "--json"]
48
+ if manifests:
49
+ args += ["--interfaces", *[str(Path(m).resolve()) for m in manifests]]
50
+ result = run_cli(*args, check=False)
51
+ if not result.stdout.strip():
52
+ raise RuntimeError(f"Langium CLI failed on {path}: {result.stderr.strip()}")
53
+ envelope: dict[str, Any] = json.loads(result.stdout)
54
+ return [
55
+ Diagnostic(
56
+ d["code"],
57
+ d["slug"],
58
+ d["severity"],
59
+ d["path"],
60
+ d["message"],
61
+ d.get("range", {}).get("start", {}).get("line"),
62
+ )
63
+ for d in envelope.get("diagnostics", [])
64
+ ]
65
+
66
+
67
+ def registry_manifests(registry: InterfaceRegistry | None, directory: Path) -> list[Path]:
68
+ """A registry written as a manifest file in `directory` (none when the registry is empty)."""
69
+ if registry is None or not registry.decls:
70
+ return []
71
+ manifest = directory / "registry.manifest.json"
72
+ manifest.write_text(json.dumps(registry.to_manifest(), default=str), encoding="utf-8")
73
+ return [manifest]