tofupilot 2.2.2__tar.gz → 2.2.4__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 (141) hide show
  1. tofupilot-2.2.4/PKG-INFO +220 -0
  2. tofupilot-2.2.4/README.md +179 -0
  3. {tofupilot-2.2.2 → tofupilot-2.2.4}/pyproject.toml +1 -3
  4. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/__init__.py +1 -1
  5. tofupilot-2.2.4/tofupilot/banner.py +5 -0
  6. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/client.py +1 -2
  7. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/utils/files.py +3 -0
  8. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/__init__.py +9 -13
  9. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/_version.py +2 -2
  10. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/attachments.py +4 -4
  11. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/client_with_error_tracking.py +1 -4
  12. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/__init__.py +15 -21
  13. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/run_createop.py +15 -25
  14. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/pyproject.toml +1 -2
  15. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/sdkconfiguration.py +4 -1
  16. tofupilot-2.2.2/PKG-INFO +0 -633
  17. tofupilot-2.2.2/README.md +0 -591
  18. tofupilot-2.2.2/tofupilot/banner.py +0 -69
  19. {tofupilot-2.2.2 → tofupilot-2.2.4}/LICENSE +0 -0
  20. {tofupilot-2.2.2 → tofupilot-2.2.4}/py.typed +0 -0
  21. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/error_tracking.py +0 -0
  22. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/openhtf/__init__.py +0 -0
  23. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/openhtf/tofupilot.py +0 -0
  24. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/openhtf/upload.py +0 -0
  25. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/pytest/__init__.py +0 -0
  26. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/pytest/plugin.py +0 -0
  27. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/pytest/pytest.ini +0 -0
  28. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/telemetry_config.py +0 -0
  29. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/__init__.py +0 -0
  30. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/constants/__init__.py +0 -0
  31. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/constants/attachments.py +0 -0
  32. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/constants/requests.py +0 -0
  33. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/models/__init__.py +0 -0
  34. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/models/models.py +0 -0
  35. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/pyproject.toml +0 -0
  36. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/responses/__init__.py +0 -0
  37. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/responses/responses.py +0 -0
  38. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/utils/__init__.py +0 -0
  39. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/utils/dates.py +0 -0
  40. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/utils/logger.py +0 -0
  41. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v1/utils/network.py +0 -0
  42. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/_hooks/__init__.py +0 -0
  43. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/_hooks/sdkhooks.py +0 -0
  44. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/_hooks/types.py +0 -0
  45. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/basesdk.py +0 -0
  46. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/batches.py +0 -0
  47. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/__init__.py +0 -0
  48. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/apierror.py +0 -0
  49. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorbadgateway.py +0 -0
  50. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorbadrequest.py +0 -0
  51. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorconflict.py +0 -0
  52. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorforbidden.py +0 -0
  53. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorinternalservererror.py +0 -0
  54. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errornotfound.py +0 -0
  55. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorunauthorized.py +0 -0
  56. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/errorunprocessablecontent.py +0 -0
  57. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/no_response_error.py +0 -0
  58. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/responsevalidationerror.py +0 -0
  59. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/errors/tofupiloterror.py +0 -0
  60. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/httpclient.py +0 -0
  61. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/attachment_deleteop.py +0 -0
  62. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/attachment_finalizeop.py +0 -0
  63. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/attachment_initializeop.py +0 -0
  64. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/batch_createop.py +0 -0
  65. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/batch_deleteop.py +0 -0
  66. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/batch_getop.py +0 -0
  67. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/batch_listop.py +0 -0
  68. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/batch_updateop.py +0 -0
  69. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorbadgateway.py +0 -0
  70. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorbadrequest.py +0 -0
  71. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorconflict.py +0 -0
  72. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorforbidden.py +0 -0
  73. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorinternalservererror.py +0 -0
  74. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errornotfound.py +0 -0
  75. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorunauthorized.py +0 -0
  76. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/errorunprocessablecontent.py +0 -0
  77. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_createop.py +0 -0
  78. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_createrevisionop.py +0 -0
  79. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_deleteop.py +0 -0
  80. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_deleterevisionop.py +0 -0
  81. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_getop.py +0 -0
  82. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_getrevisionop.py +0 -0
  83. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_listop.py +0 -0
  84. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_updateop.py +0 -0
  85. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/part_updaterevisionop.py +0 -0
  86. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_createop.py +0 -0
  87. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_createversionop.py +0 -0
  88. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_deleteop.py +0 -0
  89. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_deleteversionop.py +0 -0
  90. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_getop.py +0 -0
  91. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_getversionop.py +0 -0
  92. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_listop.py +0 -0
  93. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/procedure_updateop.py +0 -0
  94. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/run_deleteop.py +0 -0
  95. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/run_getop.py +0 -0
  96. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/run_listop.py +0 -0
  97. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/run_updateop.py +0 -0
  98. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/security.py +0 -0
  99. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_createop.py +0 -0
  100. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_getcurrentop.py +0 -0
  101. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_getop.py +0 -0
  102. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_listop.py +0 -0
  103. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_removeop.py +0 -0
  104. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/station_updateop.py +0 -0
  105. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_addchildop.py +0 -0
  106. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_createop.py +0 -0
  107. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_deleteop.py +0 -0
  108. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_getop.py +0 -0
  109. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_listop.py +0 -0
  110. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_removechildop.py +0 -0
  111. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/unit_updateop.py +0 -0
  112. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/models/user_listop.py +0 -0
  113. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/parts.py +0 -0
  114. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/procedures.py +0 -0
  115. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/py.typed +0 -0
  116. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/revisions.py +0 -0
  117. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/runs.py +0 -0
  118. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/sdk.py +0 -0
  119. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/stations.py +0 -0
  120. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/types/__init__.py +0 -0
  121. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/types/basemodel.py +0 -0
  122. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/units_sdk.py +0 -0
  123. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/user.py +0 -0
  124. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/__init__.py +0 -0
  125. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/annotations.py +0 -0
  126. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/datetimes.py +0 -0
  127. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/enums.py +0 -0
  128. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/eventstreaming.py +0 -0
  129. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/forms.py +0 -0
  130. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/headers.py +0 -0
  131. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/logger.py +0 -0
  132. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/metadata.py +0 -0
  133. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/queryparams.py +0 -0
  134. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/requestbodies.py +0 -0
  135. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/retries.py +0 -0
  136. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/security.py +0 -0
  137. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/serializers.py +0 -0
  138. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/unmarshal_json_response.py +0 -0
  139. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/url.py +0 -0
  140. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/utils/values.py +0 -0
  141. {tofupilot-2.2.2 → tofupilot-2.2.4}/tofupilot/v2/versions.py +0 -0
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: tofupilot
3
+ Version: 2.2.4
4
+ Summary: Official Python client for TofuPilot with OpenHTF integration, real-time streaming and file attachment support
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: automatic,hardware,testing,tofupilot,openhtf,sdk
8
+ Author: TofuPilot Team
9
+ Author-email: hello@tofupilot.com
10
+ Requires-Python: >=3.9.2
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Manufacturing
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Provides-Extra: dev
20
+ Requires-Dist: build ; extra == "dev"
21
+ Requires-Dist: certifi (>=2023.7.22)
22
+ Requires-Dist: httpcore (>=1.0.9)
23
+ Requires-Dist: httpx (>=0.28.1)
24
+ Requires-Dist: mypy (==1.15.0) ; extra == "dev"
25
+ Requires-Dist: openhtf
26
+ Requires-Dist: paho-mqtt (>=2.0.0)
27
+ Requires-Dist: posthog (>=3.0.0)
28
+ Requires-Dist: pydantic (>=2.11.2)
29
+ Requires-Dist: pylint (==3.2.3) ; extra == "dev"
30
+ Requires-Dist: pytest (>=6.0.0)
31
+ Requires-Dist: python-dotenv ; extra == "dev"
32
+ Requires-Dist: requests (>=2.25.0)
33
+ Requires-Dist: trycast ; extra == "dev"
34
+ Requires-Dist: twine ; extra == "dev"
35
+ Project-URL: Documentation, https://tofupilot.com/docs
36
+ Project-URL: Homepage, https://github.com/tofupilot/python-client
37
+ Project-URL: Issues, https://github.com/tofupilot/python-client/issues
38
+ Project-URL: Repository, https://github.com/tofupilot/python-client
39
+ Description-Content-Type: text/markdown
40
+
41
+ # TofuPilot Python Client
42
+
43
+ [![PyPI version](https://badge.fury.io/py/tofupilot.svg)](https://badge.fury.io/py/tofupilot)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
+ [![Tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/upview/8e092a304bae7d91aa154e19bdf694a4/raw/python-client-tests.json)](https://github.com/tofupilot/python)
46
+
47
+ The official Python client for [TofuPilot](https://tofupilot.com). Integrate your hardware test runs into one app with just a few lines of Python.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install tofupilot
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```python
58
+ import os
59
+ from tofupilot.v2 import TofuPilot
60
+
61
+ with TofuPilot(api_key=os.getenv("TOFUPILOT_API_KEY")) as client:
62
+ run = client.runs.create(
63
+ procedure_id="your-procedure-id",
64
+ serial_number="SN001",
65
+ part_number="PN001",
66
+ outcome="PASS",
67
+ )
68
+ print(f"Run created: {run.id}")
69
+ ```
70
+
71
+ ## Authentication
72
+
73
+ Set your API key as an environment variable:
74
+
75
+ ```bash
76
+ export TOFUPILOT_API_KEY="your-api-key"
77
+ ```
78
+
79
+ Or pass it directly:
80
+
81
+ ```python
82
+ client = TofuPilot(api_key="your-api-key")
83
+ ```
84
+
85
+ To point to a different server (e.g. self-hosted):
86
+
87
+ ```python
88
+ client = TofuPilot(api_key="your-api-key", server_url="https://your-instance.com/api")
89
+ ```
90
+
91
+ ## Available Resources
92
+
93
+ | Resource | Operations |
94
+ |----------|-----------|
95
+ | `client.runs` | list, create, get, delete, update |
96
+ | `client.units` | list, create, get, delete, update, add_child, remove_child |
97
+ | `client.parts` | list, create, get, delete, update |
98
+ | `client.parts.revisions` | create, get, delete, update |
99
+ | `client.procedures` | list, create, get, delete, update |
100
+ | `client.procedures.versions` | create, get, delete |
101
+ | `client.batches` | list, create, get, delete, update |
102
+ | `client.stations` | list, create, get, get_current, remove, update |
103
+ | `client.attachments` | initialize, finalize, delete, upload, download |
104
+ | `client.user` | list |
105
+
106
+ ## Usage Examples
107
+
108
+ ### Create a run with measurements
109
+
110
+ ```python
111
+ from datetime import datetime, timedelta, timezone
112
+
113
+ run = client.runs.create(
114
+ procedure_id=procedure_id,
115
+ serial_number="SN-001",
116
+ part_number="PCB-V1",
117
+ outcome="PASS",
118
+ started_at=datetime.now(timezone.utc) - timedelta(minutes=5),
119
+ ended_at=datetime.now(timezone.utc),
120
+ phases=[{
121
+ "name": "Voltage Test",
122
+ "outcome": "PASS",
123
+ "started_at": datetime.now(timezone.utc) - timedelta(minutes=5),
124
+ "ended_at": datetime.now(timezone.utc),
125
+ "measurements": [{
126
+ "name": "Output Voltage",
127
+ "outcome": "PASS",
128
+ "measured_value": 3.3,
129
+ "units": "V",
130
+ "validators": [
131
+ {"operator": ">=", "expected_value": 3.0},
132
+ {"operator": "<=", "expected_value": 3.6},
133
+ ],
134
+ }],
135
+ }],
136
+ )
137
+ ```
138
+
139
+ ### List and filter runs
140
+
141
+ ```python
142
+ result = client.runs.list(
143
+ part_numbers=["PCB-V1"],
144
+ outcomes=["PASS"],
145
+ limit=10,
146
+ )
147
+
148
+ for run in result.data:
149
+ print(f"{run.id} — {run.unit.serial_number}")
150
+ ```
151
+
152
+ ### Manage units and sub-units
153
+
154
+ ```python
155
+ # Create part and revision
156
+ client.parts.create(number="PCB-V1", name="Main Board")
157
+ client.parts.revisions.create(part_number="PCB-V1", number="REV-A")
158
+
159
+ # Create units
160
+ client.units.create(serial_number="PARENT-001", part_number="PCB-V1", revision_number="REV-A")
161
+ client.units.create(serial_number="CHILD-001", part_number="PCB-V1", revision_number="REV-A")
162
+
163
+ # Link parent-child
164
+ client.units.add_child(serial_number="PARENT-001", child_serial_number="CHILD-001")
165
+ ```
166
+
167
+ ### Upload and download attachments
168
+
169
+ ```python
170
+ # Upload a file (one line)
171
+ attachment_id = client.attachments.upload("report.pdf")
172
+
173
+ # Link to a run
174
+ client.runs.update(id=run_id, attachments=[attachment_id])
175
+
176
+ # Download an attachment
177
+ client.attachments.download(attachment, dest="local-copy.pdf")
178
+ ```
179
+
180
+ ## Error Handling
181
+
182
+ ```python
183
+ from tofupilot.v2.models.errors import ErrorNOTFOUND, ErrorBADREQUEST
184
+
185
+ try:
186
+ client.runs.get(id="nonexistent-id")
187
+ except ErrorNOTFOUND as e:
188
+ print(f"Not found: {e.message}")
189
+ except ErrorBADREQUEST as e:
190
+ print(f"Bad request: {e.message}")
191
+ ```
192
+
193
+ | Exception | Status Code |
194
+ |-----------|------------|
195
+ | `ErrorBADREQUEST` | 400 |
196
+ | `ErrorUNAUTHORIZED` | 401 |
197
+ | `ErrorFORBIDDEN` | 403 |
198
+ | `ErrorNOTFOUND` | 404 |
199
+ | `ErrorCONFLICT` | 409 |
200
+ | `ErrorUNPROCESSABLECONTENT` | 422 |
201
+ | `ErrorINTERNALSERVERERROR` | 500 |
202
+
203
+ ## Running Tests
204
+
205
+ ```bash
206
+ cd clients/python-speakeasy
207
+ cp tests/.env.local.example tests/.env.local # Set your API key and URL
208
+ python -m pytest tests/v2/
209
+ ```
210
+
211
+ ## Documentation
212
+
213
+ - [Getting Started](https://tofupilot.com/docs/dashboard)
214
+ - [API Reference](https://tofupilot.com/docs/dashboard/api/v2)
215
+ - [Changelog](https://tofupilot.com/changelog)
216
+
217
+ ## License
218
+
219
+ MIT
220
+
@@ -0,0 +1,179 @@
1
+ # TofuPilot Python Client
2
+
3
+ [![PyPI version](https://badge.fury.io/py/tofupilot.svg)](https://badge.fury.io/py/tofupilot)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/upview/8e092a304bae7d91aa154e19bdf694a4/raw/python-client-tests.json)](https://github.com/tofupilot/python)
6
+
7
+ The official Python client for [TofuPilot](https://tofupilot.com). Integrate your hardware test runs into one app with just a few lines of Python.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install tofupilot
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ import os
19
+ from tofupilot.v2 import TofuPilot
20
+
21
+ with TofuPilot(api_key=os.getenv("TOFUPILOT_API_KEY")) as client:
22
+ run = client.runs.create(
23
+ procedure_id="your-procedure-id",
24
+ serial_number="SN001",
25
+ part_number="PN001",
26
+ outcome="PASS",
27
+ )
28
+ print(f"Run created: {run.id}")
29
+ ```
30
+
31
+ ## Authentication
32
+
33
+ Set your API key as an environment variable:
34
+
35
+ ```bash
36
+ export TOFUPILOT_API_KEY="your-api-key"
37
+ ```
38
+
39
+ Or pass it directly:
40
+
41
+ ```python
42
+ client = TofuPilot(api_key="your-api-key")
43
+ ```
44
+
45
+ To point to a different server (e.g. self-hosted):
46
+
47
+ ```python
48
+ client = TofuPilot(api_key="your-api-key", server_url="https://your-instance.com/api")
49
+ ```
50
+
51
+ ## Available Resources
52
+
53
+ | Resource | Operations |
54
+ |----------|-----------|
55
+ | `client.runs` | list, create, get, delete, update |
56
+ | `client.units` | list, create, get, delete, update, add_child, remove_child |
57
+ | `client.parts` | list, create, get, delete, update |
58
+ | `client.parts.revisions` | create, get, delete, update |
59
+ | `client.procedures` | list, create, get, delete, update |
60
+ | `client.procedures.versions` | create, get, delete |
61
+ | `client.batches` | list, create, get, delete, update |
62
+ | `client.stations` | list, create, get, get_current, remove, update |
63
+ | `client.attachments` | initialize, finalize, delete, upload, download |
64
+ | `client.user` | list |
65
+
66
+ ## Usage Examples
67
+
68
+ ### Create a run with measurements
69
+
70
+ ```python
71
+ from datetime import datetime, timedelta, timezone
72
+
73
+ run = client.runs.create(
74
+ procedure_id=procedure_id,
75
+ serial_number="SN-001",
76
+ part_number="PCB-V1",
77
+ outcome="PASS",
78
+ started_at=datetime.now(timezone.utc) - timedelta(minutes=5),
79
+ ended_at=datetime.now(timezone.utc),
80
+ phases=[{
81
+ "name": "Voltage Test",
82
+ "outcome": "PASS",
83
+ "started_at": datetime.now(timezone.utc) - timedelta(minutes=5),
84
+ "ended_at": datetime.now(timezone.utc),
85
+ "measurements": [{
86
+ "name": "Output Voltage",
87
+ "outcome": "PASS",
88
+ "measured_value": 3.3,
89
+ "units": "V",
90
+ "validators": [
91
+ {"operator": ">=", "expected_value": 3.0},
92
+ {"operator": "<=", "expected_value": 3.6},
93
+ ],
94
+ }],
95
+ }],
96
+ )
97
+ ```
98
+
99
+ ### List and filter runs
100
+
101
+ ```python
102
+ result = client.runs.list(
103
+ part_numbers=["PCB-V1"],
104
+ outcomes=["PASS"],
105
+ limit=10,
106
+ )
107
+
108
+ for run in result.data:
109
+ print(f"{run.id} — {run.unit.serial_number}")
110
+ ```
111
+
112
+ ### Manage units and sub-units
113
+
114
+ ```python
115
+ # Create part and revision
116
+ client.parts.create(number="PCB-V1", name="Main Board")
117
+ client.parts.revisions.create(part_number="PCB-V1", number="REV-A")
118
+
119
+ # Create units
120
+ client.units.create(serial_number="PARENT-001", part_number="PCB-V1", revision_number="REV-A")
121
+ client.units.create(serial_number="CHILD-001", part_number="PCB-V1", revision_number="REV-A")
122
+
123
+ # Link parent-child
124
+ client.units.add_child(serial_number="PARENT-001", child_serial_number="CHILD-001")
125
+ ```
126
+
127
+ ### Upload and download attachments
128
+
129
+ ```python
130
+ # Upload a file (one line)
131
+ attachment_id = client.attachments.upload("report.pdf")
132
+
133
+ # Link to a run
134
+ client.runs.update(id=run_id, attachments=[attachment_id])
135
+
136
+ # Download an attachment
137
+ client.attachments.download(attachment, dest="local-copy.pdf")
138
+ ```
139
+
140
+ ## Error Handling
141
+
142
+ ```python
143
+ from tofupilot.v2.models.errors import ErrorNOTFOUND, ErrorBADREQUEST
144
+
145
+ try:
146
+ client.runs.get(id="nonexistent-id")
147
+ except ErrorNOTFOUND as e:
148
+ print(f"Not found: {e.message}")
149
+ except ErrorBADREQUEST as e:
150
+ print(f"Bad request: {e.message}")
151
+ ```
152
+
153
+ | Exception | Status Code |
154
+ |-----------|------------|
155
+ | `ErrorBADREQUEST` | 400 |
156
+ | `ErrorUNAUTHORIZED` | 401 |
157
+ | `ErrorFORBIDDEN` | 403 |
158
+ | `ErrorNOTFOUND` | 404 |
159
+ | `ErrorCONFLICT` | 409 |
160
+ | `ErrorUNPROCESSABLECONTENT` | 422 |
161
+ | `ErrorINTERNALSERVERERROR` | 500 |
162
+
163
+ ## Running Tests
164
+
165
+ ```bash
166
+ cd clients/python-speakeasy
167
+ cp tests/.env.local.example tests/.env.local # Set your API key and URL
168
+ python -m pytest tests/v2/
169
+ ```
170
+
171
+ ## Documentation
172
+
173
+ - [Getting Started](https://tofupilot.com/docs/dashboard)
174
+ - [API Reference](https://tofupilot.com/docs/dashboard/api/v2)
175
+ - [Changelog](https://tofupilot.com/changelog)
176
+
177
+ ## License
178
+
179
+ MIT
@@ -1,10 +1,9 @@
1
1
  [project]
2
2
  name = "tofupilot"
3
- version = "2.2.2"
3
+ version = "2.2.4"
4
4
  description = "Official Python client for TofuPilot with OpenHTF integration, real-time streaming and file attachment support"
5
5
  authors = [
6
6
  { name = "TofuPilot Team", email = "hello@tofupilot.com" },
7
- { name = "Speakeasy" }
8
7
  ]
9
8
  readme = "README.md"
10
9
  license = { text = "MIT" }
@@ -24,7 +23,6 @@ classifiers = [
24
23
  dependencies = [
25
24
  # Core v1 dependencies
26
25
  "requests>=2.25.0",
27
- "packaging>=20.0",
28
26
  "certifi>=2023.7.22",
29
27
  "openhtf",
30
28
  "paho-mqtt>=2.0.0",
@@ -2,7 +2,7 @@
2
2
 
3
3
  This package provides both v1 and v2 API clients:
4
4
  - v1: Legacy client for backward compatibility
5
- - v2: New Speakeasy-generated SDK with enhanced features and error tracking
5
+ - v2: Current SDK with enhanced features and error tracking
6
6
 
7
7
  For new projects, we recommend using the v2 client.
8
8
 
@@ -0,0 +1,5 @@
1
+ """TofuPilot client version check — intentionally empty.
2
+
3
+ Version checks are handled server-side via API response headers,
4
+ not by polling PyPI at import time.
5
+ """
@@ -42,7 +42,6 @@ from .utils import (
42
42
  api_request,
43
43
  process_openhtf_attachments,
44
44
  )
45
- from ..banner import print_banner_and_check_version
46
45
 
47
46
  from .utils import api_request
48
47
 
@@ -66,7 +65,7 @@ class TofuPilotClient:
66
65
  verify: Optional[str] = None,
67
66
  ):
68
67
  self._logger = setup_logger(logging.INFO)
69
- self._current_version = print_banner_and_check_version()
68
+ self._current_version = version("tofupilot")
70
69
 
71
70
  # Configure SSL certificate validation
72
71
  self._setup_ssl_certificates()
@@ -96,6 +96,9 @@ def upload_file(
96
96
  upload_url = response_json.get("uploadUrl")
97
97
  upload_id = response_json.get("id")
98
98
 
99
+ if not upload_id or not upload_url:
100
+ raise ValueError(f"Upload initialization failed: missing 'id' or 'uploadUrl' in response: {response_json}")
101
+
99
102
  # File storing
100
103
  with open(file_path, "rb") as file:
101
104
  content_type, _ = mimetypes.guess_type(file_path) or "application/octet-stream"
@@ -1,4 +1,8 @@
1
- """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
1
+ # Import the error tracking enhanced client by default
2
+ from .client_with_error_tracking import TofuPilotWithErrorTracking as TofuPilot
3
+ # Still export the base SDK for those who want it
4
+ from .sdk import TofuPilot as TofuPilotBase
5
+ from .sdkconfiguration import *
2
6
 
3
7
  from ._version import (
4
8
  __title__,
@@ -7,25 +11,17 @@ from ._version import (
7
11
  __gen_version__,
8
12
  __user_agent__,
9
13
  )
10
- # Import the error tracking enhanced client by default
11
- from .client_with_error_tracking import TofuPilotWithErrorTracking as TofuPilot
12
- # Still export the base SDK for those who want it
13
- from .sdk import TofuPilot as TofuPilotBase
14
- from .sdkconfiguration import *
15
-
16
14
 
17
15
  VERSION: str = __version__
18
16
  OPENAPI_DOC_VERSION = __openapi_doc_version__
19
- SPEAKEASY_GENERATOR_VERSION = __gen_version__
17
+ GENERATOR_VERSION = __gen_version__
20
18
  USER_AGENT = __user_agent__
21
19
 
22
- # Export both versions
23
20
  __all__ = [
24
- "TofuPilot", # Default with error tracking
25
- "TofuPilotBase", # Base without error tracking
26
- # Version info
21
+ "TofuPilot",
22
+ "TofuPilotBase",
27
23
  "VERSION",
28
24
  "OPENAPI_DOC_VERSION",
29
- "SPEAKEASY_GENERATOR_VERSION",
25
+ "GENERATOR_VERSION",
30
26
  "USER_AGENT",
31
27
  ]
@@ -3,10 +3,10 @@
3
3
  import importlib.metadata
4
4
 
5
5
  __title__: str = "tofupilot.v2"
6
- __version__: str = "0.7.12"
6
+ __version__: str = "2.2.4"
7
7
  __openapi_doc_version__: str = "2.0.0"
8
8
  __gen_version__: str = "2.657.1"
9
- __user_agent__: str = "speakeasy-sdk/python 0.7.12 2.657.1 2.0.0 tofupilot.v2"
9
+ __user_agent__: str = "speakeasy-sdk/python 2.2.4 2.657.1 2.0.0 tofupilot.v2"
10
10
 
11
11
  try:
12
12
  if __package__ is not None:
@@ -251,7 +251,7 @@ class Attachments(BaseSDK):
251
251
  ) -> models.AttachmentDeleteResponse:
252
252
  r"""Delete attachments
253
253
 
254
- Permanently delete attachments by their IDs and unlink them from any associated runs or units.
254
+ Permanently delete attachments by their IDs and unlink them from any associated runs or units. Removes files from storage and clears all references.
255
255
 
256
256
  :param ids: Upload IDs to delete
257
257
  :param retries: Override the default retry configuration for this method
@@ -353,7 +353,7 @@ class Attachments(BaseSDK):
353
353
  ) -> models.AttachmentDeleteResponse:
354
354
  r"""Delete attachments
355
355
 
356
- Permanently delete attachments by their IDs and unlink them from any associated runs or units.
356
+ Permanently delete attachments by their IDs and unlink them from any associated runs or units. Removes files from storage and clears all references.
357
357
 
358
358
  :param ids: Upload IDs to delete
359
359
  :param retries: Override the default retry configuration for this method
@@ -455,7 +455,7 @@ class Attachments(BaseSDK):
455
455
  ) -> models.AttachmentFinalizeResponse:
456
456
  r"""Finalize upload
457
457
 
458
- Finalize a file upload after uploading to the pre-signed URL. Validates the file and records its metadata. Link the attachment to a run or unit using Update Run or Update Unit.
458
+ Finalize a file upload after uploading to the pre-signed URL. Validates the file and records its metadata.
459
459
 
460
460
  :param id: ID of the upload to finalize
461
461
  :param retries: Override the default retry configuration for this method
@@ -552,7 +552,7 @@ class Attachments(BaseSDK):
552
552
  ) -> models.AttachmentFinalizeResponse:
553
553
  r"""Finalize upload
554
554
 
555
- Finalize a file upload after uploading to the pre-signed URL. Validates the file and records its metadata. Link the attachment to a run or unit using Update Run or Update Unit.
555
+ Finalize a file upload after uploading to the pre-signed URL. Validates the file and records its metadata.
556
556
 
557
557
  :param id: ID of the upload to finalize
558
558
  :param retries: Override the default retry configuration for this method
@@ -9,7 +9,6 @@ from pydantic_core import ValidationError
9
9
 
10
10
  from .sdk import TofuPilot
11
11
  from .errors.tofupiloterror import TofuPilotError
12
- from ..banner import print_banner_and_check_version
13
12
 
14
13
 
15
14
  def _enhance_error_message(e: TofuPilotError) -> None:
@@ -99,7 +98,7 @@ class _AttachmentsWithUpload(_ResourceWithBetterErrors):
99
98
  resp = httpx.put(init.upload_url, content=f.read(), headers={"Content-Type": content_type})
100
99
  if resp.status_code != 200:
101
100
  raise RuntimeError(f"File upload failed with status {resp.status_code}")
102
- self._resource.finalize(id=init.id, request_body={})
101
+ self._resource.finalize(id=init.id)
103
102
  return init.id
104
103
 
105
104
  def download(self, attachment, dest: Union[str, Path, None] = None) -> Path:
@@ -170,8 +169,6 @@ class TofuPilotWithErrorTracking(TofuPilot):
170
169
  **kwargs
171
170
  )
172
171
 
173
- print_banner_and_check_version()
174
-
175
172
  def __getattr__(self, name: str):
176
173
  attr = super().__getattr__(name)
177
174
  if name == 'runs':
@@ -263,6 +263,8 @@ if TYPE_CHECKING:
263
263
  RunCreateAggregationValidator,
264
264
  RunCreateAggregationValidatorOutcome,
265
265
  RunCreateAggregationValidatorTypedDict,
266
+ RunCreateExpectedValue,
267
+ RunCreateExpectedValueTypedDict,
266
268
  RunCreateLevel,
267
269
  RunCreateLog,
268
270
  RunCreateLogTypedDict,
@@ -283,15 +285,11 @@ if TYPE_CHECKING:
283
285
  RunCreateResponseTypedDict,
284
286
  RunCreateUnits,
285
287
  RunCreateUnitsTypedDict,
288
+ RunCreateValidator,
289
+ RunCreateValidatorOutcome,
290
+ RunCreateValidatorTypedDict,
286
291
  RunCreateValue,
287
292
  RunCreateValueTypedDict,
288
- Validators,
289
- ValidatorsExpectedValue,
290
- ValidatorsExpectedValueTypedDict,
291
- ValidatorsOutcome,
292
- ValidatorsTypedDict,
293
- ValidatorsUnion,
294
- ValidatorsUnionTypedDict,
295
293
  XAxis,
296
294
  XAxisAggregation,
297
295
  XAxisAggregationExpectedValue,
@@ -857,6 +855,8 @@ __all__ = [
857
855
  "RunCreateAggregationValidator",
858
856
  "RunCreateAggregationValidatorOutcome",
859
857
  "RunCreateAggregationValidatorTypedDict",
858
+ "RunCreateExpectedValue",
859
+ "RunCreateExpectedValueTypedDict",
860
860
  "RunCreateLevel",
861
861
  "RunCreateLog",
862
862
  "RunCreateLogTypedDict",
@@ -877,6 +877,9 @@ __all__ = [
877
877
  "RunCreateResponseTypedDict",
878
878
  "RunCreateUnits",
879
879
  "RunCreateUnitsTypedDict",
880
+ "RunCreateValidator",
881
+ "RunCreateValidatorOutcome",
882
+ "RunCreateValidatorTypedDict",
880
883
  "RunCreateValue",
881
884
  "RunCreateValueTypedDict",
882
885
  "RunDeleteRequest",
@@ -1111,13 +1114,6 @@ __all__ = [
1111
1114
  "UserListRequestTypedDict",
1112
1115
  "UserListResponse",
1113
1116
  "UserListResponseTypedDict",
1114
- "Validators",
1115
- "ValidatorsExpectedValue",
1116
- "ValidatorsExpectedValueTypedDict",
1117
- "ValidatorsOutcome",
1118
- "ValidatorsTypedDict",
1119
- "ValidatorsUnion",
1120
- "ValidatorsUnionTypedDict",
1121
1117
  "XAxis",
1122
1118
  "XAxisAggregation",
1123
1119
  "XAxisAggregationExpectedValue",
@@ -1362,6 +1358,8 @@ _dynamic_imports: dict[str, str] = {
1362
1358
  "RunCreateAggregationValidator": ".run_createop",
1363
1359
  "RunCreateAggregationValidatorOutcome": ".run_createop",
1364
1360
  "RunCreateAggregationValidatorTypedDict": ".run_createop",
1361
+ "RunCreateExpectedValue": ".run_createop",
1362
+ "RunCreateExpectedValueTypedDict": ".run_createop",
1365
1363
  "RunCreateLevel": ".run_createop",
1366
1364
  "RunCreateLog": ".run_createop",
1367
1365
  "RunCreateLogTypedDict": ".run_createop",
@@ -1382,15 +1380,11 @@ _dynamic_imports: dict[str, str] = {
1382
1380
  "RunCreateResponseTypedDict": ".run_createop",
1383
1381
  "RunCreateUnits": ".run_createop",
1384
1382
  "RunCreateUnitsTypedDict": ".run_createop",
1383
+ "RunCreateValidator": ".run_createop",
1384
+ "RunCreateValidatorOutcome": ".run_createop",
1385
+ "RunCreateValidatorTypedDict": ".run_createop",
1385
1386
  "RunCreateValue": ".run_createop",
1386
1387
  "RunCreateValueTypedDict": ".run_createop",
1387
- "Validators": ".run_createop",
1388
- "ValidatorsExpectedValue": ".run_createop",
1389
- "ValidatorsExpectedValueTypedDict": ".run_createop",
1390
- "ValidatorsOutcome": ".run_createop",
1391
- "ValidatorsTypedDict": ".run_createop",
1392
- "ValidatorsUnion": ".run_createop",
1393
- "ValidatorsUnionTypedDict": ".run_createop",
1394
1388
  "XAxis": ".run_createop",
1395
1389
  "XAxisAggregation": ".run_createop",
1396
1390
  "XAxisAggregationExpectedValue": ".run_createop",