iqm-exa-common 26.2__tar.gz → 26.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/CHANGELOG.rst +9 -0
  2. {iqm_exa_common-26.2/src/iqm_exa_common.egg-info → iqm_exa_common-26.3}/PKG-INFO +1 -1
  3. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/base_model.py +3 -6
  4. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/parameter.py +7 -9
  5. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/value.py +1 -2
  6. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/errors/exa_error.py +9 -10
  7. iqm_exa_common-26.3/src/exa/common/errors/server_errors.py +74 -0
  8. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/qcm_data_client.py +6 -8
  9. {iqm_exa_common-26.2 → iqm_exa_common-26.3/src/iqm_exa_common.egg-info}/PKG-INFO +1 -1
  10. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/iqm_exa_common.egg-info/SOURCES.txt +1 -0
  11. iqm_exa_common-26.3/version.txt +1 -0
  12. iqm_exa_common-26.2/version.txt +0 -1
  13. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/LICENSE.txt +0 -0
  14. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/MANIFEST.in +0 -0
  15. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/README.rst +0 -0
  16. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/API.rst +0 -0
  17. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/Makefile +0 -0
  18. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_static/.gitignore +0 -0
  19. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_static/css/custom.css +0 -0
  20. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_static/images/favicon.ico +0 -0
  21. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_static/images/logo.png +0 -0
  22. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_templates/autosummary-class-template.rst +0 -0
  23. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/_templates/autosummary-module-template.rst +0 -0
  24. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/changelog.rst +0 -0
  25. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/conf.py +0 -0
  26. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/index.rst +0 -0
  27. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/docs/license.rst +0 -0
  28. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/pyproject.toml +0 -0
  29. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/requirements/base.in +0 -0
  30. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/requirements/base.txt +0 -0
  31. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/setup.cfg +0 -0
  32. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/setup.py +0 -0
  33. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/__init__.py +0 -0
  34. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/__init__.py +0 -0
  35. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/__init__.py +0 -0
  36. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/_parameter.py +0 -0
  37. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/array.py +0 -0
  38. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/datum.py +0 -0
  39. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/nd_sweep.py +0 -0
  40. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/sequence.py +0 -0
  41. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/api/proto_serialization/setting_node.py +0 -0
  42. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/__init__.py +0 -0
  43. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/__init__.py +0 -0
  44. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/exponential_sweep.py +0 -0
  45. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/fixed_sweep.py +0 -0
  46. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/linear_sweep.py +0 -0
  47. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/__init__.py +0 -0
  48. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/center_span_base_options.py +0 -0
  49. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/center_span_options.py +0 -0
  50. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/constants.py +0 -0
  51. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/fixed_options.py +0 -0
  52. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/option_converter.py +0 -0
  53. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/start_stop_base_options.py +0 -0
  54. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/start_stop_options.py +0 -0
  55. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/option/sweep_options.py +0 -0
  56. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/sweep.py +0 -0
  57. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/control/sweep/sweep_values.py +0 -0
  58. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/__init__.py +0 -0
  59. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/setting_node.py +0 -0
  60. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/data/settingnode_v2.html.jinja2 +0 -0
  61. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/errors/__init__.py +0 -0
  62. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/helpers/__init__.py +0 -0
  63. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/helpers/data_helper.py +0 -0
  64. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/helpers/json_helper.py +0 -0
  65. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/helpers/numpy_helper.py +0 -0
  66. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/helpers/software_version_helper.py +0 -0
  67. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/logger/__init__.py +0 -0
  68. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/logger/logger.py +0 -0
  69. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/__init__.py +0 -0
  70. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/chad_model.py +0 -0
  71. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/chip_topology.py +0 -0
  72. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/file_adapter.py +0 -0
  73. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/qcm_data/immutable_base_model.py +0 -0
  74. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/sweep/__init__.py +0 -0
  75. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/sweep/database_serialization.py +0 -0
  76. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/exa/common/sweep/util.py +0 -0
  77. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/iqm_exa_common.egg-info/dependency_links.txt +0 -0
  78. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/iqm_exa_common.egg-info/requires.txt +0 -0
  79. {iqm_exa_common-26.2 → iqm_exa_common-26.3}/src/iqm_exa_common.egg-info/top_level.txt +0 -0
