iqm-exa-common 26.2__py3-none-any.whl → 26.4__py3-none-any.whl
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.
- exa/common/data/base_model.py +3 -6
- exa/common/data/parameter.py +7 -9
- exa/common/data/value.py +1 -2
- exa/common/errors/exa_error.py +9 -10
- exa/common/errors/server_errors.py +74 -0
- exa/common/qcm_data/qcm_data_client.py +6 -8
- {iqm_exa_common-26.2.dist-info → iqm_exa_common-26.4.dist-info}/METADATA +1 -1
- {iqm_exa_common-26.2.dist-info → iqm_exa_common-26.4.dist-info}/RECORD +11 -10
- {iqm_exa_common-26.2.dist-info → iqm_exa_common-26.4.dist-info}/LICENSE.txt +0 -0
- {iqm_exa_common-26.2.dist-info → iqm_exa_common-26.4.dist-info}/WHEEL +0 -0
- {iqm_exa_common-26.2.dist-info → iqm_exa_common-26.4.dist-info}/top_level.txt +0 -0
exa/common/data/base_model.py
CHANGED
|
@@ -11,12 +11,9 @@ class BaseModel(pydantic.BaseModel):
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
model_config = ConfigDict(
|
|
14
|
-
# extra="forbid",
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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
|
exa/common/data/parameter.py
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
|
|
380
|
+
UnprocessableEntityError: If ``self`` is not collection-valued.
|
|
383
381
|
|
|
384
382
|
"""
|
|
385
383
|
if self.collection_type is CollectionType.SCALAR:
|
|
386
|
-
raise
|
|
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
|
|
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:
|
exa/common/data/value.py
CHANGED
|
@@ -43,7 +43,6 @@ Uncertainty = Annotated[
|
|
|
43
43
|
WithJsonSchema(core_schema.any_schema()),
|
|
44
44
|
]
|
|
45
45
|
|
|
46
|
-
# TODO
|
|
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
|
exa/common/errors/exa_error.py
CHANGED
|
@@ -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
|
|
19
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
|
114
|
+
raise NotFoundError(error_message)
|
|
117
115
|
return response
|
|
118
116
|
|
|
119
117
|
@staticmethod
|
|
@@ -24,13 +24,14 @@ exa/common/control/sweep/option/start_stop_base_options.py,sha256=hZCIflh8NzK48c
|
|
|
24
24
|
exa/common/control/sweep/option/start_stop_options.py,sha256=HaQFNlhRPep2yrn_LuOkuZ0imwBEidpD0BTPWf-Svf4,3038
|
|
25
25
|
exa/common/control/sweep/option/sweep_options.py,sha256=BhKB7RHP0VXJ9iUQKVzeQOM4j_x9AsMhRJgoR3gkiaY,933
|
|
26
26
|
exa/common/data/__init__.py,sha256=F5SRe5QHBTjef4XJVQ63kO5Oxc_AiZnPbV560i7La0Y,644
|
|
27
|
-
exa/common/data/base_model.py,sha256=
|
|
28
|
-
exa/common/data/parameter.py,sha256=
|
|
27
|
+
exa/common/data/base_model.py,sha256=A008Gn4cVQloiCWqJOGal5C1vq_ItyOWXnFvZcHPsuc,1785
|
|
28
|
+
exa/common/data/parameter.py,sha256=oPeFy_YGfbioGd6EQfzLkp01ACs8BeEULVBQgM89k-Q,22918
|
|
29
29
|
exa/common/data/setting_node.py,sha256=WldawevXuOb2uN-bjZ04hWijKqX_MOdkyMsgxOZbrek,43190
|
|
30
30
|
exa/common/data/settingnode_v2.html.jinja2,sha256=mo-rlLLmU-Xxf6znJAisispAZK8sbV-2C13byKAtj_Q,3166
|
|
31
|
-
exa/common/data/value.py,sha256=
|
|
31
|
+
exa/common/data/value.py,sha256=mtMws5UPGx1pCADK6Q2Tx4BwCXznvVRSNQRfcQ3NMmY,1853
|
|
32
32
|
exa/common/errors/__init__.py,sha256=ArMBdpmx1EUenBpzrSNG63kmUf7PM0gCqSYnaCnL9Qk,597
|
|
33
|
-
exa/common/errors/exa_error.py,sha256=
|
|
33
|
+
exa/common/errors/exa_error.py,sha256=J8Tew1ORSrhhLn2hPfEq1mISUycHKnqTq1ZWphPW4mE,884
|
|
34
|
+
exa/common/errors/server_errors.py,sha256=4BLTUtlJRLTyR1mW2b_mAtkj90UCNZ8pS8-p_SWWfYM,2810
|
|
34
35
|
exa/common/helpers/__init__.py,sha256=IgtVD3tojIFA4MTV2mT5uYM6jb2qny9kBIIhEZT2PuI,610
|
|
35
36
|
exa/common/helpers/data_helper.py,sha256=vhzJ63g1S2JqnCj0WJJuqWcuiIwKATnQeHdWw_3gkZg,1934
|
|
36
37
|
exa/common/helpers/json_helper.py,sha256=VTcYU8FRgv3tXPifuogUWmVAzt_4JoQ_laTHolyodtA,2672
|
|
@@ -43,12 +44,12 @@ exa/common/qcm_data/chad_model.py,sha256=MQ1xuRODOA6uzb3GJ4fgYx9cXS8z1DeRGw6HYKA
|
|
|
43
44
|
exa/common/qcm_data/chip_topology.py,sha256=OJU8-CXV7wfdxrn0HqryNZmxGRoffrg0vi0aMaiYbbY,19328
|
|
44
45
|
exa/common/qcm_data/file_adapter.py,sha256=U1XZm_PEswkW7kAztbWFLMufz4mPKPupWbh5yJXdZCI,2263
|
|
45
46
|
exa/common/qcm_data/immutable_base_model.py,sha256=QXmKIWQbsbWQvovXwKT1d9jtyf2LNJtjQquIwO52zOU,901
|
|
46
|
-
exa/common/qcm_data/qcm_data_client.py,sha256=
|
|
47
|
+
exa/common/qcm_data/qcm_data_client.py,sha256=0clAbZ3HPBH9lFOe8cVmaq8hPHG5m3pyPG4O6EI97Kk,6022
|
|
47
48
|
exa/common/sweep/__init__.py,sha256=uEKk5AtzSgSnf8Y0geRPwUpqXIBIXpeCxsN64sX7F1o,591
|
|
48
49
|
exa/common/sweep/database_serialization.py,sha256=NUu1umxRQZpKtRmw1vNDsSbnofqbHvKFg_xQ2mdhY6k,7469
|
|
49
50
|
exa/common/sweep/util.py,sha256=-QE2AaH-WDkYAVH5-Z-30leLgY0x4efmby4kc1JTCgY,3732
|
|
50
|
-
iqm_exa_common-26.
|
|
51
|
-
iqm_exa_common-26.
|
|
52
|
-
iqm_exa_common-26.
|
|
53
|
-
iqm_exa_common-26.
|
|
54
|
-
iqm_exa_common-26.
|
|
51
|
+
iqm_exa_common-26.4.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
|
|
52
|
+
iqm_exa_common-26.4.dist-info/METADATA,sha256=am6t-uFa9XTRZ-sPyfDbq0_-JMZselY8tyE85wfLIRA,14548
|
|
53
|
+
iqm_exa_common-26.4.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
54
|
+
iqm_exa_common-26.4.dist-info/top_level.txt,sha256=Clphg2toaZ3_jSFRPhjMNEmLurkMNMc4lkK2EFYsSlM,4
|
|
55
|
+
iqm_exa_common-26.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|