tts-dictionary-interface 0.2.3__py3-none-any.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.
- tts_dictionary_interface/__init__.py +0 -0
- tts_dictionary_interface/base.py +244 -0
- tts_dictionary_interface/test/__init__.py +0 -0
- tts_dictionary_interface/test/conftest.py +0 -0
- tts_dictionary_interface/test/test_base.py +172 -0
- tts_dictionary_interface-0.2.3.dist-info/METADATA +64 -0
- tts_dictionary_interface-0.2.3.dist-info/RECORD +9 -0
- tts_dictionary_interface-0.2.3.dist-info/WHEEL +5 -0
- tts_dictionary_interface-0.2.3.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import pdb
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
from lxml import etree
|
|
5
|
+
|
|
6
|
+
from tts_utilities.logger import create_logger
|
|
7
|
+
logger = create_logger('semantic_dictionary')
|
|
8
|
+
|
|
9
|
+
#Requirements:
|
|
10
|
+
# Commands
|
|
11
|
+
# Every project needs to have a command dictioanry
|
|
12
|
+
# Every command class needs a stem attr
|
|
13
|
+
# Every command class needs an opcode attr
|
|
14
|
+
# Every command class needs an opscat attr
|
|
15
|
+
# Every command class needs an args attr that is an ArgumentList
|
|
16
|
+
# Every command argument needs a name attr
|
|
17
|
+
# Every command argument needs a units attr
|
|
18
|
+
# Every command argument needs a way to understand length
|
|
19
|
+
# Every command argument needs a min attr (is this true for all? or just for numeric?)
|
|
20
|
+
# Every command argument needs a max attr (is this true for all? or just for numeric?)
|
|
21
|
+
# Channels
|
|
22
|
+
# Every project needs to have a channel dictioanary
|
|
23
|
+
# Every channel class needs an measurement_id attr
|
|
24
|
+
# Every channel class needs a channel_id attr
|
|
25
|
+
# Every channel class needs a channel_name attr
|
|
26
|
+
# Every channel class needs an opscat attr
|
|
27
|
+
# Every channel class needs to have a type attr
|
|
28
|
+
# Needs to be translated to chill type (e.g. naive dn/eu/status/dnString)
|
|
29
|
+
|
|
30
|
+
class SemanticDictionary:
|
|
31
|
+
"""
|
|
32
|
+
A base reading AMPCS dictionaries (or really any XML file) more Pythonically.
|
|
33
|
+
|
|
34
|
+
This class is a bit obtuse, so please take a look at DemoSat's implementation in
|
|
35
|
+
tts-demosat/demosat_dictionary_interface for an example of how this looks when
|
|
36
|
+
realized against an actual space mission.
|
|
37
|
+
|
|
38
|
+
This class provides a dictionary-like interface to an XML tree. It allows retrieval of
|
|
39
|
+
items via specific unique IDs defined in the subclass configuration, and dynamic
|
|
40
|
+
attribute access via configured XPaths.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
ITEM_XPATHS (list): A list of XPath strings used to find items. An item in this context is
|
|
44
|
+
the primary human-parseable artifact in the XML. (e.g. for command.xml, it's a command). If
|
|
45
|
+
you extend SemanticDictionary and do "for x in SemanticDictionaryExtension: x", you will
|
|
46
|
+
iterate through these "items""
|
|
47
|
+
ITEM_HUMAN_UNIQUE_IDS (list): The expected human-readable ID for items
|
|
48
|
+
ITEM_CLASSES (list): The class to pass items ETREE elements to.
|
|
49
|
+
ATTR_XPATHS (dict): A mapping of XML xpaths to Pyhton attributes
|
|
50
|
+
"""
|
|
51
|
+
ITEM_XPATHS = []
|
|
52
|
+
ITEM_HUMAN_UNIQUE_IDS = []
|
|
53
|
+
ITEM_CLASSES = []
|
|
54
|
+
ATTR_XPATHS = {}
|
|
55
|
+
|
|
56
|
+
def __init__(self, source):
|
|
57
|
+
"""
|
|
58
|
+
Initialize the SemanticDictionary with a source XML.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
source (str, os.PathLike, etree._ElementTree, or etree.Element): The source of the
|
|
62
|
+
XML data. This can be a file path string, a Path object, a full ElementTree,
|
|
63
|
+
or a specific Element node. This library is meant to be called recursively,
|
|
64
|
+
so this might be the file passed by the user or it might be an ETREE Element
|
|
65
|
+
passed by another SemanticDictionary
|
|
66
|
+
"""
|
|
67
|
+
# Check if source is a string OR a Path object (os.PathLike covers pathlib)
|
|
68
|
+
if isinstance(source, (str, os.PathLike)):
|
|
69
|
+
# Force conversion to string for lxml.parse, just to be safe
|
|
70
|
+
tree = etree.parse(str(source))
|
|
71
|
+
self.etree = tree.getroot()
|
|
72
|
+
elif isinstance(source, etree._ElementTree):
|
|
73
|
+
self.etree = source.getroot()
|
|
74
|
+
else:
|
|
75
|
+
# Assume it is already an lxml Element
|
|
76
|
+
self.etree = source
|
|
77
|
+
|
|
78
|
+
def xpath(self, xpath):
|
|
79
|
+
"""
|
|
80
|
+
Run a raw XPath query against the root element of this dictionary. Literally just
|
|
81
|
+
a passthrough of etree.xpath so you can get to any part of the XML you want below
|
|
82
|
+
this node.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
xpath (str): The XPath query string to execute.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
list: A list of lxml elements or values matching the query.
|
|
89
|
+
"""
|
|
90
|
+
return self.etree.xpath(xpath)
|
|
91
|
+
|
|
92
|
+
def __getitem__(self, item):
|
|
93
|
+
"""
|
|
94
|
+
Retrieve a specific item from the dictionary by its unique identifier.
|
|
95
|
+
|
|
96
|
+
This iterates through the configured ITEM_XPATHS and ITEM_HUMAN_UNIQUE_IDS. It constructs
|
|
97
|
+
an XPath query to find the element where the unique ID attribute matches the provided item.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
item (str): The unique identifier value (e.g., a command stem or channel ID) to search for.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
object: An instance of the corresponding class from ITEM_CLASSES wrapping the found element.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
Exception: If no elements are found matching the identifier.
|
|
107
|
+
Exception: If more than one element is found matching the identifier.
|
|
108
|
+
"""
|
|
109
|
+
elements = []
|
|
110
|
+
xpaths = []
|
|
111
|
+
|
|
112
|
+
for itemxpath, itemid, itemclass in zip(self.ITEM_XPATHS, self.ITEM_HUMAN_UNIQUE_IDS, self.ITEM_CLASSES):
|
|
113
|
+
# 1. Build the condition list (Handles keys like 'name|abbreviation')
|
|
114
|
+
conditions = []
|
|
115
|
+
for attr in itemid.split('|'):
|
|
116
|
+
attr = attr.strip()
|
|
117
|
+
|
|
118
|
+
# Special handling: Look at text content vs attributes
|
|
119
|
+
if attr == 'text()':
|
|
120
|
+
conditions.append(f'text()="{item}"')
|
|
121
|
+
else:
|
|
122
|
+
conditions.append(f'@{attr}="{item}"')
|
|
123
|
+
|
|
124
|
+
# 2. Join with OR and wrap in brackets: [name="X" or abbreviation="X"]
|
|
125
|
+
attr_def = '[' + ' or '.join(conditions) + ']'
|
|
126
|
+
|
|
127
|
+
# 3. Execute Query
|
|
128
|
+
xpath_query = f'{itemxpath}{attr_def}'
|
|
129
|
+
xpaths.append(xpath_query)
|
|
130
|
+
elements += [itemclass(x) for x in self.etree.xpath(xpath_query)]
|
|
131
|
+
|
|
132
|
+
if len(elements) == 0:
|
|
133
|
+
raise Exception(f'No elements found with value "{item}" on xpath(s): {xpaths}')
|
|
134
|
+
if len(elements) > 1:
|
|
135
|
+
raise Exception(f'More than one element found with value "{item}" on xpath(s): {xpaths}')
|
|
136
|
+
|
|
137
|
+
return elements[0]
|
|
138
|
+
|
|
139
|
+
def __getattr__(self, attr):
|
|
140
|
+
"""
|
|
141
|
+
Retrieve a dynamic attribute or a standard XML attribute.
|
|
142
|
+
|
|
143
|
+
If the attribute is defined in ATTR_XPATHS, the configured XPath is executed.
|
|
144
|
+
Otherwise, it attempts to get the value as a standard attribute of the XML root.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
attr (str): The name of the attribute to retrieve.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Any: The wrapped object, text content, or raw element if defined in ATTR_XPATHS;
|
|
151
|
+
otherwise, the string value of the XML attribute on the root element.
|
|
152
|
+
"""
|
|
153
|
+
if attr in self.ATTR_XPATHS.keys():
|
|
154
|
+
config = self.ATTR_XPATHS[attr]
|
|
155
|
+
xpath = config[0]
|
|
156
|
+
is_text = config[1]
|
|
157
|
+
attr_class = config[2]
|
|
158
|
+
# Safely get the 4th argument, default to False
|
|
159
|
+
return_list = config[3] if len(config) > 3 else False
|
|
160
|
+
|
|
161
|
+
element_list = self.etree.xpath(xpath)
|
|
162
|
+
|
|
163
|
+
# Helper to safely extract value/text from a single result
|
|
164
|
+
def resolve_value(item):
|
|
165
|
+
if attr_class is not None:
|
|
166
|
+
return attr_class(item)
|
|
167
|
+
|
|
168
|
+
# If we asked for text, but got an Element, get .text
|
|
169
|
+
if is_text and hasattr(item, 'text'):
|
|
170
|
+
return item.text
|
|
171
|
+
|
|
172
|
+
# If we asked for text, but item is already a string (because xpath ended in /text()), return it
|
|
173
|
+
if is_text and isinstance(item, str):
|
|
174
|
+
return item
|
|
175
|
+
|
|
176
|
+
# Otherwise return the raw item (Element or String)
|
|
177
|
+
return item
|
|
178
|
+
|
|
179
|
+
# --- 1. Nothing Found ---
|
|
180
|
+
if len(element_list) == 0:
|
|
181
|
+
# If expecting a list, return empty list. Otherwise None.
|
|
182
|
+
return [] if return_list else None
|
|
183
|
+
|
|
184
|
+
# --- 2. List Requested ---
|
|
185
|
+
if return_list:
|
|
186
|
+
return [resolve_value(e) for e in element_list]
|
|
187
|
+
|
|
188
|
+
# --- 3. Single Item Requested ---
|
|
189
|
+
return resolve_value(element_list[0])
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
return self.etree.get(attr)
|
|
193
|
+
|
|
194
|
+
def __iter__(self):
|
|
195
|
+
"""
|
|
196
|
+
Iterate over all configured items in the dictionary.
|
|
197
|
+
|
|
198
|
+
This method chains together all elements found by the configured ITEM_XPATHS,
|
|
199
|
+
wrapping them in their respective ITEM_CLASSES.
|
|
200
|
+
|
|
201
|
+
Yields:
|
|
202
|
+
object: Instances of the item classes defined in the configuration.
|
|
203
|
+
"""
|
|
204
|
+
elements = []
|
|
205
|
+
for itemxpath, itemclass in zip(self.ITEM_XPATHS, self.ITEM_CLASSES):
|
|
206
|
+
elements += [itemclass(x) for x in self.etree.xpath(itemxpath)]
|
|
207
|
+
for e in elements: yield e
|
|
208
|
+
|
|
209
|
+
def __len__(self):
|
|
210
|
+
# Return the number of items in the members list
|
|
211
|
+
return len([x for x in self])
|
|
212
|
+
|
|
213
|
+
def __contains__(self, key):
|
|
214
|
+
"""
|
|
215
|
+
Enables 'if "KEY" in dictionary' syntax.
|
|
216
|
+
|
|
217
|
+
Iterates through the configured ITEM_XPATHS and ITEM_HUMAN_UNIQUE_IDS
|
|
218
|
+
to checks if an element with the given key exists.
|
|
219
|
+
"""
|
|
220
|
+
# Zip together the XPath locations and the Attribute IDs defining "uniqueness"
|
|
221
|
+
for itemxpath, itemid in zip(self.ITEM_XPATHS, self.ITEM_HUMAN_UNIQUE_IDS):
|
|
222
|
+
|
|
223
|
+
# 1. Build the condition list (Handles keys like 'name|abbreviation')
|
|
224
|
+
conditions = []
|
|
225
|
+
for attr in itemid.split('|'):
|
|
226
|
+
attr = attr.strip()
|
|
227
|
+
|
|
228
|
+
# Special handling: Look at text content vs attributes
|
|
229
|
+
if attr == 'text()':
|
|
230
|
+
conditions.append(f'text()="{key}"')
|
|
231
|
+
else:
|
|
232
|
+
conditions.append(f'@{attr}="{key}"')
|
|
233
|
+
|
|
234
|
+
# 2. Join with OR: [name="X" or abbreviation="X"]
|
|
235
|
+
predicate = " or ".join(conditions)
|
|
236
|
+
|
|
237
|
+
# 3. Construct full query: e.g. telemetry_definitions/telemetry[@name="X"]
|
|
238
|
+
query = f"{itemxpath}[{predicate}]"
|
|
239
|
+
|
|
240
|
+
# 4. Check existence (xpath returns a list; empty list is False)
|
|
241
|
+
if self.etree.xpath(query):
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
from lxml import etree
|
|
5
|
+
|
|
6
|
+
# --- MOCKING EXTERNAL DEPENDENCIES ---
|
|
7
|
+
# We mock tts_utilities before importing the module to avoid ModuleNotFoundError
|
|
8
|
+
sys.modules['tts_utilities'] = MagicMock()
|
|
9
|
+
sys.modules['tts_utilities.logger'] = MagicMock()
|
|
10
|
+
|
|
11
|
+
# Assuming the user's code is in a file named `semantic_dictionary.py`
|
|
12
|
+
# If you are pasting this into a single file, put the class definition above this line.
|
|
13
|
+
from tts_dictionary_interface.base import SemanticDictionary
|
|
14
|
+
|
|
15
|
+
# --- FIXTURES AND SETUP ---
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def sample_xml():
|
|
19
|
+
"""Creates a temporary sample XML string for testing."""
|
|
20
|
+
return b"""
|
|
21
|
+
<Project name="TestProject" version="1.0">
|
|
22
|
+
<Commands>
|
|
23
|
+
<Command stem="CMD_A" opcode="0x01" opscat="flight">
|
|
24
|
+
<Args>
|
|
25
|
+
<Arg name="arg1" units="sec" min="0" max="10"/>
|
|
26
|
+
</Args>
|
|
27
|
+
</Command>
|
|
28
|
+
<Command stem="CMD_B" opcode="0x02" opscat="ground">
|
|
29
|
+
<Args>
|
|
30
|
+
<Arg name="arg2" units="m/s"/>
|
|
31
|
+
</Args>
|
|
32
|
+
</Command>
|
|
33
|
+
<Command stem="CMD_DUP" opcode="0x03" />
|
|
34
|
+
<Command stem="CMD_DUP" opcode="0x04" />
|
|
35
|
+
</Commands>
|
|
36
|
+
<Channels>
|
|
37
|
+
<Channel id="100" name="ChanA" type="float"/>
|
|
38
|
+
</Channels>
|
|
39
|
+
<Metadata>
|
|
40
|
+
<Description>A test dictionary</Description>
|
|
41
|
+
</Metadata>
|
|
42
|
+
</Project>
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
class CommandWrapper:
|
|
46
|
+
"""A dummy wrapper class to test ITEM_CLASSES instantiation."""
|
|
47
|
+
def __init__(self, element):
|
|
48
|
+
self.element = element
|
|
49
|
+
self.stem = element.get('stem')
|
|
50
|
+
|
|
51
|
+
class ConcreteDictionary(SemanticDictionary):
|
|
52
|
+
"""
|
|
53
|
+
A concrete implementation of the base class.
|
|
54
|
+
We must populate the configuration constants to test the logic.
|
|
55
|
+
"""
|
|
56
|
+
# Look for Commands, identify them by 'stem' or 'opcode', wrap them in CommandWrapper
|
|
57
|
+
ITEM_XPATHS = ['.//Command']
|
|
58
|
+
ITEM_HUMAN_UNIQUE_IDS = ['stem|opcode']
|
|
59
|
+
ITEM_CLASSES = [CommandWrapper]
|
|
60
|
+
|
|
61
|
+
# Map 'description' to the text of the Description tag
|
|
62
|
+
# Map 'project_name' to the 'name' attribute of the root (via fallback or XPath)
|
|
63
|
+
ATTR_XPATHS = {
|
|
64
|
+
'description': ('.//Description', True, None), # (xpath, is_text, class)
|
|
65
|
+
'all_args': ('.//Arg', False, None) # Return raw element list (or first element)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def concrete_dict(tmp_path, sample_xml):
|
|
70
|
+
"""Creates an instance of the ConcreteDictionary using a temporary file."""
|
|
71
|
+
f = tmp_path / "test_dict.xml"
|
|
72
|
+
f.write_bytes(sample_xml)
|
|
73
|
+
return ConcreteDictionary(str(f))
|
|
74
|
+
|
|
75
|
+
# --- TEST CASES ---
|
|
76
|
+
|
|
77
|
+
class TestInitialization:
|
|
78
|
+
def test_init_with_path(self, tmp_path, sample_xml):
|
|
79
|
+
f = tmp_path / "test.xml"
|
|
80
|
+
f.write_bytes(sample_xml)
|
|
81
|
+
sd = SemanticDictionary(str(f))
|
|
82
|
+
assert isinstance(sd.etree, etree._Element)
|
|
83
|
+
|
|
84
|
+
def test_init_with_element(self, sample_xml):
|
|
85
|
+
root = etree.fromstring(sample_xml)
|
|
86
|
+
sd = SemanticDictionary(root)
|
|
87
|
+
assert sd.etree == root
|
|
88
|
+
|
|
89
|
+
def test_init_invalid_path(self):
|
|
90
|
+
with pytest.raises(OSError):
|
|
91
|
+
SemanticDictionary("non_existent_file.xml")
|
|
92
|
+
|
|
93
|
+
class TestGetItem:
|
|
94
|
+
"""Tests for __getitem__ (e.g., dict['key'])"""
|
|
95
|
+
|
|
96
|
+
def test_get_item_by_stem_success(self, concrete_dict):
|
|
97
|
+
# Should find CMD_A using the 'stem' attribute defined in ITEM_HUMAN_UNIQUE_IDS
|
|
98
|
+
item = concrete_dict['CMD_A']
|
|
99
|
+
assert isinstance(item, CommandWrapper)
|
|
100
|
+
assert item.element.get('opcode') == '0x01'
|
|
101
|
+
|
|
102
|
+
def test_get_item_by_opcode_success(self, concrete_dict):
|
|
103
|
+
# Should find CMD_B using the 'opcode' attribute (second part of the split)
|
|
104
|
+
item = concrete_dict['0x02']
|
|
105
|
+
assert item.stem == 'CMD_B'
|
|
106
|
+
|
|
107
|
+
def test_get_item_not_found(self, concrete_dict):
|
|
108
|
+
with pytest.raises(Exception) as excinfo:
|
|
109
|
+
_ = concrete_dict['NON_EXISTENT']
|
|
110
|
+
assert "No elements found" in str(excinfo.value)
|
|
111
|
+
|
|
112
|
+
def test_get_item_multiple_found(self, concrete_dict):
|
|
113
|
+
# CMD_DUP exists twice in the XML
|
|
114
|
+
with pytest.raises(Exception) as excinfo:
|
|
115
|
+
_ = concrete_dict['CMD_DUP']
|
|
116
|
+
assert "More than one element found" in str(excinfo.value)
|
|
117
|
+
|
|
118
|
+
class TestGetAttr:
|
|
119
|
+
"""Tests for __getattr__ (e.g., dict.attr)"""
|
|
120
|
+
|
|
121
|
+
def test_getattr_configured_xpath_text(self, concrete_dict):
|
|
122
|
+
# 'description' is mapped to .//Description text
|
|
123
|
+
assert concrete_dict.description == "A test dictionary"
|
|
124
|
+
|
|
125
|
+
def test_getattr_configured_xpath_element(self, concrete_dict):
|
|
126
|
+
# 'all_args' is mapped to .//Arg, is_text=False
|
|
127
|
+
# It should return the first matching element
|
|
128
|
+
arg = concrete_dict.all_args
|
|
129
|
+
assert isinstance(arg, etree._Element)
|
|
130
|
+
assert arg.get('name') == 'arg1'
|
|
131
|
+
|
|
132
|
+
def test_getattr_fallback_to_root_attr(self, concrete_dict):
|
|
133
|
+
# 'version' is NOT in ATTR_XPATHS, so it should look at root attributes
|
|
134
|
+
assert concrete_dict.version == "1.0"
|
|
135
|
+
|
|
136
|
+
def test_getattr_not_found_in_config(self, concrete_dict):
|
|
137
|
+
# If xpath exists in config but finds nothing in XML
|
|
138
|
+
# We need to temporarily modify the ATTR_XPATHS for this specific instance/class test
|
|
139
|
+
# or rely on a specific test case.
|
|
140
|
+
# Let's try accessing a root attribute that doesn't exist
|
|
141
|
+
assert concrete_dict.non_existent_attr is None
|
|
142
|
+
|
|
143
|
+
def test_getattr_xpath_no_elements_returns_none(self, concrete_dict):
|
|
144
|
+
# We temporarily add a broken configuration to the class
|
|
145
|
+
ConcreteDictionary.ATTR_XPATHS['broken'] = ('.//NonExistentTag', True, None)
|
|
146
|
+
|
|
147
|
+
# Access it
|
|
148
|
+
val = concrete_dict.broken
|
|
149
|
+
|
|
150
|
+
# ASSERT: It should just return None gracefully (no crash)
|
|
151
|
+
# We removed the logger check because your implementation doesn't log anymore.
|
|
152
|
+
assert val is None
|
|
153
|
+
|
|
154
|
+
class TestIteration:
|
|
155
|
+
"""Tests for __iter__"""
|
|
156
|
+
|
|
157
|
+
def test_iter_yields_wrappers(self, concrete_dict):
|
|
158
|
+
items = list(concrete_dict)
|
|
159
|
+
# We expect 4 commands total in the sample XML (CMD_A, CMD_B, and 2x CMD_DUP)
|
|
160
|
+
assert len(items) == 4
|
|
161
|
+
assert all(isinstance(x, CommandWrapper) for x in items)
|
|
162
|
+
|
|
163
|
+
stems = [x.stem for x in items]
|
|
164
|
+
assert "CMD_A" in stems
|
|
165
|
+
assert "CMD_B" in stems
|
|
166
|
+
|
|
167
|
+
class TestXPathHelper:
|
|
168
|
+
def test_xpath_method(self, concrete_dict):
|
|
169
|
+
# Testing the wrapper method .xpath()
|
|
170
|
+
results = concrete_dict.xpath('.//Command')
|
|
171
|
+
assert len(results) == 4
|
|
172
|
+
assert isinstance(results[0], etree._Element)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tts-dictionary-interface
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Core code for developing Python AMPCS dictionary interfaces.
|
|
5
|
+
Author: muszynsk
|
|
6
|
+
Author-email: muszynsk <matt.muszynski@jpl.nasa.gov>
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/NASA-JPL-Teamtools-Studio/tts_dictionary_interface
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: lxml
|
|
14
|
+
Requires-Dist: pytest
|
|
15
|
+
Requires-Dist: tomli
|
|
16
|
+
Requires-Dist: tts-utilities<=1.0.0,>=0.2.2
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
|
|
20
|
+
# Teamtools Dictionary Interface
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
## About Teamtools Studio
|
|
25
|
+
|
|
26
|
+
Teamtools Studio Utilities is part of JPL's Teamtools Studio (TTS).
|
|
27
|
+
|
|
28
|
+
TTS is an effort originated in JPL's Planning and Execution section to centralize shared repositories across missions. This benefits JPL by reducing cost through reducing duplicated code, collaborating across missions, and unifying standards for development and design across JPL.
|
|
29
|
+
|
|
30
|
+
Although Planning and Execution is primarily concerned with flight operations, the TTS suite has been generalized and atomized to the point where many of these tools are applicable during other mission phases and even in non-spaceflight contexts. Through our work flying space missions, we hope to provide tools to the open source community that have utility in data analysis or planning for any complex system where failure is not an option.
|
|
31
|
+
|
|
32
|
+
For more infomation on how to contribute, and how these libraries form a complete ecosystem for high reliability data analysis, see the [Full TTS Documentation](https://nasa-jpl-teamtools-studio.github.io/teamtools_documentation/).
|
|
33
|
+
|
|
34
|
+
## What is Teamtools Dictionary Interface?
|
|
35
|
+
|
|
36
|
+
### Overview
|
|
37
|
+
|
|
38
|
+
The TTS Dicitonary Interface is an interoperability solution to ensure that other TTS libraries (Dexter, Tower, Dante, TTS Seq, FRESH) can
|
|
39
|
+
make common assumptions about how to retreive the metadata on spacraft commands and telemetry they need in order to appropriately interpret it.
|
|
40
|
+
This helps them answer questions like "is this arugment value valid?" and "what are the units for this telemetry point?".
|
|
41
|
+
|
|
42
|
+
Dictionaries are a key part of the Flight Ground Interface for any spacecraft. They containing the information needed to translate human-readable commands and arugments to binary files that make sense to a spacecraft's command and sequencing flight software and the to take the stream of
|
|
43
|
+
ones and zeros returned from the spacecraft back into human-readable telemetry back on the ground. They are the Rosetta Stone of any space mission.
|
|
44
|
+
|
|
45
|
+
While TTS Dictionary Interface is built around AMPCS's XML dictionary schema, it can ostensibly be used with any format of dictionary so long
|
|
46
|
+
as it can be made into a Pyhton class that behaves the same way that the SemanticDictionary does. More than anything this is an interoperability
|
|
47
|
+
layer.
|
|
48
|
+
|
|
49
|
+
For projects that do not use AMPCS, the easiest way to leverage this library is likely to write some middleware to turn your dictionary format
|
|
50
|
+
into the kind of XML that would be required if you were using AMPCS. Short of that, it is likely that the only other solution is to write
|
|
51
|
+
a completely new class that looks to downstream TTS modules as if it were generated by TTS Dictionary Interface. This is likely much harder
|
|
52
|
+
to validate than simply converting to XML and validating against the multimisison AMPCS schema shared in the DemoSat extension of this library.
|
|
53
|
+
|
|
54
|
+
### Projects Currently Supported
|
|
55
|
+
|
|
56
|
+
* DemoSat
|
|
57
|
+
* Orbiting Carbon Observatory 2 (OCO-2)
|
|
58
|
+
* NISAR
|
|
59
|
+
|
|
60
|
+
## Architecture
|
|
61
|
+
|
|
62
|
+
### TTS dependencies
|
|
63
|
+
|
|
64
|
+
* TTS Utilities
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
tts_dictionary_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tts_dictionary_interface/base.py,sha256=wSlfvspI4aNqlCabaFF417LG-ss0YM_7nIUsVy07CyQ,10172
|
|
3
|
+
tts_dictionary_interface/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
tts_dictionary_interface/test/conftest.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
tts_dictionary_interface/test/test_base.py,sha256=qs7BK9_xMDJSJOhVG1tOfQ4AgUzSNiqkVK-KQhNfEeU,6557
|
|
6
|
+
tts_dictionary_interface-0.2.3.dist-info/METADATA,sha256=ucgYDLUclmA8OT4ogYUI2TrkmqaGIq7yCvbTWC3QE6M,3801
|
|
7
|
+
tts_dictionary_interface-0.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
tts_dictionary_interface-0.2.3.dist-info/top_level.txt,sha256=irz1oGRGtuTqgAI3v5XIQNQ_HcrqNQ46G3atDGfpG48,25
|
|
9
|
+
tts_dictionary_interface-0.2.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tts_dictionary_interface
|