vcti-tree-exporter-json 2.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.
- vcti_tree_exporter_json-2.0.0/LICENSE +8 -0
- vcti_tree_exporter_json-2.0.0/PKG-INFO +108 -0
- vcti_tree_exporter_json-2.0.0/README.md +74 -0
- vcti_tree_exporter_json-2.0.0/pyproject.toml +82 -0
- vcti_tree_exporter_json-2.0.0/setup.cfg +4 -0
- vcti_tree_exporter_json-2.0.0/src/vcti/tree/exporter/json/__init__.py +22 -0
- vcti_tree_exporter_json-2.0.0/src/vcti/tree/exporter/json/exporter.py +111 -0
- vcti_tree_exporter_json-2.0.0/src/vcti/tree/exporter/json/py.typed +0 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/PKG-INFO +108 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/SOURCES.txt +14 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/dependency_links.txt +1 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/requires.txt +14 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/top_level.txt +1 -0
- vcti_tree_exporter_json-2.0.0/src/vcti_tree_exporter_json.egg-info/zip-safe +1 -0
- vcti_tree_exporter_json-2.0.0/tests/test_exporter.py +165 -0
- vcti_tree_exporter_json-2.0.0/tests/test_version.py +15 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright (c) 2018-2026 Visual Collaboration Technologies Inc.
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
This software is proprietary and confidential. Unauthorized copying,
|
|
5
|
+
distribution, or use of this software, via any medium, is strictly
|
|
6
|
+
prohibited. Access is granted only to authorized VCollab developers
|
|
7
|
+
and individuals explicitly authorized by Visual Collaboration
|
|
8
|
+
Technologies Inc.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vcti-tree-exporter-json
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: JSON exporter plugin for vcti-tree-exporter: writes a vcti tree to a single .json document (nested nodes carrying name, attributes, and light arrays as JSON lists).
|
|
5
|
+
Author: Visual Collaboration Technologies Inc.
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/vcollab/vcti-python-tree-exporter-json
|
|
8
|
+
Project-URL: Repository, https://github.com/vcollab/vcti-python-tree-exporter-json
|
|
9
|
+
Project-URL: Changelog, https://github.com/vcollab/vcti-python-tree-exporter-json/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: tree,export,json,serialization,exporter,plugin
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: <3.15,>=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: vcti-tree-exporter>=2.0.0
|
|
23
|
+
Requires-Dist: vcti-tree>=2.0.0
|
|
24
|
+
Requires-Dist: vcti-datanode>=2.0.0
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
29
|
+
Provides-Extra: lint
|
|
30
|
+
Requires-Dist: ruff; extra == "lint"
|
|
31
|
+
Provides-Extra: typecheck
|
|
32
|
+
Requires-Dist: mypy; extra == "typecheck"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# JSON Tree Exporter
|
|
36
|
+
|
|
37
|
+
JSON exporter plugin for [`vcti-tree-exporter`](https://github.com/vcollab/vcti-python-tree-exporter): writes a vcti tree to a single `.json` document.
|
|
38
|
+
|
|
39
|
+
`JsonExporter` serializes any [`vcti-tree`](https://github.com/vcollab/vcti-python-tree)
|
|
40
|
+
tree whose node payloads are
|
|
41
|
+
[`vcti-datanode`](https://github.com/vcollab/vcti-python-datanode) `DataNode`s
|
|
42
|
+
to one JSON document that mirrors the tree, and ships a
|
|
43
|
+
`get_exporter_descriptor()` factory so a consumer registers it in a catalog and
|
|
44
|
+
resolves it by id (`"json"`) or by attribute lookup.
|
|
45
|
+
|
|
46
|
+
JSON is a natural fit for **structure, metadata, and light arrays** — simulation
|
|
47
|
+
settings, CAE metadata, input decks — that load straight into tooling. It is a
|
|
48
|
+
poor fit for large numeric arrays (they become bulky nested lists); use HDF5 or
|
|
49
|
+
NPZ for heavy data.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install vcti-tree-exporter-json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This pulls in `vcti-tree-exporter` (the protocol/catalog) and `numpy`.
|
|
58
|
+
|
|
59
|
+
## Tree → JSON mapping
|
|
60
|
+
|
|
61
|
+
Each node becomes an object; the document root is the tree's root node:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"name": "model",
|
|
66
|
+
"attributes": {"solver": "abaqus"},
|
|
67
|
+
"children": [
|
|
68
|
+
{"name": "info", "attributes": {"count": 7}},
|
|
69
|
+
{"name": "displacement", "attributes": {}, "data": [1.0, 2.0, 3.0]}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Tree node part | JSON |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `node.name` | `"name"` (the structural name; `null` if unset) |
|
|
77
|
+
| `node.attributes` | `"attributes"` object (always present) |
|
|
78
|
+
| `node.load()` array | `"data"` as nested lists (present only for data nodes) |
|
|
79
|
+
| children | `"children"` array (present only when non-empty) |
|
|
80
|
+
|
|
81
|
+
A node may carry both `data` and `children`. Attribute values are JSON-encoded
|
|
82
|
+
(bytes decoded, numpy scalars/arrays converted to numbers/lists); anything not
|
|
83
|
+
natively serializable falls back to its `str()`. Array dtype is not preserved —
|
|
84
|
+
the export is one-way.
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
from vcti.tree.exporter.core import build_registry, get_exporter
|
|
91
|
+
from vcti.tree.exporter.json import get_exporter_descriptor
|
|
92
|
+
|
|
93
|
+
registry = build_registry([get_exporter_descriptor()]) # plus any other format plugins
|
|
94
|
+
get_exporter(registry, "json").export(tree, Path("model.json"), overwrite=True)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or use the exporter directly:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from vcti.tree.exporter.json import JsonExporter
|
|
101
|
+
JsonExporter().export(tree, Path("model.json"))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Dependencies
|
|
105
|
+
|
|
106
|
+
- [vcti-tree-exporter](https://github.com/vcollab/vcti-python-tree-exporter) — the `Exporter` protocol and the `ExporterDescriptor` catalog.
|
|
107
|
+
- [vcti-tree](https://github.com/vcollab/vcti-python-tree) / [vcti-datanode](https://github.com/vcollab/vcti-python-datanode) — the tree and payload types.
|
|
108
|
+
- numpy. (JSON encoding itself uses only the standard library.)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# JSON Tree Exporter
|
|
2
|
+
|
|
3
|
+
JSON exporter plugin for [`vcti-tree-exporter`](https://github.com/vcollab/vcti-python-tree-exporter): writes a vcti tree to a single `.json` document.
|
|
4
|
+
|
|
5
|
+
`JsonExporter` serializes any [`vcti-tree`](https://github.com/vcollab/vcti-python-tree)
|
|
6
|
+
tree whose node payloads are
|
|
7
|
+
[`vcti-datanode`](https://github.com/vcollab/vcti-python-datanode) `DataNode`s
|
|
8
|
+
to one JSON document that mirrors the tree, and ships a
|
|
9
|
+
`get_exporter_descriptor()` factory so a consumer registers it in a catalog and
|
|
10
|
+
resolves it by id (`"json"`) or by attribute lookup.
|
|
11
|
+
|
|
12
|
+
JSON is a natural fit for **structure, metadata, and light arrays** — simulation
|
|
13
|
+
settings, CAE metadata, input decks — that load straight into tooling. It is a
|
|
14
|
+
poor fit for large numeric arrays (they become bulky nested lists); use HDF5 or
|
|
15
|
+
NPZ for heavy data.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install vcti-tree-exporter-json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This pulls in `vcti-tree-exporter` (the protocol/catalog) and `numpy`.
|
|
24
|
+
|
|
25
|
+
## Tree → JSON mapping
|
|
26
|
+
|
|
27
|
+
Each node becomes an object; the document root is the tree's root node:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"name": "model",
|
|
32
|
+
"attributes": {"solver": "abaqus"},
|
|
33
|
+
"children": [
|
|
34
|
+
{"name": "info", "attributes": {"count": 7}},
|
|
35
|
+
{"name": "displacement", "attributes": {}, "data": [1.0, 2.0, 3.0]}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Tree node part | JSON |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `node.name` | `"name"` (the structural name; `null` if unset) |
|
|
43
|
+
| `node.attributes` | `"attributes"` object (always present) |
|
|
44
|
+
| `node.load()` array | `"data"` as nested lists (present only for data nodes) |
|
|
45
|
+
| children | `"children"` array (present only when non-empty) |
|
|
46
|
+
|
|
47
|
+
A node may carry both `data` and `children`. Attribute values are JSON-encoded
|
|
48
|
+
(bytes decoded, numpy scalars/arrays converted to numbers/lists); anything not
|
|
49
|
+
natively serializable falls back to its `str()`. Array dtype is not preserved —
|
|
50
|
+
the export is one-way.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
from vcti.tree.exporter.core import build_registry, get_exporter
|
|
57
|
+
from vcti.tree.exporter.json import get_exporter_descriptor
|
|
58
|
+
|
|
59
|
+
registry = build_registry([get_exporter_descriptor()]) # plus any other format plugins
|
|
60
|
+
get_exporter(registry, "json").export(tree, Path("model.json"), overwrite=True)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or use the exporter directly:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from vcti.tree.exporter.json import JsonExporter
|
|
67
|
+
JsonExporter().export(tree, Path("model.json"))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
- [vcti-tree-exporter](https://github.com/vcollab/vcti-python-tree-exporter) — the `Exporter` protocol and the `ExporterDescriptor` catalog.
|
|
73
|
+
- [vcti-tree](https://github.com/vcollab/vcti-python-tree) / [vcti-datanode](https://github.com/vcollab/vcti-python-datanode) — the tree and payload types.
|
|
74
|
+
- numpy. (JSON encoding itself uses only the standard library.)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vcti-tree-exporter-json"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "JSON exporter plugin for vcti-tree-exporter: writes a vcti tree to a single .json document (nested nodes carrying name, attributes, and light arrays as JSON lists)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Visual Collaboration Technologies Inc."}
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.12,<3.15"
|
|
14
|
+
license = "LicenseRef-Proprietary"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
keywords = ["tree", "export", "json", "serialization", "exporter", "plugin"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"vcti-tree-exporter>=2.0.0",
|
|
29
|
+
"vcti-tree>=2.0.0",
|
|
30
|
+
"vcti-datanode>=2.0.0",
|
|
31
|
+
"numpy>=1.24",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/vcollab/vcti-python-tree-exporter-json"
|
|
36
|
+
Repository = "https://github.com/vcollab/vcti-python-tree-exporter-json"
|
|
37
|
+
Changelog = "https://github.com/vcollab/vcti-python-tree-exporter-json/blob/main/CHANGELOG.md"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
include = ["vcti.tree.exporter.json", "vcti.tree.exporter.json.*"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
"vcti.tree.exporter.json" = ["py.typed"]
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
test = ["pytest", "pytest-cov"]
|
|
48
|
+
lint = ["ruff"]
|
|
49
|
+
typecheck = ["mypy"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools]
|
|
52
|
+
zip-safe = true
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
addopts = "--cov=vcti.tree.exporter.json --cov-report=term-missing --cov-fail-under=95"
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.12"
|
|
59
|
+
strict = true
|
|
60
|
+
files = ["src"]
|
|
61
|
+
namespace_packages = true
|
|
62
|
+
explicit_package_bases = true
|
|
63
|
+
mypy_path = ["src"]
|
|
64
|
+
|
|
65
|
+
[tool.coverage.run]
|
|
66
|
+
branch = true
|
|
67
|
+
|
|
68
|
+
[tool.coverage.report]
|
|
69
|
+
exclude_also = [
|
|
70
|
+
"raise NotImplementedError",
|
|
71
|
+
"if TYPE_CHECKING:",
|
|
72
|
+
"if __name__ == .__main__.:",
|
|
73
|
+
"@(abc\\.)?abstractmethod",
|
|
74
|
+
"\\.\\.\\.",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
target-version = "py312"
|
|
79
|
+
line-length = 99
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["E", "F", "W", "I", "UP"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""vcti.tree.exporter.json — JSON exporter plugin for vcti-tree-exporter.
|
|
4
|
+
|
|
5
|
+
Provides :class:`JsonExporter`, which serializes a ``vcti.tree`` tree of
|
|
6
|
+
``vcti.datanode.DataNode`` payloads to a single ``.json`` document (nested
|
|
7
|
+
objects mirroring the hierarchy, with each node's name, attributes, and light
|
|
8
|
+
arrays), and :func:`get_exporter_descriptor`, the catalog factory a consumer
|
|
9
|
+
registers with ``vcti.tree.exporter.core.build_registry``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from importlib.metadata import version
|
|
13
|
+
|
|
14
|
+
from .exporter import JsonExporter, get_exporter_descriptor
|
|
15
|
+
|
|
16
|
+
__version__ = version("vcti-tree-exporter-json")
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"JsonExporter",
|
|
20
|
+
"__version__",
|
|
21
|
+
"get_exporter_descriptor",
|
|
22
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""JSON exporter — write a vcti tree to a single JSON document.
|
|
4
|
+
|
|
5
|
+
The tree maps directly onto JSON's own hierarchy: each node becomes an object
|
|
6
|
+
|
|
7
|
+
{"name": <str|null>, "attributes": {...}, "data": [...], "children": [...]}
|
|
8
|
+
|
|
9
|
+
where ``data`` (the node's array, as nested lists) and ``children`` are present
|
|
10
|
+
only when the node has them. The document root is the tree's root node.
|
|
11
|
+
|
|
12
|
+
JSON is a natural fit for structure, metadata, and *light* arrays (simulation
|
|
13
|
+
settings, CAE metadata); it is a poor fit for large numeric arrays, which
|
|
14
|
+
become bulky nested lists — use HDF5 or NPZ for heavy data. Array dtype is not
|
|
15
|
+
preserved (values become plain JSON numbers); this exporter is one-way.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
from vcti.datanode import DataNode
|
|
26
|
+
from vcti.tree.exporter.core import EXTENSION_ATTR, FORMAT_ATTR, ExporterDescriptor
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from vcti.tree.core import ReadOnlyTree
|
|
30
|
+
|
|
31
|
+
#: Fixed, well-known id for the JSON exporter descriptor.
|
|
32
|
+
EXPORTER_ID = "json"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _json_default(value: Any) -> Any:
|
|
36
|
+
"""Coerce a value JSON cannot serialize natively into a JSON-safe form."""
|
|
37
|
+
if isinstance(value, bytes):
|
|
38
|
+
return value.decode("utf-8", errors="replace").rstrip("\x00")
|
|
39
|
+
if isinstance(value, np.generic):
|
|
40
|
+
return value.item()
|
|
41
|
+
if isinstance(value, np.ndarray):
|
|
42
|
+
return value.tolist()
|
|
43
|
+
return str(value) # last resort so one odd value never aborts the export
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class JsonExporter:
|
|
47
|
+
"""Serialize a vcti tree to a single ``.json`` document.
|
|
48
|
+
|
|
49
|
+
Satisfies the ``vcti.tree.exporter.core.Exporter`` protocol structurally. Format
|
|
50
|
+
name and default extension live on its descriptor
|
|
51
|
+
(:func:`get_exporter_descriptor`), not on this class.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def export(
|
|
55
|
+
self,
|
|
56
|
+
tree: ReadOnlyTree[DataNode, Any],
|
|
57
|
+
path: Path,
|
|
58
|
+
*,
|
|
59
|
+
overwrite: bool = False,
|
|
60
|
+
) -> None:
|
|
61
|
+
path = Path(path)
|
|
62
|
+
if path.exists() and not overwrite:
|
|
63
|
+
raise FileExistsError(f"Output file already exists: {path}")
|
|
64
|
+
|
|
65
|
+
root_obj = self._node_to_obj(tree, tree.root_handle)
|
|
66
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
with path.open("w", encoding="utf-8") as f:
|
|
68
|
+
json.dump(root_obj, f, indent=2, ensure_ascii=False, default=_json_default)
|
|
69
|
+
|
|
70
|
+
def _node_to_obj(
|
|
71
|
+
self,
|
|
72
|
+
tree: ReadOnlyTree[DataNode, Any],
|
|
73
|
+
handle: Any,
|
|
74
|
+
) -> dict[str, Any] | None:
|
|
75
|
+
payload = tree.payload(handle)
|
|
76
|
+
if not isinstance(payload, DataNode):
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
obj: dict[str, Any] = {"name": payload.name, "attributes": dict(payload.attributes)}
|
|
80
|
+
|
|
81
|
+
# load() materialises a data leaf and is a safe no-op (None) for a
|
|
82
|
+
# metadata-only node; restore the prior state for lazy sources.
|
|
83
|
+
was_loaded = payload.is_loaded
|
|
84
|
+
array = payload.load()
|
|
85
|
+
if array is not None:
|
|
86
|
+
obj["data"] = array.tolist()
|
|
87
|
+
if not was_loaded:
|
|
88
|
+
payload.unload()
|
|
89
|
+
|
|
90
|
+
children = [
|
|
91
|
+
child_obj
|
|
92
|
+
for child in tree.children(handle)
|
|
93
|
+
if (child_obj := self._node_to_obj(tree, child)) is not None
|
|
94
|
+
]
|
|
95
|
+
if children:
|
|
96
|
+
obj["children"] = children
|
|
97
|
+
return obj
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_exporter_descriptor() -> ExporterDescriptor:
|
|
101
|
+
"""Create the :class:`ExporterDescriptor` for the JSON exporter."""
|
|
102
|
+
return ExporterDescriptor(
|
|
103
|
+
id=EXPORTER_ID,
|
|
104
|
+
name="JSON Exporter",
|
|
105
|
+
exporter=JsonExporter(),
|
|
106
|
+
description="Writes a vcti tree to a single .json document.",
|
|
107
|
+
attributes={
|
|
108
|
+
FORMAT_ATTR: "json",
|
|
109
|
+
EXTENSION_ATTR: ".json",
|
|
110
|
+
},
|
|
111
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vcti-tree-exporter-json
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: JSON exporter plugin for vcti-tree-exporter: writes a vcti tree to a single .json document (nested nodes carrying name, attributes, and light arrays as JSON lists).
|
|
5
|
+
Author: Visual Collaboration Technologies Inc.
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/vcollab/vcti-python-tree-exporter-json
|
|
8
|
+
Project-URL: Repository, https://github.com/vcollab/vcti-python-tree-exporter-json
|
|
9
|
+
Project-URL: Changelog, https://github.com/vcollab/vcti-python-tree-exporter-json/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: tree,export,json,serialization,exporter,plugin
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: <3.15,>=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: vcti-tree-exporter>=2.0.0
|
|
23
|
+
Requires-Dist: vcti-tree>=2.0.0
|
|
24
|
+
Requires-Dist: vcti-datanode>=2.0.0
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
29
|
+
Provides-Extra: lint
|
|
30
|
+
Requires-Dist: ruff; extra == "lint"
|
|
31
|
+
Provides-Extra: typecheck
|
|
32
|
+
Requires-Dist: mypy; extra == "typecheck"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# JSON Tree Exporter
|
|
36
|
+
|
|
37
|
+
JSON exporter plugin for [`vcti-tree-exporter`](https://github.com/vcollab/vcti-python-tree-exporter): writes a vcti tree to a single `.json` document.
|
|
38
|
+
|
|
39
|
+
`JsonExporter` serializes any [`vcti-tree`](https://github.com/vcollab/vcti-python-tree)
|
|
40
|
+
tree whose node payloads are
|
|
41
|
+
[`vcti-datanode`](https://github.com/vcollab/vcti-python-datanode) `DataNode`s
|
|
42
|
+
to one JSON document that mirrors the tree, and ships a
|
|
43
|
+
`get_exporter_descriptor()` factory so a consumer registers it in a catalog and
|
|
44
|
+
resolves it by id (`"json"`) or by attribute lookup.
|
|
45
|
+
|
|
46
|
+
JSON is a natural fit for **structure, metadata, and light arrays** — simulation
|
|
47
|
+
settings, CAE metadata, input decks — that load straight into tooling. It is a
|
|
48
|
+
poor fit for large numeric arrays (they become bulky nested lists); use HDF5 or
|
|
49
|
+
NPZ for heavy data.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install vcti-tree-exporter-json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This pulls in `vcti-tree-exporter` (the protocol/catalog) and `numpy`.
|
|
58
|
+
|
|
59
|
+
## Tree → JSON mapping
|
|
60
|
+
|
|
61
|
+
Each node becomes an object; the document root is the tree's root node:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"name": "model",
|
|
66
|
+
"attributes": {"solver": "abaqus"},
|
|
67
|
+
"children": [
|
|
68
|
+
{"name": "info", "attributes": {"count": 7}},
|
|
69
|
+
{"name": "displacement", "attributes": {}, "data": [1.0, 2.0, 3.0]}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Tree node part | JSON |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `node.name` | `"name"` (the structural name; `null` if unset) |
|
|
77
|
+
| `node.attributes` | `"attributes"` object (always present) |
|
|
78
|
+
| `node.load()` array | `"data"` as nested lists (present only for data nodes) |
|
|
79
|
+
| children | `"children"` array (present only when non-empty) |
|
|
80
|
+
|
|
81
|
+
A node may carry both `data` and `children`. Attribute values are JSON-encoded
|
|
82
|
+
(bytes decoded, numpy scalars/arrays converted to numbers/lists); anything not
|
|
83
|
+
natively serializable falls back to its `str()`. Array dtype is not preserved —
|
|
84
|
+
the export is one-way.
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
from vcti.tree.exporter.core import build_registry, get_exporter
|
|
91
|
+
from vcti.tree.exporter.json import get_exporter_descriptor
|
|
92
|
+
|
|
93
|
+
registry = build_registry([get_exporter_descriptor()]) # plus any other format plugins
|
|
94
|
+
get_exporter(registry, "json").export(tree, Path("model.json"), overwrite=True)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or use the exporter directly:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from vcti.tree.exporter.json import JsonExporter
|
|
101
|
+
JsonExporter().export(tree, Path("model.json"))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Dependencies
|
|
105
|
+
|
|
106
|
+
- [vcti-tree-exporter](https://github.com/vcollab/vcti-python-tree-exporter) — the `Exporter` protocol and the `ExporterDescriptor` catalog.
|
|
107
|
+
- [vcti-tree](https://github.com/vcollab/vcti-python-tree) / [vcti-datanode](https://github.com/vcollab/vcti-python-datanode) — the tree and payload types.
|
|
108
|
+
- numpy. (JSON encoding itself uses only the standard library.)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/vcti/tree/exporter/json/__init__.py
|
|
5
|
+
src/vcti/tree/exporter/json/exporter.py
|
|
6
|
+
src/vcti/tree/exporter/json/py.typed
|
|
7
|
+
src/vcti_tree_exporter_json.egg-info/PKG-INFO
|
|
8
|
+
src/vcti_tree_exporter_json.egg-info/SOURCES.txt
|
|
9
|
+
src/vcti_tree_exporter_json.egg-info/dependency_links.txt
|
|
10
|
+
src/vcti_tree_exporter_json.egg-info/requires.txt
|
|
11
|
+
src/vcti_tree_exporter_json.egg-info/top_level.txt
|
|
12
|
+
src/vcti_tree_exporter_json.egg-info/zip-safe
|
|
13
|
+
tests/test_exporter.py
|
|
14
|
+
tests/test_version.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vcti
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""Tests for the JSON exporter."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
from vcti.datanode import DataNode, EagerDataSource, LazyDataSource
|
|
12
|
+
from vcti.lookup import Rule
|
|
13
|
+
from vcti.tree.core import DictTree
|
|
14
|
+
from vcti.tree.exporter.core import (
|
|
15
|
+
EXTENSION_ATTR,
|
|
16
|
+
FORMAT_ATTR,
|
|
17
|
+
Exporter,
|
|
18
|
+
build_registry,
|
|
19
|
+
get_exporter,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from vcti.tree.exporter.json import JsonExporter, get_exporter_descriptor
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _data(array: np.ndarray, *, name: str) -> DataNode:
|
|
26
|
+
return DataNode(name=name, data_source=EagerDataSource(array))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _sample_tree() -> DictTree:
|
|
30
|
+
tree: DictTree = DictTree(DataNode(name="model", source_attributes={"solver": "abaqus"}))
|
|
31
|
+
root = tree.root_handle
|
|
32
|
+
|
|
33
|
+
# metadata-only leaf with a bytes value, a numpy scalar, and None
|
|
34
|
+
tree.create_child(
|
|
35
|
+
root,
|
|
36
|
+
DataNode(
|
|
37
|
+
name="info",
|
|
38
|
+
source_attributes={
|
|
39
|
+
"count": np.int64(7),
|
|
40
|
+
"label": b"ABAQUS",
|
|
41
|
+
"missing": None,
|
|
42
|
+
},
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# group with a data leaf (light 1-D array) and a domain "name" attribute
|
|
47
|
+
materials = tree.create_child(root, DataNode(name="materials"))
|
|
48
|
+
tree.create_child(
|
|
49
|
+
materials,
|
|
50
|
+
DataNode(
|
|
51
|
+
name="material_0",
|
|
52
|
+
data_source=EagerDataSource(np.array([1.0, 2.0, 3.0])),
|
|
53
|
+
source_attributes={"id": 1, "name": "Steel"},
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# a non-DataNode payload is skipped
|
|
58
|
+
tree.create_child(root, "not-a-datanode")
|
|
59
|
+
return tree
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _load(tmp_path, tree, name="out.json") -> dict:
|
|
63
|
+
out = tmp_path / name
|
|
64
|
+
JsonExporter().export(tree, out)
|
|
65
|
+
with out.open(encoding="utf-8") as f:
|
|
66
|
+
return json.load(f)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_protocol_conformance():
|
|
70
|
+
assert isinstance(JsonExporter(), Exporter)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_descriptor_factory_records_metadata():
|
|
74
|
+
desc = get_exporter_descriptor()
|
|
75
|
+
assert desc.id == "json"
|
|
76
|
+
assert isinstance(desc.exporter, JsonExporter)
|
|
77
|
+
assert desc.attributes[FORMAT_ATTR] == "json"
|
|
78
|
+
assert desc.attributes[EXTENSION_ATTR] == ".json"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_document_root_is_the_tree_root(tmp_path):
|
|
82
|
+
doc = _load(tmp_path, _sample_tree())
|
|
83
|
+
assert doc["name"] == "model"
|
|
84
|
+
assert doc["attributes"]["solver"] == "abaqus"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_nested_structure_names_and_attributes(tmp_path):
|
|
88
|
+
doc = _load(tmp_path, _sample_tree())
|
|
89
|
+
names = {child["name"] for child in doc["children"]}
|
|
90
|
+
assert names == {"info", "materials"} # the non-DataNode child is skipped
|
|
91
|
+
|
|
92
|
+
info = next(c for c in doc["children"] if c["name"] == "info")
|
|
93
|
+
assert info["attributes"]["count"] == 7 # numpy scalar -> python int
|
|
94
|
+
assert info["attributes"]["label"] == "ABAQUS" # bytes -> decoded str
|
|
95
|
+
assert info["attributes"]["missing"] is None
|
|
96
|
+
assert "children" not in info # leaves carry no children key
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_data_leaf_serialised_as_list_with_attributes(tmp_path):
|
|
100
|
+
doc = _load(tmp_path, _sample_tree())
|
|
101
|
+
materials = next(c for c in doc["children"] if c["name"] == "materials")
|
|
102
|
+
(mat,) = materials["children"]
|
|
103
|
+
assert mat["data"] == [1.0, 2.0, 3.0]
|
|
104
|
+
# intrinsic name is the structural name; domain attribute "name" is separate
|
|
105
|
+
assert mat["name"] == "material_0"
|
|
106
|
+
assert mat["attributes"] == {"id": 1, "name": "Steel"}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_ndarray_valued_attribute_becomes_list(tmp_path):
|
|
110
|
+
tree: DictTree = DictTree(DataNode(name="root"))
|
|
111
|
+
tree.create_child(
|
|
112
|
+
tree.root_handle,
|
|
113
|
+
DataNode(name="n", source_attributes={"D": np.array([1.0, 2.0, 3.0])}),
|
|
114
|
+
)
|
|
115
|
+
doc = _load(tmp_path, tree)
|
|
116
|
+
assert doc["children"][0]["attributes"]["D"] == [1.0, 2.0, 3.0]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_unserialisable_attribute_falls_back_to_str(tmp_path):
|
|
120
|
+
tree: DictTree = DictTree(DataNode(name="root"))
|
|
121
|
+
tree.create_child(tree.root_handle, DataNode(name="thing", source_attributes={"x": {1, 2}}))
|
|
122
|
+
doc = _load(tmp_path, tree)
|
|
123
|
+
# a set is not JSON-native -> str() fallback
|
|
124
|
+
assert isinstance(doc["children"][0]["attributes"]["x"], str)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_lazy_node_materialised_then_released(tmp_path):
|
|
128
|
+
tree: DictTree = DictTree(DataNode(name="root"))
|
|
129
|
+
node = DataNode(name="lazy", data_source=LazyDataSource(lambda: np.arange(3), shape=(3,)))
|
|
130
|
+
tree.create_child(tree.root_handle, node)
|
|
131
|
+
assert not node.is_loaded
|
|
132
|
+
doc = _load(tmp_path, tree)
|
|
133
|
+
assert doc["children"][0]["data"] == [0, 1, 2]
|
|
134
|
+
assert not node.is_loaded # restored to the prior state
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_node_with_both_data_and_children_is_representable(tmp_path):
|
|
138
|
+
tree: DictTree = DictTree(DataNode(name="root"))
|
|
139
|
+
parent = tree.create_child(tree.root_handle, _data(np.array([9]), name="parent"))
|
|
140
|
+
tree.create_child(parent, DataNode(name="kid"))
|
|
141
|
+
doc = _load(tmp_path, tree)
|
|
142
|
+
parent_obj = doc["children"][0]
|
|
143
|
+
assert parent_obj["data"] == [9]
|
|
144
|
+
assert parent_obj["children"][0]["name"] == "kid"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_overwrite_behavior(tmp_path):
|
|
148
|
+
out = tmp_path / "out.json"
|
|
149
|
+
JsonExporter().export(_sample_tree(), out)
|
|
150
|
+
with pytest.raises(FileExistsError):
|
|
151
|
+
JsonExporter().export(_sample_tree(), out)
|
|
152
|
+
JsonExporter().export(_sample_tree(), out, overwrite=True) # no error
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_creates_parent_directories(tmp_path):
|
|
156
|
+
out = tmp_path / "nested" / "dir" / "out.json"
|
|
157
|
+
JsonExporter().export(_sample_tree(), out)
|
|
158
|
+
assert out.exists()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_registry_integration():
|
|
162
|
+
registry = build_registry([get_exporter_descriptor()])
|
|
163
|
+
assert isinstance(get_exporter(registry, "json"), JsonExporter)
|
|
164
|
+
(desc,) = registry.lookup.filter([Rule(EXTENSION_ATTR, "==", ".json")])
|
|
165
|
+
assert desc.id == "json"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
|
|
2
|
+
# See LICENSE for details.
|
|
3
|
+
"""Version tests for vcti-tree-exporter-json."""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import vcti.tree.exporter.json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestVersion:
|
|
11
|
+
def test_version_exists(self):
|
|
12
|
+
assert hasattr(vcti.tree.exporter.json, "__version__")
|
|
13
|
+
|
|
14
|
+
def test_version_is_valid_semver(self):
|
|
15
|
+
assert re.match(r"^\d+\.\d+\.\d+", vcti.tree.exporter.json.__version__)
|