labmate 0.9.0__tar.gz → 0.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {labmate-0.9.0/labmate.egg-info → labmate-0.10.1}/PKG-INFO +1 -1
  2. {labmate-0.9.0 → labmate-0.10.1}/labmate/__config__.py +1 -1
  3. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/acquisition_data.py +1 -1
  4. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/acquisition_manager.py +17 -6
  5. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/analysis_data.py +4 -2
  6. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition_notebook/acquisition_analysis_manager.py +40 -19
  7. labmate-0.10.1/labmate/logger/__init__.py +79 -0
  8. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/file_read.py +10 -1
  9. {labmate-0.9.0 → labmate-0.10.1/labmate.egg-info}/PKG-INFO +1 -1
  10. {labmate-0.9.0 → labmate-0.10.1}/labmate.egg-info/SOURCES.txt +1 -1
  11. labmate-0.9.0/labmate/acquisition/logger_setup.py +0 -5
  12. {labmate-0.9.0 → labmate-0.10.1}/LICENCE +0 -0
  13. {labmate-0.9.0 → labmate-0.10.1}/MANIFEST.in +0 -0
  14. {labmate-0.9.0 → labmate-0.10.1}/README.md +0 -0
  15. {labmate-0.9.0 → labmate-0.10.1}/docs/about.md +0 -0
  16. {labmate-0.9.0 → labmate-0.10.1}/docs/acquisition_notebook.md +0 -0
  17. {labmate-0.9.0 → labmate-0.10.1}/docs/code/acquisition_loop.md +0 -0
  18. {labmate-0.9.0 → labmate-0.10.1}/docs/code/acquisition_manager.md +0 -0
  19. {labmate-0.9.0 → labmate-0.10.1}/docs/code/acquisition_notebook_manager.md +0 -0
  20. {labmate-0.9.0 → labmate-0.10.1}/docs/code/analysis_data.md +0 -0
  21. {labmate-0.9.0 → labmate-0.10.1}/docs/code/linting.md +0 -0
  22. {labmate-0.9.0 → labmate-0.10.1}/docs/code/parsing_config.md +0 -0
  23. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/acquisition_and_analysis_notebook.ipynb +0 -0
  24. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/aqm_simple_example.ipynb +0 -0
  25. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/files/cfg.py +0 -0
  26. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/files/dummy_config1.txt +0 -0
  27. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/files/dummy_config2.txt +0 -0
  28. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/files/dummy_config3.py +0 -0
  29. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/files/init_analyse.py +0 -0
  30. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/more/acquisition_and_analysis_notebook_with_magic.ipynb +0 -0
  31. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/more/h5nparray.ipynb +0 -0
  32. {labmate-0.9.0 → labmate-0.10.1}/docs/examples/more/loop_example.ipynb +0 -0
  33. {labmate-0.9.0 → labmate-0.10.1}/docs/h5nparray.md +0 -0
  34. {labmate-0.9.0 → labmate-0.10.1}/docs/index.md +0 -0
  35. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.4.0.md +0 -0
  36. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.5.0.md +0 -0
  37. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.6.0.md +0 -0
  38. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.6.1.md +0 -0
  39. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.7.0.md +0 -0
  40. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/0.8.0.md +0 -0
  41. {labmate-0.9.0 → labmate-0.10.1}/docs/releases/index.md +0 -0
  42. {labmate-0.9.0 → labmate-0.10.1}/docs/starting_guide/advanced_examples.md +0 -0
  43. {labmate-0.9.0 → labmate-0.10.1}/docs/starting_guide/fine_tune.md +0 -0
  44. {labmate-0.9.0 → labmate-0.10.1}/docs/starting_guide/first_steps.md +0 -0
  45. {labmate-0.9.0 → labmate-0.10.1}/docs/starting_guide/install.md +0 -0
  46. {labmate-0.9.0 → labmate-0.10.1}/labmate/__init__.py +0 -0
  47. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/__init__.py +0 -0
  48. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/acquisition_loop.py +0 -0
  49. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/analysis_loop.py +0 -0
  50. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/config_file.py +0 -0
  51. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition/custom_lint.py +0 -0
  52. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition_notebook/__init__.py +0 -0
  53. {labmate-0.9.0 → labmate-0.10.1}/labmate/acquisition_notebook/display_widget.py +0 -0
  54. {labmate-0.9.0 → labmate-0.10.1}/labmate/attrdict/__init__.py +0 -0
  55. {labmate-0.9.0 → labmate-0.10.1}/labmate/attrdict/attrdict_class.py +0 -0
  56. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/__init__.py +0 -0
  57. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/buttons.py +0 -0
  58. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/html_output.py +0 -0
  59. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/links.py +0 -0
  60. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/main.py +0 -0
  61. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/platform_utils/__init__.py +0 -0
  62. {labmate-0.9.0 → labmate-0.10.1}/labmate/display/platform_utils/windows_utils.py +0 -0
  63. {labmate-0.9.0 → labmate-0.10.1}/labmate/parsing/__init__.py +0 -0
  64. {labmate-0.9.0 → labmate-0.10.1}/labmate/parsing/brackets_score.py +0 -0
  65. {labmate-0.9.0 → labmate-0.10.1}/labmate/parsing/parsed_value.py +0 -0
  66. {labmate-0.9.0 → labmate-0.10.1}/labmate/parsing/saving.py +0 -0
  67. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/__init__.py +0 -0
  68. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/async_utils.py +0 -0
  69. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/autoreload.py +0 -0
  70. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/errors.py +0 -0
  71. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/lint.py +0 -0
  72. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/random_utils.py +0 -0
  73. {labmate-0.9.0 → labmate-0.10.1}/labmate/utils/title_parsing.py +0 -0
  74. {labmate-0.9.0 → labmate-0.10.1}/labmate.egg-info/dependency_links.txt +0 -0
  75. {labmate-0.9.0 → labmate-0.10.1}/labmate.egg-info/requires.txt +0 -0
  76. {labmate-0.9.0 → labmate-0.10.1}/labmate.egg-info/top_level.txt +0 -0
  77. {labmate-0.9.0 → labmate-0.10.1}/requirements-docs.txt +0 -0
  78. {labmate-0.9.0 → labmate-0.10.1}/requirements.txt +0 -0
  79. {labmate-0.9.0 → labmate-0.10.1}/setup.cfg +0 -0
  80. {labmate-0.9.0 → labmate-0.10.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labmate
3
- Version: 0.9.0
3
+ Version: 0.10.1
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,3 +1,3 @@
1
1
  """This file contains all package constants."""
2
2
 
3
- __version__ = "0.9.0"
3
+ __version__ = "0.10.1"
@@ -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
@@ -6,12 +6,11 @@ from typing import List, Literal, Optional, Protocol, Tuple, TypedDict, TypeVar,
6
6
 
7
7
  from dh5 import DH5
8
8
  from dh5.path import Path
9
- from matplotlib.backends.backend_pdf import PdfPages
10
9
 
11
10
  from .. import utils
11
+ from ..logger import logger
12
12
  from .analysis_loop import AnalysisLoop
13
13
  from .config_file import ConfigFile
14
- from .logger_setup import logger
15
14
 
16
15
  _T = TypeVar("_T", bound="AnalysisData")
17
16
 
@@ -216,6 +215,9 @@ class AnalysisData(DH5):
216
215
  else:
217
216
  if not full_fig_name.endswith(".pdf"):
218
217
  raise ValueError("Metadata can be added only to pdf files.")
218
+
219
+ from matplotlib.backends.backend_pdf import PdfPages
220
+
219
221
  pdf_fig = PdfPages(full_fig_name)
220
222
  fig.savefig(pdf_fig, format="pdf", **kwargs) # type: ignore
221
223
  metadata = metadata or {}
@@ -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,10 +319,20 @@ 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
+ )
316
336
 
