py-teststand 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. py_teststand-0.1.0/.gitignore +37 -0
  2. py_teststand-0.1.0/.python-version +1 -0
  3. py_teststand-0.1.0/LICENSE +23 -0
  4. py_teststand-0.1.0/PKG-INFO +160 -0
  5. py_teststand-0.1.0/README.md +121 -0
  6. py_teststand-0.1.0/examples/build_sequence.py +87 -0
  7. py_teststand-0.1.0/examples/create_variables.py +108 -0
  8. py_teststand-0.1.0/examples/execute_subsequence.py +58 -0
  9. py_teststand-0.1.0/examples/insert_step.py +125 -0
  10. py_teststand-0.1.0/examples/update_station_options.py +75 -0
  11. py_teststand-0.1.0/pyproject.toml +136 -0
  12. py_teststand-0.1.0/src/py_teststand/__init__.py +289 -0
  13. py_teststand-0.1.0/src/py_teststand/adapters/__init__.py +37 -0
  14. py_teststand-0.1.0/src/py_teststand/adapters/activex.py +657 -0
  15. py_teststand-0.1.0/src/py_teststand/adapters/adapter.py +695 -0
  16. py_teststand-0.1.0/src/py_teststand/adapters/cvi.py +290 -0
  17. py_teststand-0.1.0/src/py_teststand/adapters/dll.py +476 -0
  18. py_teststand-0.1.0/src/py_teststand/adapters/dotnet.py +987 -0
  19. py_teststand-0.1.0/src/py_teststand/adapters/htbasic.py +142 -0
  20. py_teststand-0.1.0/src/py_teststand/adapters/labview.py +1553 -0
  21. py_teststand-0.1.0/src/py_teststand/adapters/labview_nxg.py +835 -0
  22. py_teststand-0.1.0/src/py_teststand/adapters/python.py +484 -0
  23. py_teststand-0.1.0/src/py_teststand/adapters/sequence.py +381 -0
  24. py_teststand-0.1.0/src/py_teststand/analyzer/__init__.py +49 -0
  25. py_teststand-0.1.0/src/py_teststand/analyzer/analysis_context.py +165 -0
  26. py_teststand-0.1.0/src/py_teststand/analyzer/analysis_message.py +34 -0
  27. py_teststand-0.1.0/src/py_teststand/analyzer/analysis_utilities.py +88 -0
  28. py_teststand-0.1.0/src/py_teststand/analyzer/rule.py +112 -0
  29. py_teststand-0.1.0/src/py_teststand/analyzer/rule_configuration.py +78 -0
  30. py_teststand-0.1.0/src/py_teststand/analyzer/rule_setting_values.py +21 -0
  31. py_teststand-0.1.0/src/py_teststand/analyzer/types.py +0 -0
  32. py_teststand-0.1.0/src/py_teststand/core/__init__.py +1 -0
  33. py_teststand-0.1.0/src/py_teststand/core/com_wrapper.py +213 -0
  34. py_teststand-0.1.0/src/py_teststand/core/engine.py +3787 -0
  35. py_teststand-0.1.0/src/py_teststand/core/exceptions.py +292 -0
  36. py_teststand-0.1.0/src/py_teststand/core/external_report_viewers.py +99 -0
  37. py_teststand-0.1.0/src/py_teststand/core/file_information.py +106 -0
  38. py_teststand-0.1.0/src/py_teststand/core/images.py +33 -0
  39. py_teststand-0.1.0/src/py_teststand/core/search.py +169 -0
  40. py_teststand-0.1.0/src/py_teststand/core/utility.py +17 -0
  41. py_teststand-0.1.0/src/py_teststand/execution/__init__.py +1 -0
  42. py_teststand-0.1.0/src/py_teststand/execution/additional_results.py +188 -0
  43. py_teststand-0.1.0/src/py_teststand/execution/breakpoint.py +36 -0
  44. py_teststand-0.1.0/src/py_teststand/execution/database_options.py +116 -0
  45. py_teststand-0.1.0/src/py_teststand/execution/edit_args.py +63 -0
  46. py_teststand-0.1.0/src/py_teststand/execution/execution.py +926 -0
  47. py_teststand-0.1.0/src/py_teststand/execution/interactive_args.py +118 -0
  48. py_teststand-0.1.0/src/py_teststand/execution/output_record_stream.py +170 -0
  49. py_teststand-0.1.0/src/py_teststand/execution/report.py +302 -0
  50. py_teststand-0.1.0/src/py_teststand/execution/result_log.py +328 -0
  51. py_teststand-0.1.0/src/py_teststand/execution/sync_manager.py +713 -0
  52. py_teststand-0.1.0/src/py_teststand/execution/thread.py +198 -0
  53. py_teststand-0.1.0/src/py_teststand/execution/watch_expression.py +179 -0
  54. py_teststand-0.1.0/src/py_teststand/ext/__init__.py +1 -0
  55. py_teststand-0.1.0/src/py_teststand/ext/events.py +220 -0
  56. py_teststand-0.1.0/src/py_teststand/ext/step_types.py +32 -0
  57. py_teststand-0.1.0/src/py_teststand/messaging/__init__.py +1 -0
  58. py_teststand-0.1.0/src/py_teststand/messaging/output_message.py +122 -0
  59. py_teststand-0.1.0/src/py_teststand/messaging/output_messages.py +60 -0
  60. py_teststand-0.1.0/src/py_teststand/messaging/ui_message.py +116 -0
  61. py_teststand-0.1.0/src/py_teststand/property/__init__.py +1 -0
  62. py_teststand-0.1.0/src/py_teststand/property/array_dimensions.py +45 -0
  63. py_teststand-0.1.0/src/py_teststand/property/data_type.py +96 -0
  64. py_teststand-0.1.0/src/py_teststand/property/property_object.py +1190 -0
  65. py_teststand-0.1.0/src/py_teststand/property/property_object_file.py +330 -0
  66. py_teststand-0.1.0/src/py_teststand/sequence/__init__.py +1 -0
  67. py_teststand-0.1.0/src/py_teststand/sequence/code_template.py +63 -0
  68. py_teststand-0.1.0/src/py_teststand/sequence/expression.py +230 -0
  69. py_teststand-0.1.0/src/py_teststand/sequence/location.py +609 -0
  70. py_teststand-0.1.0/src/py_teststand/sequence/sequence.py +558 -0
  71. py_teststand-0.1.0/src/py_teststand/sequence/sequence_context.py +438 -0
  72. py_teststand-0.1.0/src/py_teststand/sequence/sequence_file.py +457 -0
  73. py_teststand-0.1.0/src/py_teststand/sequence/step.py +1296 -0
  74. py_teststand-0.1.0/src/py_teststand/sequence/step_group.py +14 -0
  75. py_teststand-0.1.0/src/py_teststand/sequence/step_type.py +640 -0
  76. py_teststand-0.1.0/src/py_teststand/sequence/type_palette.py +22 -0
  77. py_teststand-0.1.0/src/py_teststand/station/__init__.py +1 -0
  78. py_teststand-0.1.0/src/py_teststand/station/search_directories.py +125 -0
  79. py_teststand-0.1.0/src/py_teststand/station/station_options.py +592 -0
  80. py_teststand-0.1.0/src/py_teststand/ui/__init__.py +78 -0
  81. py_teststand-0.1.0/src/py_teststand/ui/application_manager.py +776 -0
  82. py_teststand-0.1.0/src/py_teststand/ui/button_ctrl.py +237 -0
  83. py_teststand-0.1.0/src/py_teststand/ui/checkbox_ctrl.py +153 -0
  84. py_teststand-0.1.0/src/py_teststand/ui/combobox_ctrl.py +158 -0
  85. py_teststand-0.1.0/src/py_teststand/ui/command.py +256 -0
  86. py_teststand-0.1.0/src/py_teststand/ui/connections.py +2411 -0
  87. py_teststand-0.1.0/src/py_teststand/ui/entry_point.py +81 -0
  88. py_teststand-0.1.0/src/py_teststand/ui/events.py +483 -0
  89. py_teststand-0.1.0/src/py_teststand/ui/execution_view_manager.py +415 -0
  90. py_teststand-0.1.0/src/py_teststand/ui/expression_edit_ctrl.py +477 -0
  91. py_teststand-0.1.0/src/py_teststand/ui/insertion_palette.py +77 -0
  92. py_teststand-0.1.0/src/py_teststand/ui/label_ctrl.py +159 -0
  93. py_teststand-0.1.0/src/py_teststand/ui/list_box_ctrl.py +261 -0
  94. py_teststand-0.1.0/src/py_teststand/ui/listbar_ctrl.py +200 -0
  95. py_teststand-0.1.0/src/py_teststand/ui/menu_item.py +244 -0
  96. py_teststand-0.1.0/src/py_teststand/ui/report_view_ctrl.py +147 -0
  97. py_teststand-0.1.0/src/py_teststand/ui/sequence_file_view_manager.py +380 -0
  98. py_teststand-0.1.0/src/py_teststand/ui/sequence_view_ctrl.py +436 -0
  99. py_teststand-0.1.0/src/py_teststand/ui/status_bar.py +240 -0
  100. py_teststand-0.1.0/src/py_teststand/ui/styles.py +373 -0
  101. py_teststand-0.1.0/src/py_teststand/ui/variables_view_ctrl.py +42 -0
  102. py_teststand-0.1.0/src/py_teststand/undo/__init__.py +1 -0
  103. py_teststand-0.1.0/src/py_teststand/undo/undo_item.py +118 -0
  104. py_teststand-0.1.0/src/py_teststand/undo/undo_item_creator.py +57 -0
  105. py_teststand-0.1.0/src/py_teststand/undo/undo_stack.py +105 -0
  106. py_teststand-0.1.0/src/py_teststand/users/__init__.py +1 -0
  107. py_teststand-0.1.0/src/py_teststand/users/user.py +108 -0
  108. py_teststand-0.1.0/src/py_teststand/users/users_file.py +56 -0
  109. py_teststand-0.1.0/src/py_teststand/workspace/__init__.py +1 -0
  110. py_teststand-0.1.0/src/py_teststand/workspace/workspace_file.py +86 -0
  111. py_teststand-0.1.0/src/py_teststand/workspace/workspace_object.py +190 -0
  112. py_teststand-0.1.0/tests/__init__.py +0 -0
  113. py_teststand-0.1.0/tests/conftest.py +115 -0
  114. py_teststand-0.1.0/tests/integration/test_api_compatibility.py +76 -0
  115. py_teststand-0.1.0/tests/integration/test_memory_leaks.py +57 -0
  116. py_teststand-0.1.0/tests/test_core.py +103 -0
  117. py_teststand-0.1.0/tests/unit/__init__.py +0 -0
  118. py_teststand-0.1.0/tests/unit/adapters/test_activex_adapter.py +36 -0
  119. py_teststand-0.1.0/tests/unit/adapters/test_dotnet_runtime_kind.py +27 -0
  120. py_teststand-0.1.0/tests/unit/adapters/test_python_adapter.py +163 -0
  121. py_teststand-0.1.0/tests/unit/analyzer/__init__.py +0 -0
  122. py_teststand-0.1.0/tests/unit/analyzer/test_rule_authoring.py +162 -0
  123. py_teststand-0.1.0/tests/unit/core/__init__.py +0 -0
  124. py_teststand-0.1.0/tests/unit/core/test_engine.py +117 -0
  125. py_teststand-0.1.0/tests/unit/core/test_enums.py +210 -0
  126. py_teststand-0.1.0/tests/unit/core/test_errors.py +75 -0
  127. py_teststand-0.1.0/tests/unit/core/test_expression.py +42 -0
  128. py_teststand-0.1.0/tests/unit/core/test_property_object_file.py +283 -0
  129. py_teststand-0.1.0/tests/unit/core/test_sequence.py +158 -0
  130. py_teststand-0.1.0/tests/unit/core/test_sequence_file.py +64 -0
  131. py_teststand-0.1.0/tests/unit/core/test_step.py +258 -0
  132. py_teststand-0.1.0/tests/unit/execution/test_database_options.py +136 -0
  133. py_teststand-0.1.0/tests/unit/execution/test_execution.py +82 -0
  134. py_teststand-0.1.0/tests/unit/sequence/test_sequence.py +82 -0
  135. py_teststand-0.1.0/tests/unit/sequence/test_sequence_file.py +84 -0
  136. py_teststand-0.1.0/tests/unit/test_core_mechanics.py +87 -0
  137. py_teststand-0.1.0/tests/unit/ui/test_events.py +141 -0
  138. py_teststand-0.1.0/tests/unit/ui/test_execution_view_manager.py +71 -0
  139. py_teststand-0.1.0/tests/unit/ui/test_sequence_file_view_manager.py +65 -0
