spectre-core 0.0.1__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 (72) hide show
  1. spectre_core/__init__.py +3 -0
  2. spectre_core/cfg.py +116 -0
  3. spectre_core/chunks/__init__.py +206 -0
  4. spectre_core/chunks/base.py +160 -0
  5. spectre_core/chunks/chunk_register.py +15 -0
  6. spectre_core/chunks/factory.py +26 -0
  7. spectre_core/chunks/library/__init__.py +8 -0
  8. spectre_core/chunks/library/callisto/__init__.py +0 -0
  9. spectre_core/chunks/library/callisto/chunk.py +101 -0
  10. spectre_core/chunks/library/fixed/__init__.py +0 -0
  11. spectre_core/chunks/library/fixed/chunk.py +185 -0
  12. spectre_core/chunks/library/sweep/__init__.py +0 -0
  13. spectre_core/chunks/library/sweep/chunk.py +400 -0
  14. spectre_core/dynamic_imports.py +22 -0
  15. spectre_core/exceptions.py +17 -0
  16. spectre_core/file_handlers/base.py +94 -0
  17. spectre_core/file_handlers/configs.py +269 -0
  18. spectre_core/file_handlers/json.py +36 -0
  19. spectre_core/file_handlers/text.py +21 -0
  20. spectre_core/logging.py +222 -0
  21. spectre_core/plotting/__init__.py +5 -0
  22. spectre_core/plotting/base.py +194 -0
  23. spectre_core/plotting/factory.py +26 -0
  24. spectre_core/plotting/format.py +19 -0
  25. spectre_core/plotting/library/__init__.py +7 -0
  26. spectre_core/plotting/library/frequency_cuts/panel.py +74 -0
  27. spectre_core/plotting/library/integral_over_frequency/panel.py +34 -0
  28. spectre_core/plotting/library/spectrogram/panel.py +92 -0
  29. spectre_core/plotting/library/time_cuts/panel.py +77 -0
  30. spectre_core/plotting/panel_register.py +13 -0
  31. spectre_core/plotting/panel_stack.py +148 -0
  32. spectre_core/receivers/__init__.py +6 -0
  33. spectre_core/receivers/base.py +415 -0
  34. spectre_core/receivers/factory.py +19 -0
  35. spectre_core/receivers/library/__init__.py +7 -0
  36. spectre_core/receivers/library/rsp1a/__init__.py +0 -0
  37. spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
  38. spectre_core/receivers/library/rsp1a/gr/fixed.py +104 -0
  39. spectre_core/receivers/library/rsp1a/gr/sweep.py +129 -0
  40. spectre_core/receivers/library/rsp1a/receiver.py +68 -0
  41. spectre_core/receivers/library/rspduo/__init__.py +0 -0
  42. spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
  43. spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +110 -0
  44. spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +135 -0
  45. spectre_core/receivers/library/rspduo/receiver.py +68 -0
  46. spectre_core/receivers/library/test/__init__.py +0 -0
  47. spectre_core/receivers/library/test/gr/__init__.py +0 -0
  48. spectre_core/receivers/library/test/gr/cosine_signal_1.py +83 -0
  49. spectre_core/receivers/library/test/gr/tagged_staircase.py +93 -0
  50. spectre_core/receivers/library/test/receiver.py +174 -0
  51. spectre_core/receivers/receiver_register.py +22 -0
  52. spectre_core/receivers/validators.py +205 -0
  53. spectre_core/spectrograms/__init__.py +3 -0
  54. spectre_core/spectrograms/analytical.py +205 -0
  55. spectre_core/spectrograms/array_operations.py +77 -0
  56. spectre_core/spectrograms/spectrogram.py +461 -0
  57. spectre_core/spectrograms/transform.py +267 -0
  58. spectre_core/watchdog/__init__.py +6 -0
  59. spectre_core/watchdog/base.py +105 -0
  60. spectre_core/watchdog/event_handler_register.py +15 -0
  61. spectre_core/watchdog/factory.py +22 -0
  62. spectre_core/watchdog/library/__init__.py +10 -0
  63. spectre_core/watchdog/library/fixed/__init__.py +0 -0
  64. spectre_core/watchdog/library/fixed/event_handler.py +41 -0
  65. spectre_core/watchdog/library/sweep/event_handler.py +55 -0
  66. spectre_core/watchdog/watcher.py +50 -0
  67. spectre_core/web_fetch/callisto.py +101 -0
  68. spectre_core-0.0.1.dist-info/LICENSE +674 -0
  69. spectre_core-0.0.1.dist-info/METADATA +40 -0
  70. spectre_core-0.0.1.dist-info/RECORD +72 -0
  71. spectre_core-0.0.1.dist-info/WHEEL +5 -0
  72. spectre_core-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import os
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Optional
8
+ from warnings import warn
9
+
10
+ class BaseFileHandler(ABC):
11
+ def __init__(self,
12
+ parent_path: str,
13
+ base_file_name: str,
14
+ extension: Optional[str] = None):
15
+ self._parent_path = parent_path
16
+ self._base_file_name = base_file_name
17
+ self._extension = extension
18
+
19
+
20
+ @abstractmethod
21
+ def read(self) -> Any:
22
+ pass
23
+
24
+
25
+ @property
26
+ def parent_path(self) -> str:
27
+ return self._parent_path
28
+
29
+
30
+ @property
31
+ def base_file_name(self) -> str:
32
+ return self._base_file_name
33
+
34
+
35
+ @property
36
+ def extension(self) -> Optional[str]:
37
+ return self._extension
38
+
39
+
40
+ @property
41
+ def file_name(self) -> str:
42
+ return self._base_file_name if (self._extension is None) else f"{self._base_file_name}.{self._extension}"
43
+
44
+
45
+ @property
46
+ def file_path(self) -> str:
47
+ return os.path.join(self._parent_path, self.file_name)
48
+
49
+
50
+ @property
51
+ def exists(self) -> bool:
52
+ return os.path.exists(self.file_path)
53
+
54
+
55
+ def make_parent_path(self) -> None:
56
+ os.makedirs(self.parent_path, exist_ok=True)
57
+
58
+
59
+ def delete(self,
60
+ doublecheck_delete = True) -> None:
61
+ if not self.exists:
62
+ warn(f"{self.file_path} does not exist. No deletion taking place")
63
+ return
64
+ else:
65
+ if doublecheck_delete:
66
+ self.doublecheck_delete()
67
+ os.remove(self.file_path)
68
+
69
+
70
+ def cat(self) -> None:
71
+ print(self.read())
72
+
73
+
74
+ def _doublecheck_action(self,
75
+ action_message: str) -> None:
76
+ proceed_with_action = False
77
+ while not proceed_with_action:
78
+ user_input = input(f"{action_message} [y/n]: ").strip().lower()
79
+ if user_input == "y":
80
+ proceed_with_action = True
81
+ elif user_input == "n":
82
+ print("Operation cancelled by the user")
83
+ raise exit(1)
84
+ else:
85
+ print(f"Please enter one of [y/n], received {user_input}")
86
+ proceed_with_action = False
87
+
88
+
89
+ def doublecheck_overwrite(self) -> None:
90
+ self._doublecheck_action(action_message=f"The file '{self.file_path}' already exists. Overwrite?")
91
+
92
+
93
+ def doublecheck_delete(self) -> None:
94
+ self._doublecheck_action(action_message=f"Are you sure you would like to delete '{self.file_path}'?")
@@ -0,0 +1,269 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from typing import Any, Optional, Type, Tuple
6
+ from abc import ABC
7
+ import ast
8
+
9
+ from spectre_core.file_handlers.json import JsonHandler
10
+ from spectre_core.cfg import JSON_CONFIGS_DIR_PATH
11
+ from spectre_core.exceptions import InvalidTagError
12
+
13
+
14
+ def _unpack_param(param: str) -> list[str, str]:
15
+ """Seperate a string of the form "a=b" into a list [a,b]."""
16
+ if not param or '=' not in param:
17
+ raise ValueError(f'Invalid format: "{param}". Expected "KEY=VALUE".')
18
+ if param.startswith('=') or param.endswith('='):
19
+ raise ValueError(f'Invalid format: "{param}". Expected "KEY=VALUE".')
20
+ # remove leading and trailing whitespace.
21
+ param = param.strip()
22
+ return param.split('=', 1)
23
+
24
+
25
+ def _params_to_string_dict(params: list[str]) -> dict[str, str]:
26
+ """Converts a list with string elements of the form "a=b" into a dictionary where the key value pairs are "a": "b"."""
27
+ d = {}
28
+ for param in params:
29
+ key, value = _unpack_param(param)
30
+ d[key] = value
31
+ return d
32
+
33
+
34
+ def _convert_to_dict(v: str) -> dict:
35
+ """Evaluate literally a string containing a Python dictionary expression."""
36
+ return ast.literal_eval(v)
37
+
38
+
39
+ def _convert_to_bool(v: str) -> bool:
40
+ """Evaluate literally a string representation of a boolean as a boolean"""
41
+ v = v.lower()
42
+ if v in ('true', '1', 't', 'y', 'yes'):
43
+ return True
44
+ if v in ('false', '0', 'f', 'n', 'no'):
45
+ return False
46
+ raise ValueError(f'Cannot convert {v} to bool.')
47
+
48
+
49
+ def _convert_string_to_type(value: str,
50
+ target_type: Type) -> Any:
51
+ """Cast a string as the target type."""
52
+ if target_type == bool:
53
+ return _convert_to_bool(value)
54
+ elif target_type == dict:
55
+ return _convert_to_dict(value)
56
+ return target_type(value)
57
+
58
+
59
+ def _type_cast_string_dict(d: dict[str, str],
60
+ type_template: dict[str, Type]) -> dict[str, Any]:
61
+ """Cast the values of the input dictionary according to a type template."""
62
+ casted_d = {}
63
+ for key, value in d.items():
64
+ target_type = type_template.get(key)
65
+ if target_type is None:
66
+ raise KeyError(f'Key "{key}" not found in type template. Expected keys: {list(type_template.keys())}')
67
+ try:
68
+ casted_d[key] = _convert_string_to_type(value, target_type)
69
+ except ValueError:
70
+ raise ValueError(f'Failed to convert key "{key}" with value "{value}" to {target_type.__name__}.')
71
+ return casted_d
72
+
73
+
74
+ def _validate_keys(d: dict[str, Any],
75
+ type_template: dict[str, type],
76
+ ignore_keys: Optional[list] = None) -> None:
77
+ """Validate that the keys in the input dictionary map one-to-one to the input type template."""
78
+ if ignore_keys is None:
79
+ ignore_keys = []
80
+
81
+ type_template_keys = set(type_template.keys())
82
+ input_keys = set(d.keys())
83
+ ignore_keys = set(ignore_keys)
84
+
85
+ missing_keys = type_template_keys - input_keys
86
+ invalid_keys = input_keys - type_template_keys - ignore_keys
87
+
88
+ errors = []
89
+
90
+ if missing_keys:
91
+ errors.append(f"Missing keys: {', '.join(missing_keys)}")
92
+
93
+ if invalid_keys:
94
+ errors.append(f"Invalid keys: {', '.join(invalid_keys)}")
95
+
96
+ if errors:
97
+ raise KeyError("Key errors found! " + " ".join(errors))
98
+
99
+
100
+ def _validate_types(d: dict[str, Any],
101
+ type_template: dict[str, type],
102
+ ignore_keys: Optional[list] = None) -> None:
103
+ """Validate the types in the input dictionary are consistent with the input type template."""
104
+
105
+ if ignore_keys is None:
106
+ ignore_keys = []
107
+
108
+ for k, v in d.items():
109
+ if k in ignore_keys:
110
+ continue
111
+ expected_type = type_template[k]
112
+ if expected_type is None:
113
+ raise KeyError(f'Type not found for key "{k}" in the type template.')
114
+
115
+ if not isinstance(v, expected_type):
116
+ raise TypeError(f'Expected {expected_type} for "{k}", but got {type(v)}.')
117
+
118
+
119
+ def validate_against_type_template(d: dict[str, Any],
120
+ type_template: dict[str, type],
121
+ ignore_keys: Optional[list] = None) -> None:
122
+ """Validates the keys and values of the input dictionary, according to the input type template."""
123
+ _validate_keys(d,
124
+ type_template,
125
+ ignore_keys = ignore_keys)
126
+ _validate_types(d,
127
+ type_template,
128
+ ignore_keys = ignore_keys)
129
+
130
+
131
+ def type_cast_params(params: list[str],
132
+ type_template: dict[str, type]) -> dict[str, Any]:
133
+ d = _params_to_string_dict(params)
134
+ return _type_cast_string_dict(d,
135
+ type_template)
136
+
137
+
138
+
139
+ class SPECTREConfig(JsonHandler, ABC):
140
+ def __init__(self,
141
+ tag: str,
142
+ config_type: str,
143
+ **kwargs):
144
+ self._validate_tag(tag)
145
+ self._tag = tag
146
+ self._config_type = config_type
147
+
148
+ self._dict = None # cache
149
+ super().__init__(JSON_CONFIGS_DIR_PATH,
150
+ f"{config_type}_config_{tag}",
151
+ **kwargs)
152
+
153
+
154
+ @property
155
+ def tag(self) -> str:
156
+ return self._tag
157
+
158
+
159
+ @property
160
+ def config_type(self) -> str:
161
+ return self._config_type
162
+
163
+
164
+ @property
165
+ def dict(self) -> dict[str, Any]:
166
+ if self._dict is None:
167
+ self._dict = self.read()
168
+ return self._dict
169
+
170
+
171
+ def _validate_tag(self, tag: str) -> None:
172
+ if "_" in tag:
173
+ raise InvalidTagError(f"Tags cannot contain an underscore. Received {tag}")
174
+ if "callisto" in tag:
175
+ raise InvalidTagError(f'"callisto" cannot be a substring in a native tag. Received "{tag}"')
176
+
177
+
178
+ def __getitem__(self,
179
+ key: str) -> Any:
180
+ return self.dict[key]
181
+
182
+
183
+ def get(self,
184
+ *args,
185
+ **kwargs) -> Any:
186
+ return self.dict.get(*args,
187
+ **kwargs)
188
+
189
+
190
+ def update(self,
191
+ *args,
192
+ **kwargs) -> None:
193
+ self.dict.update(*args, **kwargs)
194
+
195
+
196
+ def items(self):
197
+ return self.dict.items()
198
+
199
+
200
+ def keys(self):
201
+ return self.dict.keys()
202
+
203
+
204
+ def values(self):
205
+ return self.dict.values()
206
+
207
+
208
+ class FitsConfig(SPECTREConfig):
209
+
210
+ type_template = {
211
+ "ORIGIN": str,
212
+ "TELESCOP": str,
213
+ "INSTRUME": str,
214
+ "OBJECT": str,
215
+ "OBS_LAT": float,
216
+ "OBS_LONG": float,
217
+ "OBS_ALT": float
218
+ }
219
+
220
+ def __init__(self,
221
+ tag: str,
222
+ **kwargs):
223
+ super().__init__(tag,
224
+ "fits",
225
+ **kwargs)
226
+
227
+ def get_create_fits_config_cmd(self,
228
+ tag: str,
229
+ as_string: bool = False) -> list[str] | str:
230
+ command_as_list = ["spectre", "create", "fits-config", "-t", tag]
231
+ for key, value in self.type_template.items():
232
+ command_as_list += ["-p"]
233
+ command_as_list += [f"{key}={value.__name__}"]
234
+ if as_string:
235
+ return " ".join(command_as_list)
236
+ else:
237
+ return command_as_list
238
+
239
+
240
+ def save_params(self,
241
+ params: list[str],
242
+ doublecheck_overwrite: bool = True
243
+ ) -> None:
244
+ d = type_cast_params(params,
245
+ self.type_template)
246
+ self.save(d,
247
+ doublecheck_overwrite = doublecheck_overwrite)
248
+
249
+
250
+ class CaptureConfig(SPECTREConfig):
251
+ def __init__(self,
252
+ tag: str,
253
+ **kwargs):
254
+ super().__init__(tag,
255
+ "capture",
256
+ **kwargs)
257
+
258
+
259
+ def get_receiver_metadata(self) -> Tuple[str, str]:
260
+
261
+ receiver_name, mode = self.get("receiver"), self.get("mode")
262
+
263
+ if receiver_name is None:
264
+ raise ValueError("Invalid capture config! Receiver name is not specified.")
265
+
266
+ if mode is None:
267
+ raise ValueError("Invalid capture config! Receiver mode is not specified.")
268
+
269
+ return receiver_name, mode
@@ -0,0 +1,36 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from typing import Any
6
+ import json
7
+
8
+ from spectre_core.file_handlers.base import BaseFileHandler
9
+
10
+ class JsonHandler(BaseFileHandler):
11
+ def __init__(self,
12
+ parent_path: str,
13
+ base_file_name: str,
14
+ extension: str = "json",
15
+ **kwargs):
16
+ super().__init__(parent_path,
17
+ base_file_name,
18
+ extension,
19
+ **kwargs)
20
+
21
+
22
+ def read(self) -> dict[str, Any]:
23
+ with open(self.file_path, 'r') as f:
24
+ return json.load(f)
25
+
26
+
27
+ def save(self,
28
+ d: dict,
29
+ doublecheck_overwrite: bool = True) -> None:
30
+ self.make_parent_path()
31
+
32
+ if self.exists and doublecheck_overwrite:
33
+ self.doublecheck_overwrite()
34
+
35
+ with open(self.file_path, 'w') as file:
36
+ json.dump(d, file, indent=4)
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from spectre_core.file_handlers.base import BaseFileHandler
6
+
7
+ class TextHandler(BaseFileHandler):
8
+ def __init__(self,
9
+ parent_path: str,
10
+ base_file_name: str,
11
+ extension: str = "txt",
12
+ **kwargs):
13
+ super().__init__(parent_path,
14
+ base_file_name,
15
+ extension,
16
+ **kwargs)
17
+
18
+
19
+ def read(self) -> dict:
20
+ with open(self.file_path, 'r') as f:
21
+ return f.read()
@@ -0,0 +1,222 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+
6
+ from logging import getLogger
7
+ _LOGGER = getLogger(__name__)
8
+
9
+ import os
10
+ import logging
11
+ from typing import Callable, Optional
12
+ import warnings
13
+ from collections import OrderedDict
14
+ from datetime import datetime
15
+
16
+ from spectre_core.file_handlers.text import TextHandler
17
+ from spectre_core.cfg import (
18
+ LOGS_DIR_PATH,
19
+ DEFAULT_DATETIME_FORMAT,
20
+ get_logs_dir_path
21
+ )
22
+
23
+ PROCESS_TYPES = [
24
+ "USER",
25
+ "WORKER"
26
+ ]
27
+
28
+
29
+ def validate_process_type(process_type: str
30
+ ) -> None:
31
+ if process_type not in PROCESS_TYPES:
32
+ raise ValueError(f"Invalid process type: {process_type}. Expected one of {PROCESS_TYPES}")
33
+
34
+
35
+ class LogHandler(TextHandler):
36
+ def __init__(self,
37
+ datetime_stamp: str,
38
+ pid: str,
39
+ process_type: str):
40
+ self._datetime_stamp = datetime_stamp
41
+ self._pid = pid
42
+ validate_process_type(process_type)
43
+ self._process_type = process_type
44
+
45
+ dt = datetime.strptime(datetime_stamp, DEFAULT_DATETIME_FORMAT)
46
+ date_dir = os.path.join(dt.strftime("%Y"), dt.strftime("%m"), dt.strftime("%d"))
47
+ parent_path = os.path.join(LOGS_DIR_PATH, date_dir)
48
+ base_file_name = f"{datetime_stamp}_{pid}_{process_type}"
49
+
50
+ super().__init__(parent_path, base_file_name, extension = "log")
51
+
52
+
53
+ @property
54
+ def datetime_stamp(self) -> str:
55
+ return self._datetime_stamp
56
+
57
+
58
+ @property
59
+ def pid(self) -> str:
60
+ return self._pid
61
+
62
+
63
+ @property
64
+ def process_type(self) -> str:
65
+ return self._process_type
66
+
67
+
68
+ class LogHandlers:
69
+ def __init__(self,
70
+ process_type: Optional[str] = None,
71
+ year: Optional[int] = None,
72
+ month: Optional[int] = None,
73
+ day: Optional[int] = None):
74
+ self._log_handler_map: dict[str, LogHandler] = OrderedDict()
75
+ self._process_type = process_type
76
+ self.set_date(year, month, day)
77
+
78
+
79
+ @property
80
+ def process_type(self) -> str:
81
+ return self._process_type
82
+
83
+
84
+ @property
85
+ def year(self) -> Optional[int]:
86
+ return self._year
87
+
88
+
89
+ @property
90
+ def month(self) -> Optional[int]:
91
+ return self._month
92
+
93
+
94
+ @property
95
+ def day(self) -> Optional[int]:
96
+ return self._day
97
+
98
+
99
+ @property
100
+ def logs_dir_path(self) -> str:
101
+ return get_logs_dir_path(self.year, self.month, self.day)
102
+
103
+
104
+ @property
105
+ def log_handler_map(self) -> dict[str, LogHandler]:
106
+ if not self._log_handler_map: # check for empty dictionary
107
+ self._update_log_handler_map()
108
+ return self._log_handler_map
109
+
110
+
111
+ @property
112
+ def log_handler_list(self) -> list[LogHandler]:
113
+ return list(self.log_handler_map.values())
114
+
115
+
116
+ @property
117
+ def num_logs(self) -> int:
118
+ return len(self.log_handler_list)
119
+
120
+
121
+ @property
122
+ def file_names(self) -> list[str]:
123
+ return list(self.log_handler_map.keys())
124
+
125
+
126
+ def set_date(self,
127
+ year: Optional[int],
128
+ month: Optional[int],
129
+ day: Optional[int]) -> None:
130
+ self._year = year
131
+ self._month = month
132
+ self._day = day
133
+ self._update_log_handler_map()
134
+
135
+
136
+ def _update_log_handler_map(self) -> None:
137
+ log_files = [f for (_, _, files) in os.walk(self.logs_dir_path) for f in files]
138
+
139
+ if not log_files:
140
+ warning_message = "No logs found, setting log map to an empty dictionary"
141
+ _LOGGER.warning(warning_message)
142
+ warnings.warn(warning_message)
143
+ return
144
+
145
+ for log_file in log_files:
146
+ file_name, _ = os.path.splitext(log_file)
147
+ log_start_time, pid, process_type = file_name.split("_")
148
+
149
+ if self.process_type and process_type != self.process_type:
150
+ continue
151
+
152
+ self._log_handler_map[file_name] = LogHandler(log_start_time, pid, process_type)
153
+
154
+ self._log_handler_map = OrderedDict(sorted(self._log_handler_map.items()))
155
+
156
+
157
+ def update(self) -> None:
158
+ """Public alias for setting log handler map"""
159
+ self._update_log_handler_map()
160
+
161
+
162
+ def __iter__(self):
163
+ yield from self.log_handler_list
164
+
165
+
166
+ def get_log_handler_from_file_name(self,
167
+ file_name: str) -> LogHandler:
168
+ # auto strip the extension if present
169
+ file_name, _ = os.path.splitext(file_name)
170
+ try:
171
+ return self.log_handler_map[file_name]
172
+ except KeyError:
173
+ raise FileNotFoundError(f"Log handler for file name '{file_name}' not found in log map")
174
+
175
+
176
+ def get_log_handler_from_pid(self,
177
+ pid: str) -> LogHandler:
178
+ for log_handler in self.log_handler_list:
179
+ if log_handler.pid == pid:
180
+ return log_handler
181
+ raise FileNotFoundError(f"Log handler for PID '{pid}' not found in log map")
182
+
183
+
184
+ def configure_root_logger(process_type: str,
185
+ level: int = logging.INFO
186
+ ) -> LogHandler:
187
+ system_datetime = datetime.now()
188
+ datetime_stamp = system_datetime.strftime(DEFAULT_DATETIME_FORMAT)
189
+ pid = os.getpid()
190
+ log_handler = LogHandler(datetime_stamp, pid, process_type)
191
+ log_handler.make_parent_path()
192
+
193
+ # configure the root logger
194
+ logger = logging.getLogger()
195
+ logger.setLevel(level)
196
+ # Remove any existing handlers to avoid duplicate logs
197
+ for handler in logger.handlers:
198
+ logger.removeHandler(handler)
199
+ # Set up file handler with specific filename
200
+ file_handler = logging.FileHandler(log_handler.file_path)
201
+ file_handler.setLevel(level)
202
+ formatter = logging.Formatter("[%(asctime)s] [%(levelname)8s] --- %(message)s (%(name)s:%(lineno)s)")
203
+ file_handler.setFormatter(formatter)
204
+ # and add it to the root logger
205
+ logger.addHandler(file_handler)
206
+
207
+ return log_handler
208
+
209
+ # Logger must be passed in to preserve context of the service function
210
+ def log_call(logger: logging.Logger
211
+ ) -> Callable:
212
+ def decorator(func: Callable) -> Callable:
213
+ def wrapper(*args, **kwargs):
214
+ try:
215
+ logger.info(f"Calling the function: {func.__name__}")
216
+ return func(*args, **kwargs)
217
+ except Exception as e:
218
+ logger.error(f"An error occurred while calling the function: {func.__name__}",
219
+ exc_info=True)
220
+ raise
221
+ return wrapper
222
+ return decorator
@@ -0,0 +1,5 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import spectre_core.plotting.library