labmate 0.10.4__py3-none-any.whl → 0.10.6__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.
Files changed (35) hide show
  1. labmate/__config__.py +1 -1
  2. labmate/acquisition/__init__.py +3 -2
  3. labmate/acquisition/acquisition_data.py +18 -0
  4. labmate/acquisition/acquisition_loop.py +11 -18
  5. labmate/acquisition/acquisition_manager.py +101 -41
  6. labmate/acquisition/analysis_data.py +15 -25
  7. labmate/acquisition/analysis_loop.py +9 -7
  8. labmate/acquisition/backend.py +24 -0
  9. labmate/acquisition/config_file.py +5 -3
  10. labmate/acquisition/custom_lint.py +2 -3
  11. labmate/acquisition_notebook/__init__.py +2 -2
  12. labmate/acquisition_notebook/acquisition_analysis_manager.py +30 -45
  13. labmate/acquisition_notebook/display_widget.py +9 -9
  14. labmate/attrdict/attrdict_class.py +4 -10
  15. labmate/display/__init__.py +2 -3
  16. labmate/display/buttons.py +1 -0
  17. labmate/display/links.py +2 -1
  18. labmate/display/main.py +3 -2
  19. labmate/display/platform_utils/__init__.py +3 -1
  20. labmate/display/platform_utils/windows_utils.py +3 -9
  21. labmate/parsing/__init__.py +3 -5
  22. labmate/parsing/parsed_value.py +30 -10
  23. labmate/parsing/saving.py +2 -6
  24. labmate/utils/async_utils.py +1 -0
  25. labmate/utils/autoreload.py +2 -0
  26. labmate/utils/file_read.py +12 -13
  27. labmate/utils/lint.py +9 -11
  28. labmate/utils/random_utils.py +1 -0
  29. labmate/utils/title_parsing.py +4 -14
  30. {labmate-0.10.4.dist-info → labmate-0.10.6.dist-info}/METADATA +13 -2
  31. labmate-0.10.6.dist-info/RECORD +41 -0
  32. {labmate-0.10.4.dist-info → labmate-0.10.6.dist-info}/WHEEL +1 -1
  33. labmate-0.10.4.dist-info/RECORD +0 -40
  34. {labmate-0.10.4.dist-info → labmate-0.10.6.dist-info/licenses}/LICENCE +0 -0
  35. {labmate-0.10.4.dist-info → labmate-0.10.6.dist-info}/top_level.txt +0 -0
@@ -19,10 +19,12 @@ from ..acquisition import AcquisitionManager, AnalysisData
19
19
  from ..logger import logger
20
20
  from . import display_widget
21
21
 
22
+
22
23
  if TYPE_CHECKING:
23
24
  from dh5.path import Path
24
25
 
25
26
  from ..acquisition import FigureProtocol
27
+ from ..acquisition.backend import AcquisitionBackend
26
28
  from ..acquisition.config_file import ConfigFile
27
29
 
28
30
  # from ..logger import Logger
@@ -65,7 +67,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
65
67
  _analysis_cell_str = None
66
68
  _is_old_data = False
67
69
  _last_fig_name = None
68
- _default_config_files: Tuple[str, ...] = tuple()
70
+ _default_config_files: Tuple[str, ...] = ()
69
71
  _acquisition_started = 0
70
72
  _linting_external_vars = None
71
73
  _analysis_cell_prerun_hook: Optional[Tuple[_CallableWithNoArgs, ...]] = None
@@ -83,6 +85,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
83
85
  save_on_edit_analysis: Optional[bool] = None,
84
86
  save_fig_inside_h5: bool = False,
85
87
  shell: Any = True,
88
+ backend: Optional[Union["AcquisitionBackend", Iterable["AcquisitionBackend"]]] = None,
86
89
  ):
