@opentrons/shared-data 0.0.1-alpha.20 → 0.1.0-alpha.0

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 (154) hide show
  1. package/Makefile +1 -1
  2. package/lib/opentrons-shared-data.js +1 -1
  3. package/lib/opentrons-shared-data.js.map +1 -1
  4. package/package.json +1 -1
  5. package/js/__tests__/__snapshots__/pipettes.test.ts.snap +0 -5969
  6. package/js/__tests__/deckSchemas.test.ts +0 -87
  7. package/js/__tests__/errors.test.js +0 -21
  8. package/js/__tests__/getAreSlotsAdjacent.test.ts +0 -128
  9. package/js/__tests__/getWellNamePerMultiTip.test.ts +0 -162
  10. package/js/__tests__/labwareDefQuirks.test.ts +0 -37
  11. package/js/__tests__/labwareDefSchemaV1.test.ts +0 -82
  12. package/js/__tests__/labwareDefSchemaV2.test.ts +0 -298
  13. package/js/__tests__/moduleAccessors.test.ts +0 -50
  14. package/js/__tests__/moduleSpecsSchema.test.ts +0 -77
  15. package/js/__tests__/pipetteSchemaV2.test.ts +0 -107
  16. package/js/__tests__/pipetteSpecSchemas.test.ts +0 -48
  17. package/js/__tests__/pipettes.test.ts +0 -58
  18. package/js/__tests__/protocolSchemaV4.test.ts +0 -137
  19. package/js/__tests__/protocolSchemaV5.test.ts +0 -137
  20. package/js/__tests__/protocolSchemaV6.test.ts +0 -132
  21. package/js/__tests__/protocolSchemaV7.test.ts +0 -64
  22. package/js/__tests__/protocolValidation.test.ts +0 -83
  23. package/js/__tests__/sortWells.test.ts +0 -40
  24. package/js/__tests__/splitWellsOnColumn.test.ts +0 -21
  25. package/js/__tests__/validateErrors.test.js +0 -34
  26. package/js/constants.ts +0 -338
  27. package/js/cypressUtils.ts +0 -35
  28. package/js/errors.ts +0 -33
  29. package/js/fixtures.ts +0 -161
  30. package/js/getLabware.ts +0 -154
  31. package/js/gripper.ts +0 -29
  32. package/js/helpers/__tests__/getAdapterName.test.ts +0 -31
  33. package/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.ts +0 -12
  34. package/js/helpers/__tests__/getVectorDifference.test.ts +0 -25
  35. package/js/helpers/__tests__/getVectorSum.test.ts +0 -21
  36. package/js/helpers/__tests__/labwareInference.test.ts +0 -84
  37. package/js/helpers/__tests__/orderWells.test.ts +0 -87
  38. package/js/helpers/__tests__/parseProtocolData.test.ts +0 -165
  39. package/js/helpers/__tests__/volume.test.ts +0 -108
  40. package/js/helpers/__tests__/wellSets.test.ts +0 -352
  41. package/js/helpers/get96Channel384WellPlateWells.ts +0 -44
  42. package/js/helpers/getAddressableAreasInProtocol.ts +0 -59
  43. package/js/helpers/getLoadedLabwareDefinitionsByUri.ts +0 -30
  44. package/js/helpers/getModuleVizDims.ts +0 -118
  45. package/js/helpers/getOccludedSlotCountForModule.ts +0 -13
  46. package/js/helpers/getVectorDifference.ts +0 -12
  47. package/js/helpers/getVectorSum.ts +0 -12
  48. package/js/helpers/getWellNamePerMultiTip.ts +0 -86
  49. package/js/helpers/getWellTotalVolume.ts +0 -16
  50. package/js/helpers/index.ts +0 -347
  51. package/js/helpers/labwareInference.ts +0 -92
  52. package/js/helpers/orderWells.ts +0 -69
  53. package/js/helpers/parseProtocolData.ts +0 -141
  54. package/js/helpers/volume.ts +0 -35
  55. package/js/helpers/wellIsRect.ts +0 -6
  56. package/js/helpers/wellSets.ts +0 -155
  57. package/js/index.ts +0 -14
  58. package/js/labwareTools/__tests__/__snapshots__/createIrregularLabware.test.ts.snap +0 -3
  59. package/js/labwareTools/__tests__/__snapshots__/createLabware.test.ts.snap +0 -3
  60. package/js/labwareTools/__tests__/createDefaultDisplayName.test.ts +0 -111
  61. package/js/labwareTools/__tests__/createIrregularLabware.test.ts +0 -226
  62. package/js/labwareTools/__tests__/createLabware.test.ts +0 -147
  63. package/js/labwareTools/index.ts +0 -452
  64. package/js/modules.ts +0 -97
  65. package/js/pipettes.ts +0 -89
  66. package/js/protocols.ts +0 -290
  67. package/js/schema.ts +0 -80
  68. package/js/scripts/generateDeckLayersFromSVG.js +0 -46
  69. package/js/titleCase.ts +0 -30
  70. package/js/types.ts +0 -600
  71. package/python/.coveragerc +0 -4
  72. package/python/.flake8 +0 -32
  73. package/python/Config.in +0 -10
  74. package/python/Makefile +0 -134
  75. package/python/Pipfile +0 -28
  76. package/python/Pipfile.lock +0 -645
  77. package/python/buildroot.mk +0 -30
  78. package/python/mypy.ini +0 -41
  79. package/python/opentrons_shared_data/__init__.py +0 -17
  80. package/python/opentrons_shared_data/_version.py +0 -20
  81. package/python/opentrons_shared_data/command/__init__.py +0 -52
  82. package/python/opentrons_shared_data/deck/__init__.py +0 -96
  83. package/python/opentrons_shared_data/deck/dev_types.py +0 -180
  84. package/python/opentrons_shared_data/errors/__init__.py +0 -30
  85. package/python/opentrons_shared_data/errors/categories.py +0 -60
  86. package/python/opentrons_shared_data/errors/codes.py +0 -108
  87. package/python/opentrons_shared_data/errors/exceptions.py +0 -915
  88. package/python/opentrons_shared_data/gripper/__init__.py +0 -63
  89. package/python/opentrons_shared_data/gripper/constants.py +0 -6
  90. package/python/opentrons_shared_data/gripper/gripper_definition.py +0 -101
  91. package/python/opentrons_shared_data/labware/__init__.py +0 -22
  92. package/python/opentrons_shared_data/labware/constants.py +0 -7
  93. package/python/opentrons_shared_data/labware/dev_types.py +0 -140
  94. package/python/opentrons_shared_data/labware/labware_definition.py +0 -328
  95. package/python/opentrons_shared_data/load.py +0 -57
  96. package/python/opentrons_shared_data/module/__init__.py +0 -74
  97. package/python/opentrons_shared_data/module/dev_types.py +0 -141
  98. package/python/opentrons_shared_data/pipette/__init__.py +0 -107
  99. package/python/opentrons_shared_data/pipette/dev_types.py +0 -177
  100. package/python/opentrons_shared_data/pipette/file_operation_helpers.py +0 -37
  101. package/python/opentrons_shared_data/pipette/load_data.py +0 -261
  102. package/python/opentrons_shared_data/pipette/model_constants.py +0 -123
  103. package/python/opentrons_shared_data/pipette/mutable_configurations.py +0 -431
  104. package/python/opentrons_shared_data/pipette/pipette_definition.py +0 -503
  105. package/python/opentrons_shared_data/pipette/pipette_load_name_conversions.py +0 -240
  106. package/python/opentrons_shared_data/pipette/scripts/__init__.py +0 -0
  107. package/python/opentrons_shared_data/pipette/scripts/build_json_script.py +0 -469
  108. package/python/opentrons_shared_data/pipette/scripts/update_configuration_files.py +0 -450
  109. package/python/opentrons_shared_data/pipette/types.py +0 -193
  110. package/python/opentrons_shared_data/protocol/__init__.py +0 -13
  111. package/python/opentrons_shared_data/protocol/constants.py +0 -98
  112. package/python/opentrons_shared_data/protocol/dev_types.py +0 -436
  113. package/python/opentrons_shared_data/protocol/models/__init__.py +0 -48
  114. package/python/opentrons_shared_data/protocol/models/protocol_schema_v6.py +0 -145
  115. package/python/opentrons_shared_data/protocol/models/protocol_schema_v7.py +0 -95
  116. package/python/opentrons_shared_data/protocol/models/protocol_schema_v8.py +0 -110
  117. package/python/opentrons_shared_data/protocol/models/shared_models.py +0 -111
  118. package/python/opentrons_shared_data/py.typed +0 -0
  119. package/python/opentrons_shared_data/robot/__init__.py +0 -33
  120. package/python/opentrons_shared_data/robot/dev_types.py +0 -37
  121. package/python/pytest.ini +0 -2
  122. package/python/setup.cfg +0 -5
  123. package/python/setup.py +0 -179
  124. package/python/tests/__init__.py +0 -1
  125. package/python/tests/conftest.py +0 -0
  126. package/python/tests/deck/__init__.py +0 -0
  127. package/python/tests/deck/test_list_names.py +0 -12
  128. package/python/tests/deck/test_position.py +0 -113
  129. package/python/tests/deck/test_typechecks.py +0 -28
  130. package/python/tests/errors/__init__.py +0 -1
  131. package/python/tests/errors/test_error_completeness.py +0 -19
  132. package/python/tests/errors/test_exceptions.py +0 -74
  133. package/python/tests/gripper/__init__.py +0 -0
  134. package/python/tests/gripper/test_definition.py +0 -30
  135. package/python/tests/gripper/test_load.py +0 -16
  136. package/python/tests/labware/__init__.py +0 -13
  137. package/python/tests/labware/test_typechecks.py +0 -21
  138. package/python/tests/module/__init__.py +0 -20
  139. package/python/tests/module/test_load.py +0 -49
  140. package/python/tests/module/test_typechecks.py +0 -34
  141. package/python/tests/pipette/__init__.py +0 -0
  142. package/python/tests/pipette/test_load_data.py +0 -103
  143. package/python/tests/pipette/test_mutable_configurations.py +0 -266
  144. package/python/tests/pipette/test_pipette_definition.py +0 -183
  145. package/python/tests/pipette/test_pipette_load_name_conversions.py +0 -222
  146. package/python/tests/pipette/test_typechecks.py +0 -50
  147. package/python/tests/pipette/test_validate_schema.py +0 -37
  148. package/python/tests/protocol/__init__.py +0 -15
  149. package/python/tests/protocol/test_protocol_schema_v6.py +0 -107
  150. package/python/tests/protocol/test_protocol_schema_v7.py +0 -34
  151. package/python/tests/protocol/test_protocol_schema_v8.py +0 -20
  152. package/python/tests/protocol/test_typechecks.py +0 -36
  153. package/python/tests/robot/__init__.py +0 -0
  154. package/python/tests/robot/test_typechecks.py +0 -20