@@ -0,0 +1,37 @@
1
+ .DS_Store
2
+ Thumbs.db
3
+ .vscode/
4
+ .idea/
5
+ *.swp
6
+ *.swo
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .venv
12
+ .venv/
13
+ venv/
14
+ env/
15
+ ENV/
16
+ dist/
17
+ build/
18
+ *.egg-info/
19
+ *.egg
20
+ .eggs/
21
+ .ruff_cache/
22
+ .mypy_cache/
23
+ .pytest_cache/
24
+ .coverage
25
+ .coverage.*
26
+ htmlcov/
27
+ .nox/
28
+ .tox/
29
+
30
+ # Dynamic Generated Documentation (#TODO)
31
+ /site/
32
+ /public/
33
+ docs/
34
+ /tmp/
35
+ /temp/
36
+ scratch/
37
+ *.log
@@ -0,0 +1 @@
1
+ 3.8
@@ -0,0 +1,23 @@
1
+ SPDX-License-Identifier: MIT
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2026 Dominik Rajchel
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-teststand
3
+ Version: 0.1.0
4
+ Summary: Community object-oriented Python 3 bindings for the National Instruments TestStand™ COM API
5
+ Project-URL: Homepage, https://github.com/TheDomcio/py-teststand
6
+ Project-URL: Repository, https://github.com/TheDomcio/py-teststand
7
+ Project-URL: Issues, https://github.com/TheDomcio/py-teststand/issues
8
+ Project-URL: Bug Reports, https://github.com/TheDomcio/py-teststand/issues
9
+ Project-URL: Source Code, https://github.com/TheDomcio/py-teststand
10
+ Project-URL: TestStand API Reference, https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html
11
+ Author: Dominik Rajchel
12
+ License: MIT
13
+ License-File: LICENSE
14
+ Keywords: automation,com,manufacturing,national-instruments,ni,python,pywin32,station-provisioning,test-automation,teststand,windows
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Manufacturing
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 7
21
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
22
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.8
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Programming Language :: Python :: 3.13
31
+ Classifier: Programming Language :: Python :: 3.14
32
+ Classifier: Topic :: Software Development :: Testing
33
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
34
+ Classifier: Typing :: Typed
35
+ Requires-Python: >=3.8.20
36
+ Requires-Dist: pywin32>=305; sys_platform == 'win32'
37
+ Requires-Dist: typing-extensions>=4.0.0
38
+ Description-Content-Type: text/markdown
39
+
40
+ # py-teststand
41
+
42
+ Community object-oriented Python 3 bindings for the [National Instruments TestStand™ COM API](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html).
43
+
44
+ > ⚠️ **Early Implementation Stage** — Consider as experimental. Interfaces may change between releases without prior notice.
45
+
46
+ ## Overview
47
+
48
+ `py-teststand` exposes the [TestStand™ COM API](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) as an object-oriented Python interface via [pywin32](https://pypi.org/project/pywin32/).
49
+
50
+ ### Name
51
+
52
+ The package is named `py-teststand` (with a dash) to avoid naming collision with the [pytest testing framework](https://pytest.org/) and for easier relation to TestStand™ test executive.
53
+
54
+ [![PyPI version](https://img.shields.io/pypi/v/py-teststand.svg)](https://pypi.org/project/py-teststand/)
55
+ [![Python versions](https://img.shields.io/pypi/pyversions/py-teststand.svg)](https://pypi.org/project/py-teststand/)
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
57
+ [![Build Status](https://github.com/TheDomcio/py-teststand/actions/workflows/tests.yml/badge.svg)](https://github.com/TheDomcio/py-teststand/actions)
58
+
59
+ ---
60
+
61
+ ## Project Status
62
+
63
+ `py-teststand` is a hobby project maintained on a best-effort basis
64
+ and is **not** under active full-time development (for first release).
65
+ There are no guaranteed scheduled release cadences or support (but feel free to contact).
66
+
67
+ That said:
68
+
69
+ - **Bug reports and feature requests** are welcome via [GitHub Issues](https://github.com/TheDomcio/py-teststand/issues).
70
+ - **Pull requests** are welcomed and reviewed. If you are working with the TestStand™ COM API and have improvements, fixes, or additional bindings, contributions are encouraged.
71
+
72
+ If you encounter a missing TestStand™ binding, an incorrect type annotation, or unexpected TestStand™ COM dispatch behavior, opening an issue with a reproducible case is the most effective way to get it addressed, I will try to investigate and find solution as fast as possible.
73
+
74
+ ---
75
+
76
+ ## Implementation Notes
77
+
78
+ ### Type Library Generation
79
+
80
+ Python class stubs and interface definitions are generated from the TestStand™ [COM Type Library](https://learn.microsoft.com/en-us/windows/win32/com/type-libraries-and-the-component-object-model) (`.tlb`) using [pywin32's](https://pypi.org/project/pywin32/) [`makepy`](https://github.com/mhammond/pywin32/blob/main/com/win32com/client/makepy.py) utility. The generated output is cached as a [pywin32 dispatch cache](https://mhammond.github.io/pywin32/) module and checked into the repository, meaning a live TestStand™ installation is not required at import time — only at runtime when TestStand™ COM objects are actually instantiated.
81
+
82
+ The generation process follows this pipeline:
83
+
84
+ 1. **TLB introspection** — `makepy` reads the registered TestStand™ COM type library via the Windows registry and reflects all exposed interfaces, `CoClass` definitions, enumerations, and dispatch IDs.
85
+ 2. **Cache dump** — The reflected TestStand™ metadata is serialized into a Python module stored under `win32com/gen_py/`, keyed by the TestStand™ type library GUID and version. This cache is committed to the repository so users do not need to run `makepy` themselves.
86
+ 3. **Wrapper generation** — `py-teststand` classes are authored on top of the cached TestStand™ dispatch definitions, adding Python type annotations and translating raw COM `VARIANT` and `IDispatch` returns into typed Python objects where applicable.
87
+
88
+ This approach means the bindings target a specific version of the TestStand™ type library. Compatibility across TestStand™ engine versions is maintained by keeping the wrapper surface aligned with stable, long-lived TestStand™ COM interfaces present across the supported version range.
89
+
90
+ ---
91
+
92
+ ## Design Goals
93
+
94
+ - **Python 3.8 minimum** — Python 3.8 (uv supports / distributes even 3.8.20) is the last CPython release with official Windows 7 support. Many manufacturing and test environments run long-lifecycle OS images on air-gapped station hardware where upgrading the OS is not feasible on short timescales. Maintaining a 3.8-compatible codebase allows incremental adoption of Python-based automation on existing TestStand™ station hardware without requiring a platform migration first.
95
+ - **TestStand™ station options as code** — The [TestStand™ Station Options](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) object model is fully exposed, allowing TestStand™ search directories, model paths, station globals, and result processing configuration to be read and written programmatically. This makes TestStand™ station configuration reproducible and suitable for provisioners such as [Ansible](https://www.ansible.com/), [Chef](https://www.chef.io/), or custom deployment scripts — replacing manual point-and-click TestStand™ setup with version-controlled configuration.
96
+ - **No TestStand™ documentation mirroring** — The library does **not** duplicate or paraphrase the [official TestStand™ API reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) in its docstrings, as eproducing NI's documentation would introduce a secondary source that diverges from the official TestStand™ spec as new versions evolve, creating a risk of misinformation, therefore for authoritative descriptions of TestStand™ COM objects, properties, method parameters, and return value semantics etc. refer directly to the [TestStand™ API Reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html).
97
+
98
+ ---
99
+
100
+ ## Technical Stack
101
+
102
+ | Tool | Purpose |
103
+ | :----------------------------------------------- | :---------------------------------------------------------- |
104
+ | [**uv**](https://github.com/astral-sh/uv) | Python package and project manager |
105
+ | [**ty**](https://github.com/astral-sh/ty) | Static type checker for interface validation |
106
+ | [**ruff**](https://github.com/astral-sh/ruff) | Linter and code formatter |
107
+ | [**pytest**](https://pytest.org/) | Unit and integration test runner |
108
+ | [**pywin32**](https://pypi.org/project/pywin32/) | Windows COM dispatch layer and TestStand™ TLB introspection |
109
+
110
+ ---
111
+
112
+ ## Compatibility
113
+
114
+ | Component | Versions |
115
+ | :---------------------------------------------------------------------------------------------------------------------- | :----------- |
116
+ | [**Windows**](https://www.microsoft.com/en-us/windows) | 7 — 11 |
117
+ | [**Python**](https://www.python.org/downloads/) | 3.8 — 3.14 |
118
+ | [**TestStand™**](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) | 2016 — 2026+ |
119
+
120
+ > Older TestStand™ engine versions may also work if the underlying TestStand™ COM interfaces have not changed, but they were not explicitly tested.
121
+
122
+ ---
123
+
124
+ ## Features
125
+
126
+ - **Pythonic attribute access** — TestStand™ COM properties are accessible via standard Python attribute notation instead of raw `Dispatch` calls.
127
+ - **Type annotations** — All public members carry type hints compatible with [ty](https://github.com/astral-sh/ty).
128
+ - **TestStand™ station options provisioning** — Read and write TestStand™ station-level configuration suitable for use in automated deployment pipelines (like test stations install scripts).
129
+ - **Minimal binding surface** — No behavior is added beyond what the TestStand™ COM layer provides. Edge cases and error conditions follow the TestStand™ COM API contract documented in the [official TestStand™ reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html). The binding layer is intentionally lightweight and not overcommented, signatures and object hierarchy map directly to the TestStand™ COM API without adding abstraction or reinterpreting behavior (but modules themselves are sorted under domains for easier management).
130
+ - **No live TestStand™ installation required at import time** — The committed pywin32 dispatch cache allows the library to be imported and partially used (type checking, configuration building) without a TestStand™ installation present.
131
+
132
+ ---
133
+
134
+ ## Installation
135
+
136
+ ### [pip](https://pip.pypa.io/en/stable/)
137
+
138
+ ```powershell
139
+ pip install py-teststand
140
+ ```
141
+
142
+ ### [uv](https://github.com/astral-sh/uv)
143
+
144
+ ```powershell
145
+ uv pip install py-teststand
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Popularity Over Time
151
+
152
+ [![Star History Chart](https://api.star-history.com/svg?repos=TheDomcio/py-teststand&type=Date)](https://star-history.com/#TheDomcio/py-teststand&Date)
153
+
154
+ ---
155
+
156
+ ## Legal
157
+
158
+ TestStand™ is a registered trademark of [National Instruments Corporation](https://www.ni.com/). Refer to [NI's TestStand™ licensing options](https://www.ni.com/docs/en-US/bundle/teststand/page/teststand-licensing-options.html) for information on required licenses to operate the TestStand™ engine.
159
+
160
+ `py-teststand` is an independent community project and is not affiliated with, endorsed by, or maintained by National Instruments or its parent company [Emerson](https://www.emerson.com/). References to the TestStand™ API are made solely for interoperability purposes.
@@ -0,0 +1,121 @@
1
+ # py-teststand
2
+
3
+ Community object-oriented Python 3 bindings for the [National Instruments TestStand™ COM API](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html).
4
+
5
+ > ⚠️ **Early Implementation Stage** — Consider as experimental. Interfaces may change between releases without prior notice.
6
+
7
+ ## Overview
8
+
9
+ `py-teststand` exposes the [TestStand™ COM API](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) as an object-oriented Python interface via [pywin32](https://pypi.org/project/pywin32/).
10
+
11
+ ### Name
12
+
13
+ The package is named `py-teststand` (with a dash) to avoid naming collision with the [pytest testing framework](https://pytest.org/) and for easier relation to TestStand™ test executive.
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/py-teststand.svg)](https://pypi.org/project/py-teststand/)
16
+ [![Python versions](https://img.shields.io/pypi/pyversions/py-teststand.svg)](https://pypi.org/project/py-teststand/)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
18
+ [![Build Status](https://github.com/TheDomcio/py-teststand/actions/workflows/tests.yml/badge.svg)](https://github.com/TheDomcio/py-teststand/actions)
19
+
20
+ ---
21
+
22
+ ## Project Status
23
+
24
+ `py-teststand` is a hobby project maintained on a best-effort basis
25
+ and is **not** under active full-time development (for first release).
26
+ There are no guaranteed scheduled release cadences or support (but feel free to contact).
27
+
28
+ That said:
29
+
30
+ - **Bug reports and feature requests** are welcome via [GitHub Issues](https://github.com/TheDomcio/py-teststand/issues).
31
+ - **Pull requests** are welcomed and reviewed. If you are working with the TestStand™ COM API and have improvements, fixes, or additional bindings, contributions are encouraged.
32
+
33
+ If you encounter a missing TestStand™ binding, an incorrect type annotation, or unexpected TestStand™ COM dispatch behavior, opening an issue with a reproducible case is the most effective way to get it addressed, I will try to investigate and find solution as fast as possible.
34
+
35
+ ---
36
+
37
+ ## Implementation Notes
38
+
39
+ ### Type Library Generation
40
+
41
+ Python class stubs and interface definitions are generated from the TestStand™ [COM Type Library](https://learn.microsoft.com/en-us/windows/win32/com/type-libraries-and-the-component-object-model) (`.tlb`) using [pywin32's](https://pypi.org/project/pywin32/) [`makepy`](https://github.com/mhammond/pywin32/blob/main/com/win32com/client/makepy.py) utility. The generated output is cached as a [pywin32 dispatch cache](https://mhammond.github.io/pywin32/) module and checked into the repository, meaning a live TestStand™ installation is not required at import time — only at runtime when TestStand™ COM objects are actually instantiated.
42
+
43
+ The generation process follows this pipeline:
44
+
45
+ 1. **TLB introspection** — `makepy` reads the registered TestStand™ COM type library via the Windows registry and reflects all exposed interfaces, `CoClass` definitions, enumerations, and dispatch IDs.
46
+ 2. **Cache dump** — The reflected TestStand™ metadata is serialized into a Python module stored under `win32com/gen_py/`, keyed by the TestStand™ type library GUID and version. This cache is committed to the repository so users do not need to run `makepy` themselves.
47
+ 3. **Wrapper generation** — `py-teststand` classes are authored on top of the cached TestStand™ dispatch definitions, adding Python type annotations and translating raw COM `VARIANT` and `IDispatch` returns into typed Python objects where applicable.
48
+
49
+ This approach means the bindings target a specific version of the TestStand™ type library. Compatibility across TestStand™ engine versions is maintained by keeping the wrapper surface aligned with stable, long-lived TestStand™ COM interfaces present across the supported version range.
50
+
51
+ ---
52
+
53
+ ## Design Goals
54
+
55
+ - **Python 3.8 minimum** — Python 3.8 (uv supports / distributes even 3.8.20) is the last CPython release with official Windows 7 support. Many manufacturing and test environments run long-lifecycle OS images on air-gapped station hardware where upgrading the OS is not feasible on short timescales. Maintaining a 3.8-compatible codebase allows incremental adoption of Python-based automation on existing TestStand™ station hardware without requiring a platform migration first.
56
+ - **TestStand™ station options as code** — The [TestStand™ Station Options](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) object model is fully exposed, allowing TestStand™ search directories, model paths, station globals, and result processing configuration to be read and written programmatically. This makes TestStand™ station configuration reproducible and suitable for provisioners such as [Ansible](https://www.ansible.com/), [Chef](https://www.chef.io/), or custom deployment scripts — replacing manual point-and-click TestStand™ setup with version-controlled configuration.
57
+ - **No TestStand™ documentation mirroring** — The library does **not** duplicate or paraphrase the [official TestStand™ API reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) in its docstrings, as eproducing NI's documentation would introduce a secondary source that diverges from the official TestStand™ spec as new versions evolve, creating a risk of misinformation, therefore for authoritative descriptions of TestStand™ COM objects, properties, method parameters, and return value semantics etc. refer directly to the [TestStand™ API Reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html).
58
+
59
+ ---
60
+
61
+ ## Technical Stack
62
+
63
+ | Tool | Purpose |
64
+ | :----------------------------------------------- | :---------------------------------------------------------- |
65
+ | [**uv**](https://github.com/astral-sh/uv) | Python package and project manager |
66
+ | [**ty**](https://github.com/astral-sh/ty) | Static type checker for interface validation |
67
+ | [**ruff**](https://github.com/astral-sh/ruff) | Linter and code formatter |
68
+ | [**pytest**](https://pytest.org/) | Unit and integration test runner |
69
+ | [**pywin32**](https://pypi.org/project/pywin32/) | Windows COM dispatch layer and TestStand™ TLB introspection |
70
+
71
+ ---
72
+
73
+ ## Compatibility
74
+
75
+ | Component | Versions |
76
+ | :---------------------------------------------------------------------------------------------------------------------- | :----------- |
77
+ | [**Windows**](https://www.microsoft.com/en-us/windows) | 7 — 11 |
78
+ | [**Python**](https://www.python.org/downloads/) | 3.8 — 3.14 |
79
+ | [**TestStand™**](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html) | 2016 — 2026+ |
80
+
81
+ > Older TestStand™ engine versions may also work if the underlying TestStand™ COM interfaces have not changed, but they were not explicitly tested.
82
+
83
+ ---
84
+
85
+ ## Features
86
+
87
+ - **Pythonic attribute access** — TestStand™ COM properties are accessible via standard Python attribute notation instead of raw `Dispatch` calls.
88
+ - **Type annotations** — All public members carry type hints compatible with [ty](https://github.com/astral-sh/ty).
89
+ - **TestStand™ station options provisioning** — Read and write TestStand™ station-level configuration suitable for use in automated deployment pipelines (like test stations install scripts).
90
+ - **Minimal binding surface** — No behavior is added beyond what the TestStand™ COM layer provides. Edge cases and error conditions follow the TestStand™ COM API contract documented in the [official TestStand™ reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/tshelp/teststand-api-reference.html). The binding layer is intentionally lightweight and not overcommented, signatures and object hierarchy map directly to the TestStand™ COM API without adding abstraction or reinterpreting behavior (but modules themselves are sorted under domains for easier management).
91
+ - **No live TestStand™ installation required at import time** — The committed pywin32 dispatch cache allows the library to be imported and partially used (type checking, configuration building) without a TestStand™ installation present.
92
+
93
+ ---
94
+
95
+ ## Installation
96
+
97
+ ### [pip](https://pip.pypa.io/en/stable/)
98
+
99
+ ```powershell
100
+ pip install py-teststand
101
+ ```
102
+
103
+ ### [uv](https://github.com/astral-sh/uv)
104
+
105
+ ```powershell
106
+ uv pip install py-teststand
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Popularity Over Time
112
+
113
+ [![Star History Chart](https://api.star-history.com/svg?repos=TheDomcio/py-teststand&type=Date)](https://star-history.com/#TheDomcio/py-teststand&Date)
114
+
115
+ ---
116
+
117
+ ## Legal
118
+
119
+ TestStand™ is a registered trademark of [National Instruments Corporation](https://www.ni.com/). Refer to [NI's TestStand™ licensing options](https://www.ni.com/docs/en-US/bundle/teststand/page/teststand-licensing-options.html) for information on required licenses to operate the TestStand™ engine.
120
+
121
+ `py-teststand` is an independent community project and is not affiliated with, endorsed by, or maintained by National Instruments or its parent company [Emerson](https://www.emerson.com/). References to the TestStand™ API are made solely for interoperability purposes.
@@ -0,0 +1,87 @@
1
+ """Build a TestStand sequence file from scratch with NumericLimitTest steps.
2
+
3
+ Creates a new sequence file, populates ``MainSequence`` with two
4
+ NumericLimitTest steps (a temperature check and a voltage monitor) whose
5
+ high/low limits are configured through the underlying
6
+ ``PropertyObject``, adds a ``CustomSubsequence`` containing an ``Action``
7
+ step so downstream examples (``insert_step.py``) have something to target,
8
+ prints a verification summary of the constructed sequence, and writes
9
+ the file to a temp directory.
10
+
11
+ Demonstrates:
12
+ - Creating sequence files and adding subsequences with ``new_sequence``
13
+ - Building steps with ``Engine.new_step`` (no adapter / None adapter)
14
+ - Setting standard step properties (``name``, ``precondition``, ``record_result``)
15
+ - Reaching into nested TestStand properties (``Limits.High`` / ``Limits.Low``)
16
+ via ``step.as_property_object()``
17
+ - Inserting steps into a specific ``StepGroup`` at a chosen index
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import tempfile
23
+ import uuid
24
+ from pathlib import Path
25
+
26
+ from py_teststand import Engine
27
+ from py_teststand.sequence.step_group import StepGroup
28
+
29
+ ROOT_TEMP_DIR = Path(tempfile.gettempdir()) / "py-teststand"
30
+ LATEST_POINTER = ROOT_TEMP_DIR / "latest_sequence.txt"
31
+
32
+
33
+ def main() -> None:
34
+ run_dir = ROOT_TEMP_DIR / uuid.uuid4().hex
35
+ run_dir.mkdir(parents=True, exist_ok=True)
36
+ output_path = run_dir / "test_sequence.seq"
37
+
38
+ with Engine() as engine:
39
+ sequence_file = engine.new_sequence_file()
40
+ main_sequence = sequence_file.get_sequence_by_name("MainSequence")
41
+
42
+ first_step = engine.new_step(adapter_key_name="", step_type_name="NumericLimitTest")
43
+ first_step.name = "Temperature Check"
44
+ first_step.precondition = "Locals.TempSensorPresent == True"
45
+ first_step.record_result = True
46
+
47
+ first_step_property_object = first_step.as_property_object()
48
+ first_step_property_object["Limits.High"] = 85.0
49
+ first_step_property_object["Limits.Low"] = 15.0
50
+
51
+ main_sequence.insert_step(first_step, 0, StepGroup.Main)
52
+
53
+ second_step = engine.new_step(adapter_key_name="", step_type_name="NumericLimitTest")
54
+ second_step.name = "Voltage Monitor"
55
+ second_step.precondition = "Locals.DUTPowered == True"
56
+
57
+ second_step_property_object = second_step.as_property_object()
58
+ second_step_property_object["Limits.High"] = 5.25
59
+ second_step_property_object["Limits.Low"] = 4.75
60
+
61
+ main_sequence.insert_step(second_step, 1, StepGroup.Main)
62
+
63
+ sub_seq = engine.new_sequence()
64
+ sub_seq.name = "CustomSubsequence"
65
+ sequence_file.insert_sequence(sub_seq)
66
+
67
+ init_step = engine.new_step(adapter_key_name="", step_type_name="Action")
68
+ init_step.name = "Initialize Hardware"
69
+ sub_seq.insert_step(init_step, 0, StepGroup.Main)
70
+
71
+ print(f"Created sequence with {main_sequence.get_num_steps()} steps:")
72
+ for i in range(main_sequence.get_num_steps()):
73
+ s = main_sequence.get_step(i)
74
+ po_s = s.as_property_object()
75
+
76
+ print(f" [{i}] {s.name}")
77
+ print(f" Limits: Low={po_s['Limits.Low']}, High={po_s['Limits.High']}")
78
+ print(f" Precond: {s.precondition}")
79
+
80
+ sequence_file.save(str(output_path))
81
+ LATEST_POINTER.write_text(str(output_path), encoding="utf-8")
82
+ print(f"\nSaved sequence file to {output_path}")
83
+ print(f"Pointer written to {LATEST_POINTER}")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
@@ -0,0 +1,108 @@
1
+ """Create string variables across every TestStand variable scope.
2
+
3
+ Opens the sequence file produced by ``build_sequence.py`` and populates it
4
+ with new string variables across the four standard TestStand scopes:
5
+
6
+ - **Sequence Locals** — temporary per-call storage on ``MainSequence``
7
+ (``Locals.OperatorName``).
8
+ - **Sequence Parameters** — caller-supplied inputs on ``MainSequence``
9
+ (``Parameters.DUTSerial``).
10
+ - **File Globals** — values shared across every sequence in the file
11
+ (``FileGlobals.BatchID``).
12
+ - **Station Globals** — values shared across every sequence file run on
13
+ this station, persisted via ``Engine.commit_globals_to_disk``
14
+ (``StationInfo.StationName``).
15
+
16
+ Also adds a second subsequence ``MeasurementRoutine`` with its own
17
+ parameter and local so the example shows how variables are scoped per
18
+ sequence rather than per file.
19
+
20
+ Demonstrates:
21
+ - Creating sub-properties of arbitrary type via
22
+ ``PropertyObject.new_sub_property`` + ``PropValType``
23
+ - Reading and writing values with ``PropertyObject.__setitem__`` / ``__getitem__``
24
+ - Reaching ``Sequence.locals`` / ``Sequence.parameters`` /
25
+ ``SequenceFile.file_globals`` / ``Engine.globals``
26
+ - Adding subsequences with ``SequenceFile.new_sequence``
27
+ - Committing station-globals changes to disk
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import tempfile
33
+ from pathlib import Path
34
+
35
+ from py_teststand import Engine
36
+ from py_teststand.property.property_object import PropValType
37
+
38
+
39
+ def _ensure_string_var(container, name: str, value: str) -> None:
40
+ """Create a scalar string sub-property and assign it if it does not exist."""
41
+ if not container.exists(name, 0):
42
+ container.new_sub_property(name, PropValType.String, False, "")
43
+ container[name] = value
44
+
45
+
46
+ ROOT_TEMP_DIR = Path(tempfile.gettempdir()) / "py-teststand"
47
+ LATEST_POINTER = ROOT_TEMP_DIR / "latest_sequence.txt"
48
+
49
+
50
+ def main() -> None:
51
+ if not LATEST_POINTER.exists():
52
+ print(f"Error: Pointer file not found at {LATEST_POINTER}")
53
+ print("Run build_sequence.py first.")
54
+ return
55
+
56
+ sequence_path = Path(LATEST_POINTER.read_text(encoding="utf-8").strip())
57
+
58
+ if not sequence_path.exists():
59
+ print(f"Error: Sequence file not found at {sequence_path}")
60
+ return
61
+
62
+ with Engine() as engine:
63
+ with engine.get_sequence_file(str(sequence_path)) as seq_file:
64
+ main_sequence = seq_file.get_sequence_by_name("MainSequence")
65
+
66
+ with main_sequence.locals as main_locals:
67
+ _ensure_string_var(main_locals, "OperatorName", "Alice")
68
+
69
+ with main_sequence.parameters as main_params:
70
+ _ensure_string_var(main_params, "DUTSerial", "SN-000000")
71
+
72
+ with seq_file.file_globals as file_globals:
73
+ _ensure_string_var(file_globals, "BatchID", "BATCH-2026-Q2-001")
74
+
75
+ measurement_seq = seq_file.new_sequence("MeasurementRoutine")
76
+ with measurement_seq.parameters as measurement_params:
77
+ _ensure_string_var(measurement_params, "ChannelLabel", "CH-A")
78
+ with measurement_seq.locals as measurement_locals:
79
+ _ensure_string_var(measurement_locals, "LastReading", "")
80
+
81
+ seq_file.save()
82
+
83
+ print("Variables created:")
84
+ with main_sequence.locals as ml:
85
+ print(f" MainSequence.Locals.OperatorName = {ml['OperatorName']!r}")
86
+ with main_sequence.parameters as mp:
87
+ print(f" MainSequence.Parameters.DUTSerial = {mp['DUTSerial']!r}")
88
+ with seq_file.file_globals as fg:
89
+ print(f" FileGlobals.BatchID = {fg['BatchID']!r}")
90
+ with measurement_seq.parameters as msp:
91
+ print(f" MeasurementRoutine.Parameters.Channel= {msp['ChannelLabel']!r}")
92
+ with measurement_seq.locals as msl:
93
+ print(f" MeasurementRoutine.Locals.LastReading= {msl['LastReading']!r}")
94
+
95
+ with engine.globals as station_globals:
96
+ if not station_globals.exists("StationInfo", 0):
97
+ station_globals.new_sub_property("StationInfo", PropValType.Container, False, "")
98
+ station_info = station_globals.get_property_object("StationInfo", 0)
99
+ assert station_info is not None
100
+ with station_info as info:
101
+ _ensure_string_var(info, "StationName", "STATION_01")
102
+ print(f" StationGlobals.StationInfo.StationName = {info['StationName']!r}")
103
+
104
+ engine.commit_globals_to_disk(prompt_on_save_conflicts=False)
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()
@@ -0,0 +1,58 @@
1
+ """Build and run a sequence end-to-end using only built-in TestStand step types.
2
+
3
+ Creates a fresh in-memory sequence file, appends three ``Action`` steps to
4
+ ``MainSequence`` using the None adapter (no external code module), starts an
5
+ execution, waits for it to finish, and walks the resulting ``ResultList`` to
6
+ print the recorded step name and status for each step.
7
+
8
+ The example focuses on the execution + result reporting surface — no code
9
+ modules, no LabVIEW/DLL adapters — so it can be exercised on any TestStand
10
+ engine without external dependencies.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from py_teststand import Engine
16
+ from py_teststand.core.engine import AdapterKeyName
17
+ from py_teststand.sequence.step_group import StepGroup
18
+
19
+
20
+ def main() -> None:
21
+ with Engine() as engine:
22
+ sequence_file = engine.new_sequence_file()
23
+ main_sequence = sequence_file.get_sequence_by_name("MainSequence")
24
+
25
+ for name in ("Initialize", "Run Test", "Cleanup"):
26
+ step = engine.new_step(AdapterKeyName.NoneAdapterKeyName, "Action")
27
+ step.name = name
28
+ step.record_result = True
29
+ main_sequence.insert_step(step, main_sequence.get_num_steps(), StepGroup.Main)
30
+
31
+ with engine.new_execution(sequence_file, "MainSequence") as execution:
32
+ execution.wait_for_end_ex(-1)
33
+
34
+ results = execution.result_object
35
+ if results is None:
36
+ return
37
+
38
+ with results as res:
39
+ if not res.exists("ResultList", 0):
40
+ print("No results recorded.")
41
+ return
42
+
43
+ result_list_obj = res.get_property_object("ResultList", 0)
44
+ if result_list_obj is None:
45
+ return
46
+
47
+ with result_list_obj as result_list:
48
+ for step_result in result_list:
49
+ if step_result is None:
50
+ continue
51
+ with step_result as sr:
52
+ name = sr.get_val_string("TS.StepName", 0)
53
+ status = sr.get_val_string("Status", 0)
54
+ print(f"{name}: {status}")
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()