@@ -2,6 +2,15 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 26.3 (2025-03-05)
6
+ =========================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Add new error classes designed for client-server communication via station control client.
12
+ - Remove general RequestError and use new specific error classes instead.
13
+
5
14
  Version 26.2 (2025-03-03)
6
15
  =========================
7
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-exa-common
3
- Version: 26.2
3
+ Version: 26.3
4
4
  Summary: Framework for control and measurement of superconducting qubits: common library
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -11,12 +11,9 @@ class BaseModel(pydantic.BaseModel):
11
11
  """
12
12
 
13
13
  model_config = ConfigDict(
14
- # extra="forbid", # Forbid any extra attributes
15
- # TODO (Marko): Use "ignore" at least temporarily
16
- # Data sent by old server code might include extra attributes like "parent_name",
17
- # which fails with a new client using extra="ignore".
18
- # Before merging to master, consider if we want to be strict with extra="forbid",
19
- # or would it be better to use extra="ignore" (shouldn't be needed if we update the server code anyway).
14
+ # TODO: Consider setting extra="forbid", extra="ignore" is needed only if new clients use older servers
15
+ # After station control API versioning, backwards compatibility works, i.e. old clients can use newer servers.
16
+ # Reverse shouldn't be needed, since we don't promise any forwards compatibility.
20
17
  extra="ignore", # Ignore any extra attributes
21
18
  validate_assignment=True, # Validate the data when the model is changed
22
19
  validate_default=True, # Validate default values during validation
@@ -70,7 +70,7 @@ import xarray as xr
70
70
  from exa.common.control.sweep.sweep_values import SweepValues
71
71
  from exa.common.data.base_model import BaseModel
72
72
  from exa.common.data.value import ObservationValue
73
- from exa.common.errors.exa_error import InvalidParameterValueError
73
+ from exa.common.errors.server_errors import ValidationError
74
74
 
75
75
  CastType = str | list["CastType"] | None
76
76
 
@@ -225,9 +225,7 @@ class Parameter(BaseModel):
225
225
  )
226
226
  if self.element_indices is not None:
227
227
  if self.collection_type is not CollectionType.SCALAR:
228
- raise InvalidParameterValueError(
229
- "Element-wise parameter must have CollectionType.SCALAR collection type."
230
- )
228
+ raise ValidationError("Element-wise parameter must have 'CollectionType.SCALAR' collection type.")
231
229
 
232
230
  match self.element_indices:
233
231
  case [_i, *_more_is] as idxs:
@@ -236,7 +234,7 @@ class Parameter(BaseModel):
236
234
  case int(idx):
237
235
  idxs = [idx]
238
236
  case _:
239
- raise InvalidParameterValueError("element_indices must be one or more ints.")
237
+ raise ValidationError("Parameter 'element_indices' must be one or more ints.")
240
238
  object.__setattr__(self, "element_indices", idxs)
241
239
 
242
240
  object.__setattr__(self, "_parent_name", self.name)
@@ -379,12 +377,12 @@ class Parameter(BaseModel):
379
377
  The element-wise parameter.
380
378
 
381
379
  Raises:
382
- InvalidParameterValueError: If ``self`` is not collection-valued.
380
+ UnprocessableEntityError: If ``self`` is not collection-valued.
383
381
 
384
382
  """
385
383
  if self.collection_type is CollectionType.SCALAR:
