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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )
@@ -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,14 @@
1
+ vcti-tree-exporter>=2.0.0
2
+ vcti-tree>=2.0.0
3
+ vcti-datanode>=2.0.0
4
+ numpy>=1.24
5
+
6
+ [lint]
7
+ ruff
8
+
9
+ [test]
10
+ pytest
11
+ pytest-cov
12
+
13
+ [typecheck]
14
+ mypy
@@ -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__)