harmonized-telemetry-format 0.0.2a1__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.
- harmonized_telemetry_format-0.0.2a1/PKG-INFO +26 -0
- harmonized_telemetry_format-0.0.2a1/README.md +11 -0
- harmonized_telemetry_format-0.0.2a1/harmonized_telemetry_format.egg-info/PKG-INFO +26 -0
- harmonized_telemetry_format-0.0.2a1/harmonized_telemetry_format.egg-info/SOURCES.txt +12 -0
- harmonized_telemetry_format-0.0.2a1/harmonized_telemetry_format.egg-info/dependency_links.txt +1 -0
- harmonized_telemetry_format-0.0.2a1/harmonized_telemetry_format.egg-info/top_level.txt +1 -0
- harmonized_telemetry_format-0.0.2a1/htf_core/__init__.py +0 -0
- harmonized_telemetry_format-0.0.2a1/htf_core/models.py +39 -0
- harmonized_telemetry_format-0.0.2a1/htf_core/reader.py +115 -0
- harmonized_telemetry_format-0.0.2a1/htf_core/writer.py +58 -0
- harmonized_telemetry_format-0.0.2a1/pyproject.toml +28 -0
- harmonized_telemetry_format-0.0.2a1/setup.cfg +4 -0
- harmonized_telemetry_format-0.0.2a1/tests/test_reader.py +83 -0
- harmonized_telemetry_format-0.0.2a1/tests/test_writer.py +31 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: harmonized-telemetry-format
|
|
3
|
+
Version: 0.0.2a1
|
|
4
|
+
Summary: Core library for reading and writing the Harmonized Telemetry Format (HTF).
|
|
5
|
+
Author-email: Max Schlosser <schlosse@hs-mittweida.de>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Schloool/harmonized-telemetry-format
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/Schloool/harmonized-telemetry-format/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Harmonized Telemetry Format (HTF)
|
|
17
|
+
|
|
18
|
+
Detailed description coming soon.
|
|
19
|
+
|
|
20
|
+
Exemplary HTF file content:
|
|
21
|
+
```htf
|
|
22
|
+
[static_metadata;s]12.51
|
|
23
|
+
[dim_metadata;index;delta]2;0.08;4;0.484
|
|
24
|
+
(channel;s;50;135)0=12.27;1=12.42;...
|
|
25
|
+
(empty_channel;m;50;135)0=;
|
|
26
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Harmonized Telemetry Format (HTF)
|
|
2
|
+
|
|
3
|
+
Detailed description coming soon.
|
|
4
|
+
|
|
5
|
+
Exemplary HTF file content:
|
|
6
|
+
```htf
|
|
7
|
+
[static_metadata;s]12.51
|
|
8
|
+
[dim_metadata;index;delta]2;0.08;4;0.484
|
|
9
|
+
(channel;s;50;135)0=12.27;1=12.42;...
|
|
10
|
+
(empty_channel;m;50;135)0=;
|
|
11
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: harmonized-telemetry-format
|
|
3
|
+
Version: 0.0.2a1
|
|
4
|
+
Summary: Core library for reading and writing the Harmonized Telemetry Format (HTF).
|
|
5
|
+
Author-email: Max Schlosser <schlosse@hs-mittweida.de>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Schloool/harmonized-telemetry-format
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/Schloool/harmonized-telemetry-format/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Harmonized Telemetry Format (HTF)
|
|
17
|
+
|
|
18
|
+
Detailed description coming soon.
|
|
19
|
+
|
|
20
|
+
Exemplary HTF file content:
|
|
21
|
+
```htf
|
|
22
|
+
[static_metadata;s]12.51
|
|
23
|
+
[dim_metadata;index;delta]2;0.08;4;0.484
|
|
24
|
+
(channel;s;50;135)0=12.27;1=12.42;...
|
|
25
|
+
(empty_channel;m;50;135)0=;
|
|
26
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
harmonized_telemetry_format.egg-info/PKG-INFO
|
|
4
|
+
harmonized_telemetry_format.egg-info/SOURCES.txt
|
|
5
|
+
harmonized_telemetry_format.egg-info/dependency_links.txt
|
|
6
|
+
harmonized_telemetry_format.egg-info/top_level.txt
|
|
7
|
+
htf_core/__init__.py
|
|
8
|
+
htf_core/models.py
|
|
9
|
+
htf_core/reader.py
|
|
10
|
+
htf_core/writer.py
|
|
11
|
+
tests/test_reader.py
|
|
12
|
+
tests/test_writer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
htf_core
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HarmonizedTelemetryChannel:
|
|
5
|
+
"""A telemetry channel used within a recording."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, name: str, unit: Optional[str], frequency: Optional[int],
|
|
8
|
+
total_values: int, values: list[tuple[int, object]]):
|
|
9
|
+
self.name = name
|
|
10
|
+
self.unit = unit
|
|
11
|
+
self.frequency = frequency
|
|
12
|
+
self.total_values = total_values
|
|
13
|
+
self.values = values
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HarmonizedMetadataEntry:
|
|
17
|
+
"""
|
|
18
|
+
Unified class for both static metadata (single record, n=1) and dimensional aggregations (n>=1).
|
|
19
|
+
Maps directly to the HTF line structure: [name;property_1;property_2;...]value1_1;value1_2;value2_1;value2_2;...
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# TODO: Values are wrong
|
|
23
|
+
def __init__(self, name: str, column_names: list[str], column_values: dict[str, list[object]]):
|
|
24
|
+
self.name = name
|
|
25
|
+
self.column_names = column_names
|
|
26
|
+
self.column_values = column_values
|
|
27
|
+
self._n = len(column_names)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_static(self) -> bool:
|
|
31
|
+
"""Helper to quickly identify single-record static metadata."""
|
|
32
|
+
return self._n == 1 and len(self.column_values) == 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HarmonizedTelemetryRecording:
|
|
36
|
+
"""Representation of a complete telemetry recording with metadata and channels."""
|
|
37
|
+
def __init__(self, metadata: Optional[list[HarmonizedMetadataEntry]], channels: list[HarmonizedTelemetryChannel]):
|
|
38
|
+
self.metadata = metadata
|
|
39
|
+
self.channels = channels
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import re
|
|
3
|
+
from io import TextIOWrapper
|
|
4
|
+
|
|
5
|
+
from htf_core.models import HarmonizedTelemetryRecording, HarmonizedMetadataEntry, HarmonizedTelemetryChannel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
METADATA_REGEX = re.compile(
|
|
9
|
+
r"^\[(?P<preamble_content>[^]]+)]"
|
|
10
|
+
r"(?P<data_content>.*)$"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
CHANNEL_REGEX = re.compile(
|
|
14
|
+
r"^\("
|
|
15
|
+
r"(?P<name>[^;]+);"
|
|
16
|
+
r"(?P<unit>[^;]+);"
|
|
17
|
+
r"(?P<frequency>[^;]*);"
|
|
18
|
+
r"(?P<value_count>[^)]+)\)"
|
|
19
|
+
r"(?P<data_content>.*)$"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HtfReader:
|
|
24
|
+
def __init__(self, entries: list[str]):
|
|
25
|
+
self.entries = entries
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_str(cls, text: str):
|
|
29
|
+
content = text.split("\n")
|
|
30
|
+
return cls(content)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_file(cls, file: TextIOWrapper):
|
|
34
|
+
content = file.readlines()
|
|
35
|
+
return cls(content)
|
|
36
|
+
|
|
37
|
+
def read(self) -> HarmonizedTelemetryRecording:
|
|
38
|
+
metadata_entries = []
|
|
39
|
+
telemetry_channels = []
|
|
40
|
+
for entry in self.entries:
|
|
41
|
+
metadata_match = METADATA_REGEX.match(entry)
|
|
42
|
+
if metadata_match:
|
|
43
|
+
metadata_entries.append(self.read_metadata_entry(entry))
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
channel_match = CHANNEL_REGEX.match(entry)
|
|
47
|
+
if channel_match:
|
|
48
|
+
telemetry_channels.append(self.read_telemetry_channel(entry))
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
raise ValueError(f"Entry does not match metadata or channel format: {entry}")
|
|
52
|
+
return HarmonizedTelemetryRecording(
|
|
53
|
+
metadata=metadata_entries if len(metadata_entries) > 0 else None,
|
|
54
|
+
channels=telemetry_channels
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def read_metadata_entry(line: str) -> HarmonizedMetadataEntry:
|
|
59
|
+
match = METADATA_REGEX.match(line)
|
|
60
|
+
if not match:
|
|
61
|
+
raise ValueError(f"Line does not match metadata format: {line}")
|
|
62
|
+
|
|
63
|
+
preamble_content = match.group("preamble_content")
|
|
64
|
+
data_content = match.group("data_content")
|
|
65
|
+
|
|
66
|
+
parts = preamble_content.split(";")
|
|
67
|
+
name = parts[0]
|
|
68
|
+
column_names = parts[1:]
|
|
69
|
+
|
|
70
|
+
column_values = {col_name: [] for col_name in column_names}
|
|
71
|
+
data_values = data_content.split(";")
|
|
72
|
+
for i, value in enumerate(data_values):
|
|
73
|
+
col_name = column_names[i % len(column_names)]
|
|
74
|
+
column_values[col_name].append(value)
|
|
75
|
+
|
|
76
|
+
return HarmonizedMetadataEntry(
|
|
77
|
+
name=name,
|
|
78
|
+
column_names=column_names,
|
|
79
|
+
column_values=column_values,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def read_telemetry_channel(line: str) -> HarmonizedTelemetryChannel:
|
|
84
|
+
match = CHANNEL_REGEX.match(line)
|
|
85
|
+
if not match:
|
|
86
|
+
raise ValueError(f"Line does not match channel format: {line}")
|
|
87
|
+
|
|
88
|
+
name = match.group("name")
|
|
89
|
+
unit = match.group("unit")
|
|
90
|
+
frequency_str = match.group("frequency")
|
|
91
|
+
frequency = int(frequency_str) if frequency_str else None
|
|
92
|
+
total_values = int(match.group("value_count"))
|
|
93
|
+
data_content = match.group("data_content")
|
|
94
|
+
|
|
95
|
+
values = []
|
|
96
|
+
if data_content:
|
|
97
|
+
value_pairs = data_content.split(";")
|
|
98
|
+
for pair in value_pairs:
|
|
99
|
+
index_str, value_str = pair.split("=", 1)
|
|
100
|
+
index = int(index_str)
|
|
101
|
+
value = ast.literal_eval(value_str) if value_str else None
|
|
102
|
+
|
|
103
|
+
# Omit duplicate consecutive values
|
|
104
|
+
if index > 0 and value == values[-1][1]:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
values.append((index, value))
|
|
108
|
+
|
|
109
|
+
return HarmonizedTelemetryChannel(
|
|
110
|
+
name=name,
|
|
111
|
+
unit=unit,
|
|
112
|
+
frequency=frequency,
|
|
113
|
+
total_values=total_values,
|
|
114
|
+
values=values
|
|
115
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from htf_core.models import HarmonizedTelemetryRecording, HarmonizedMetadataEntry, HarmonizedTelemetryChannel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HtfWriter:
|
|
5
|
+
def __init__(self, recording: HarmonizedTelemetryRecording):
|
|
6
|
+
self.recording = recording
|
|
7
|
+
|
|
8
|
+
def serialize(self) -> str:
|
|
9
|
+
lines = []
|
|
10
|
+
if self.recording.metadata:
|
|
11
|
+
for entry in self.recording.metadata:
|
|
12
|
+
lines.append(self.compose_metadata_entry(entry))
|
|
13
|
+
|
|
14
|
+
for channel in self.recording.channels:
|
|
15
|
+
lines.append(self.compose_channel(channel))
|
|
16
|
+
|
|
17
|
+
return "\n".join(lines)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def compose_metadata_entry(entry: HarmonizedMetadataEntry) -> str:
|
|
21
|
+
headers = ";".join(entry.column_names)
|
|
22
|
+
preamble = f"[{entry.name};{headers}]"
|
|
23
|
+
|
|
24
|
+
if not entry.column_names:
|
|
25
|
+
raise "No column names provided"
|
|
26
|
+
|
|
27
|
+
first_column_key = entry.column_names[0]
|
|
28
|
+
num_rows = len(entry.column_values.get(first_column_key, []))
|
|
29
|
+
|
|
30
|
+
row_data_list = []
|
|
31
|
+
|
|
32
|
+
for data_index in range(num_rows):
|
|
33
|
+
current_row_values = []
|
|
34
|
+
|
|
35
|
+
for col_name in entry.column_names:
|
|
36
|
+
column_list = entry.column_values.get(col_name, [])
|
|
37
|
+
|
|
38
|
+
if data_index < len(column_list):
|
|
39
|
+
current_row_values.append(str(column_list[data_index]))
|
|
40
|
+
|
|
41
|
+
row_data_list.append(";".join(current_row_values))
|
|
42
|
+
|
|
43
|
+
data = ";".join(row_data_list)
|
|
44
|
+
|
|
45
|
+
return f"{preamble}{data}"
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def compose_channel(channel: HarmonizedTelemetryChannel) -> str:
|
|
49
|
+
frequency = channel.frequency if channel.frequency is not None else ""
|
|
50
|
+
preamble = f"({channel.name};{channel.unit};{frequency};{channel.total_values})"
|
|
51
|
+
|
|
52
|
+
# Create index-value pairs, omitting repeating values
|
|
53
|
+
value_pairs = [f"{index}={value}"
|
|
54
|
+
for index, value in channel.values
|
|
55
|
+
if index == 0 or value != channel.values[index - 1]]
|
|
56
|
+
|
|
57
|
+
values = ";".join(value_pairs)
|
|
58
|
+
return f"{preamble}{values}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "harmonized-telemetry-format"
|
|
7
|
+
version = "0.0.2a1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Max Schlosser", email="schlosse@hs-mittweida.de" },
|
|
10
|
+
]
|
|
11
|
+
description = "Core library for reading and writing the Harmonized Telemetry Format (HTF)."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
dependencies = []
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
"Homepage" = "https://github.com/Schloool/harmonized-telemetry-format"
|
|
25
|
+
"Bug Tracker" = "https://github.com/Schloool/harmonized-telemetry-format/issues"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
packages = ["htf_core"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
from htf_core.reader import HtfReader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Test(TestCase):
|
|
7
|
+
def test_reads_complete_recording(self):
|
|
8
|
+
entries = [
|
|
9
|
+
"[Metadata;Property1;Property2]Value1;Value2",
|
|
10
|
+
"(ChannelName;Unit;10;3)0=100;1=200;2=300"
|
|
11
|
+
]
|
|
12
|
+
reader = HtfReader(entries=entries)
|
|
13
|
+
|
|
14
|
+
recording = reader.read()
|
|
15
|
+
|
|
16
|
+
self.assertIsNotNone(recording.metadata)
|
|
17
|
+
self.assertEqual(len(recording.metadata), 1)
|
|
18
|
+
self.assertEqual(len(recording.channels), 1)
|
|
19
|
+
|
|
20
|
+
def test_parses_valid_metadata(self):
|
|
21
|
+
valid_metadata_line = "[Metadata;Property1;Property2]Value1;Value2"
|
|
22
|
+
reader = HtfReader(entries=[valid_metadata_line])
|
|
23
|
+
|
|
24
|
+
metadata_entry = reader.read_metadata_entry(valid_metadata_line)
|
|
25
|
+
|
|
26
|
+
self.assertEqual(metadata_entry.name, "Metadata")
|
|
27
|
+
self.assertEqual(metadata_entry.column_names, ["Property1", "Property2"])
|
|
28
|
+
self.assertEqual(metadata_entry.column_values, {
|
|
29
|
+
"Property1": ["Value1"],
|
|
30
|
+
"Property2": ["Value2"]
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
def test_parses_valid_channel(self):
|
|
34
|
+
valid_channel_line = "(ChannelName;Unit;10;5)0=100;1=200;2=300"
|
|
35
|
+
reader = HtfReader(entries=[valid_channel_line])
|
|
36
|
+
|
|
37
|
+
channel = reader.read_telemetry_channel(valid_channel_line)
|
|
38
|
+
|
|
39
|
+
self.assertEqual(channel.name, "ChannelName")
|
|
40
|
+
self.assertEqual(channel.unit, "Unit")
|
|
41
|
+
self.assertEqual(channel.frequency, 10)
|
|
42
|
+
self.assertEqual(channel.total_values, 5)
|
|
43
|
+
self.assertEqual(channel.values, [(0, 100), (1, 200), (2, 300)])
|
|
44
|
+
|
|
45
|
+
def test_emits_duplicate_channel_values(self):
|
|
46
|
+
valid_channel_line = "(ChannelName;Unit;;5)0=100;1=100;2=200;3=200;4=300"
|
|
47
|
+
reader = HtfReader(entries=[valid_channel_line])
|
|
48
|
+
|
|
49
|
+
channel = reader.read_telemetry_channel(valid_channel_line)
|
|
50
|
+
|
|
51
|
+
self.assertIsNone(channel.frequency)
|
|
52
|
+
self.assertEqual(channel.total_values, 5)
|
|
53
|
+
self.assertEqual(channel.values, [(0, 100), (2, 200), (4, 300)])
|
|
54
|
+
|
|
55
|
+
def test_sets_none_values_for_missing_frequency(self):
|
|
56
|
+
channel_line = "(ChannelName;Unit;;3)0=100;1=200;2=300"
|
|
57
|
+
reader = HtfReader(entries=[channel_line])
|
|
58
|
+
|
|
59
|
+
channel = reader.read_telemetry_channel(channel_line)
|
|
60
|
+
|
|
61
|
+
self.assertIsNone(channel.frequency)
|
|
62
|
+
|
|
63
|
+
def test_sets_none_values_for_missing_channel_values(self):
|
|
64
|
+
channel_line = "(ChannelName;Unit;;3)0="
|
|
65
|
+
reader = HtfReader(entries=[channel_line])
|
|
66
|
+
|
|
67
|
+
channel = reader.read_telemetry_channel(channel_line)
|
|
68
|
+
|
|
69
|
+
self.assertEqual(channel.values, [(0, None)])
|
|
70
|
+
|
|
71
|
+
def test_throws_on_invalid_metadata(self):
|
|
72
|
+
invalid_metadata_line = "[InvalidMetadata;Data;Data"
|
|
73
|
+
reader = HtfReader(entries=[invalid_metadata_line])
|
|
74
|
+
|
|
75
|
+
with self.assertRaises(ValueError):
|
|
76
|
+
reader.read_metadata_entry(invalid_metadata_line)
|
|
77
|
+
|
|
78
|
+
def test_throws_on_invalid_channel(self):
|
|
79
|
+
invalid_channel_line = "(InvalidChannel;Unit;Frequency;TotalValues"
|
|
80
|
+
reader = HtfReader(entries=[invalid_channel_line])
|
|
81
|
+
|
|
82
|
+
with self.assertRaises(ValueError):
|
|
83
|
+
reader.read_telemetry_channel(invalid_channel_line)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
from htf_core.models import HarmonizedMetadataEntry
|
|
4
|
+
from htf_core.writer import HtfWriter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Test(TestCase):
|
|
8
|
+
def test_compose_static_metadata_entry(self):
|
|
9
|
+
entry = HarmonizedMetadataEntry(
|
|
10
|
+
name="Static",
|
|
11
|
+
column_names=["Property1"],
|
|
12
|
+
column_values={"Property1": [1]}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
text = HtfWriter.compose_metadata_entry(entry)
|
|
16
|
+
|
|
17
|
+
self.assertEqual(text, "[Static;Property1]1")
|
|
18
|
+
|
|
19
|
+
def test_compose_dimensional_metadata_entry(self):
|
|
20
|
+
entry = HarmonizedMetadataEntry(
|
|
21
|
+
name="Dimensions",
|
|
22
|
+
column_names=["Dim1", "Dim2"],
|
|
23
|
+
column_values={
|
|
24
|
+
"Dim1": [10, 20],
|
|
25
|
+
"Dim2": ["A", "B"]
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
text = HtfWriter.compose_metadata_entry(entry)
|
|
30
|
+
|
|
31
|
+
self.assertEqual(text, "[Dimensions;Dim1;Dim2]10;A;20;B")
|