iqm-station-control-client 11.3.1__tar.gz → 12.0.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.
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/CHANGELOG.rst +34 -8
- iqm_station_control_client-12.0.0/MANIFEST.in +4 -0
- {iqm_station_control_client-11.3.1/src/iqm_station_control_client.egg-info → iqm_station_control_client-12.0.0}/PKG-INFO +3 -3
- iqm_station_control_client-12.0.0/docs/changelog.rst +8 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/pyproject.toml +4 -5
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/requirements/base.txt +2 -2
- iqm_station_control_client-12.0.0/src/iqm/station_control/client/authentication.py +239 -0
- iqm_station_control_client-12.0.0/src/iqm/station_control/client/iqm_server/iqm_server_client.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/list_models.py +16 -11
- iqm_station_control_client-12.0.0/src/iqm/station_control/client/py.typed +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/qon.py +1 -1
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/run_serializers.py +5 -4
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/struct_serializer.py +1 -1
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/station_control.py +140 -154
- iqm_station_control_client-12.0.0/src/iqm/station_control/client/utils.py +33 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/__init__.py +21 -2
- iqm_station_control_client-12.0.0/src/iqm/station_control/interface/models/circuit.py +348 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/dynamic_quantum_architecture.py +61 -3
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/jobs.py +41 -12
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/observation_set.py +28 -4
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/run.py +8 -8
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/sweep.py +7 -1
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/type_aliases.py +1 -2
- iqm_station_control_client-12.0.0/src/iqm/station_control/interface/py.typed +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/station_control.py +1 -1
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0/src/iqm_station_control_client.egg-info}/PKG-INFO +3 -3
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm_station_control_client.egg-info/SOURCES.txt +2 -19
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm_station_control_client.egg-info/requires.txt +1 -1
- iqm_station_control_client-12.0.0/version.txt +1 -0
- iqm_station_control_client-11.3.1/MANIFEST.in +0 -10
- iqm_station_control_client-11.3.1/docs/changelog.rst +0 -2
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/__init__.py +0 -14
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/error.py +0 -30
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/grpc_utils.py +0 -156
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/iqm_server_client.py +0 -489
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/__init__.py +0 -43
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.py +0 -48
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi +0 -45
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py +0 -152
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/common_pb2.py +0 -43
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/common_pb2.pyi +0 -32
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py +0 -17
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/job_pb2.py +0 -57
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/job_pb2.pyi +0 -107
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py +0 -436
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/qc_pb2.py +0 -51
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/qc_pb2.pyi +0 -57
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py +0 -163
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.py +0 -39
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi +0 -26
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py +0 -17
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/testing/__init__.py +0 -13
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/iqm_server/testing/iqm_server_mock.py +0 -102
- iqm_station_control_client-11.3.1/src/iqm/station_control/client/utils.py +0 -71
- iqm_station_control_client-11.3.1/version.txt +0 -1
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/LICENSE.txt +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/README.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/API.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/Makefile +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/_static/css/custom.css +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/_static/images/favicon.ico +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/_static/images/logo.png +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/conf.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/index.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/docs/license.rst +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/requirements/base.in +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/requirements/base.in.internal +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/setup.cfg +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/setup.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/__init__.py +0 -0
- /iqm_station_control_client-11.3.1/src/iqm/station_control/client/py.typed → /iqm_station_control_client-12.0.0/src/iqm/station_control/client/iqm_server/error.py +0 -0
- /iqm_station_control_client-11.3.1/src/iqm/station_control/interface/py.typed → /iqm_station_control_client-12.0.0/src/iqm/station_control/client/iqm_server/grpc_utils.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/__init__.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/channel_property_serializer.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/datetime_serializers.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/playlist_serializers.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/setting_node_serializer.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/sweep_serializers.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/client/serializers/task_serializers.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/__init__.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/list_with_meta.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/dut.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/monitor.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/observation.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/sequence.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/models/static_quantum_architecture.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm/station_control/interface/pydantic_base.py +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm_station_control_client.egg-info/dependency_links.txt +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/src/iqm_station_control_client.egg-info/top_level.txt +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/tests/.pylintrc +0 -0
- {iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/tests/__init__.py +0 -0
|
@@ -1,11 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
Changelog
|
|
3
|
-
=========
|
|
4
|
-
|
|
5
|
-
Version 11.3.1 (2025-10-27)
|
|
1
|
+
Version 12.0.0 (2025-11-19)
|
|
6
2
|
===========================
|
|
7
3
|
|
|
8
|
-
|
|
4
|
+
Breaking changes
|
|
5
|
+
----------------
|
|
6
|
+
|
|
7
|
+
- :class:`.IqmServerClient` has been removed from station-control-client package.
|
|
8
|
+
Use new REST-based implementation of :class:`.IQMServerClient` in iqm-client package.
|
|
9
|
+
|
|
10
|
+
Features
|
|
11
|
+
--------
|
|
12
|
+
|
|
13
|
+
- Add new circuit and job related models to station control interface,
|
|
14
|
+
used in IQM client when serializing/deserializing data in IQMServer communication
|
|
15
|
+
- Deprecate ``observation_ids`` in ``ObservationSetWithObservations``, use ``observations`` instead.
|
|
16
|
+
This is to unify the data with IQMServer.
|
|
17
|
+
- :class:`StationControlClient` now accepts the ``token`` and ``tokens_file`` parameters,
|
|
18
|
+
and can use the :envvar:`IQM_TOKEN` and :envvar:`IQM_TOKENS_FILE` environment variables
|
|
19
|
+
to get the authentication token.
|
|
20
|
+
- In case major versions differ, log warning instead of raising an error in the python package
|
|
21
|
+
version compatibility check when connecting to station-control server with
|
|
22
|
+
:class:`iqm.station_control.client.station_control.StationControlClient`.
|
|
23
|
+
- :class:`StationControlClient` now accepts the ``token`` and ``tokens_file`` parameters,
|
|
24
|
+
and can use the :envvar:`IQM_TOKEN` and :envvar:`IQM_TOKENS_FILE` environment variables
|
|
25
|
+
to get the authentication token.
|
|
26
|
+
- In case major versions differ, log warning instead of raising an error in the python package
|
|
27
|
+
version compatibility check when connecting to station-control server with
|
|
28
|
+
:class:`iqm.station_control.client.station_control.StationControlClient`.
|
|
29
|
+
|
|
30
|
+
Bug fixes
|
|
31
|
+
---------
|
|
32
|
+
|
|
33
|
+
- Restrict RunDefinition fields to fix issues with serializing None and roundtrip resulting in empty dictionary.
|
|
34
|
+
- Fix python package compatibility version check when connecting to station-control server.
|
|
9
35
|
|
|
10
36
|
Version 11.3.0 (2025-10-23)
|
|
11
37
|
===========================
|
|
@@ -382,7 +408,7 @@ Version 3.12.0 (2025-04-07)
|
|
|
382
408
|
Features
|
|
383
409
|
--------
|
|
384
410
|
|
|
385
|
-
- Fix package version in published docs footers, :issue:`SW-1392`.
|
|
411
|
+
- Fix package version in published docs footers, :issue:`SW-1392`.
|
|
386
412
|
|
|
387
413
|
Version 3.11.0 (2025-04-03)
|
|
388
414
|
===========================
|
|
@@ -827,7 +853,7 @@ Version 1.2 (2024-07-05)
|
|
|
827
853
|
|
|
828
854
|
Features
|
|
829
855
|
--------
|
|
830
|
-
- Bump exa-common to 25.3
|
|
856
|
+
- Bump exa-common to 25.3
|
|
831
857
|
|
|
832
858
|
|
|
833
859
|
Version 1.1 (2024-07-04)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iqm-station-control-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 12.0.0
|
|
4
4
|
Summary: Python client for communicating with Station Control Service
|
|
5
5
|
Author-email: IQM Finland Oy <info@meetiqm.com>
|
|
6
6
|
License: Apache License
|
|
@@ -204,7 +204,7 @@ License: Apache License
|
|
|
204
204
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
205
205
|
See the License for the specific language governing permissions and
|
|
206
206
|
limitations under the License.
|
|
207
|
-
Project-URL: Documentation, https://
|
|
207
|
+
Project-URL: Documentation, https://docs.meetiqm.com/iqm-station-control-client/
|
|
208
208
|
Project-URL: Homepage, https://pypi.org/project/iqm-station-control-client/
|
|
209
209
|
Classifier: Development Status :: 4 - Beta
|
|
210
210
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -213,7 +213,6 @@ Classifier: Intended Audience :: Science/Research
|
|
|
213
213
|
Requires-Python: >=3.11
|
|
214
214
|
Description-Content-Type: text/x-rst
|
|
215
215
|
License-File: LICENSE.txt
|
|
216
|
-
Requires-Dist: iqm-exa-common<28,>=27
|
|
217
216
|
Requires-Dist: iqm-data-definitions<3.0,>=2.18
|
|
218
217
|
Requires-Dist: opentelemetry-exporter-otlp<2.0,>=1.25.0
|
|
219
218
|
Requires-Dist: protobuf<5.0,>=4.25.3
|
|
@@ -225,6 +224,7 @@ Requires-Dist: requests==2.32.3
|
|
|
225
224
|
Requires-Dist: types-requests
|
|
226
225
|
Requires-Dist: tqdm>=4.59.0
|
|
227
226
|
Requires-Dist: types-tqdm
|
|
227
|
+
Requires-Dist: iqm-exa-common
|
|
228
228
|
|
|
229
229
|
Station control client library
|
|
230
230
|
==============================
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# This file is managed by monotool.py, do not edit by hand
|
|
2
1
|
[build-system]
|
|
3
2
|
build-backend = "setuptools.build_meta"
|
|
4
3
|
requires = [ "setuptools", "setuptools_scm[toml]",]
|
|
@@ -18,7 +17,7 @@ email = "info@meetiqm.com"
|
|
|
18
17
|
file = "LICENSE.txt"
|
|
19
18
|
|
|
20
19
|
[project.urls]
|
|
21
|
-
Documentation = "https://
|
|
20
|
+
Documentation = "https://docs.meetiqm.com/iqm-station-control-client/"
|
|
22
21
|
Homepage = "https://pypi.org/project/iqm-station-control-client/"
|
|
23
22
|
|
|
24
23
|
[tool.mypy]
|
|
@@ -58,7 +57,7 @@ nb_diff_ignore = [ "/metadata/language_info", "/metadata/widgets", "/cells/*/exe
|
|
|
58
57
|
|
|
59
58
|
[tool.ruff.lint]
|
|
60
59
|
ignore = [ "D203", "D213",]
|
|
61
|
-
select = [ "E4", "E7", "E9", "E5", "F", "Q", "PL", "I", "D", "UP007", "UP006", "UP035", "ANN001", "ANN201", "ANN202",]
|
|
60
|
+
select = [ "E4", "E7", "E9", "E5", "F", "Q", "PL", "I", "D", "UP007", "UP006", "UP035", "ANN001", "ANN201", "ANN202", "NPY201",]
|
|
62
61
|
unfixable = [ "F401",]
|
|
63
62
|
|
|
64
63
|
[tool.ruff.lint.isort]
|
|
@@ -69,10 +68,10 @@ known-third-party = [ "exa", "iqm",]
|
|
|
69
68
|
relative-imports-order = "closest-to-furthest"
|
|
70
69
|
|
|
71
70
|
[tool.ruff.lint.per-file-ignores]
|
|
72
|
-
"**/__init__.py" = [ "F401", "PLR0402",]
|
|
71
|
+
"**/__init__.py" = [ "D104", "F401", "PLR0402",]
|
|
73
72
|
"**/docs/*" = [ "E402", "D100",]
|
|
74
73
|
"**/setup.py" = [ "D100", "D103", "I001", "ANN201",]
|
|
75
|
-
"**/src/*" = [ "PLR2004", "D400", "D415", "D205", "D401", "D417", "D100", "
|
|
74
|
+
"**/src/*" = [ "PLR2004", "D400", "D415", "D205", "D401", "D417", "D100", "D107", "D105", "D102", "D404",]
|
|
76
75
|
"**/tests/*" = [ "F632", "PLR2004", "PLR0402", "PLC0414", "D", "ANN001", "ANN201", "ANN202",]
|
|
77
76
|
|
|
78
77
|
[tool.ruff.lint.pylint]
|
{iqm_station_control_client-11.3.1 → iqm_station_control_client-12.0.0}/requirements/base.txt
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
iqm-exa-common>=27,<28
|
|
2
1
|
iqm-data-definitions >= 2.18, < 3.0
|
|
3
2
|
opentelemetry-exporter-otlp >= 1.25.0, < 2.0
|
|
4
3
|
protobuf >= 4.25.3, < 5.0
|
|
@@ -9,4 +8,5 @@ PyYAML >= 6.0, < 7.0
|
|
|
9
8
|
requests == 2.32.3
|
|
10
9
|
types-requests
|
|
11
10
|
tqdm >= 4.59.0
|
|
12
|
-
types-tqdm
|
|
11
|
+
types-tqdm
|
|
12
|
+
iqm-exa-common
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Copyright 2024 IQM client developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""This module contains user authentication related classes and functions required by IQMClient."""
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from base64 import b64decode
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import time
|
|
22
|
+
from typing import Any, TypeAlias
|
|
23
|
+
|
|
24
|
+
from iqm.iqm_server_client.errors import ClientAuthenticationError, ClientConfigurationError
|
|
25
|
+
|
|
26
|
+
REFRESH_MARGIN_SECONDS = 60
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
AuthHeaderCallback: TypeAlias = Callable[[], str]
|
|
30
|
+
"""Function that returns an authorization header containing a bearer token."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TokenManager:
|
|
34
|
+
"""TokenManager manages the access token required for user authentication.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
token: Long-lived IQM token in plain text format.
|
|
38
|
+
tokens_file: Path to a tokens file used for authentication.
|
|
39
|
+
auth_header_callback: Callback function that returns an Authorization header containing a bearer token.
|
|
40
|
+
use_env_vars: Iff True, the first two parameters can also be read from the environment variables
|
|
41
|
+
:envvar:`IQM_TOKEN` and :envvar:`IQM_TOKENS_FILE`, respectively.
|
|
42
|
+
Environment variables can not be mixed with initialisation arguments.
|
|
43
|
+
|
|
44
|
+
At most one auth parameter should be given.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def time_left_seconds(token: Any) -> int:
|
|
50
|
+
"""Check how much time is left until the token expires.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Time left on token in seconds.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
if not token or not isinstance(token, str):
|
|
57
|
+
return 0
|
|
58
|
+
parts = token.split(".", 2)
|
|
59
|
+
if len(parts) != 3:
|
|
60
|
+
return 0
|
|
61
|
+
# Add padding to adjust body length to a multiple of 4 chars as required by base64 decoding
|
|
62
|
+
try:
|
|
63
|
+
body = parts[1] + ("=" * (-len(parts[1]) % 4))
|
|
64
|
+
exp_time = int(json.loads(b64decode(body)).get("exp", "0"))
|
|
65
|
+
return max(0, exp_time - int(time.time()))
|
|
66
|
+
except (UnicodeDecodeError, json.decoder.JSONDecodeError, ValueError, TypeError):
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
token: str | None = None,
|
|
72
|
+
tokens_file: str | None = None,
|
|
73
|
+
auth_header_callback: AuthHeaderCallback | None = None,
|
|
74
|
+
*,
|
|
75
|
+
use_env_vars: bool = True,
|
|
76
|
+
):
|
|
77
|
+
def _format_names(variable_names: list[str]) -> str:
|
|
78
|
+
"""Format a list of variable names"""
|
|
79
|
+
return ", ".join(f'"{name}"' for name in variable_names)
|
|
80
|
+
|
|
81
|
+
auth_parameters: dict[str, str] = {}
|
|
82
|
+
|
|
83
|
+
init_parameters = {"token": token, "tokens_file": tokens_file}
|
|
84
|
+
init_params_given = [key for key, value in init_parameters.items() if value]
|
|
85
|
+
if auth_header_callback:
|
|
86
|
+
init_params_given.append("auth_header_callback")
|
|
87
|
+
|
|
88
|
+
if use_env_vars:
|
|
89
|
+
env_variables = {"token": "IQM_TOKEN", "tokens_file": "IQM_TOKENS_FILE"}
|
|
90
|
+
env_vars_given = [name for name in env_variables.values() if os.environ.get(name)]
|
|
91
|
+
else:
|
|
92
|
+
env_variables = {}
|
|
93
|
+
env_vars_given = []
|
|
94
|
+
|
|
95
|
+
if init_params_given and env_vars_given:
|
|
96
|
+
raise ClientConfigurationError(
|
|
97
|
+
"Authentication parameters given both as initialisation args and as environment variables: "
|
|
98
|
+
+ f"initialisation args {_format_names(init_params_given)}, "
|
|
99
|
+
+ f"environment variables {_format_names(env_vars_given)}."
|
|
100
|
+
+ " Parameter sources must not be mixed."
|
|
101
|
+
)
|
|
102
|
+
auth_params_given = init_params_given + env_vars_given
|
|
103
|
+
if len(auth_params_given) > 1:
|
|
104
|
+
raise ClientConfigurationError(
|
|
105
|
+
f"No more than one authentication parameter may be given, received {auth_params_given}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self._token_provider: TokenProviderInterface | None = None
|
|
109
|
+
self._auth_header_callback: AuthHeaderCallback | None = None
|
|
110
|
+
self._access_token: str | None = None
|
|
111
|
+
|
|
112
|
+
if auth_header_callback:
|
|
113
|
+
self._auth_header_callback = auth_header_callback
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if env_vars_given:
|
|
117
|
+
auth_parameters = {key: value for key, name in env_variables.items() if (value := os.environ.get(name))}
|
|
118
|
+
else:
|
|
119
|
+
auth_parameters = {key: value for key, value in init_parameters.items() if value}
|
|
120
|
+
|
|
121
|
+
if not auth_parameters:
|
|
122
|
+
return # no authentication
|
|
123
|
+
if set(auth_parameters) == {"token"}:
|
|
124
|
+
# This is not necessarily a JWT token
|
|
125
|
+
self._token_provider = ExternalToken(auth_parameters["token"])
|
|
126
|
+
elif set(auth_parameters) == {"tokens_file"}:
|
|
127
|
+
self._token_provider = TokensFileReader(auth_parameters["tokens_file"])
|
|
128
|
+
else:
|
|
129
|
+
raise ClientConfigurationError("Not possible.")
|
|
130
|
+
|
|
131
|
+
def get_auth_header_callback(self) -> AuthHeaderCallback | None:
|
|
132
|
+
"""Return a callback providing an Authorization header, or None if we cannot provide it."""
|
|
133
|
+
return self._get_bearer_token if self._token_provider else self._auth_header_callback
|
|
134
|
+
|
|
135
|
+
def _get_bearer_token(self, retries: int = 1) -> str:
|
|
136
|
+
"""Return a valid bearer token.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
ClientAuthenticationError: getting the token failed
|
|
140
|
+
RuntimeError: There is no token provider (authentication disabled)
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
if self._token_provider is None:
|
|
144
|
+
raise RuntimeError("There is no token provider.")
|
|
145
|
+
|
|
146
|
+
# Use the existing access token if it is still valid
|
|
147
|
+
if TokenManager.time_left_seconds(self._access_token) > REFRESH_MARGIN_SECONDS:
|
|
148
|
+
return f"Bearer {self._access_token}"
|
|
149
|
+
|
|
150
|
+
# Otherwise, get a new access token from token provider
|
|
151
|
+
try:
|
|
152
|
+
self._access_token = self._token_provider.get_token()
|
|
153
|
+
return f"Bearer {self._access_token}"
|
|
154
|
+
except ClientAuthenticationError:
|
|
155
|
+
if retries < 1:
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
# Try again
|
|
159
|
+
return self._get_bearer_token(retries - 1)
|
|
160
|
+
|
|
161
|
+
def close(self) -> bool:
|
|
162
|
+
"""Close the configured token provider.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
True if closing was successful
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ClientAuthenticationError: closing failed
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
if self._token_provider is None:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
self._token_provider.close()
|
|
175
|
+
self._token_provider = None
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TokenProviderInterface(ABC):
|
|
180
|
+
"""Interface to token provider"""
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def get_token(self) -> str:
|
|
184
|
+
"""Return a valid access token.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ClientAuthenticationError: acquiring the token failed
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def close(self) -> None:
|
|
193
|
+
"""Close the authentication session.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
ClientAuthenticationError: closing the session failed
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ExternalToken(TokenProviderInterface):
|
|
202
|
+
"""Holds an external token"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, token: str):
|
|
205
|
+
self._token: str | None = token
|
|
206
|
+
|
|
207
|
+
def get_token(self) -> str:
|
|
208
|
+
if self._token is None:
|
|
209
|
+
raise ClientAuthenticationError("No external token available")
|
|
210
|
+
return self._token
|
|
211
|
+
|
|
212
|
+
def close(self) -> None:
|
|
213
|
+
self._token = None
|
|
214
|
+
raise ClientAuthenticationError("Can not close externally managed auth session")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class TokensFileReader(TokenProviderInterface):
|
|
218
|
+
"""Reads token from a file"""
|
|
219
|
+
|
|
220
|
+
def __init__(self, tokens_file: str):
|
|
221
|
+
self._path: str | None = tokens_file
|
|
222
|
+
|
|
223
|
+
def get_token(self) -> str:
|
|
224
|
+
try:
|
|
225
|
+
if self._path is None:
|
|
226
|
+
raise ClientAuthenticationError("No tokens file available")
|
|
227
|
+
with open(self._path, encoding="utf-8") as file:
|
|
228
|
+
raw_data = file.read()
|
|
229
|
+
json_data = json.loads(raw_data)
|
|
230
|
+
token = json_data.get("access_token")
|
|
231
|
+
if TokenManager.time_left_seconds(token) <= 0:
|
|
232
|
+
raise ClientAuthenticationError("Access token in file has expired or is not valid")
|
|
233
|
+
except (FileNotFoundError, IsADirectoryError, json.decoder.JSONDecodeError) as e:
|
|
234
|
+
raise ClientAuthenticationError(rf"Failed to read access token from file '{self._path}': {e}") from e
|
|
235
|
+
return token
|
|
236
|
+
|
|
237
|
+
def close(self) -> None:
|
|
238
|
+
self._path = None
|
|
239
|
+
raise ClientAuthenticationError("Can not close externally managed auth session")
|
iqm_station_control_client-12.0.0/src/iqm/station_control/client/iqm_server/iqm_server_client.py
ADDED
|
File without changes
|
|
@@ -32,6 +32,7 @@ from iqm.station_control.interface.models import (
|
|
|
32
32
|
RunLite,
|
|
33
33
|
SequenceMetadataData,
|
|
34
34
|
StaticQuantumArchitecture,
|
|
35
|
+
TimelineEntry,
|
|
35
36
|
)
|
|
36
37
|
from iqm.station_control.interface.pydantic_base import PydanticBase
|
|
37
38
|
|
|
@@ -70,41 +71,45 @@ class ListModel(RootModel):
|
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
class DutList(ListModel):
|
|
74
|
+
class DutList(ListModel): # noqa: D101
|
|
74
75
|
root: list[DutData]
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
class DutFieldDataList(ListModel):
|
|
78
|
+
class DutFieldDataList(ListModel): # noqa: D101
|
|
78
79
|
root: list[DutFieldData]
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
class ObservationDataList(ListModel):
|
|
82
|
+
class ObservationDataList(ListModel): # noqa: D101
|
|
82
83
|
root: list[ObservationData]
|
|
83
84
|
|
|
84
85
|
|
|
85
|
-
class ObservationDefinitionList(ListModel):
|
|
86
|
+
class ObservationDefinitionList(ListModel): # noqa: D101
|
|
86
87
|
root: list[ObservationDefinition]
|
|
87
88
|
|
|
88
89
|
|
|
89
|
-
class ObservationLiteList(ListModel):
|
|
90
|
+
class ObservationLiteList(ListModel): # noqa: D101
|
|
90
91
|
root: list[ObservationLite]
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
class ObservationUpdateList(ListModel):
|
|
94
|
+
class ObservationUpdateList(ListModel): # noqa: D101
|
|
94
95
|
root: list[ObservationUpdate]
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
class ObservationSetDataList(ListModel):
|
|
98
|
+
class ObservationSetDataList(ListModel): # noqa: D101
|
|
98
99
|
root: list[ObservationSetData]
|
|
99
100
|
|
|
100
101
|
|
|
101
|
-
class
|
|
102
|
+
class RunLiteList(ListModel): # noqa: D101
|
|
103
|
+
root: list[RunLite]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SequenceMetadataDataList(ListModel): # noqa: D101
|
|
102
107
|
root: list[SequenceMetadataData]
|
|
103
108
|
|
|
104
109
|
|
|
105
|
-
class StaticQuantumArchitectureList(ListModel):
|
|
110
|
+
class StaticQuantumArchitectureList(ListModel): # noqa: D101
|
|
106
111
|
root: list[StaticQuantumArchitecture]
|
|
107
112
|
|
|
108
113
|
|
|
109
|
-
class
|
|
110
|
-
root: list[
|
|
114
|
+
class TimelineEntryList(ListModel): # noqa: D101
|
|
115
|
+
root: list[TimelineEntry]
|
|
File without changes
|
|
@@ -23,7 +23,7 @@ from typing import Annotated, Any, Final, TypeAlias
|
|
|
23
23
|
from pydantic import Field
|
|
24
24
|
from pydantic.dataclasses import dataclass
|
|
25
25
|
|
|
26
|
-
from iqm.station_control.interface.models
|
|
26
|
+
from iqm.station_control.interface.models import ObservationBase
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
|
|
@@ -40,8 +40,8 @@ def serialize_run_definition(run_definition: RunDefinition) -> RunDefinitionProt
|
|
|
40
40
|
username=run_definition.username,
|
|
41
41
|
experiment_name=run_definition.experiment_name,
|
|
42
42
|
experiment_label=run_definition.experiment_label,
|
|
43
|
-
options=serialize_struct(run_definition.options),
|
|
44
|
-
additional_run_properties=serialize_struct(run_definition.additional_run_properties),
|
|
43
|
+
options=serialize_struct(run_definition.options),
|
|
44
|
+
additional_run_properties=serialize_struct(run_definition.additional_run_properties),
|
|
45
45
|
software_version_set_id=run_definition.software_version_set_id,
|
|
46
46
|
components=run_definition.components,
|
|
47
47
|
default_data_parameters=run_definition.default_data_parameters,
|
|
@@ -50,8 +50,9 @@ def serialize_run_definition(run_definition: RunDefinition) -> RunDefinitionProt
|
|
|
50
50
|
run_definition_proto.sweep_definition_payload.Pack(
|
|
51
51
|
serialize_sweep_definition(run_definition.sweep_definition), type_url_prefix="iqm-data-definitions"
|
|
52
52
|
)
|
|
53
|
-
for key, sweep in run_definition.hard_sweeps.items():
|
|
53
|
+
for key, sweep in run_definition.hard_sweeps.items():
|
|
54
54
|
run_definition_proto.hard_sweeps[key].CopyFrom(proto_serialization.nd_sweep.pack(sweep, minimal=False))
|
|
55
|
+
|
|
55
56
|
return run_definition_proto
|
|
56
57
|
|
|
57
58
|
|
|
@@ -94,7 +95,7 @@ def serialize_run_data(run_data: RunData) -> dict:
|
|
|
94
95
|
"options": run_data.options,
|
|
95
96
|
"additional_run_properties": run_data.additional_run_properties,
|
|
96
97
|
"software_version_set_id": run_data.software_version_set_id,
|
|
97
|
-
"hard_sweeps": {key: encode_nd_sweeps(value) for key, value in run_data.hard_sweeps.items()},
|
|
98
|
+
"hard_sweeps": {key: encode_nd_sweeps(value) for key, value in run_data.hard_sweeps.items()},
|
|
98
99
|
"components": run_data.components,
|
|
99
100
|
"default_data_parameters": run_data.default_data_parameters,
|
|
100
101
|
"default_sweep_parameters": run_data.default_sweep_parameters,
|
|
@@ -33,7 +33,7 @@ def deserialize_struct(proto: pb.Struct) -> dict:
|
|
|
33
33
|
return {key: _deserialize_value(value) for key, value in proto.fields.items()}
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def _serialize_value(value: None | float | str | bool | dict | list
|
|
36
|
+
def _serialize_value(value: None | float | str | bool | dict | list) -> pb.Value:
|
|
37
37
|
"""Serialize a value into a Value protobuf representation."""
|
|
38
38
|
if value is None:
|
|
39
39
|
return pb.Value(null_value=True)
|