@@ -1,431 +0,0 @@
1
- import logging
2
- import json
3
- import re
4
- from pathlib import Path
5
- from typing import Optional, List, Dict, Any, cast
6
-
7
- from .pipette_definition import PipetteConfigurations, PipetteModelVersionType
8
- from .model_constants import (
9
- MUTABLE_CONFIGS_V1,
10
- VALID_QUIRKS,
11
- _MAP_KEY_TO_V2,
12
- _MIN_MAX_LOOKUP,
13
- _TYPE_LOOKUP,
14
- _UNITS_LOOKUP,
15
- )
16
- from .load_data import load_definition, load_serial_lookup_table
17
- from .types import (
18
- MutableConfig,
19
- Quirks,
20
- QuirkConfig,
21
- TypeOverrides,
22
- OverrideType,
23
- LiquidClasses,
24
- )
25
- from .pipette_load_name_conversions import (
26
- convert_pipette_model,
27
- convert_to_pipette_name_type,
28
- )
29
- from .file_operation_helpers import (
30
- MutableConfigurationEncoder,
31
- MutableConfigurationDecoder,
32
- )
33
- from .dev_types import PipetteModel, PipetteName
34
-
35
-
36
- log = logging.getLogger(__name__)
37
-
38
- PIPETTE_SERIAL_MODEL_LOOKUP = load_serial_lookup_table()
39
- SERIAL_STUB_REGEX = re.compile(r"P[0-9]{1,3}[KSMHV]{1,2}V[0-9]{2}")
40
- LIQUID_CLASS = LiquidClasses.default
41
-
42
-
43
- def _edit_non_quirk(
44
- mutable_config_key: str, new_mutable_value: MutableConfig, base_dict: Dict[str, Any]
45
- ) -> None:
46
- def _do_edit_non_quirk(
47
- new_value: MutableConfig, existing: Dict[Any, Any], keypath: List[Any]
48
- ) -> None:
49
- thiskey: Any = keypath[0]
50
- if thiskey in [lc.name for lc in LiquidClasses]:
51
- thiskey = LiquidClasses[thiskey]
52
- if len(keypath) > 1:
53
- restkeys = keypath[1:]
54
- if thiskey == "##EACHTIP##":
55
- for key in existing.keys():
56
- _do_edit_non_quirk(new_value, existing[key], restkeys)
57
- else:
58
- _do_edit_non_quirk(new_value, existing[thiskey], restkeys)
59
- else:
60
- # This was the last key
61
- if thiskey == "##EACHTIP##":
62
- for key in existing.keys():
63
- existing[key] = new_value.value
64
- else:
65
- existing[thiskey] = new_value.value
66
-
67
- new_names = _MAP_KEY_TO_V2[mutable_config_key]
68
- _do_edit_non_quirk(new_mutable_value, base_dict, new_names)
69
-
70
-
71
- def _migrate_to_v2_configurations(
72
- base_configurations: PipetteConfigurations,
73
- v1_mutable_configs: OverrideType,
74
- ) -> PipetteConfigurations:
75
- """Helper function to migration V1 Configs to the V2 format.
76
-
77
- Given an input of v1 mutable configs, look up the equivalent keyed
78
- value of that configuration."""
79
- quirks_list = []
80
- dict_of_base_model = base_configurations.dict(by_alias=True)
81
- for c, v in v1_mutable_configs.items():
82
- if isinstance(v, str):
83
- # ignore the saved model
84
- continue
85
- if isinstance(v, bool):
86
- # ignore any accidental top level quirks.
87
- continue
88
- if c == "quirks" and isinstance(v, dict):
89
- quirks_list.extend([b.name for b in v.values() if b.value])
90
- elif isinstance(v, MutableConfig):
91
- _edit_non_quirk(c, v, dict_of_base_model)
92
-
93
- dict_of_base_model["quirks"] = list(
94
- set(dict_of_base_model["quirks"]).union(set(quirks_list))
95
- )
96
-
97
- # re-serialization is not great for this nested enum so we need
98
- # to perform this workaround.
99
- dict_of_base_model["liquid_properties"][LIQUID_CLASS]["supportedTips"] = {
100
- k.name: v
101
- for k, v in dict_of_base_model["liquid_properties"][LIQUID_CLASS][
102
- "supportedTips"
103
- ].items()
104
- }
105
- dict_of_base_model["liquid_properties"] = {
106
- k.name: v for k, v in dict_of_base_model["liquid_properties"].items()
107
- }
108
- dict_of_base_model["plungerPositionsConfigurations"] = {
109
- k.name: v
110
- for k, v in dict_of_base_model["plungerPositionsConfigurations"].items()
111
- }
112
- return PipetteConfigurations.parse_obj(dict_of_base_model)
113
-
114
-
115
- def _load_available_overrides(
116
- pipette_serial_number: str, pipette_override_path: Path
117
- ) -> OverrideType:
118
- """Load the available overrides from disk."""
119
- pipette_override = pipette_override_path / f"{pipette_serial_number}.json"
120
- try:
121
- with open(pipette_override, "r") as f:
122
- return json.load(f, cls=MutableConfigurationDecoder)
123
- except json.JSONDecodeError as e:
124
- log.warning(f"pipette override for {pipette_serial_number} is corrupt: {e}")
125
- pipette_override.unlink()
126
- raise FileNotFoundError()
127
-
128
-
129
- def _list_all_mutable_configs(
130
- on_disk_overrides: OverrideType, base_configurations: Dict[str, Any]
131
- ) -> OverrideType:
132
- """Get all available mutable configurations"""
133
- all_mutable_configs = set([*MUTABLE_CONFIGS_V1, *VALID_QUIRKS]) - set(["quirks"])
134
- on_disk_configs = set(
135
- [
136
- v.name
137
- for v in on_disk_overrides.values()
138
- if isinstance(v, MutableConfig) or isinstance(v, QuirkConfig)
139
- ]
140
- )
141
-
142
- missing_configurations = all_mutable_configs.difference(on_disk_configs)
143
-
144
- default_configurations: OverrideType = {"quirks": {}}
145
- for c in missing_configurations:
146
- if c in VALID_QUIRKS:
147
- if Quirks(c) in base_configurations["quirks"]:
148
- default_configurations["quirks"][c] = QuirkConfig.validate_and_build( # type: ignore
149
- c, True
150
- )
151
- else:
152
- default_configurations[c] = _find_default(c, base_configurations)
153
- default_configurations.update(on_disk_overrides)
154
- return default_configurations
155
-
156
-
157
- def _get_default_value_for(config: Dict[str, Any], keypath: List[str]) -> Any:
158
- def _do_get_default_value_for(
159
- remaining_config: Dict[Any, Any], keypath: List[str]
160
- ) -> Any:
161
- first: Any = keypath[0]
162
- if first in [lc.name for lc in LiquidClasses]:
163
- first = LiquidClasses[first]
164
- if len(keypath) > 1:
165
- rest = keypath[1:]
166
- if first == "##EACHTIP##":
167
- tip_list = list(remaining_config.keys())
168
- tip_list.sort(key=lambda o: o.value)
169
- return _do_get_default_value_for(remaining_config[tip_list[-1]], rest)
170
- else:
171
- return _do_get_default_value_for(remaining_config[first], rest)
172
- else:
173
- if first == "###EACHTIP##":
174
- tip_list = list(remaining_config.keys())
175
- tip_list.sort(key=lambda o: o.value)
176
- return remaining_config[tip_list[-1]]
177
- elif first == "currentByTipCount":
178
- # return the value for the most tips at a time
179
- cbt = remaining_config[first]
180
- return cbt[next(reversed(sorted(cbt.keys())))]
181
- else:
182
- return remaining_config[first]
183
-
184
- return _do_get_default_value_for(config, keypath)
185
-
186
-
187
- def _find_default(name: str, configs: Dict[str, Any]) -> MutableConfig:
188
- """Find the default value from the configs and return it as a mutable config."""
189
- keypath = _MAP_KEY_TO_V2[name]
190
- nested_name = keypath[-1]
191
-
192
- if name == "pickUpCurrent":
193
- min_max_dict = _MIN_MAX_LOOKUP["current"]
194
- type_lookup = _TYPE_LOOKUP["current"]
195
- units_lookup = _UNITS_LOOKUP["current"]
196
- else:
197
- min_max_dict = _MIN_MAX_LOOKUP[nested_name]
198
- type_lookup = _TYPE_LOOKUP[nested_name]
199
- units_lookup = _UNITS_LOOKUP[nested_name]
200
- default_value = _get_default_value_for(configs, keypath)
201
- return MutableConfig(
202
- value=default_value,
203
- default=default_value,
204
- min=min_max_dict["min"],
205
- max=min_max_dict["max"],
206
- type=type_lookup,
207
- units=units_lookup,
208
- name=name,
209
- )
210
-
211
-
212
- def known_pipettes(pipette_override_path: Path) -> List[str]:
213
- """List pipette IDs for which we have known overrides"""
214
- return [
215
- fi.stem
216
- for fi in pipette_override_path.iterdir()
217
- if fi.is_file() and ".json" in fi.suffixes
218
- ]
219
-
220
-
221
- def _load_full_mutable_configs(
222
- pipette_model: PipetteModelVersionType, overrides: OverrideType
223
- ) -> OverrideType:
224
- base_configs = load_definition(
225
- pipette_model.pipette_type,
226
- pipette_model.pipette_channels,
227
- pipette_model.pipette_version,
228
- )
229
- base_configs_dict = base_configs.dict(by_alias=True)
230
- full_mutable_configs = _list_all_mutable_configs(overrides, base_configs_dict)
231
-
232
- if not full_mutable_configs.get("name"):
233
- full_mutable_configs["name"] = str(
234
- convert_to_pipette_name_type(cast(PipetteName, str(pipette_model)))
235
- )
236
-
237
- if not full_mutable_configs.get("model"):
238
- full_mutable_configs["model"] = str(pipette_model)
239
-
240
- return full_mutable_configs
241
-
242
-
243
- def list_mutable_configs(
244
- pipette_serial_number: str, pipette_override_path: Path
245
- ) -> OverrideType:
246
- """
247
- Returns dict of mutable configs only.
248
- """
249
-
250
- mutable_configs: OverrideType = {}
251
-
252
- try:
253
- mutable_configs = _load_available_overrides(
254
- pipette_serial_number, pipette_override_path
255
- )
256
- except FileNotFoundError:
257
- log.info(f"Pipette id {pipette_serial_number} not found")
258
- # This is mimicing behavior from the original file
259
- # which returned an empty dict if no on disk value was found.
260
- return mutable_configs
261
-
262
- serial_key_match = SERIAL_STUB_REGEX.match(pipette_serial_number)
263
-
264
- if serial_key_match:
265
- serial_key = serial_key_match.group(0)
266
- else:
267
- serial_key = ""
268
- pipette_model = convert_pipette_model(
269
- cast(PipetteModel, PIPETTE_SERIAL_MODEL_LOOKUP[serial_key])
270
- )
271
- return _load_full_mutable_configs(pipette_model, mutable_configs)
272
-
273
-
274
- def list_mutable_configs_with_defaults(
275
- pipette_model: PipetteModelVersionType,
276
- pipette_serial_number: Optional[str],
277
- pipette_override_path: Path,
278
- ) -> OverrideType:
279
- """
280
- Returns dict of mutable configs only, with their defaults.
281
- """
282
- mutable_configs: OverrideType = {}
283
- if pipette_serial_number:
284
- try:
285
- mutable_configs = _load_available_overrides(
286
- pipette_serial_number, pipette_override_path
287
- )
288
- except FileNotFoundError:
289
- pass
290
- return _load_full_mutable_configs(pipette_model, mutable_configs)
291
-
292
-
293
- def load_with_mutable_configurations(
294
- pipette_model: PipetteModelVersionType,
295
- pipette_override_path: Path,
296
- pipette_serial_number: Optional[str] = None,
297
- ) -> PipetteConfigurations:
298
- """
299
- Load pipette config data with any overrides available.
300
-
301
- This function reads from disk each time, so changes to the overrides
302
- will be picked up in subsequent calls.
303
-
304
- :param str pipette_model: The pipette model name (i.e. "p10_single_v1.3")
305
- for which to load configuration
306
- :param pipette_serial_number: An (optional) unique ID for the pipette to locate
307
- config overrides. If the ID is not specified, the system
308
- assumes this is a simulated pipette and does not
309
- save settings. If the ID is specified but no overrides
310
- corresponding to the ID are found, the system creates a
311
- new overrides file for it.
312
- :type pipette_serial_number: str or None
313
- :raises KeyError: if ``pipette_model`` is not in the top-level keys of
314
- the pipetteModelSpecs.json file (and therefore not in
315
- :py:attr:`configs`)
316
-
317
- :returns PipetteConfig: The configuration, loaded and checked
318
- """
319
- base_configurations = load_definition(
320
- pipette_model.pipette_type,
321
- pipette_model.pipette_channels,
322
- pipette_model.pipette_version,
323
- )
324
- # Load overrides if we have a pipette id
325
- if pipette_serial_number:
326
- try:
327
- override = _load_available_overrides(
328
- pipette_serial_number, pipette_override_path
329
- )
330
- except FileNotFoundError:
331
- pass
332
- else:
333
- base_configurations = _migrate_to_v2_configurations(
334
- base_configurations, override
335
- )
336
- # the ulPerMm functions are structured in pipetteModelSpecs.json as
337
- # a list sorted from oldest to newest. That means the latest functions
338
- # are always the last element and, as of right now, the older ones are
339
- # the first element (for models that only have one function, the first
340
- # and last elements are the same, which is fine). If we add more in the
341
- # future, we’ll have to change this code to select items more
342
- # intelligently
343
- # TODO (lc 06/27) Handle versioned ul per mm
344
-
345
- return base_configurations
346
-
347
-
348
- def _add_new_overrides_to_existing( # noqa: C901
349
- base_configs_dict: Dict[str, Any],
350
- existing_overrides: OverrideType,
351
- overrides: TypeOverrides,
352
- ) -> OverrideType:
353
- """Helper function to add new overrides to the existing ones"""
354
- # FIXME remove the validation here for the file save and rely
355
- # on the validation in the robot server. We unfortunately have
356
- # to keep the validation here until we decide to fully wipe/migrate
357
- # files saved on disk because some of them have unexpected
358
- # data entries.
359
- if not existing_overrides.get("quirks"):
360
- existing_overrides["quirks"] = {}
361
- for key, value in overrides.items():
362
- # If an existing override is saved as null from endpoint, remove from
363
- # overrides file
364
- if value is None and existing_overrides.get(key):
365
- del existing_overrides[key]
366
- elif isinstance(value, bool):
367
- if key in VALID_QUIRKS:
368
- existing_overrides["quirks"][key] = QuirkConfig.validate_and_build( # type: ignore
369
- key, value
370
- )
371
- elif key not in MUTABLE_CONFIGS_V1:
372
- # Unfortunately, some of the files got corrupted,
373
- # so we have to check that the key doesn't exist
374
- # in mutable configs before throwing an error.
375
- raise ValueError(
376
- f"{value} is invalid for {key} or {key} is not a supported quirk."
377
- )
378
- elif existing_overrides.get(key):
379
- del existing_overrides[key]
380
- elif value:
381
- if existing_overrides.get(key):
382
- existing_overrides[key].validate_and_add(value) # type: ignore
383
- else:
384
- new_mutable_config = _find_default(key, base_configs_dict)
385
- new_mutable_config.validate_and_add(value)
386
- existing_overrides[key] = new_mutable_config
387
- return existing_overrides
388
-
389
-
390
- def save_overrides(
391
- pipette_serial_number: str, overrides: TypeOverrides, pipette_override_path: Path
392
- ) -> None:
393
- """
394
- Save overrides for the pipette.
395
-
396
- :param pipette_id: The pipette id
397
- :param overrides: The incoming values
398
- :return: None
399
- """
400
- # need to load defaults
401
- serial_key_match = SERIAL_STUB_REGEX.match(pipette_serial_number)
402
- if serial_key_match:
403
- serial_key = serial_key_match.group(0)
404
- else:
405
- serial_key = ""
406
- pipette_model = convert_pipette_model(
407
- cast(PipetteModel, PIPETTE_SERIAL_MODEL_LOOKUP[serial_key])
408
- )
409
-
410
- base_configs = load_definition(
411
- pipette_model.pipette_type,
412
- pipette_model.pipette_channels,
413
- pipette_model.pipette_version,
414
- )
415
- base_configs_dict = base_configs.dict(by_alias=True)
416
- try:
417
- existing_overrides = _load_available_overrides(
418
- pipette_serial_number, pipette_override_path
419
- )
420
- except FileNotFoundError:
421
- existing_overrides = {"quirks": {}}
422
-
423
- updated_overrides = _add_new_overrides_to_existing(
424
- base_configs_dict, existing_overrides, overrides
425
- )
426
-
427
- if not updated_overrides.get("model"):
428
- updated_overrides["model"] = str(pipette_model)
429
-
430
- with open(pipette_override_path / f"{pipette_serial_number}.json", "w") as file:
431
- json.dump(updated_overrides, file, cls=MutableConfigurationEncoder)