87
90
  """
88
91
  AcquisitionAnalysisManager.
@@ -115,7 +118,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
115
118
  self.shell = shell
116
119
 
117
120
  if use_magic:
118
- from .acquisition_magic_class import load_ipython_extension
121
+ from .acquisition_magic_class import load_ipython_extension # pyright: ignore[reportMissingImports] # noqa: I001
119
122
 
120
123
  load_ipython_extension(aqm=self, shell=self.shell)
121
124
 
@@ -131,6 +134,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
131
134
  config_files=config_files,
132
135
  save_files=save_files,
133
136
  save_on_edit=save_on_edit,
137
+ backend=backend,
134
138
  )
135
139
 
136
140
  @property
@@ -173,7 +177,8 @@ class AcquisitionAnalysisManager(AcquisitionManager):
173
177
  Args:
174
178
  fig (Figure, optional): Figure that should be saved. Figure could be any class with
175
179
  function save_fig implemented. By default gets plt.gcf().
176
- name (str, optional): Name of the fig. It's a suffix that will be added to the filename. Defaults to None.
180
+ name (str, optional): Name of the fig. It's a suffix that will be added to the filename.
181
+ Defaults to None.
177
182
  extensions(str, optional): Extensions of the file. Defaults to `pdf`.
178
183
  tight_layout(bool, optional): True to call fig.tight_layout(). Defaults to True.
179
184
 
@@ -220,6 +225,10 @@ class AcquisitionAnalysisManager(AcquisitionManager):
220
225
  fig = fig or fig_or_name
221
226
  self.save_fig_only(fig=fig, name=name, **kwds)
222
227
  self.save_analysis_cell(name=name, cell=cell)
228
+
229
+ if self.current_acquisition is not None:
230
+ self._schedule_backend_save(self.current_acquisition)
231
+
223
232
  if self._connected_widgets:
224
233
  display_widget.display_widgets(
225
234
  self._connected_widgets,
@@ -248,15 +257,12 @@ class AcquisitionAnalysisManager(AcquisitionManager):
248
257
  acquisition_finished = time.time()
249
258
  if not self._once_saved:
250
259
  additional_info: Dict[str, Any] = {
251
- "acquisition_duration": acquisition_finished
252
- - self._acquisition_started,
260
+ "acquisition_duration": acquisition_finished - self._acquisition_started,
253
261
  "logs": self.logger.getvalue(),
254
262
  "prints": self.logger.get_stdout(),
255
263
  }
256
264
  if self._default_config_files:
257
- additional_info.update(
258
- {"default_config_files": self._default_config_files}
259
- )
265
+ additional_info.update({"default_config_files": self._default_config_files})
260
266
  kwds.update({"info": additional_info})
261
267
 
262
268
  super().save_acquisition(update_, file_suffix=file_suffix, **kwds)
@@ -292,9 +298,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
292
298
  - If default configuration files are provided, they are set in the loaded data.
293
299
  """
294
300
  filename = self._get_full_filename(filename)
295
- if not os.path.exists(
296
- filename if filename.endswith(".h5") else filename + ".h5"
297
- ):
301
+ if not os.path.exists(filename if filename.endswith(".h5") else filename + ".h5"): # noqa: PTH110
298
302
  raise ValueError(f"File {filename} cannot be found")
299
303
 
300
304
  data = AnalysisData(
@@ -386,9 +390,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
386
390
  display_warning("Old data analysis")
387
391
 
388
392
  filename = str(filepath or self._get_full_filename(filename)) # type: ignore
389
- filename = (
390
- (filename.rsplit(".h5", 1)[0]) if filename.endswith(".h5") else filename
391
- )
393
+ filename = (filename.rsplit(".h5", 1)[0]) if filename.endswith(".h5") else filename
392
394
 
393
395
  else:
394
396
  self._is_old_data = False
@@ -403,8 +405,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
403
405
  )
404
406
  or (
405
407
  acquisition_name[0] == r"^"
406
- and re.match(acquisition_name, self.current_experiment_name)
407
- is None
408
+ and re.match(acquisition_name, self.current_experiment_name) is None
408
409
  )
409
410
  ):
410
411
  raise ValueError(
@@ -413,14 +414,14 @@ class AcquisitionAnalysisManager(AcquisitionManager):
413
414
  )
414
415
 
415
416
  filename = str(self.current_filepath) # without h5
416
- self.logger.info(os.path.basename(filename))
417
+ self.logger.info(os.path.basename(filename)) # noqa: PTH119
417
418
 
418
419
  if (
419
420
  (not self._is_old_data)
420
421
  and (self.shell is not None)
421
422
  and (
422
- "acquisition_cell(" in self.shell.last_execution_result.info.raw_cell
423
- and not self.shell.last_execution_result.success
423
+ "acquisition_cell(" in self.shell.last_execution_result.info.raw_cell # type: ignore
424
+ and not self.shell.last_execution_result.success # type: ignore
424
425
  )
425
426
  ):
426
427
  raise ChildProcessError(
@@ -428,7 +429,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
428
429
  "Check if everything is ok and executive again"
429
430
  )
430
431
 
431
- if os.path.exists(filename + ".h5"):
432
+ if os.path.exists(filename + ".h5"): # noqa: PTH110
432
433
  self._load_analysis_data(filename)
433
434
  else:
434
435
  if self._is_old_data:
@@ -438,9 +439,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
438
439
  if cell is not None:
439
440
  self.save_analysis_cell(cell=cell)
440
441
 
441
- if (self._analysis_cell_str is not None) and (
442
- self._linting_external_vars is not None
443
- ):
442
+ if (self._analysis_cell_str is not None) and (self._linting_external_vars is not None):
444
443
  from ..acquisition import custom_lint
445
444
  from ..utils import lint
446
445
 
@@ -453,9 +452,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
453
452
  run_on_call=custom_lint.on_call_functions,
454
453
  )
455
454
  for var in lint_result.external_vars:
456
- self.logger.warning(
457
- "External variable used inside the analysis code: %s", var
458
- )
455
+ self.logger.warning("External variable used inside the analysis code: %s", var)
459
456
  for error in lint_result.errors:
460
457
  self.logger.warning(error)
461
458
 
@@ -480,15 +477,13 @@ class AcquisitionAnalysisManager(AcquisitionManager):
480
477
 
481
478
  filepath = utils.get_path_from_filename(filename)
482
479
  if isinstance(filepath, tuple):
483
- return os.path.join(self.data_directory, *filepath)
480
+ return os.path.join(self.data_directory, *filepath) # noqa: PTH118
484
481
  return filepath
485
482
 
486
483
  def parse_config_file(self, config_file_name: str, /) -> "ConfigFile":
487
484
  return self.data.parse_config_file(config_file_name)
488
485
 
489
- def parse_config(
490
- self, config_files: Optional[Tuple[str, ...]] = None
491
- ) -> "ConfigFile":
486
+ def parse_config(self, config_files: Optional[Tuple[str, ...]] = None) -> "ConfigFile":
492
487
  return self.data.parse_config(config_files=config_files)
493
488
 
494
489
  @property
@@ -510,16 +505,12 @@ class AcquisitionAnalysisManager(AcquisitionManager):
510
505
  ):
511
506
  from ..utils import lint
512
507
 
513
- allowed_variables = (
514
- set() if allowed_variables is None else set(allowed_variables)
515
- )
508
+ allowed_variables = set() if allowed_variables is None else set(allowed_variables)
516
509
  if init_file is not None:
517
510
  allowed_variables.update(lint.find_variables_from_file(init_file)[0])
518
511
  self._linting_external_vars = allowed_variables
519
512
 
520
- def set_default_config_files(
521
- self, config_files: Union[str, Tuple[str, ...], List[str]], /
522
- ):
513
+ def set_default_config_files(self, config_files: Union[str, Tuple[str, ...], List[str]], /):
523
514
  self._default_config_files = (
524
515
  (config_files,) if isinstance(config_files, str) else tuple(config_files)
525
516
  )
@@ -566,7 +557,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
566
557
  if after_text is not None:
567
558
  if not isinstance(params, str):
568
559
  raise ValueError(
569
- "Cannot use after_text with multiple params"
560
+ "Cannot use after_text with multiple params. "
570
561
  "Use params=[(param, after_text), ...] instead."
571
562
  )
572
563
  return self.display_param_link(params=[(params, after_text)], title=title)
@@ -616,11 +607,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
616
607
  self.update_config_params_on_disk({param: value})
617
608
 
618
609
  buttons = (
619
- [
620
- display.buttons.create_button(
621
- update_value, param, value, name="Update"
622
- )
623
- ]
610
+ [display.buttons.create_button(update_value, param, value, name="Update")]
624
611
  if update_button
625
612
  else None
626
613
  )
@@ -653,9 +640,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
653
640
 
654
641
  def connect_default_widget(
655
642
  self,
656
- objs: Union[
657
- "display_widget.WidgetProtocol", List["display_widget.WidgetProtocol"]
658
- ],
643
+ objs: Union["display_widget.WidgetProtocol", List["display_widget.WidgetProtocol"]],
659
644
  ):
660
645
  if not isinstance(objs, (list, tuple)):
661
646
  objs = [objs]
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
4
4
  from .. import display as lm_display
5
5
  from ..display import platform_utils
6
6
 
7
+
7
8
  if TYPE_CHECKING:
8
9
  from labmate.acquisition_notebook import AcquisitionAnalysisManager
9
10
 
@@ -19,12 +20,11 @@ def _create_file_link(aqm: "AcquisitionAnalysisManager", level_up) -> str:
19
20
  filepath = _get_filepath(aqm)
20
21
  if filepath is None:
21
22
  return ""
22
- link_name = os.path.basename(filepath)
23
- link = "/".join(
24
- os.path.abspath(filepath).replace("\\", "/").split("/")[-level_up:]
25
- ).replace(" ", "%20")
26
- link = f"[{link_name}](//kyrylo-gr.github.io/h5viewer/open?url={link})"
27
- return link
23
+ link_name = os.path.basename(filepath) # noqa: PTH119
24
+ link = "/".join(os.path.abspath(filepath).replace("\\", "/").split("/")[-level_up:]).replace( # noqa: PTH100
25
+ " ", "%20"
26
+ )
27
+ return f"[{link_name}](//kyrylo-gr.github.io/h5viewer/open?url={link})"
28
28
 
29
29
 
30
30
  def display_widgets(objs: List["WidgetProtocol"], *args, **kwargs):
@@ -193,14 +193,14 @@ class OpenFinderButton(BaseWidget):
193
193
  import sys
194
194
 
195
195
  def open_finder():
196
- path = os.path.abspath(filepath) + ".h5"
196
+ path = os.path.abspath(filepath) + ".h5" # noqa: PTH100
197
197
  if sys.platform == "win32":
198
198
  path = path.replace("/", "\\")
199
199
  subprocess.run(["explorer", "/select,", path], shell=True)
200
200
  elif sys.platform == "darwin":
201
- subprocess.Popen(["open", "-R", os.path.dirname(path)])
201
+ subprocess.Popen(["open", "-R", os.path.dirname(path)]) # noqa: PTH120
202
202
  else:
203
- subprocess.Popen(["nautilus", "--select", os.path.dirname(path)])
203
+ subprocess.Popen(["nautilus", "--select", os.path.dirname(path)]) # noqa: PTH120
204
204
 
205
205
  self.widget = lm_display.buttons.create_button(open_finder, name="Open finder")
206
206
  return self.widget
@@ -92,21 +92,15 @@ class AttrDict(dict):
92
92
  keys_with_values = self.__get_value_for_output(keys)
93
93
  return utils_parse.format_title(keys_with_values, max_length=max_length)
94
94
 
95
- def __get_value_for_output(
96
- self, keys: List[str]
97
- ) -> List[utils_parse.ValueForPrint]:
98
- """Prepare the values for output. Returns list of ValueForPrint(key, value, unit, format))."""
95
+ def __get_value_for_output(self, keys: List[str]) -> List[utils_parse.ValueForPrint]:
96
+ """Prepare values for output. Returns list of ValueForPrint(key, value, unit, format)."""
99
97
  keys_with_values = []
100
98
  for key in keys:
101
99
  key_value, key_units, key_format = utils_parse.parse_get_format(key)
102
100
  if key_value in self:
103
101
  keys_with_values.append(
104
- utils_parse.ValueForPrint(
105
- key_value, self[key_value], key_units, key_format
106
- )
102
+ utils_parse.ValueForPrint(key_value, self[key_value], key_units, key_format)
107
103
  )
108
104
  else:
109
- raise ValueError(
110
- f"Cannot find key={key} inside {self.__class__.__name__}"
111
- )
105
+ raise ValueError(f"Cannot find key={key} inside {self.__class__.__name__}")
112
106
  return keys_with_values
@@ -10,6 +10,7 @@ from .main import (
10
10
  logger,
11
11
  )
12
12
 
13
+
13
14
  __all__ = ["links", "buttons", "logger", "html_output"]
14
15
 
15
16
 
@@ -20,9 +21,7 @@ class _LazyModule:
20
21
 
21
22
  def __getattr__(self, name):
22
23
  if self.__module is None:
23
- self.__module = importlib.import_module(
24
- f".{self.__name}", package=__package__
25
- )
24
+ self.__module = importlib.import_module(f".{self.__name}", package=__package__)
26
25
  return getattr(self.__module, name)
27
26
 
28
27
  # @property
@@ -4,6 +4,7 @@ from typing import TypeVar
4
4
 
5
5
  from .main import display, widgets
6
6
 
7
+
7
8
  # from functools import wraps
8
9
  # def add_display_method(func):
9
10
  # """If a function changes the data it should be saved.
labmate/display/links.py CHANGED
@@ -23,5 +23,6 @@ def create_link(
23
23
  after_text = after_text if after_text else ""
24
24
  return (
25
25
  f"<a href='{file}:{line_no}' target='_blank' style='margin: 0!; padding:0!;'>"
26
- f"{link_text}</a> {after_text}"
26
+ f"{link_text}"
27
+ f"</a> {after_text}"
27
28
  )
labmate/display/main.py CHANGED
@@ -4,6 +4,7 @@ import logging
4
4
  import sys
5
5
  from typing import Callable, List
6
6
 
7
+
7
8
  logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARNING)
8
9
  logger = logging.getLogger(__name__)
9
10
  handler = logging.StreamHandler()
@@ -28,13 +29,13 @@ except (ImportError, AttributeError):
28
29
  # For testing purposes every IPython function should have a simpler version.
29
30
  # pylint: disable=C0115, C0103, R0903
30
31
 
31
- def HTML(text): # noqa: D103
32
+ def HTML(text): # noqa: D103, N802
32
33
  return text
33
34
 
34
35
  def display(text): # noqa: D103
35
36
  logger.info(text)
36
37
 
37
- class widgets: # noqa: D101
38
+ class widgets: # noqa: D101, N801
38
39
  class Button: # noqa: D106
39
40
  func: str
40
41
  description: str
@@ -2,9 +2,11 @@
2
2
 
3
3
  The goal it to have a single interface for all such functions.
4
4
  """
5
+
5
6
  import os
6
- import sys
7
7
  import subprocess
8
+ import sys
9
+
8
10
 
9
11
  if os.name == "nt":
10
12
  from . import windows_utils as current_utils
@@ -1,9 +1,7 @@
1
1
  """This submodule contains functions that are specific for windows."""
2
2
 
3
3
 
4
- def copy_fig(
5
- fig=None, format_=None, text_to_copy=None, **kwargs
6
- ): # pragma: no cover <--
4
+ def copy_fig(fig=None, format_=None, text_to_copy=None, **kwargs): # pragma: no cover <--
7
5
  """Copy fig to the clipboard.
8
6
 
9
7
  Parameters
@@ -73,9 +71,7 @@ def copy_fig(
73
71
  im = Image.open(buf)
74
72
  with BytesIO() as output:
75
73
  im.convert("RGB").save(output, "BMP")
76
- data = output.getvalue()[
77
- 14:
78
- ] # The file header off-set of BMP is 14 bytes
74
+ data = output.getvalue()[14:] # The file header off-set of BMP is 14 bytes
79
75
  format_id = win32clipboard.CF_DIB # DIB = device independent bitmap
80
76
 
81
77
  except ImportError:
@@ -89,8 +85,6 @@ def copy_fig(
89
85
  win32clipboard.EmptyClipboard()
90
86
  win32clipboard.SetClipboardData(format_id, data)
91
87
  if text_to_copy:
92
- win32clipboard.SetClipboardData(
93
- win32clipboard.CF_TEXT, text_to_copy.encode("utf-8")
94
- )
88
+ win32clipboard.SetClipboardData(win32clipboard.CF_TEXT, text_to_copy.encode("utf-8"))
95
89
 
96
90
  win32clipboard.CloseClipboard()
@@ -10,10 +10,11 @@ Examples:
10
10
 
11
11
 
12
12
  """
13
+
13
14
  from typing import Dict
14
15
 
15
- from .parsed_value import ParsedValue
16
16
  from .brackets_score import BracketsScore
17
+ from .parsed_value import ParsedValue
17
18
 
18
19
 
19
20
  def parse_str(file: str, /) -> Dict[str, ParsedValue]:
@@ -36,10 +37,7 @@ def parse_str(file: str, /) -> Dict[str, ParsedValue]:
36
37
  if not brackets.is_zero():
37
38
  continue
38
39
 
39
- if "# value: " in value:
40
- value_eval = value[value.rfind("# value: ") + 9 :]
41
- else:
42
- value_eval = None
40
+ value_eval = value[value.rfind("# value: ") + 9 :] if ("# value: " in value) else None
43
41
 
44
42
  value = value.split("#")[0].strip()
45
43
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  from typing import Any, Union
4
4
 
5
+ import numpy as np
6
+
5
7
 
6
8
  def parse_value(value: str) -> Union[str, int, float]:
7
9
  """Convert str to int or float if possible."""
@@ -16,14 +18,14 @@ def parse_value(value: str) -> Union[str, int, float]:
16
18
  value_without_underscores[0] == "-" and value[1:].isdigit()
17
19
  ):
18
20
  return int(value)
19
- elif value_without_underscores.replace(".", "").isdigit() or (
20
- value[0] == "-" and value_without_underscores[1:].replace(".", "").isdigit()
21
- ):
22
- return float(value)
23
21
  elif (
24
- (value[0].isdigit() or (value[0] == "-" and value[1].isdigit()))
25
- and value[-1].isdigit()
26
- and "e" in value
22
+ value_without_underscores.replace(".", "").isdigit()
23
+ or (value[0] == "-" and value_without_underscores[1:].replace(".", "").isdigit())
24
+ or (
25
+ (value[0].isdigit() or (value[0] == "-" and value[1].isdigit()))
26
+ and value[-1].isdigit()
27
+ and "e" in value
28
+ )
27
29
  ):
28
30
  return float(value)
29
31
  except ValueError:
@@ -44,9 +46,11 @@ class ParsedValue:
44
46
 
45
47
  Examples:
46
48
  >>> v1 = ParsedValue("1", 1)
47
- >>> v2 = ParsedValue("2", 2)
49
+ >>> v2 = ParsedValue("6/3", 2)
48
50
  >>> v1 + v2
49
51
  3
52
+ >>> v2.unpack()
53
+ ('6/3', 2)
50
54
 
51
55
  """
52
56
 
@@ -63,8 +67,11 @@ class ParsedValue:
63
67
  self.original = parse_value(original)
64
68
  self.value = parse_value(value)
65
69
 
66
- def __iter__(self):
67
- return iter((self.original, self.value))
70
+ # def __iter__(self):
71
+ # return iter((self.original, self.value))
72
+
73
+ def unpack(self):
74
+ return self.original, self.value
68
75
 
69
76
  def __str__(self) -> str:
70
77
  return str(self.value)
@@ -81,9 +88,18 @@ class ParsedValue:
81
88
  def __abs__(self) -> float:
82
89
  return abs(self.value) # type: ignore
83
90
 
91
+ def __int__(self) -> int:
92
+ return int(self.value) # type: ignore
93
+
84
94
  def __float__(self) -> float:
85
95
  return float(self.value) # type: ignore
86
96
 
97
+ def __array__(self, dtype=None, **kwargs):
98
+ # So numpy/matplotlib can use it: np.asarray(obj) works
99
+ if dtype is not None:
100
+ return np.array(self.value, dtype=dtype, **kwargs)
101
+ return np.array(self.value, **kwargs)
102
+
87
103
  def __neg__(self) -> float:
88
104
  return -self.value # type: ignore
89
105
 
@@ -195,3 +211,7 @@ class ParsedValue:
195
211
  if isinstance(other, ParsedValue):
196
212
  return other.value
197
213
  return other
214
+
215
+ def __hash__(self) -> int:
216
+ return hash(self.value)
217
+ # return hash((self.original, self.value))
labmate/parsing/saving.py CHANGED
@@ -27,9 +27,7 @@ def append_values_from_modules_to_files(
27
27
  return configs
28
28
 
29
29
 
30
- def append_values_from_module_to_file(
31
- body: str, module, separator=" # value: "
32
- ) -> str:
30
+ def append_values_from_module_to_file(body: str, module, separator=" # value: ") -> str:
33
31
  """Add values from a module to a given file body.
34
32
 
35
33
  Args:
@@ -68,9 +66,7 @@ def append_values_from_module_to_file(
68
66
  for key, (val, _) in parse_str(line).items():
69
67
  real_val = variables.get(key, "")
70
68
  if (
71
- isinstance(val, str)
72
- and isinstance(real_val, str)
73
- and real_val != val.strip("\"'")
69
+ isinstance(val, str) and isinstance(real_val, str) and real_val != val.strip("\"'")
74
70
  ) or (
75
71
  isinstance(val, str)
76
72
  and isinstance(real_val, (float, int, complex))
@@ -1,4 +1,5 @@
1
1
  """Async utilities."""
2
+
2
3
  from dh5.utils.async_utils import ( # pylint: disable=unused-import # noqa: F401
3
4
  sleep,
4
5
  )
@@ -12,8 +12,10 @@ import labmate.utils.autoreload
12
12
  ```
13
13
 
14
14
  """
15
+
15
16
  from IPython.core.getipython import get_ipython
16
17
 
18
+
17
19
  shell = get_ipython()
18
20
  if shell:
19
21
  shell.run_cell("%load_ext autoreload")
@@ -1,8 +1,8 @@
1
1
  """Different method to read files."""
2
2
 
3
3
  import json
4
- import os
5
4
  import re
5
+ from pathlib import Path
6
6
  from typing import Any, Dict, List, Optional
7
7
 
8
8
  from ..parsing.brackets_score import BracketsScore
@@ -20,13 +20,13 @@ def read_file(file: str, /) -> str:
20
20
  Raises:
21
21
  ValueError: If the file does not exist or is not a file.
22
22
  """
23
- if not os.path.isfile(file):
23
+ if not Path(file).is_file():
24
24
  raise ValueError(
25
- "Cannot read a file if it doesn't exist or it's not a file."
26
- f"Path: {os.path.abspath(file)}"
25
+ "Cannot read a file if it doesn't exist or it's not a file. "
26
+ f"Path: {Path(file).absolute()}"
27
27
  )
28
28
 
29
- with open(file, "r", encoding="utf-8") as file_opened:
29
+ with open(file, encoding="utf-8") as file_opened:
30
30
  return file_opened.read()
31
31
 
32
32
 
@@ -40,15 +40,16 @@ def read_files(files: List[str], /) -> Dict[str, str]:
40
40
  A dictionary where the keys are the file names and the values are the contents of the files.
41
41
 
42
42
  Raises:
43
- ValueError: If some of the files have the same name, which would cause a key collision in the dictionary.
43
+ ValueError: If some of the files have the same name,
44
+ which would cause a key collision in the dict.
44
45
  """
45
46
  configs: Dict[str, str] = {}
46
47
  for config_file in files:
47
- config_file_name = os.path.basename(config_file)
48
+ config_file_name = Path(config_file).name
48
49
  if config_file_name in configs:
49
50
  raise ValueError(
50
- "Some of the files have the same name. So it cannot be pushed into dictionary to"
51
- " preserve unique key"
51
+ "Some of the files have the same name. "
52
+ "So it cannot be pushed into dict to preserve unique key"
52
53
  )
53
54
  configs[config_file_name] = read_file(config_file)
54
55
  return configs
@@ -62,7 +63,7 @@ def update_file_variable(file, params: Dict[str, Any]):
62
63
  file (str): The path to the file to update.
63
64
  params (Dict[str, Any]): The parameters to update the file with.
64
65
  """
65
- with open(file, "r", encoding="utf-8") as file_opened:
66
+ with open(file, encoding="utf-8") as file_opened:
66
67
  lines = file_opened.readlines()
67
68
  brackets = BracketsScore()
68
69
  current_param: Optional[str] = None
@@ -87,9 +88,7 @@ def update_file_variable(file, params: Dict[str, Any]):
87
88
  value_str = json.JSONEncoder().encode(params[current_param])
88
89
 
89
90
  print("value_str", value_str)
90
- if re.match(
91
- r"^['\"]?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?['\"]?$", value_str
92
- ):
91
+ if re.match(r"^['\"]?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?['\"]?$", value_str):
93
92
  value_str = value_str.replace('"', "")
94
93
 
95
94
  lines.insert(