386
- raise InvalidParameterValueError(
387
- "Cannot create an element-wise parameter from a parameter with CollectionType.SCALAR."
384
+ raise ValidationError(
385
+ "Cannot create an element-wise parameter from a parameter with 'CollectionType.SCALAR'."
388
386
  )
389
387
  return Parameter(
390
388
  name=self.name,
@@ -433,7 +431,7 @@ class Setting(BaseModel):
433
431
  # To avoid extra complexity, let Pydantic deal with the raw data and change the value in "after" validation.
434
432
  object.__setattr__(self, "value", self.parameter.collection_type.cast(self.value))
435
433
  if self.value is not None and not self.parameter.validate(self.value):
436
- raise InvalidParameterValueError("Invalid value {} for parameter {}.".format(self.value, self.parameter))
434
+ raise ValidationError(f"Invalid value '{self.value}' for parameter '{self.parameter}'.")
437
435
  return self
438
436
 
439
437
  def update(self, value: ObservationValue) -> Setting:
@@ -43,7 +43,6 @@ Uncertainty = Annotated[
43
43
  WithJsonSchema(core_schema.any_schema()),
44
44
  ]
45
45
 
46
- # TODO (Marko): Consider if we want to rename these?
47
- # We have "as" imports so seems like more descriptive name would be better.
46
+ # TODO: Consider if we want to rename these permanently to avoid unnecessary "as" imports
48
47
  ObservationValue = Value
49
48
  ObservationUncertainty = Uncertainty
@@ -1,3 +1,6 @@
1
+ import warnings
2
+
3
+
1
4
  class ExaError(Exception):
2
5
  """Base class for exa errors.
3
6
 
@@ -10,25 +13,21 @@ class ExaError(Exception):
10
13
  super().__init__(message, *args)
11
14
  self.message = message
12
15
 
16
+ def __str__(self):
17
+ return self.message
18
+
13
19
 
14
20
  class UnknownSettingError(ExaError, AttributeError):
15
21
  """This SettingNode does not have a given key."""
16
22
 
17
23
 
18
- class InvalidParameterValueError(ExaError, ValueError):
19
- """The value set does not conform to the parameter restrictions."""
24
+ class EmptyComponentListError(ExaError, ValueError):
25
+ """Error raised when an empty list is given as components for running an experiment."""
20
26
 
21
27
 
22
28
  class InvalidSweepOptionsTypeError(ExaError, TypeError):
23
29
  """The type of sweep options is invalid."""
24
30
 
25
31
  def __init__(self, options: str, *args):
32
+ warnings.warn("InvalidSweepOptionsTypeError is deprecated.", DeprecationWarning)
26
33
  super().__init__(f"Options have unsupported type of {options}", *args)
27
-
28
-
29
- class RequestError(ExaError):
30
- """Error raised when something went wrong on the server after sending a request."""
31
-
32
-
33
- class EmptyComponentListError(ExaError, ValueError):
34
- """Error raised when an empty list is given as components for running an experiment."""
@@ -0,0 +1,74 @@
1
+ # Copyright 2024 IQM
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
+ """Errors used in the client-server communication."""
15
+
16
+ from http import HTTPStatus
17
+
18
+ from exa.common.errors.exa_error import ExaError
19
+
20
+
21
+ class StationControlError(ExaError):
22
+ """Base class for station control errors used in client-server communication."""
23
+
24
+ # TODO: StationControlError shouldn't need to inherit ExaError
25
+ # Some clients might still expect ExaErrors, thus inheriting here to avoid issues because of that.
26
+ # Ideally, we would keep server errors (raised by station control) and any client side errors separately.
27
+
28
+
29
+ class BadRequestError(StationControlError):
30
+ """Error raised when the request syntax is invalid or the method is unsupported in general."""
31
+
32
+
33
+ class UnauthorizedError(StationControlError):
34
+ """Error raised when the user is not authorized."""
35
+
36
+
37
+ class ForbiddenError(StationControlError):
38
+ """Error raised when the operation is forbidden for the user."""
39
+
40
+
41
+ class NotFoundError(StationControlError):
42
+ """Error raised when nothing was found with the given parameters.
43
+
44
+ This should be used when it's expected that something is found, for example when trying to find with an exact ID.
45
+ """
46
+
47
+
48
+ class ValidationError(StationControlError):
49
+ """Error raised when something is unprocessable in general, for example if the input value is not acceptable."""
50
+
51
+
52
+ class InternalServerError(StationControlError):
53
+ """Error raised when an unexpected error happened on the server side.
54
+
55
+ This error should never be raised when something expected happens,
56
+ and whenever the client encounters this, it should be considered as a server bug.
57
+ """
58
+
59
+
60
+ class ServiceUnavailableError(StationControlError):
61
+ """Error raised when the service is unavailable."""
62
+
63
+
64
+ ERROR_TO_STATUS_CODE_MAPPING = {
65
+ BadRequestError: HTTPStatus.BAD_REQUEST, # 400
66
+ UnauthorizedError: HTTPStatus.UNAUTHORIZED, # 401
67
+ ForbiddenError: HTTPStatus.FORBIDDEN, # 403
68
+ NotFoundError: HTTPStatus.NOT_FOUND, # 404
69
+ ValidationError: HTTPStatus.UNPROCESSABLE_ENTITY, # 422
70
+ InternalServerError: HTTPStatus.INTERNAL_SERVER_ERROR, # 500
71
+ ServiceUnavailableError: HTTPStatus.SERVICE_UNAVAILABLE, # 503
72
+ }
73
+
74
+ STATUS_CODE_TO_ERROR_MAPPING = {value: key for key, value in ERROR_TO_STATUS_CODE_MAPPING.items()}
@@ -24,7 +24,8 @@ from typing import Any
24
24
  from packaging.version import Version
25
25
  import requests
26
26
 
27
- from exa.common.errors.exa_error import ExaError, RequestError
27
+ from exa.common.errors.exa_error import ExaError
28
+ from exa.common.errors.server_errors import NotFoundError, ValidationError
28
29
  from exa.common.qcm_data.file_adapter import FileAdapter
29
30
 
30
31
  MIN_SUPPORTED_CONTENT_FORMAT_VERSION = Version("1.0")
@@ -70,10 +71,7 @@ class QCMDataClient:
70
71
 
71
72
  @root_url.setter
72
73
  def root_url(self, root_url: str) -> None:
73
- """Sets the remote QCM Data service URL.
74
-
75
- Cache for :func:`get_chad` has to be reset when the URL changes.
76
- """
74
+ """Sets the remote QCM Data service URL."""
77
75
  if self._root_url != root_url:
78
76
  self._root_url = root_url
79
77
 
@@ -90,14 +88,14 @@ class QCMDataClient:
90
88
  url_tail = f"/cheddars/{chip_label}?target=in-house"
91
89
  try:
92
90
  response = self._send_request(self.session.get, f"{self.root_url}{url_tail}")
93
- except RequestError as err:
91
+ except NotFoundError as err:
94
92
  if self._fallback_root_url:
95
93
  response = self._send_request(self.session.get, f"{self._fallback_root_url}{url_tail}")
96
94
  else:
97
95
  raise err
98
96
  data = response.json().get("data")
99
97
  if not data:
100
- raise RequestError(f"Chip design record for {chip_label} does not contain data.")
98
+ raise ValidationError(f"Chip design record for {chip_label} does not contain data.")
101
99
  self._validate_chip_design_record(data, chip_label)
102
100
  return data
103
101
 
@@ -113,7 +111,7 @@ class QCMDataClient:
113
111
  error_message = response_dict["detail"]
114
112
  except json.JSONDecodeError:
115
113
  error_message = response.text
116
- raise RequestError(f"{url} returned error code {response.status_code}: {error_message}")
114
+ raise NotFoundError(error_message)
117
115
  return response
118
116
 
119
117
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-exa-common
3
- Version: 26.2
3
+ Version: 26.3
4
4
  Summary: Framework for control and measurement of superconducting qubits: common library
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -52,6 +52,7 @@ src/exa/common/data/settingnode_v2.html.jinja2
52
52
  src/exa/common/data/value.py
53
53
  src/exa/common/errors/__init__.py
54
54
  src/exa/common/errors/exa_error.py
55
+ src/exa/common/errors/server_errors.py
55
56
  src/exa/common/helpers/__init__.py
56
57
  src/exa/common/helpers/data_helper.py
57
58
  src/exa/common/helpers/json_helper.py
@@ -0,0 +1 @@
1
+ 26.3
@@ -1 +0,0 @@
1
- 26.2
File without changes
File without changes
File without changes
File without changes
File without changes