317
337
  if step == 1:
318
338
  utils.run_functions(self._acquisition_cell_prerun_hook)
@@ -368,7 +388,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
368
388
  )
369
389
 
370
390
  filename = str(self.current_filepath) # without h5
371
- logger.info(os.path.basename(filename))
391
+ self.logger.info(os.path.basename(filename))
372
392
 
373
393
  if (
374
394
  (not self._is_old_data)
@@ -408,11 +428,12 @@ class AcquisitionAnalysisManager(AcquisitionManager):
408
428
  run_on_call=custom_lint.on_call_functions,
409
429
  )
410
430
  for var in lint_result.external_vars:
411
- logger.warning(
431
+ self.logger.warning(
412
432
  "External variable used inside the analysis code: %s", var
413
433
  )
414
434
  for error in lint_result.errors:
415
- logger.warning(error)
435
+ self.logger.warning(error)
436
+
416
437
  utils.run_functions(self._analysis_cell_prerun_hook)
417
438
  utils.run_functions(prerun)
418
439
 
@@ -537,7 +558,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
537
558
 
538
559
  res = self.find_param_in_config(param_text)
539
560
  if res is None:
540
- logger.warning(
561
+ self.logger.warning(
541
562
  "Parameter '%s' cannot be found in default config files.", param
542
563
  )
543
564
  continue
@@ -558,7 +579,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
558
579
  param_eq = f"{param.strip()} = "
559
580
  res = self.find_param_in_config(param_eq)
560
581
  if res is None:
561
- logger.warning(
582
+ self.logger.warning(
562
583
  "Parameter '%s' cannot be found in default config files.", param
563
584
  )
564
585
  continue
@@ -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")
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import re
5
6
  from typing import Any, Dict, List, Optional
6
7
 
7
8
  from ..parsing.brackets_score import BracketsScore
@@ -76,12 +77,20 @@ def update_file_variable(file, params: Dict[str, Any]):
76
77
  if param in params:
77
78
  current_param = param
78
79
  start_line = lines.index(line)
80
+
79
81
  brackets.update_from_str(line)
80
82
  if brackets.is_zero() and current_param is not None:
81
83
  end_line = lines.index(line)
82
- end_comment = line.split("#")[-1].strip() if "#" in line else None
84
+ end_comment = "#".join(line.split("#")[1:]).strip() if "#" in line else None
83
85
  del lines[start_line : end_line + 1]
84
86
  value_str = json.JSONEncoder().encode(params[current_param])
87
+
88
+ print("value_str", value_str)
89
+ if re.match(
90
+ r"^['\"]?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?['\"]?$", value_str
91
+ ):
92
+ value_str = value_str.replace('"', "")
93
+
85
94
  lines.insert(
86
95
  start_line,
87
96
  (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labmate
3
- Version: 0.9.0
3
+ Version: 0.10.1
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
@@ -50,7 +50,6 @@ labmate/acquisition/analysis_data.py
50
50
  labmate/acquisition/analysis_loop.py
51
51
  labmate/acquisition/config_file.py
52
52
  labmate/acquisition/custom_lint.py
53
- labmate/acquisition/logger_setup.py
54
53
  labmate/acquisition_notebook/__init__.py
55
54
  labmate/acquisition_notebook/acquisition_analysis_manager.py
56
55
  labmate/acquisition_notebook/display_widget.py
@@ -63,6 +62,7 @@ labmate/display/links.py
63
62
  labmate/display/main.py
64
63
  labmate/display/platform_utils/__init__.py
65
64
  labmate/display/platform_utils/windows_utils.py
65
+ labmate/logger/__init__.py
66
66
  labmate/parsing/__init__.py
67
67
  labmate/parsing/brackets_score.py
68
68
  labmate/parsing/parsed_value.py
@@ -1,5 +0,0 @@
1
- """Setup logger for acquisition module."""
2
- import logging
3
-
4
- logger = logging.getLogger(__name__)
5
- logger.setLevel(logging.WARNING)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes