labmate 0.8.4__py3-none-any.whl → 0.10.0__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.
labmate/__config__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """This file contains all package constants."""
2
2
 
3
- __version__ = "0.8.4"
3
+ __version__ = "0.10.0"
@@ -4,8 +4,8 @@ from typing import Dict, List, Optional, Union
4
4
 
5
5
  from dh5 import DH5
6
6
 
7
+ from ..logger import logger
7
8
  from ..utils.file_read import read_files
8
- from .logger_setup import logger
9
9
 
10
10
 
11
11
  class NotebookAcquisitionData(DH5):
@@ -26,15 +26,17 @@ class AcquisitionManager:
26
26
  """AcquisitionManager."""
27
27
 
28
28
  _data_directory: Path
29
- config_files = []
30
- config_files_eval = {}
29
+ config_files: List[str] = []
30
+ config_files_eval: Dict[str, str] = {}
31
+ _configs_last_modified: List[float] = []
31
32
 
32
- _current_acquisition = None
33
- _current_filepath = None
33
+ _current_acquisition: Optional[NotebookAcquisitionData] = None
34
+ _current_filepath: Optional[str] = None
34
35
 
35
- _save_files = False
36
- _save_on_edit = True
36
+ _save_files: bool = False
37
+ _save_on_edit: bool = True
37
38
  _init_code = None
39
+ _once_saved: bool
38
40
 
39
41
  cell: Optional[str] = None
40
42
 
@@ -54,9 +56,11 @@ class AcquisitionManager:
54
56
 
55
57
  self._current_acquisition = None
56
58
  self._acquisition_tmp_data = None
59
+ self._once_saved = False
57
60
 
58
61
  self.config_files = []
59
62
  self.config_files_eval = {}
63
+ self._configs_last_modified = []
60
64
 
61
65
  if data_directory is not None:
62
66
  self.data_directory = Path(data_directory)
@@ -187,13 +191,19 @@ class AcquisitionManager:
187
191
  return None
188
192
  return AcquisitionTmpData(**jsn.read(path))
189
193
 
194
+ def _get_configs_last_modified(self) -> List[float]:
195
+ return [os.path.getmtime(file) for file in self.config_files]
196
+
190
197
  def new_acquisition(
191
198
  self, name: str, cell: Optional[str] = None, save_on_edit: Optional[bool] = None
192
199
  ) -> NotebookAcquisitionData:
193
200
  """Create a new acquisition with the given experiment name."""
194
201
  self._current_acquisition = None
202
+ self._once_saved = False
195
203
  self.cell = cell
196
204
  configs = read_files(self.config_files)
205
+ self._configs_last_modified = self._get_configs_last_modified()
206
+
197
207
  if self.config_files_eval:
198
208
  configs = append_values_from_modules_to_files(
199
209
  configs, self.config_files_eval
@@ -310,4 +320,5 @@ class AcquisitionManager:
310
320
  acq_data.save_additional_info()
311
321
  if acq_data.save_on_edit is False:
312
322
  acq_data.save()
323
+ self._once_saved = True
313
324
  return self
@@ -1,15 +1,16 @@
1
1
  """AnalysisData class."""
2
2
 
3
+ import json
3
4
  import os
4
- from typing import List, Literal, Optional, Protocol, Tuple, TypeVar, Union
5
+ from typing import List, Literal, Optional, Protocol, Tuple, TypedDict, TypeVar, Union
5
6
 
6
7
  from dh5 import DH5
7
8
  from dh5.path import Path
8
9
 
9
10
  from .. import utils
11
+ from ..logger import logger
10
12
  from .analysis_loop import AnalysisLoop
11
13
  from .config_file import ConfigFile
12
- from .logger_setup import logger
13
14
 
14
15
  _T = TypeVar("_T", bound="AnalysisData")
15
16
 
@@ -21,6 +22,13 @@ class FigureProtocol(Protocol):
21
22
  """Save the figure to a file."""
22
23
 
23
24
 
25
+ class PdfMetadataDict(TypedDict, total=False):
26
+ """Metadata for the pdf file."""
27
+
28
+ Subject: Union[str, dict]
29
+ Keywords: Union[str, dict]
30
+
31
+
24
32
  class AnalysisData(DH5):
25
33
  """A subclass of DH5 that provides additional functionality for analyzing data.
26
34
 
@@ -167,6 +175,7 @@ class AnalysisData(DH5):
167
175
  name: Optional[Union[str, int]] = None,
168
176
  extensions: Optional[str] = None,
169
177
  tight_layout: bool = True,
178
+ metadata: Optional[PdfMetadataDict] = None,
170
179
  **kwargs,
171
180
  ) -> _T:
172
181
  """Save the figure with the filename (...)_FIG_name.
@@ -201,7 +210,26 @@ class AnalysisData(DH5):
201
210
  )
202
211
  if tight_layout and hasattr(fig, "tight_layout"):
203
212
  fig.tight_layout() # type: ignore
204
- fig.savefig(full_fig_name, **kwargs)
213
+ if metadata is None:
214
+ fig.savefig(full_fig_name, **kwargs)
215
+ else:
216
+ if not full_fig_name.endswith(".pdf"):
217
+ raise ValueError("Metadata can be added only to pdf files.")
218
+
219
+ from matplotlib.backends.backend_pdf import PdfPages
220
+
221
+ pdf_fig = PdfPages(full_fig_name)
222
+ fig.savefig(pdf_fig, format="pdf", **kwargs) # type: ignore
223
+ metadata = metadata or {}
224
+ if not isinstance(metadata.get("Subject", ""), str):
225
+ metadata["Subject"] = json.dumps(metadata.get("Subject"))
226
+
227
+ if not isinstance(metadata.get("Keywords", ""), str):
228
+ metadata["Keywords"] = json.dumps(metadata.get("Keywords"))
229
+
230
+ pdf_metadata = pdf_fig.infodict()
231
+ pdf_metadata.update(metadata)
232
+ pdf_fig.close()
205
233
 
206
234
  self._figure_saved = True
207
235
 
@@ -16,6 +16,7 @@ from typing import (
16
16
 
17
17
  from .. import display, utils
18
18
  from ..acquisition import AcquisitionManager, AnalysisData
19
+ from ..logger import logger
19
20
  from . import display_widget
20
21
 
21
22
  if TYPE_CHECKING:
@@ -24,19 +25,17 @@ if TYPE_CHECKING:
24
25
  from ..acquisition import FigureProtocol
25
26
  from ..acquisition.config_file import ConfigFile
26
27
 
28
+ # from ..logger import Logger
29
+
27
30
 
28
31
  logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)
29
- logger = logging.getLogger(__name__)
30
- handler = logging.StreamHandler()
31
- formatter = logging.Formatter("%(levelname)s:%(message)s")
32
- handler.setFormatter(formatter)
33
- logger.addHandler(handler)
34
- logger.setLevel(logging.INFO)
35
- logger.propagate = False
36
32
 
37
33
  _CallableWithNoArgs = Callable[[], Any]
38
34
 
39
35
 
36
+ CATCH_PRINT = True
37
+
38
+
40
39
  class AcquisitionAnalysisManager(AcquisitionManager):
41
40
  """AcquisitionAnalysisManager.
42
41
 
@@ -126,6 +125,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
126
125
  self._save_on_edit_analysis = save_on_edit_analysis
127
126
  self._save_fig_inside_h5 = save_fig_inside_h5
128
127
 
128
+ self._logger = logger
129
129
  super().__init__(
130
130
  data_directory=str(data_directory),
131
131
  config_files=config_files,
@@ -133,6 +133,10 @@ class AcquisitionAnalysisManager(AcquisitionManager):
133
133
  save_on_edit=save_on_edit,
134
134
  )
135
135
 
136
+ @property
137
+ def logger(self):
138
+ return self._logger
139
+
136
140
  @property
137
141
  def current_acquisition(self):
138
142
  """Return current acquisition if it's not an old data analyses."""
@@ -240,13 +244,19 @@ class AcquisitionAnalysisManager(AcquisitionManager):
240
244
 
241
245
  def save_acquisition(self, **kwds) -> "AcquisitionAnalysisManager":
242
246
  acquisition_finished = time.time()
243
- additional_info: Dict[str, Any] = {
244
- "acquisition_duration": acquisition_finished - self._acquisition_started
245
- }
246
- if self._default_config_files:
247
- additional_info.update({"default_config_files": self._default_config_files})
247
+ if not self._once_saved:
248
+ additional_info: Dict[str, Any] = {
249
+ "acquisition_duration": acquisition_finished
250
+ - self._acquisition_started,
251
+ "logs": self.logger.getvalue(),
252
+ "prints": self.logger.get_stdout(),
253
+ }
254
+ if self._default_config_files:
255
+ additional_info.update(
256
+ {"default_config_files": self._default_config_files}
257
+ )
248
258
 
249
- kwds.update({"info": additional_info})
259
+ kwds.update({"info": additional_info})
250
260
 
251
261
  super().save_acquisition(**kwds)
252
262
  self._load_analysis_data()
@@ -309,12 +319,24 @@ class AcquisitionAnalysisManager(AcquisitionManager):
309
319
  self._current_acquisition.current_step = step
310
320
  self._current_acquisition.set_cell(cell, step=step)
311
321
  self._current_acquisition.save_cell(cell, suffix=str(step))
322
+ configs_modified = self._get_configs_last_modified()
323
+ if configs_modified != self._configs_last_modified:
324
+ raise ValueError(
325
+ "Config files were modified since the previous acquisition step. "
326
+ "Please rerun the acquisition from the first step."
327
+ )
328
+ self.logger.stdout_flush()
312
329
  else:
330
+ self.logger.reset()
313
331
  self.new_acquisition(name=name, cell=cell, save_on_edit=save_on_edit)
314
332
 
315
- logger.info(self.current_filepath.basename)
333
+ self.logger.info( # pylint: disable=W1203
334
+ f"{step}:{self.current_filepath.basename}"
335
+ )
336
+
337
+ if step == 1:
338
+ utils.run_functions(self._acquisition_cell_prerun_hook)
316
339
 
317
- utils.run_functions(self._acquisition_cell_prerun_hook)
318
340
  utils.run_functions(prerun)
319
341
 
320
342
  return self
@@ -366,7 +388,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
366
388
  )
367
389
 
368
390
  filename = str(self.current_filepath) # without h5
369
- logger.info(os.path.basename(filename))
391
+ self.logger.info(os.path.basename(filename))
370
392
 
371
393
  if (
372
394
  (not self._is_old_data)
@@ -406,11 +428,12 @@ class AcquisitionAnalysisManager(AcquisitionManager):
406
428
  run_on_call=custom_lint.on_call_functions,
407
429
  )
408
430
  for var in lint_result.external_vars:
409
- logger.warning(
431
+ self.logger.warning(
410
432
  "External variable used inside the analysis code: %s", var
411
433
  )
412
434
  for error in lint_result.errors:
413
- logger.warning(error)
435
+ self.logger.warning(error)
436
+
414
437
  utils.run_functions(self._analysis_cell_prerun_hook)
415
438
  utils.run_functions(prerun)
416
439
 
@@ -535,7 +558,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
535
558
 
536
559
  res = self.find_param_in_config(param_text)
537
560
  if res is None:
538
- logger.warning(
561
+ self.logger.warning(
539
562
  "Parameter '%s' cannot be found in default config files.", param
540
563
  )
541
564
  continue
@@ -545,6 +568,57 @@ class AcquisitionAnalysisManager(AcquisitionManager):
545
568
  links += link + "<br/>"
546
569
  return display.display_html(links)
547
570
 
571
+ def display_cfg_link(
572
+ self,
573
+ parameters: Dict[str, Any],
574
+ ):
575
+ from labmate.display import html_output
576
+
577
+ links = []
578
+ for param, value in parameters.items():
579
+ param_eq = f"{param.strip()} = "
580
+ res = self.find_param_in_config(param_eq)
581
+ if res is None:
582
+ self.logger.warning(
583
+ "Parameter '%s' cannot be found in default config files.", param
584
+ )
585
+ continue
586
+ file, line_no = res
587
+ file = self._config_files_names_to_path.get(file, file)
588
+
589
+ def update_value(param, value):
590
+ self.update_config_params_on_disk({param: value})
591
+
592
+ buttons = [
593
+ display.buttons.create_button(update_value, param, value, name="Update")
594
+ ]
595
+
596
+ link = html_output.create_link_row(
597
+ link_text=f"{param} = ",
598
+ link_url=f"{file}:{line_no}",
599
+ text=str(value),
600
+ buttons=buttons, # type: ignore
601
+ )
602
+ links.append(link)
603
+ return display.display_widgets_vertically(links, class_="labmate-params")
604
+
605
+ def update_config_params_on_disk(self, params: Dict[str, Any]):
606
+ # params_per_files = {}
607
+ # for param, value in params.items():
608
+ # res = self.find_param_in_data_config(param)
609
+ # if res is None:
610
+ # raise ValueError(
611
+ # f"Parameter '{param}' cannot be found in default config files."
612
+ # )
613
+ # file, _ = res
614
+ # params_per_files.setdefault(file, {})[param] = value
615
+
616
+ for file in self.config_files:
617
+ file = self._config_files_names_to_path.get(file, file)
618
+ utils.file_read.update_file_variable(file, params)
619
+
620
+ return self
621
+
548
622
  def connect_default_widget(
549
623
  self,
550
624
  objs: Union[
@@ -1,20 +1,38 @@
1
- """Module with widgets that can be displayed with AcquisitionAnalysisManager."""
2
-
3
1
  import os
4
2
  from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
5
3
 
6
4
  from .. import display as lm_display
7
5
  from ..display import platform_utils
8
6
 
9
- # import abc
10
-
11
-
12
7
  if TYPE_CHECKING:
13
8
  from labmate.acquisition_notebook import AcquisitionAnalysisManager
14
9
 
15
10
  _T = TypeVar("_T")
16
11
 
17
12
 
13
+ def _get_filepath(aqm: "AcquisitionAnalysisManager") -> Optional[str]:
14
+ filepath = aqm.current_analysis or aqm.current_acquisition
15
+ return filepath.filepath if filepath else None
16
+
17
+
18
+ def _create_file_link(aqm: "AcquisitionAnalysisManager", level_up) -> str:
19
+ filepath = _get_filepath(aqm)
20
+ if filepath is None:
21
+ 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
28
+
29
+
30
+ def display_widgets(objs: List["WidgetProtocol"], *args, **kwargs):
31
+ """Create (with *args, **kwargs) and display a list of widgets."""
32
+ widgets = [obj.create(*args, **kwargs) for obj in objs]
33
+ lm_display.display_widgets(widgets) # type: ignore
34
+
35
+
18
36
  class WidgetProtocol(Protocol):
19
37
  """Protocol for widgets that can be displayed with AcquisitionAnalysisManager.
20
38
 
@@ -73,7 +91,7 @@ class BaseWidget:
73
91
  raise NotImplementedError("This method is not implemented for the base class.")
74
92
 
75
93
 
76
- class CopyFilePathButton(BaseWidget):
94
+ class CopyFileURLPathButton(BaseWidget):
77
95
  """Create button to copy file path to clipboard.
78
96
 
79
97
  Examples:
@@ -180,32 +198,9 @@ class OpenFinderButton(BaseWidget):
180
198
  path = path.replace("/", "\\")
181
199
  subprocess.run(["explorer", "/select,", path], shell=True)
182
200
  elif sys.platform == "darwin":
183
- subprocess.Popen(["open", "-R", path])
201
+ subprocess.Popen(["open", "-R", os.path.dirname(path)])
184
202
  else:
185
- subprocess.Popen(["nautilus", "--select", path])
203
+ subprocess.Popen(["nautilus", "--select", os.path.dirname(path)])
186
204
 
187
205
  self.widget = lm_display.buttons.create_button(open_finder, name="Open finder")
188
206
  return self.widget
189
-
190
-
191
- def _get_filepath(aqm: "AcquisitionAnalysisManager") -> Optional[str]:
192
- filepath = aqm.current_analysis or aqm.current_acquisition
193
- return filepath.filepath if filepath else None
194
-
195
-
196
- def _create_file_link(aqm: "AcquisitionAnalysisManager", level_up) -> str:
197
- filepath = _get_filepath(aqm)
198
- if filepath is None:
199
- return ""
200
- link_name = os.path.basename(filepath)
201
- link = "/".join(
202
- os.path.abspath(filepath).replace("\\", "/").split("/")[-level_up:]
203
- ).replace(" ", "%20")
204
- link = f"[{link_name}](//kyrylo-gr.github.io/h5viewer/open?url={link})"
205
- return link
206
-
207
-
208
- def display_widgets(objs: List["WidgetProtocol"], *args, **kwargs):
209
- """Create (with *args, **kwargs) and display a list of widgets."""
210
- widgets = [obj.create(*args, **kwargs) for obj in objs]
211
- lm_display.display_widgets(widgets) # type: ignore
@@ -2,9 +2,15 @@
2
2
  import importlib
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from .main import display, display_html, display_widgets, logger
5
+ from .main import (
6
+ display,
7
+ display_html,
8
+ display_widgets,
9
+ display_widgets_vertically,
10
+ logger,
11
+ )
6
12
 
7
- __all__ = ["links", "buttons", "logger"]
13
+ __all__ = ["links", "buttons", "logger", "html_output"]
8
14
 
9
15
 
10
16
  class _LazyModule:
@@ -28,7 +34,8 @@ class _LazyModule:
28
34
 
29
35
  links = _LazyModule("links")
30
36
  buttons = _LazyModule("buttons")
37
+ html_output = _LazyModule("html_output")
31
38
 
32
39
 
33
40
  if TYPE_CHECKING:
34
- from . import buttons, links
41
+ from . import buttons, html_output, links
@@ -1,5 +1,7 @@
1
1
  """This submodule contains functions that create Button widgets."""
2
+
2
3
  from typing import TypeVar
4
+
3
5
  from .main import display, widgets
4
6
 
5
7
  # from functools import wraps
@@ -69,6 +71,8 @@ def create_button(func, *args, name=None, **kwargs) -> "DisplayingButton":
69
71
  def on_button_click(_):
70
72
  func(*args, **kwargs)
71
73
 
74
+ button.style.button_color = "transparent" # type: ignore
75
+
72
76
  button.on_click(on_button_click)
73
77
  return button
74
78
 
@@ -1,5 +1,9 @@
1
1
  """This submodule contains functions that create different html."""
2
2
 
3
+ from typing import List, Optional
4
+
5
+ from .main import widgets
6
+
3
7
 
4
8
  def display_warning(text: str):
5
9
  """Display div warning block with `text`.
@@ -13,3 +17,47 @@ def display_warning(text: str):
13
17
  >{text}</div>"""
14
18
 
15
19
  display_html(str(html))
20
+
21
+
22
+ def create_link_row(
23
+ link_text: str,
24
+ link_url: str,
25
+ text: str,
26
+ buttons: Optional[List[widgets.Button]] = None,
27
+ ):
28
+
29
+ # Create the HTML link with custom styling
30
+ link_widget = widgets.HTML(
31
+ value=f'<a href="{link_url}" target="_blank" onclick="return false;">{link_text}</a>'
32
+ )
33
+
34
+ # Create the text with custom styling
35
+ text_widget = widgets.HTML(
36
+ value=f'<span style="padding: 0 10px;">{text}</span>',
37
+ layout=widgets.Layout(background_color="transparent"),
38
+ )
39
+
40
+ custom_css = """
41
+ <style>
42
+ .cell-output-ipywidget-background:has(.labmate-params) {
43
+ background: transparent !important;
44
+ }
45
+ .labmate-params, .labmate-params * {
46
+ color: inherit !important;
47
+ }
48
+ .labmate-params a {
49
+ text-decoration: underline;
50
+ color: blue;
51
+
52
+ }
53
+
54
+ </style>
55
+ """
56
+ buttons = buttons or []
57
+
58
+ hbox = widgets.HBox(
59
+ [widgets.HTML(custom_css), link_widget, text_widget, *buttons],
60
+ layout=widgets.Layout(background_color="transparent"),
61
+ )
62
+ hbox.add_class("labmate-params")
63
+ return hbox
labmate/display/main.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """This submodule contains functions that help to display content in IPython."""
2
+
2
3
  import logging
3
4
  import sys
4
5
  from typing import Callable, List
5
6
 
6
-
7
7
  logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARNING)
8
8
  logger = logging.getLogger(__name__)
9
9
  handler = logging.StreamHandler()
@@ -18,9 +18,9 @@ try:
18
18
  if "pytest" in sys.modules:
19
19
  raise ImportError
20
20
 
21
- from IPython.core.display import HTML # type: ignore
22
- from IPython.core import display # type: ignore
23
21
  import ipywidgets as widgets # pylint: disable=W0611 # type: ignore
22
+ from IPython.core import display # type: ignore
23
+ from IPython.core.display import HTML # type: ignore
24
24
 
25
25
  display = display.display_functions.display
26
26
 
@@ -49,9 +49,17 @@ except ImportError:
49
49
  def __init__(self, lst: list) -> None: # noqa: D107
50
50
  pass
51
51
 
52
+ class VBox: # noqa: D106
53
+ def __init__(self, lst: list) -> None: # noqa: D107
54
+ pass
55
+
52
56
  class CoreWidget: # noqa: D106
53
57
  pass
54
58
 
59
+ class Layout:
60
+ def __init__(self, *args, **kwargs) -> None:
61
+ del args, kwargs
62
+
55
63
  # pylint: enable=C0115, C0103, R0903
56
64
 
57
65
 
@@ -64,3 +72,11 @@ def display_widgets(objs: List[widgets.CoreWidget]):
64
72
  """Display the given list of widgets in a HBox ."""
65
73
  button_row = widgets.HBox(objs)
66
74
  display(button_row)
75
+
76
+
77
+ def display_widgets_vertically(objs: List[widgets.CoreWidget], class_: str = ""):
78
+ """Display the given list of widgets in a VBox ."""
79
+ button_row = widgets.VBox(objs)
80
+ if class_:
81
+ button_row.add_class(class_) # type: ignore
82
+ display(button_row)
@@ -0,0 +1,79 @@
1
+ import io
2
+ import logging
3
+ import sys
4
+
5
+
6
+ class BufferCatcher(io.StringIO):
7
+ _last_value = None
8
+
9
+ @property
10
+ def last_value(self):
11
+ if self._last_value is None:
12
+ return self.getvalue()
13
+ return self._last_value
14
+
15
+ def close(self) -> None:
16
+ self._last_value = self.getvalue()
17
+ return super().close()
18
+
19
+
20
+ class StreamHandler(logging.StreamHandler):
21
+ stream: io.StringIO
22
+
23
+ def __init__(self, stream=None):
24
+ if stream is None:
25
+ stream = io.StringIO()
26
+ super().__init__(stream)
27
+
28
+ def reset(self):
29
+ self.stream.close()
30
+ stream = io.StringIO()
31
+ self.setStream(stream)
32
+
33
+
34
+ class Logger(logging.Logger):
35
+ stdout_message: str
36
+
37
+ def __init__(self, name, level=logging.NOTSET):
38
+ super().__init__(name, level)
39
+
40
+ self.logger_handler = StreamHandler()
41
+ logger_formatter = logging.Formatter(
42
+ "%(name)s:%(filename)s:%(asctime)s:\n%(levelname)s:%(message)s"
43
+ )
44
+ self.logger_handler.setFormatter(logger_formatter)
45
+ self.logger_handler.setLevel(logging.DEBUG)
46
+ self.addHandler(self.logger_handler)
47
+
48
+ logger_handler_short = logging.StreamHandler()
49
+ logger_formatter_short = logging.Formatter("%(levelname)s:%(message)s")
50
+ logger_handler_short.setFormatter(logger_formatter_short)
51
+ logger_handler_short.setLevel(logging.INFO)
52
+ self.addHandler(logger_handler_short)
53
+
54
+ self.propagate = False
55
+ self.stdout_buffer = BufferCatcher()
56
+ self.stdout_message = ""
57
+
58
+ def reset(self):
59
+ self.logger_handler.reset()
60
+
61
+ self.stdout_setup()
62
+ self.stdout_message = ""
63
+
64
+ def stdout_flush(self):
65
+ self.stdout_message += f"\n{self.stdout_buffer.last_value}"
66
+ self.stdout_setup()
67
+
68
+ def stdout_setup(self):
69
+ self.stdout_buffer = BufferCatcher()
70
+ sys.stdout._buffer = self.stdout_buffer # type: ignore # pylint: disable=protected-access
71
+
72
+ def getvalue(self):
73
+ return self.logger_handler.stream.getvalue()
74
+
75
+ def get_stdout(self):
76
+ return self.stdout_message + f"\n{self.stdout_buffer.last_value}"
77
+
78
+
79
+ logger = Logger("Labmate")
labmate/utils/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
- from .random_utils import * # noqa
1
+ from . import file_read # noqa
2
2
  from . import title_parsing # noqa
3
+ from .random_utils import * # noqa
@@ -1,6 +1,10 @@
1
1
  """Different method to read files."""
2
+
3
+ import json
2
4
  import os
3
- from typing import Dict, List
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from ..parsing.brackets_score import BracketsScore
4
8
 
5
9
 
6
10
  def read_file(file: str, /) -> str:
@@ -47,3 +51,46 @@ def read_files(files: List[str], /) -> Dict[str, str]:
47
51
  )
48
52
  configs[config_file_name] = read_file(config_file)
49
53
  return configs
54
+
55
+
56
+ def update_file_variable(file, params: Dict[str, Any]):
57
+ """
58
+ Update the variables in a file with the given parameters.
59
+
60
+ Args:
61
+ file (str): The path to the file to update.
62
+ params (Dict[str, Any]): The parameters to update the file with.
63
+ """
64
+ with open(file, "r", encoding="utf-8") as file_opened:
65
+ lines = file_opened.readlines()
66
+ brackets = BracketsScore()
67
+ current_param: Optional[str] = None
68
+ # print(lines)
69
+ for line in lines:
70
+ # print(line)
71
+ if len(line) == 0:
72
+ continue
73
+ if brackets.is_zero() and "=" in line:
74
+ param = line.split("=")[0].strip()
75
+ # print("param", param)
76
+ if param in params:
77
+ current_param = param
78
+ start_line = lines.index(line)
79
+ brackets.update_from_str(line)
80
+ if brackets.is_zero() and current_param is not None:
81
+ end_line = lines.index(line)
82
+ end_comment = line.split("#")[-1].strip() if "#" in line else None
83
+ del lines[start_line : end_line + 1]
84
+ value_str = json.JSONEncoder().encode(params[current_param])
85
+ lines.insert(
86
+ start_line,
87
+ (
88
+ f"{current_param} = {value_str}"
89
+ + (f" # {end_comment}" if end_comment is not None else "")
90
+ + "\n"
91
+ ),
92
+ )
93
+ current_param = None
94
+
95
+ with open(file, "w", encoding="utf-8") as file_opened:
96
+ file_opened.writelines(lines)
@@ -17,7 +17,11 @@ def parse_get_format(key: str) -> Tuple[str, Optional[str], Optional[str]]:
17
17
  args = key.split("__")
18
18
  if len(args) >= 3:
19
19
  return args[0], args[1], args[2]
20
- elif len(args) == 2 and len(args[1]) > 0 and args[1][0].isdigit():
20
+ elif (
21
+ len(args) == 2
22
+ and len(args[1]) > 0
23
+ and (args[1][0].isdigit() or args[1][0] in (".", "_"))
24
+ ):
21
25
  return args[0], None, args[1]
22
26
  elif len(args) == 2:
23
27
  return args[0], args[1], None
@@ -32,6 +36,23 @@ class ValueForPrint(NamedTuple):
32
36
  units: Optional[str] = None
33
37
  format: Optional[str] = None
34
38
 
39
+ def format_value(self, format_spec: Optional[str] = None) -> str:
40
+ format_spec = format_spec or self.format
41
+ if not format_spec:
42
+ return str(self.value)
43
+ if format_spec.endswith("p"):
44
+ format_spec = format_spec[:-1] + "e"
45
+ value_str = format(self.value, format_spec)
46
+ number, power = value_str.split("e")
47
+ number = number.rstrip("0_").rstrip(".") if "." in number else number
48
+ power = (
49
+ (power[0].lstrip("+0") + power[1:].lstrip("+0"))
50
+ if len(power) > 1
51
+ else power
52
+ )
53
+ return f"{number}e{power}"
54
+ return format(self.value, format_spec)
55
+
35
56
 
36
57
  def format_title(values: List[ValueForPrint], max_length: Optional[int] = None) -> str:
37
58
  """Create title out of a list of valuesForPrint.
@@ -47,11 +68,7 @@ def format_title(values: List[ValueForPrint], max_length: Optional[int] = None)
47
68
  last_line_len = 0
48
69
  for value in values:
49
70
  units = f" ({value.units})" if value.units is not None else ""
50
- value_str = (
51
- value.value
52
- if value.format is None
53
- else value.value.__format__(f".{value.format}")
54
- )
71
+ value_str = value.format_value()
55
72
  new_txt = f"{value.key} = {value_str}{units}"
56
73
  if not max_length or (
57
74
  (last_line_len + len(new_txt) < max_length) or last_line_len == 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labmate
3
- Version: 0.8.4
3
+ Version: 0.10.0
4
4
  Summary: Data management library to save data and plots to hdf5 files
5
5
  Home-page: https://github.com/kyrylo-gr/labmate
6
6
  Author: kyrylo.gr | LKB-OMQ
@@ -1,40 +1,40 @@
1
- labmate/__config__.py,sha256=vSn0Wg7zasd-lKnYyP7LRB_e4JcnIeULAsgcJKNrSgM,71
1
+ labmate/__config__.py,sha256=klTEHuAm-AhAQg9qYq-W6YyefUJiHBGfIPWnRQr-eT0,72
2
2
  labmate/__init__.py,sha256=aHQiPLldCXIJqz6wwcfyFU9sGLofUR3W5sXBIRzK2n4,182
3
3
  labmate/acquisition/__init__.py,sha256=8q3dy18lL32A9y_Du8GggpLgJqDMFcFKddHrySMavrM,269
4
- labmate/acquisition/acquisition_data.py,sha256=FUF0JTHl5_nmgPHUHFocTQEwPlxuCAVT5Xsf338jVhI,6686
4
+ labmate/acquisition/acquisition_data.py,sha256=UcpUaT1SkmnqGMyzP8cgH2JjLRtWZZdTk5S0JHf-v9c,6681
5
5
  labmate/acquisition/acquisition_loop.py,sha256=fiiseV21GB7pczHSEJrlLPiTSQ2u9y5VMDyCO7Hn0vY,11106
6
- labmate/acquisition/acquisition_manager.py,sha256=uzGCWKCcOIwKTZsKp12OY1rvyWaFBlBsBDRR7mFZwj0,10496
7
- labmate/acquisition/analysis_data.py,sha256=DF9NshXBWu74gK8b_ZOGIuveabkzbLIDXfyui1c4yK8,14695
6
+ labmate/acquisition/acquisition_manager.py,sha256=D94ahyQuzsEZdJzEUHJcN2aRwGoVYu4YDMKDCa7JnCU,10992
7
+ labmate/acquisition/analysis_data.py,sha256=svpkOp12iMhBlR7nBgCFQXiOzQtwiQ3v2wXrZNpYdmk,15711
8
8
  labmate/acquisition/analysis_loop.py,sha256=1Y8lyPkTCNwskM8DkwrMXSOt0hNmBHcWJaQjdVZ81Hs,5075
9
9
  labmate/acquisition/config_file.py,sha256=1WwqaKTM-R5xZQHqqqGUi2QCF0PC1ag2mFgPOJuEdWI,2212
10
10
  labmate/acquisition/custom_lint.py,sha256=x4vNoOnbH3A4Odu2DQVtBsuSPo5JfvRpo8_EP0EOmgM,1005
11
- labmate/acquisition/logger_setup.py,sha256=udTp-0S4cqhGdUGQlk3G3Eg51wePEGraNG-69P9fTOo,129
12
11
  labmate/acquisition_notebook/__init__.py,sha256=ZtOGQtmPqEM1IRrL-_JYo4xYA87lFQ5JY5GmKcZz9z0,251
13
- labmate/acquisition_notebook/acquisition_analysis_manager.py,sha256=PEP5gXnuB2EMazjM-aMcLt4Ws81AgqYZCgZmoD20Lo8,20105
14
- labmate/acquisition_notebook/display_widget.py,sha256=VNSo7r5nY_8biq-PAJeLvU_nzJ71kS82gFuOy-762BQ,6918
12
+ labmate/acquisition_notebook/acquisition_analysis_manager.py,sha256=cnsaIxgJc0yU1odPbqFY1VCmVVZT9Lwyyt6KWdkC4_U,22615
13
+ labmate/acquisition_notebook/display_widget.py,sha256=yS7KeZ362djhHYFldQMpz8keVT7G2MgHsCmGMlXT81s,6858
15
14
  labmate/attrdict/__init__.py,sha256=MvuZVe7j4a0HxGMxim_K2cv-dhqZOfzdeMiTX-SRgDg,58
16
15
  labmate/attrdict/attrdict_class.py,sha256=4lKXe7oZo_lLHefmf5vAOKhibWgGDffJcxMhaWLvGs4,4047
17
- labmate/display/__init__.py,sha256=tD9kCR-2VpV2Fvm7cpw4mDgqMCEXIXh_AOO5KLawxEo,844
18
- labmate/display/buttons.py,sha256=_WbtS5PVpjgH2UEaSM5MACGHqcWiCSV3xN8di-MI7hk,2861
19
- labmate/display/html_output.py,sha256=SRsrszxBxuv-sQaWEkW_fyxFbImyJVj76iMTysqxKa4,425
16
+ labmate/display/__init__.py,sha256=u6HL38LpkeiGDkcIqE4GVvsEGF8OgZ776cSOHG5dmLA,966
17
+ labmate/display/buttons.py,sha256=ReFLn5KD-KOlxjZPCoT32lt1jNMN8Xka3K35oh17ZLo,2926
18
+ labmate/display/html_output.py,sha256=aBDVzaR39PfFxchKcY9W5OpiYjIzB6t3WzNjzjubjzg,1591
20
19
  labmate/display/links.py,sha256=YgCNxowca-oHxqozGWbe4oGLzwltnZ-C0v3E5fQnK0U,746
21
- labmate/display/main.py,sha256=5M0-ckxhkCH0LXaZN6HD61b0noMypolKC9foEQmXx9w,1852
20
+ labmate/display/main.py,sha256=2bjLbRmK5H1PYhtoiyZHzisu6CyQ6AmzlFtEhFZkStg,2354
22
21
  labmate/display/platform_utils/__init__.py,sha256=GOWB9vXF-wxGZfdHQJlQWy-hy2V2KXUP3JwxgN91Fq4,1136
23
22
  labmate/display/platform_utils/windows_utils.py,sha256=4Z_avuJIZ_KoXkuRZOH2667t2wEljzNBMP6fbNDknuk,3268
23
+ labmate/logger/__init__.py,sha256=Ks4bUNO_rNTFsw0Sh3JAfK1tZq8mqHoqXHP89xpexp0,2177
24
24
  labmate/parsing/__init__.py,sha256=AHNB502jlm6PGd49_PJjvSxt97fxJeXnIfXYh8HV5x0,1312
25
25
  labmate/parsing/brackets_score.py,sha256=zzup7z6o57YUGeMr5FOSTo3nz9Z62s2omxqFV3M9MmI,988
26
26
  labmate/parsing/parsed_value.py,sha256=UYB_gCkV3DiFQRjcprnQUPw--FqYPqb3pii-PQsbHf0,5261
27
27
  labmate/parsing/saving.py,sha256=pwCdYI9shrpKyFceRPNbPcbEfJbNQ7Xj0AMsDOr5qLA,2548
28
- labmate/utils/__init__.py,sha256=PExO1TzVGwb9dXVZ733iML2H-ZFly-dksiHLgFU1xr4,72
28
+ labmate/utils/__init__.py,sha256=g9LSaSVDFC4Uo7hbdd66aT7QewuXNRheZ8PZlMrcsNw,104
29
29
  labmate/utils/async_utils.py,sha256=mSfmpF7I3M5KePkPtoS-OcuoCkFDHPKjf-RVF0P3R48,118
30
30
  labmate/utils/autoreload.py,sha256=wKi1GgWyRu1h101OguVRpO3zQXZ8qsFj-K-1P8PKuq8,305
31
31
  labmate/utils/errors.py,sha256=ly7-JQStTKmPiMuT0w3eXFw1O8-1kpTsqZT2jebpJ-I,140
32
- labmate/utils/file_read.py,sha256=DAalvKehwEqVZjWSPhKZm8Myh6gfFA5gGT6WM0dPmyw,1536
32
+ labmate/utils/file_read.py,sha256=mRHRIyqejtDlfLuPyafEYcfKfb4bac7zHbt-DW091Vg,3155
33
33
  labmate/utils/lint.py,sha256=7llJbZUAM-ikEpmU_ZzraqOwGUuJPgk1wAf3aYMJdxg,9312
34
34
  labmate/utils/random_utils.py,sha256=ZA3gK9P-eTcd_a3BTS_ZeJI5A0GM_GXL7X3yUqnPTO4,690
35
- labmate/utils/title_parsing.py,sha256=RpbiZwuKnpIq-X5S5iS90oD8iOqocWcI27MGuB4kTLU,1988
36
- labmate-0.8.4.dist-info/LICENCE,sha256=J9XIxdJExlWYZuxhhKtk4oYILvUz8-JM0y_leRQCKUE,7488
37
- labmate-0.8.4.dist-info/METADATA,sha256=_VWg55ngbUh7P6kxlZ38iK3i89oWgIDis0PJ3EoDN4A,3210
38
- labmate-0.8.4.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
39
- labmate-0.8.4.dist-info/top_level.txt,sha256=WWAn6t2zNWsp02gRq6f5cSsGebcs-4L6HBFk0XrcY0o,8
40
- labmate-0.8.4.dist-info/RECORD,,
35
+ labmate/utils/title_parsing.py,sha256=5csdqiD6w6pzyqRon38V2WeGA00CifSrMMtoWZmk0Ok,2644
36
+ labmate-0.10.0.dist-info/LICENCE,sha256=J9XIxdJExlWYZuxhhKtk4oYILvUz8-JM0y_leRQCKUE,7488
37
+ labmate-0.10.0.dist-info/METADATA,sha256=_8Qsw4NxSoayjiPoPvdunyV-a_ovkwjxYaqThycQvVQ,3211
38
+ labmate-0.10.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
39
+ labmate-0.10.0.dist-info/top_level.txt,sha256=WWAn6t2zNWsp02gRq6f5cSsGebcs-4L6HBFk0XrcY0o,8
40
+ labmate-0.10.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,5 +0,0 @@
1
- """Setup logger for acquisition module."""
2
- import logging
3
-
4
- logger = logging.getLogger(__name__)
5
- logger.setLevel(logging.WARNING)