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.
Files changed (23) hide show
  1. tts_dictionary_interface-0.2.3/.gitignore +188 -0
  2. tts_dictionary_interface-0.2.3/PKG-INFO +64 -0
  3. tts_dictionary_interface-0.2.3/README.md +45 -0
  4. tts_dictionary_interface-0.2.3/ci/sphinx_ignore_tags.txt +20 -0
  5. tts_dictionary_interface-0.2.3/docs/_static/bugfix.css +12 -0
  6. tts_dictionary_interface-0.2.3/docs/conf.py +71 -0
  7. tts_dictionary_interface-0.2.3/docs/foo.rst +5 -0
  8. tts_dictionary_interface-0.2.3/docs/index.rst +15 -0
  9. tts_dictionary_interface-0.2.3/license.md +14 -0
  10. tts_dictionary_interface-0.2.3/pyproject.toml +40 -0
  11. tts_dictionary_interface-0.2.3/setup.cfg +4 -0
  12. tts_dictionary_interface-0.2.3/setup.py +8 -0
  13. tts_dictionary_interface-0.2.3/src/__init__.py +0 -0
  14. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/__init__.py +0 -0
  15. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/base.py +244 -0
  16. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/__init__.py +0 -0
  17. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/conftest.py +0 -0
  18. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface/test/test_base.py +172 -0
  19. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/PKG-INFO +64 -0
  20. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/SOURCES.txt +21 -0
  21. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/dependency_links.txt +1 -0
  22. tts_dictionary_interface-0.2.3/src/tts_dictionary_interface.egg-info/requires.txt +4 -0
  23. 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
+ ![Project logo](https://github.com/NASA-JPL-Teamtools-Studio/teamtools_documentation/blob/main/docs/images/tts_image_artifacts/tts_dictionary_interface.png)
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
+ ![Project logo](https://github.com/NASA-JPL-Teamtools-Studio/teamtools_documentation/blob/main/docs/images/tts_image_artifacts/tts_dictionary_interface.png)
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,5 @@
1
+ List
2
+ ===============
3
+
4
+ .. automodule:: src.foo
5
+ :members:
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
@@ -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
@@ -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
+ ![Project logo](https://github.com/NASA-JPL-Teamtools-Studio/teamtools_documentation/blob/main/docs/images/tts_image_artifacts/tts_dictionary_interface.png)
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,4 @@
1
+ lxml
2
+ pytest
3
+ tomli
4
+ tts-utilities<=1.0.0,>=0.2.2