tts-dictionary-interface 0.2.3__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.
- tts_dictionary_interface-0.2.3/.gitignore +188 -0
- tts_dictionary_interface-0.2.3/PKG-INFO +64 -0
- tts_dictionary_interface-0.2.3/README.md +45 -0
- tts_dictionary_interface-0.2.3/ci/sphinx_ignore_tags.txt +20 -0
- tts_dictionary_interface-0.2.3/docs/_static/bugfix.css +12 -0
- tts_dictionary_interface-0.2.3/docs/conf.py +71 -0
- tts_dictionary_interface-0.2.3/docs/foo.rst +5 -0
- tts_dictionary_interface-0.2.3/docs/index.rst +15 -0
- tts_dictionary_interface-0.2.3/license.md +14 -0
- tts_dictionary_interface-0.2.3/pyproject.toml +40 -0
- tts_dictionary_interface-0.2.3/setup.cfg +4 -0
- tts_dictionary_interface-0.2.3/setup.py +8 -0
- tts_dictionary_interface-0.2.3/src/__init__.py +0 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/__init__.py +0 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/base.py +244 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/__init__.py +0 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/conftest.py +0 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/test_base.py +172 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/PKG-INFO +64 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/SOURCES.txt +21 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/dependency_links.txt +1 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/requires.txt +4 -0
- tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
docsrc/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
.python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# poetry
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
104
|
+
#poetry.lock
|
|
105
|
+
|
|
106
|
+
# pdm
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
108
|
+
#pdm.lock
|
|
109
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
110
|
+
# in version control.
|
|
111
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
112
|
+
.pdm.toml
|
|
113
|
+
|
|
114
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
115
|
+
__pypackages__/
|
|
116
|
+
|
|
117
|
+
# Celery stuff
|
|
118
|
+
celerybeat-schedule
|
|
119
|
+
celerybeat.pid
|
|
120
|
+
|
|
121
|
+
# SageMath parsed files
|
|
122
|
+
*.sage.py
|
|
123
|
+
|
|
124
|
+
# Environments
|
|
125
|
+
.env
|
|
126
|
+
.venv
|
|
127
|
+
env/
|
|
128
|
+
venv/
|
|
129
|
+
ENV/
|
|
130
|
+
env.bak/
|
|
131
|
+
venv.bak/
|
|
132
|
+
|
|
133
|
+
# Spyder project settings
|
|
134
|
+
.spyderproject
|
|
135
|
+
.spyproject
|
|
136
|
+
|
|
137
|
+
# Rope project settings
|
|
138
|
+
.ropeproject
|
|
139
|
+
|
|
140
|
+
# mkdocs documentation
|
|
141
|
+
/site
|
|
142
|
+
|
|
143
|
+
# mypy
|
|
144
|
+
.mypy_cache/
|
|
145
|
+
.dmypy.json
|
|
146
|
+
dmypy.json
|
|
147
|
+
|
|
148
|
+
# Pyre type checker
|
|
149
|
+
.pyre/
|
|
150
|
+
|
|
151
|
+
# pytype static type analyzer
|
|
152
|
+
.pytype/
|
|
153
|
+
|
|
154
|
+
# Cython debug symbols
|
|
155
|
+
cython_debug/
|
|
156
|
+
|
|
157
|
+
# PyCharm
|
|
158
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
159
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
160
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
161
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
162
|
+
#.idea/
|
|
163
|
+
|
|
164
|
+
env
|
|
165
|
+
venv
|
|
166
|
+
|
|
167
|
+
# IDEs and editors
|
|
168
|
+
.idea/
|
|
169
|
+
.vscode/
|
|
170
|
+
*.sublime-project
|
|
171
|
+
*.sublime-workspace
|
|
172
|
+
|
|
173
|
+
# Jupyter notebooks
|
|
174
|
+
.ipynb_checkpoints/
|
|
175
|
+
|
|
176
|
+
# Jupyter lab workspace
|
|
177
|
+
.jupyter/
|
|
178
|
+
|
|
179
|
+
# Miscellaneous
|
|
180
|
+
.DS_Store
|
|
181
|
+
Thumbs.db
|
|
182
|
+
._*
|
|
183
|
+
|
|
184
|
+
# office tmp files
|
|
185
|
+
~$*
|
|
186
|
+
|
|
187
|
+
#nfs temp files
|
|
188
|
+
.nfs*
|
|
@@ -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,45 @@
|
|
|
1
|
+
# Teamtools Dictionary Interface
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
## About Teamtools Studio
|
|
6
|
+
|
|
7
|
+
Teamtools Studio Utilities is part of JPL's Teamtools Studio (TTS).
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
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/).
|
|
14
|
+
|
|
15
|
+
## What is Teamtools Dictionary Interface?
|
|
16
|
+
|
|
17
|
+
### Overview
|
|
18
|
+
|
|
19
|
+
The TTS Dicitonary Interface is an interoperability solution to ensure that other TTS libraries (Dexter, Tower, Dante, TTS Seq, FRESH) can
|
|
20
|
+
make common assumptions about how to retreive the metadata on spacraft commands and telemetry they need in order to appropriately interpret it.
|
|
21
|
+
This helps them answer questions like "is this arugment value valid?" and "what are the units for this telemetry point?".
|
|
22
|
+
|
|
23
|
+
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
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
While TTS Dictionary Interface is built around AMPCS's XML dictionary schema, it can ostensibly be used with any format of dictionary so long
|
|
27
|
+
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
|
|
28
|
+
layer.
|
|
29
|
+
|
|
30
|
+
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
|
|
31
|
+
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
|
|
32
|
+
a completely new class that looks to downstream TTS modules as if it were generated by TTS Dictionary Interface. This is likely much harder
|
|
33
|
+
to validate than simply converting to XML and validating against the multimisison AMPCS schema shared in the DemoSat extension of this library.
|
|
34
|
+
|
|
35
|
+
### Projects Currently Supported
|
|
36
|
+
|
|
37
|
+
* DemoSat
|
|
38
|
+
* Orbiting Carbon Observatory 2 (OCO-2)
|
|
39
|
+
* NISAR
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
### TTS dependencies
|
|
44
|
+
|
|
45
|
+
* TTS Utilities
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#When tts-build-sphinx-docs runs, it will collect all tags and branches and attempt to build
|
|
2
|
+
#docs for each in the gh-pages branch. This is a config file that allows developers to
|
|
3
|
+
#specifically exclude any branches or tags they need. Each line is the name of a tag or
|
|
4
|
+
#branch.
|
|
5
|
+
#
|
|
6
|
+
#This file makes no attemtpt to tell whether a line is a tag or a branch as TTS assumes that
|
|
7
|
+
#the naming conventions will always be different, and that tags should be tied to releases,
|
|
8
|
+
#so they should always have semvar names, whereas branches should be more descriptive using
|
|
9
|
+
#typical agile branch naming conventions.
|
|
10
|
+
#
|
|
11
|
+
#The most typical use case is to exclude tags that were created before you added the docs
|
|
12
|
+
#prerequisites, or to exclude the gh-pages branch itself since no code is expected to live
|
|
13
|
+
#there. All TTS repos are expected to have a gh-pages branch, but it is expected that it
|
|
14
|
+
#will always be managed by tts-build-sphinx-docs. You do not need to manually create
|
|
15
|
+
#a gh-pages branch because tts-build-sphinx-docs will create one if it does not exist.
|
|
16
|
+
#
|
|
17
|
+
#tts-build-sphinx-docs will also always remove all files from gh-pages and rebuild all docs
|
|
18
|
+
#from scratch
|
|
19
|
+
v1.0.1
|
|
20
|
+
gh-pages
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Resolves a bug where the text in the header
|
|
3
|
+
"[package name] [package version] documentation"
|
|
4
|
+
was getting stacked up on top of itself when the variable portions were too long. Something with floats
|
|
5
|
+
and elements not taking up the whole width of their contents.
|
|
6
|
+
|
|
7
|
+
Needed to add it because setuptools-scm version names can be very long, especially for versions that
|
|
8
|
+
are build off of a commit with no tag
|
|
9
|
+
*/
|
|
10
|
+
.navbar-header-items__start {
|
|
11
|
+
width: auto;
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# TTS-specifc add ons
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from setuptools_scm import get_version
|
|
5
|
+
import toml
|
|
6
|
+
|
|
7
|
+
# Configuration file for the Sphinx documentation builder.
|
|
8
|
+
#
|
|
9
|
+
# This file only contains a selection of the most common options. For a full
|
|
10
|
+
# list see the documentation:
|
|
11
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
12
|
+
|
|
13
|
+
# -- Path setup --------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
# If extensions (or modules to document with autodoc) are in another directory,
|
|
16
|
+
# add these directories to sys.path here. If the directory is relative to the
|
|
17
|
+
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import importlib.metadata
|
|
23
|
+
sys.path.insert(0, os.path.abspath('..'))
|
|
24
|
+
|
|
25
|
+
# -- Project information -----------------------------------------------------
|
|
26
|
+
|
|
27
|
+
pyproject = toml.load(Path(__file__).parent.parent.joinpath("pyproject.toml"))
|
|
28
|
+
project = pyproject["project"]["name"]
|
|
29
|
+
release = get_version(root='..', relative_to=__file__)
|
|
30
|
+
version = '.'.join(release.split('.')[:2])
|
|
31
|
+
|
|
32
|
+
copyright = f'{datetime.now().strftime('%Y')} JPL'
|
|
33
|
+
author = 'JPL Teamtools Studio'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# -- General configuration ---------------------------------------------------
|
|
37
|
+
|
|
38
|
+
# Add any Sphinx extension module names here, as strings. They can be
|
|
39
|
+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
40
|
+
# ones.
|
|
41
|
+
extensions = ['sphinx.ext.autodoc']
|
|
42
|
+
|
|
43
|
+
# Add any paths that contain templates here, relative to this directory.
|
|
44
|
+
templates_path = ['_templates']
|
|
45
|
+
|
|
46
|
+
# List of patterns, relative to source directory, that match files and
|
|
47
|
+
# directories to ignore when looking for source files.
|
|
48
|
+
# This pattern also affects html_static_path and html_extra_path.
|
|
49
|
+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
50
|
+
|
|
51
|
+
# The master toctree document.
|
|
52
|
+
master_doc = 'index'
|
|
53
|
+
|
|
54
|
+
pygments_style = 'sphinx'
|
|
55
|
+
# html_theme = 'nature'
|
|
56
|
+
# html_theme = 'sphinx_material'
|
|
57
|
+
html_theme = 'pydata_sphinx_theme'
|
|
58
|
+
|
|
59
|
+
# -- Options for HTML output -------------------------------------------------
|
|
60
|
+
|
|
61
|
+
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
62
|
+
# a list of builtin themes.
|
|
63
|
+
#
|
|
64
|
+
|
|
65
|
+
# Add any paths that contain custom static files (such as style sheets) here,
|
|
66
|
+
# relative to this directory. They are copied after the builtin static files,
|
|
67
|
+
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
68
|
+
html_static_path = ['_static']
|
|
69
|
+
html_css_files = ['bugfix.css']
|
|
70
|
+
html_copy_source = False
|
|
71
|
+
html_show_sourcelink = False
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Welcome to the Teamtools Starter Template documentation!
|
|
2
|
+
=============================================================
|
|
3
|
+
|
|
4
|
+
.. toctree::
|
|
5
|
+
:maxdepth: 2
|
|
6
|
+
:caption: Contents:
|
|
7
|
+
|
|
8
|
+
foo
|
|
9
|
+
|
|
10
|
+
Indices and tables
|
|
11
|
+
==================
|
|
12
|
+
|
|
13
|
+
* :ref:`genindex`
|
|
14
|
+
* :ref:`modindex`
|
|
15
|
+
* :ref:`search`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (c) 2025 California Institute of Technology (Caltech). U.S. Government sponsorship acknowledged.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
8
|
+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
+
Neither the name of Caltech nor its operating division, the Jet Propulsion Laboratory, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Open Source License Approved by Caltech/JPL APACHE LICENSE, VERSION 2.0
|
|
11
|
+
|
|
12
|
+
Text version: https://www.apache.org/licenses/LICENSE-2.0.txt
|
|
13
|
+
SPDX short identifier: Apache-2.0
|
|
14
|
+
OSI Approved License: https://opensource.org/licenses/Apache-2.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tts-dictionary-interface"
|
|
3
|
+
description = "Core code for developing Python AMPCS dictionary interfaces."
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.6"
|
|
6
|
+
license = {text = "Apache-2.0"}
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "muszynsk", email = "matt.muszynski@jpl.nasa.gov" },
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"lxml",
|
|
16
|
+
"pytest",
|
|
17
|
+
"tomli",
|
|
18
|
+
"tts-utilities>=0.2.2,<=1.0.0"
|
|
19
|
+
]
|
|
20
|
+
dynamic = ["version"]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/NASA-JPL-Teamtools-Studio/tts_dictionary_interface"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools_scm]
|
|
29
|
+
version_scheme = "guess-next-dev"
|
|
30
|
+
local_scheme = "no-local-version"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = [
|
|
34
|
+
"setuptools",
|
|
35
|
+
"wheel",
|
|
36
|
+
"toml",
|
|
37
|
+
"tts-utilities>=0.2.1,<1.0.0",
|
|
38
|
+
"setuptools-scm"
|
|
39
|
+
]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Helper file to support installing locally on older Python systems.
|
|
2
|
+
# Once we can move 100% to a system like pyproject.toml or poetry,
|
|
3
|
+
# we can ditch this file and setup_from_pyproject. But as long
|
|
4
|
+
# as we have missions on 3.6.4, we should keep it in place so
|
|
5
|
+
# we can dev locally on those projects and install this locally.
|
|
6
|
+
from tts_utilities.setup_from_pyproject import setup_from_pyproject
|
|
7
|
+
|
|
8
|
+
setup_from_pyproject()
|
|
File without changes
|
|
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,21 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
README.md
|
|
3
|
+
license.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
ci/sphinx_ignore_tags.txt
|
|
7
|
+
docs/conf.py
|
|
8
|
+
docs/foo.rst
|
|
9
|
+
docs/index.rst
|
|
10
|
+
docs/_static/bugfix.css
|
|
11
|
+
src/__init__.py
|
|
12
|
+
src/tts_dictionary_interface/__init__.py
|
|
13
|
+
src/tts_dictionary_interface/base.py
|
|
14
|
+
src/tts_dictionary_interface.egg-info/PKG-INFO
|
|
15
|
+
src/tts_dictionary_interface.egg-info/SOURCES.txt
|
|
16
|
+
src/tts_dictionary_interface.egg-info/dependency_links.txt
|
|
17
|
+
src/tts_dictionary_interface.egg-info/requires.txt
|
|
18
|
+
src/tts_dictionary_interface.egg-info/top_level.txt
|
|
19
|
+
src/tts_dictionary_interface/test/__init__.py
|
|
20
|
+
src/tts_dictionary_interface/test/conftest.py
|
|
21
|
+
src/tts_dictionary_interface/test/test_base.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tts_dictionary_interface
|