cgse-common 2025.0.4__py3-none-any.whl → 2025.0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgse-common
3
- Version: 2025.0.4
3
+ Version: 2025.0.5
4
4
  Summary: Software framework to support hardware testing
5
5
  Author: IVS KU Leuven
6
6
  Maintainer-email: Rik Huygen <rik.huygen@kuleuven.be>, Sara Regibo <sara.regibo@kuleuven.be>
@@ -2,10 +2,10 @@ egse/bits.py,sha256=1DPcC5R-8w-KLfbr2WLx-4Ezfwm6jpRMz-E5QfQ_YKw,11138
2
2
  egse/calibration.py,sha256=DZ-LgEwamTWDukMqTr2JLCPt3M1lw6W0SfpHQfG7cXg,8989
3
3
  egse/command.py,sha256=su35PU_0Bru1Es4up1lJFps9FTQpJ7xcxl9Y4w8suxA,22880
4
4
  egse/config.py,sha256=Ib1Ddt0wFwRrioBvMs7Md-wgEEPdK3eaXCt0H7HJEow,14888
5
- egse/control.py,sha256=gM9II-BgtgJjG7NqxyY8nBtMiRD0b9azGYFeD9_WU6s,12816
5
+ egse/control.py,sha256=cH0EA5mqaS3nQc35x1Sa0o2iM2Q4_hsrak6GOdcRMMU,12793
6
6
  egse/decorators.py,sha256=M0PavrPcTwhWS6yLlEo-mOS20lcy90Mym7HUVzPNmhs,13404
7
7
  egse/device.py,sha256=SIMROwB9YdNdFowTcZp1HW_el8LWVWDJ5tDUxs58YuY,8500
8
- egse/env.py,sha256=GdoUDq5trQhSNXwMDjQlCb7Rqs7fgd_eF9ge6yDYZpw,22136
8
+ egse/env.py,sha256=RnqUb_LF_9pjrt2Vqf7b-Pa66XstiKyqgzA_HFlp7Mc,27777
9
9
  egse/exceptions.py,sha256=Tc1xqUrWxV6SXxc9xB9viK_O-GVa_SpzqZUVEhZkwzA,1801
10
10
  egse/hk.py,sha256=5WMOGlx043XmEPj7ML1TkDzA8ZaTYLD-i5OcNUxE268,31163
11
11
  egse/metrics.py,sha256=cZKMEe3OTT2uomj7vXjEl54JD0CStfEC4nCgS6U5YSM,3794
@@ -16,21 +16,21 @@ egse/obsid.py,sha256=-HPuHApZrr3Nj1J2-qqnIiE814C-gm4FSHdM2afKdRY,5883
16
16
  egse/persistence.py,sha256=Lx6LMJ1-dh8N43XF7tTM6RwD0sSETiGQ9DNqug-G-zQ,2160
17
17
  egse/plugin.py,sha256=Fd_QnABm33_dLjaj1zDgEZr3RKy-N88U5Hz5OZm9Jj0,2684
18
18
  egse/process.py,sha256=mQ2ojeL_9oE_QkMJlQDPd1290z0j2mOrGXrlrWtOtzI,16615
19
- egse/protocol.py,sha256=Psy0iOLPTgARn1VqeKtPCSKepHr_S2KW58UYwjOA6J0,23827
19
+ egse/protocol.py,sha256=G1HTU7xfS4xE1Oi1diDOTWQUTkPLyuooawWCOY6p-b0,23828
20
20
  egse/proxy.py,sha256=pMKdnF62SXm0quLoKfgvK9GFkH2mLMB5fWNrZenfqQQ,18100
21
21
  egse/reload.py,sha256=rDT0bC6RFeRhW38wSgFcxr30h8FvaKkoGp_OE-AwBB4,4388
22
22
  egse/resource.py,sha256=VoB7BVrQULT_SJ1XioDzB59-uH47nUcN-KNVLvFxiFE,15163
23
- egse/response.py,sha256=WdUUgYe1ABhPAy1HL8lVCM3Gu0Vgy3niaRi_twZuLgo,2700
24
- egse/services.py,sha256=ZgkF0Rx_PykOVHAOV1cKduJdUhuY6A4DgwjPJWRGj3U,7642
23
+ egse/response.py,sha256=WFtWftovrmmn92NW4mhJjibMtCWteZmAzNuPLVsV-lg,2716
24
+ egse/services.py,sha256=rGS_5mSUFDw8wISEU_nuS9P9_XNtHvpViD9oSH9FqKo,7709
25
25
  egse/services.yaml,sha256=p8QBF56zLI21iJ9skt65VlNz4rIqRoFfBTZxOIUZCZ4,1853
26
- egse/settings.py,sha256=iu17w0xvLhZPXImOt_C3duy2XamClQpKuP-OkCEIZVo,13481
26
+ egse/settings.py,sha256=E8i9nYyz6n931XP0BQu8xmtP3gHICBaSP4VMKL47IXs,15160
27
27
  egse/settings.yaml,sha256=M6WMtTbNqNuXsdEf-OzKz0dAxkFTRabpugmIJ2HKyZk,290
28
- egse/setup.py,sha256=KbByR_6J-deIiY53zBKEaoR3jLwCfdG-TyUo6rqCudo,42902
28
+ egse/setup.py,sha256=wFHqRQtDdkdbJeXH6hF28xUYnYhv-TYPIV-PunUoEow,43789
29
29
  egse/state.py,sha256=ekcCZu_DZKkKYn-5iWG7ij7Aif2WYMNVs5h3cia-cVc,5352
30
- egse/system.py,sha256=t2NrtTAPXbMgBxsqicGjti-b8oEmiHtQu2Ydm77q4lI,48941
30
+ egse/system.py,sha256=KCXz8eH3PNwLaB7ww3OCkK_tQoaTYhh2FEwX1mvN8RQ,48996
31
31
  egse/version.py,sha256=-EMuiSn2eEp8QJX6csmBEu1m2yGqlXHJj2Hj49w6a2k,6217
32
32
  egse/zmq_ser.py,sha256=2-nwVUBWZ3vvosKNmlWobHJrIJA2HlM3V5a63Gz2JY0,1819
33
- cgse_common-2025.0.4.dist-info/METADATA,sha256=EFTHkcjwC9ISJ4nL2xY_4Ma57lPDJS9UovA5rtm8rpI,2574
34
- cgse_common-2025.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- cgse_common-2025.0.4.dist-info/entry_points.txt,sha256=E-KaQ9NGWAP1XvLHncNxq5oa22EAf9sOpBZWphXcxiE,34
36
- cgse_common-2025.0.4.dist-info/RECORD,,
33
+ cgse_common-2025.0.5.dist-info/METADATA,sha256=RBKcR3sDE90ugMy7tQy4c28Qfqae2f6vzEkCWwQ7kjA,2574
34
+ cgse_common-2025.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
+ cgse_common-2025.0.5.dist-info/entry_points.txt,sha256=E-KaQ9NGWAP1XvLHncNxq5oa22EAf9sOpBZWphXcxiE,34
36
+ cgse_common-2025.0.5.dist-info/RECORD,,
egse/control.py CHANGED
@@ -5,7 +5,6 @@ import abc
5
5
  import logging
6
6
  import pickle
7
7
  import threading
8
- from typing import Any
9
8
 
10
9
  import zmq
11
10
 
egse/env.py CHANGED
@@ -40,32 +40,38 @@ WARNING:
40
40
  from __future__ import annotations
41
41
 
42
42
  __all__ = [
43
- "get_project_name",
44
- "get_site_id",
45
- "get_data_storage_location",
46
- "set_data_storage_location",
47
- "get_data_storage_location_env_name",
43
+ "env_var",
48
44
  "get_conf_data_location",
49
- "set_conf_data_location",
50
45
  "get_conf_data_location_env_name",
51
46
  "get_conf_repo_location",
52
- "set_conf_repo_location",
53
47
  "get_conf_repo_location_env_name",
48
+ "get_data_storage_location",
49
+ "get_data_storage_location_env_name",
50
+ "get_local_settings",
51
+ "get_local_settings_env_name",
54
52
  "get_log_file_location",
55
- "set_log_file_location",
56
53
  "get_log_file_location_env_name",
57
- "get_local_settings",
54
+ "get_project_name",
55
+ "get_site_id",
56
+ "set_conf_data_location",
57
+ "set_conf_repo_location",
58
+ "set_data_storage_location",
58
59
  "set_local_settings",
59
- "get_local_settings_env_name",
60
+ "set_log_file_location",
60
61
  ]
61
62
 
63
+ import contextlib
62
64
  import os
63
65
  import warnings
64
66
  from pathlib import Path
65
67
 
66
68
  from egse.system import all_logging_disabled
69
+ from egse.system import get_caller_info
67
70
  from egse.system import ignore_m_warning
68
71
 
72
+ from rich.console import Console
73
+ console = Console(width=100)
74
+
69
75
  # Every project shall have a PROJECT and a SITE_ID environment variable set. This variable will be used to
70
76
  # create the other environment variables that are specific to the project.
71
77
 
@@ -123,11 +129,18 @@ class _Env:
123
129
  self._env = {}
124
130
 
125
131
  def set(self, key, value):
126
- self._env[key] = value
132
+ if value is None:
133
+ if key in self._env:
134
+ del self._env[key]
135
+ else:
136
+ self._env[key] = value
127
137
 
128
138
  def get(self, key) -> str:
129
139
  return self._env.get(key, NoValue())
130
140
 
141
+ def __rich__(self):
142
+ return self._env
143
+
131
144
 
132
145
  _env = _Env()
133
146
 
@@ -159,28 +172,60 @@ def _check_no_value(var_name, value):
159
172
  """Raise a ValueError when the value for the variable is NoValue."""
160
173
  if value == NoValue():
161
174
  project = _env.get("PROJECT")
162
- env_name = var_name if var_name == "SITE_ID" else f"{project}_{var_name}"
175
+ env_name = var_name if var_name in ("PROJECT", "SITE_ID") else f"{project}_{var_name}"
163
176
  raise ValueError(
164
177
  f"The environment variable {env_name} is not set. "
165
178
  f"Please set the environment variable before proceeding."
166
179
  )
167
180
 
168
181
 
182
+ def set_default_environment(
183
+ project: str,
184
+ site_id: str,
185
+ data_storage_location: str | Path,
186
+ conf_data_location: str | Path | None = None,
187
+ conf_repo_location: str | Path | None = None,
188
+ log_file_location: str | Path | None = None,
189
+ local_settings: str | Path | None = None,
190
+ ):
191
+ set_project_name(project)
192
+ set_site_id(site_id)
193
+ set_data_storage_location(data_storage_location)
194
+ set_conf_data_location(conf_data_location)
195
+ set_conf_repo_location(conf_repo_location)
196
+ set_log_file_location(log_file_location)
197
+ set_local_settings(local_settings)
198
+
199
+
169
200
  def get_project_name():
201
+ """Get the PROJECT name. Return None when the PROJECT is not set."""
170
202
  return _env.get("PROJECT") or None
171
203
 
172
204
 
205
+ def set_project_name(name: str):
206
+ """Set the environment variable PROJECT and its internal representation."""
207
+ os.environ["PROJECT"] = name
208
+ _env.set("PROJECT", name)
209
+
210
+
173
211
  def get_site_id():
212
+ """Get the SITE_ID. Return None if the SITE_ID is not set."""
174
213
  return _env.get("SITE_ID") or None
175
214
 
176
215
 
216
+ def set_site_id(name: str):
217
+ """Set the environment variable SITE_ID and its internal representation."""
218
+ os.environ["SITE_ID"] = name
219
+ _env.set("SITE_ID", name)
220
+
221
+
177
222
  def get_data_storage_location_env_name() -> str:
178
223
  """Returns the name of the environment variable for the project."""
179
224
  project = _env.get("PROJECT")
180
225
  return f"{project}_DATA_STORAGE_LOCATION"
181
226
 
182
227
 
183
- def set_data_storage_location(location: str | Path):
228
+ def set_data_storage_location(location: str | Path | None):
184
229
  """
185
230
  Sets the environment variable and the internal representation to the given value.
186
231
 
@@ -189,14 +234,19 @@ def set_data_storage_location(location: str | Path):
189
234
  """
190
235
  env_name = get_data_storage_location_env_name()
191
236
 
192
- # Check if location exists and is readable
237
+ if location is None:
238
+ if env_name in os.environ:
239
+ del os.environ[env_name]
240
+ _env.set("DATA_STORAGE_LOCATION", None)
241
+ return
242
+
193
243
  if not Path(location).exists():
194
244
  warnings.warn(
195
245
  f"The location you provided for the environment variable {env_name} doesn't exist: {location}."
196
246
  )
197
247
 
198
- os.environ[env_name] = location
199
- _env.set('DATA_STORAGE_LOCATION', location)
248
+ os.environ[env_name] = str(location)
249
+ _env.set('DATA_STORAGE_LOCATION', str(location))
200
250
 
201
251
 
202
252
  def get_data_storage_location(site_id: str = None) -> str:
@@ -223,6 +273,9 @@ def get_data_storage_location(site_id: str = None) -> str:
223
273
  """
224
274
  global _env
225
275
 
276
+ project = _env.get("PROJECT")
277
+ _check_no_value("PROJECT", project)
278
+
226
279
  site_id = site_id or _env.get("SITE_ID")
227
280
  _check_no_value("SITE_ID", site_id)
228
281
 
@@ -240,7 +293,7 @@ def get_conf_data_location_env_name() -> str:
240
293
  return f"{project}_CONF_DATA_LOCATION"
241
294
 
242
295
 
243
- def set_conf_data_location(location: str | Path):
296
+ def set_conf_data_location(location: str | Path | None):
244
297
  """
245
298
  Sets the environment variable and the internal representation to the given value.
246
299
 
@@ -250,7 +303,12 @@ def set_conf_data_location(location: str | Path):
250
303
 
251
304
  env_name = get_conf_data_location_env_name()
252
305
 
253
- # Check if location exists and is readable
306
+ if location is None:
307
+ if env_name in os.environ:
308
+ del os.environ[env_name]
309
+ _env.set("CONF_DATA_LOCATION", None)
310
+ return
311
+
254
312
  if not Path(location).exists():
255
313
  warnings.warn(
256
314
  f"The location you provided for the environment variable {env_name} doesn't exist: {location}."
@@ -303,7 +361,7 @@ def get_log_file_location_env_name():
303
361
  return f"{project}_LOG_FILE_LOCATION"
304
362
 
305
363
 
306
- def set_log_file_location(location: str | Path):
364
+ def set_log_file_location(location: str | Path | None):
307
365
  """
308
366
  Sets the environment variable and the internal representation to the given value.
309
367
 
@@ -313,7 +371,12 @@ def set_log_file_location(location: str | Path):
313
371
 
314
372
  env_name = get_log_file_location_env_name()
315
373
 
316
- # Check if location exists and is readable
374
+ if location is None:
375
+ if env_name in os.environ:
376
+ del os.environ[env_name]
377
+ _env.set("LOG_FILE_LOCATION", None)
378
+ return
379
+
317
380
  if not Path(location).exists():
318
381
  warnings.warn(
319
382
  f"The location you provided for the environment variable {env_name} doesn't exist: {location}."
@@ -365,17 +428,24 @@ def get_local_settings_env_name() -> str:
365
428
  return f"{project}_LOCAL_SETTINGS"
366
429
 
367
430
 
368
- def set_local_settings(path: str | Path):
431
+ def set_local_settings(path: str | Path | None):
369
432
  """
370
433
  Sets the environment variable and the internal representation to the given value.
371
434
 
435
+ When the path is set to None, the environment variable will be unset.
436
+
372
437
  Warnings:
373
438
  Issues a warning when the given path doesn't exist.
374
439
  """
375
440
 
376
441
  env_name = get_local_settings_env_name()
377
442
 
378
- # Check if location exists and is readable
443
+ if path is None:
444
+ if env_name in os.environ:
445
+ del os.environ[env_name]
446
+ _env.set('LOCAL_SETTINGS', None)
447
+ return
448
+
379
449
  if not Path(path).exists():
380
450
  warnings.warn(
381
451
  f"The location you provided for the environment variable {env_name} doesn't exist: {path}."
@@ -399,34 +469,61 @@ def get_local_settings() -> str:
399
469
  return local_settings or None
400
470
 
401
471
 
472
+ def has_conf_repo_location() -> bool:
473
+ location = _env.get("CONF_REPO_LOCATION")
474
+ return True if location else False
475
+
476
+
402
477
  def get_conf_repo_location_env_name() -> str:
403
478
  """Returns the name of the environment variable for the project."""
404
479
  project = _env.get("PROJECT")
405
480
  return f"{project}_CONF_REPO_LOCATION"
406
481
 
407
482
 
408
- def get_conf_repo_location() -> str:
409
- """Returns the fully qualified name of the location of the repository with configuration and calibration data."""
483
+ def get_conf_repo_location() -> str | None:
484
+ """
485
+ Returns the fully qualified name of the location of the repository with
486
+ configuration and calibration data.
487
+
488
+ Returns None if no environment variable was defined or if the location doesn't exist.
489
+ In both cases a Warning is issued.
490
+ """
410
491
 
411
492
  location = _env.get("CONF_REPO_LOCATION")
412
493
 
413
- if location and not Path(location).exists():
494
+ if location in (None, NoValue()):
495
+ warnings.warn(
496
+ f"The environment variable for the configuration data repository is "
497
+ f"not defined ({get_conf_repo_location_env_name()})."
498
+ )
499
+ return None
500
+
501
+ if not Path(location).exists():
414
502
  warnings.warn(f"The location of the configuration data repository doesn't exist: {location}.")
503
+ return None
415
504
 
416
- return location or None
505
+ return location
417
506
 
418
507
 
419
- def set_conf_repo_location(location: str):
508
+ def set_conf_repo_location(location: str | Path | None):
420
509
  """
421
510
  Sets the environment variable and the internal representation to the given value.
422
511
 
512
+ When the location is None, the environment variable will be unset and its internal
513
+ representation will be NoValue().
514
+
423
515
  Warnings:
424
516
  Issues a warning when the given location doesn't exist.
425
517
  """
426
518
 
427
519
  env_name = get_conf_repo_location_env_name()
428
520
 
429
- # Check if location exists and is readable
521
+ if location is None:
522
+ if env_name in os.environ:
523
+ del os.environ[env_name]
524
+ _env.set("CONF_REPO_LOCATION", None)
525
+ return
526
+
430
527
  if not Path(location).exists():
431
528
  warnings.warn(
432
529
  f"The location you provided for the environment variable {env_name} doesn't exist: {location}."
@@ -436,7 +533,69 @@ def set_conf_repo_location(location: str):
436
533
  _env.set('CONF_REPO_LOCATION', location)
437
534
 
438
535
 
439
- ignore_m_warning('egse.env')
536
+ def print_env():
537
+ """
538
+ Prints out the mandatory and known environment variables at the time of the
539
+ function call. The function and lineno is also printed for information.
540
+ """
541
+ col_width = 30
542
+
543
+ console = Console(width=200)
544
+
545
+ with warnings.catch_warnings():
546
+ warnings.simplefilter("ignore")
547
+ caller_info = get_caller_info(level=2)
548
+ console.print(f"[b]Environment as in {caller_info.filename}:{caller_info.lineno}[/]")
549
+ console.print(f" {'PROJECT':{col_width}s}: {get_project_name()}")
550
+ console.print(f" {'SITE_ID':{col_width}s}: {get_site_id()}")
551
+ console.print(f" {get_data_storage_location_env_name():{col_width}s}: {get_data_storage_location()}")
552
+ console.print(f" {get_log_file_location_env_name():{col_width}s}: {get_log_file_location()}")
553
+ console.print(f" {get_conf_data_location_env_name():{col_width}s}: {get_conf_data_location()}")
554
+ console.print(f" {get_conf_repo_location_env_name():{col_width}s}: {get_conf_repo_location()}")
555
+ console.print(f" {get_local_settings_env_name():{col_width}s}: {get_local_settings()}")
556
+
557
+
558
+ @contextlib.contextmanager
559
+ def env_var(**kwargs):
560
+ """
561
+ Context manager to run some code that need alternate settings for environment variables.
562
+ This will automatically initialize the CGSE environment upon entry and re-initialize upon exit.
563
+
564
+ NOTE: This context manager is different from the one from `egse.system` because of the CGSE environment changes.
565
+
566
+ Args:
567
+ **kwargs: dictionary with environment variables that are needed
568
+
569
+ Examples:
570
+
571
+ >>> from egse.env import env_var
572
+ >>> with env_var(PLATO_DATA_STORAGE_LOCATION="/Users/rik/data"):
573
+ ... # do stuff that needs these alternate setting
574
+ ... pass
575
+
576
+ """
577
+ saved_env = {}
578
+
579
+ for k, v in kwargs.items():
580
+ saved_env[k] = os.environ.get(k)
581
+ if v is None:
582
+ if k in os.environ:
583
+ del os.environ[k]
584
+ else:
585
+ os.environ[k] = v
586
+
587
+ initialize()
588
+
589
+ yield
590
+
591
+ for k, v in saved_env.items():
592
+ if v is None:
593
+ if k in os.environ:
594
+ del os.environ[k]
595
+ else:
596
+ os.environ[k] = v
597
+
598
+ initialize()
440
599
 
441
600
 
442
601
  def main(args: list | None = None): # pragma: no cover
@@ -517,8 +676,10 @@ def main(args: list | None = None): # pragma: no cover
517
676
  location = get_data_storage_location()
518
677
  if not Path(location).exists():
519
678
  if args.mkdir:
520
- rich.print(f" [green]⟶ Creating data storage location: {location}[/]")
679
+ rich.print(f" [green]⟶ Creating data storage location: {location} (+ daily + obs)[/]")
521
680
  Path(location).mkdir(parents=True)
681
+ (Path(location) / "daily").mkdir()
682
+ (Path(location) / "obs").mkdir()
522
683
  else:
523
684
  rich.print(" [red]⟶ ERROR: The data storage location doesn't exist![/]")
524
685
  else:
@@ -540,6 +701,16 @@ def main(args: list | None = None): # pragma: no cover
540
701
  except ValueError as exc:
541
702
  rich.print(f" get_conf_data_location() = [red]{exc}[/]")
542
703
 
704
+ try:
705
+ rich.print(f" {get_conf_repo_location() = }", flush=True, end="")
706
+ location = get_conf_repo_location()
707
+ if location is None or not Path(location).exists():
708
+ rich.print(" [red]⟶ ERROR: The configuration repository location doesn't exist![/]")
709
+ else:
710
+ rich.print()
711
+ except ValueError as exc:
712
+ rich.print(f" get_conf_repo_location() = [red]{exc}[/]")
713
+
543
714
  try:
544
715
  rich.print(f" {get_log_file_location() = }", flush=True, end="")
545
716
  location = get_log_file_location()
@@ -623,6 +794,9 @@ def main(args: list | None = None): # pragma: no cover
623
794
  # PLATO_COMMON_EGSE_PATH - YES
624
795
 
625
796
 
797
+ ignore_m_warning('egse.env')
798
+
799
+
626
800
  if __name__ == "__main__":
627
801
  import sys
628
802
  main(sys.argv[1:])
egse/protocol.py CHANGED
@@ -20,7 +20,7 @@ from prometheus_client import Summary
20
20
  from egse.command import Command
21
21
  from egse.command import CommandExecution
22
22
  from egse.control import ControlServer
23
- from egse.control import Failure
23
+ from egse.response import Failure
24
24
  from egse.device import DeviceConnectionObserver
25
25
  from egse.system import format_datetime
26
26
  from egse.zmq_ser import bind_address
egse/response.py CHANGED
@@ -2,9 +2,10 @@
2
2
  This module provides functionality to handle responses from the control servers.
3
3
  """
4
4
  __all__ = [
5
- "Success",
6
5
  "Failure",
7
6
  "Message",
7
+ "Response",
8
+ "Success",
8
9
  ]
9
10
 
10
11
  from typing import Any
egse/services.py CHANGED
@@ -10,6 +10,7 @@ logging levels, or to quit the control server in a controlled way.
10
10
 
11
11
  import inspect
12
12
  import logging
13
+ from pathlib import Path
13
14
 
14
15
  from egse.command import ClientServerCommand
15
16
  from egse.control import ControlServer
@@ -22,7 +23,9 @@ from egse.zmq_ser import connect_address
22
23
 
23
24
  LOGGER = logging.getLogger(__name__)
24
25
 
25
- SERVICE_SETTINGS = Settings.load(filename="services.yaml")
26
+ HERE = Path(__file__).parent
27
+
28
+ SERVICE_SETTINGS = Settings.load(filename=str(HERE / "services.yaml"))
26
29
 
27
30
 
28
31
  class ServiceCommand(ClientServerCommand):
egse/settings.py CHANGED
@@ -8,7 +8,7 @@ settings are loaded from a file called ``settings.yaml``. The default yaml confi
8
8
  located in the same directory as this module.
9
9
 
10
10
  The YAML file is read and the configuration parameters for the given group are
11
- made available as instance variables of the returned class.
11
+ available as instance variables of the returned class.
12
12
 
13
13
  The intended use is as follows:
14
14
 
@@ -54,24 +54,27 @@ If that file is located at a specific location, also use the ``location=`` keywo
54
54
  The above code will read the complete YAML file, i.e. all the groups into a dictionary.
55
55
 
56
56
  """
57
+ from __future__ import annotations
57
58
 
58
59
  import logging
59
- import os
60
- import pathlib
61
60
  import re
61
+ from pathlib import Path
62
+ from typing import Any
62
63
 
63
64
  import yaml # This module is provided by the pip package PyYaml - pip install pyyaml
64
65
 
65
66
  from egse.env import get_local_settings
66
67
  from egse.env import get_local_settings_env_name
67
68
  from egse.exceptions import FileIsEmptyError
68
- from egse.system import AttributeDict
69
- from egse.system import get_caller_info
69
+ from egse.system import attrdict
70
+ from egse.system import get_package_location
70
71
  from egse.system import ignore_m_warning
71
72
  from egse.system import recursive_dict_update
72
73
 
73
74
  _LOGGER = logging.getLogger(__name__)
74
75
 
76
+ _HERE = Path(__file__).resolve().parent
77
+
75
78
 
76
79
  class SettingsError(Exception):
77
80
  pass
@@ -109,6 +112,107 @@ SAFE_LOADER.add_implicit_resolver(
109
112
  list(u'-+0123456789.'))
110
113
 
111
114
 
115
+ def get_settings_locations(location: str | Path = None, filename: str = "settings.yaml") -> list[Path]:
116
+
117
+ yaml_locations: set[Path] = set()
118
+
119
+ if location is None:
120
+ package_locations = get_package_location("egse") # `egse` is a namespace
121
+
122
+ for package_location in package_locations:
123
+ if (package_location / filename).exists():
124
+ yaml_locations.add(package_location)
125
+
126
+ yaml_locations.add(_HERE)
127
+ _LOGGER.debug(f"yaml_locations in Settings.load(): {yaml_locations}")
128
+
129
+ else:
130
+
131
+ package_location = Path(location).resolve()
132
+ if (package_location / filename).exists():
133
+ yaml_locations.add(package_location)
134
+ else:
135
+ _LOGGER.warning(f"No '{filename}' file found at {package_location}.")
136
+
137
+ return list(yaml_locations)
138
+
139
+
140
+ def load_global_settings(locations, filename: str = "settings.yaml", force: bool = False) -> attrdict:
141
+
142
+ global_settings = attrdict()
143
+
144
+ for yaml_location in locations:
145
+ try:
146
+ yaml_document = read_configuration_file(str(yaml_location / filename), force=force)
147
+ recursive_dict_update(global_settings, yaml_document)
148
+ except FileNotFoundError as exc:
149
+ raise SettingsError(
150
+ f"Filename {filename} not found at location {locations}."
151
+ ) from exc
152
+
153
+ return global_settings
154
+
155
+
156
+ def load_local_settings(force: bool = False):
157
+
158
+ local_settings = {}
159
+ try:
160
+ local_settings_location = get_local_settings()
161
+
162
+ if local_settings_location:
163
+ _LOGGER.debug(f"Using {local_settings_location} to update global settings.")
164
+ try:
165
+ yaml_document_local = read_configuration_file(local_settings_location, force=force)
166
+ if yaml_document_local is None:
167
+ raise FileIsEmptyError()
168
+ local_settings = attrdict(
169
+ {name: value for name, value in yaml_document_local.items()}
170
+ )
171
+ except FileNotFoundError as exc:
172
+ raise SettingsError(
173
+ f"Local settings YAML file '{local_settings_location}' not found. "
174
+ f"Check your environment variable {get_local_settings_env_name()}."
175
+ ) from exc
176
+ except FileIsEmptyError:
177
+ _LOGGER.warning(
178
+ f"Local settings YAML file '{local_settings_location}' is empty. "
179
+ f"No local settings were loaded.")
180
+
181
+ except KeyError as exc:
182
+ _LOGGER.warning(f"The environment variable {get_local_settings_env_name()} is not defined. (from "
183
+ f"{exc.__class__.__name__}: {exc})")
184
+
185
+ return local_settings
186
+
187
+
188
+ def read_configuration_file(filename: str, *, force=False):
189
+ """
190
+ Read the YAML input configuration file. The configuration file is only read
191
+ once and memoized as load optimization.
192
+
193
+ Args:
194
+ filename (str): the fully qualified filename of the YAML file
195
+ force (bool): force reloading the file
196
+
197
+ Returns:
198
+ a dictionary containing all the configuration settings from the YAML file.
199
+ """
200
+ if force or not Settings.is_memoized(filename):
201
+
202
+ _LOGGER.debug(f"Parsing YAML configuration file {filename}.")
203
+
204
+ with open(filename, "r") as stream:
205
+ try:
206
+ yaml_document = yaml.load(stream, Loader=SAFE_LOADER)
207
+ except yaml.YAMLError as exc:
208
+ _LOGGER.error(exc)
209
+ raise SettingsError(f"Error loading YAML document {filename}") from exc
210
+
211
+ Settings.add_memoized(filename, yaml_document)
212
+
213
+ return Settings.get_memoized(filename) or {}
214
+
215
+
112
216
  class Settings:
113
217
  """
114
218
  The Settings class provides a load() method that loads configuration settings for a group
@@ -116,9 +220,7 @@ class Settings:
116
220
  """
117
221
 
118
222
  __memoized_yaml = {} # Memoized settings yaml files
119
-
120
223
  __profile = False # Used for profiling methods and functions
121
- __simulation = False # Use simulation mode where applicable and possible
122
224
 
123
225
  LOG_FORMAT_DEFAULT = "%(levelname)s:%(module)s:%(lineno)d:%(message)s"
124
226
  LOG_FORMAT_FULL = "%(asctime)23s:%(levelname)8s:%(lineno)5d:%(name)-20s: %(message)s"
@@ -132,40 +234,35 @@ class Settings:
132
234
  LOG_FORMAT_DATE = "%d/%m/%Y %H:%M:%S"
133
235
 
134
236
  @classmethod
135
- def read_configuration_file(cls, filename: str, *, force=False):
136
- """
137
- Read the YAML input configuration file. The configuration file is only read
138
- once and memoized as load optimization.
139
-
140
- Args:
141
- filename (str): the fully qualified filename of the YAML file
142
- force (bool): force reloading the file
237
+ def get_memoized_locations(cls) -> list:
238
+ return list(cls.__memoized_yaml.keys())
143
239
 
144
- Returns:
145
- a dictionary containing all the configuration settings from the YAML file.
146
- """
147
- if force or filename not in cls.__memoized_yaml:
240
+ @classmethod
241
+ def is_memoized(cls, filename: str) -> bool:
242
+ return filename in cls.__memoized_yaml
148
243
 
149
- _LOGGER.debug(f"Parsing YAML configuration file {filename}.")
244
+ @classmethod
245
+ def add_memoized(cls, filename: str, yaml_document: Any):
246
+ cls.__memoized_yaml[filename] = yaml_document
150
247
 
151
- with open(filename, "r") as stream:
152
- try:
153
- yaml_document = yaml.load(stream, Loader=SAFE_LOADER)
154
- except yaml.YAMLError as exc:
155
- _LOGGER.error(exc)
156
- raise SettingsError(f"Error loading YAML document {filename}") from exc
248
+ @classmethod
249
+ def get_memoized(cls, filename: str):
250
+ return cls.__memoized_yaml.get(filename)
157
251
 
158
- cls.__memoized_yaml[filename] = yaml_document
252
+ @classmethod
253
+ def clear_memoized(cls):
254
+ cls.__memoized_yaml.clear()
159
255
 
160
- return cls.__memoized_yaml[filename]
256
+ @classmethod
257
+ def set_profiling(cls, flag):
258
+ cls.__profile = flag
161
259
 
162
260
  @classmethod
163
- def get_memoized_locations(cls):
164
- return cls.__memoized_yaml.keys()
261
+ def profiling(cls):
262
+ return cls.__profile
165
263
 
166
264
  @classmethod
167
- def load(cls, group_name=None, filename="settings.yaml", location=None, *, force=False,
168
- add_local_settings=True):
265
+ def load(cls, group_name=None, filename="settings.yaml", location=None, *, force=False, add_local_settings=True):
169
266
  """
170
267
  Load the settings for the given group from YAML configuration file.
171
268
  When no group is provided, the complete configuration is returned.
@@ -186,7 +283,7 @@ class Settings:
186
283
  Args:
187
284
  group_name (str): the name of one of the main groups from the YAML file
188
285
  filename (str): the name of the YAML file to read
189
- location (str): the path to the location of the YAML file
286
+ location (str, Path): the path to the location of the YAML file
190
287
  force (bool): force reloading the file
191
288
  add_local_settings (bool): update the Settings with site specific local settings
192
289
 
@@ -197,99 +294,58 @@ class Settings:
197
294
  a SettingsError when the group is not defined in the YAML file.
198
295
  """
199
296
 
200
- _THIS_FILE_LOCATION = pathlib.Path(__file__).resolve().parent
201
-
202
- if location is None:
203
-
204
- # Check if the yaml file is located at the location of the caller,
205
- # if not, use the file that is located where the Settings module is located.
206
-
207
- caller_dir = get_caller_info(level=2).filename
208
- caller_dir = pathlib.Path(caller_dir).resolve().parent
209
-
210
- if (caller_dir / filename).is_file():
211
- yaml_location = caller_dir
212
- else:
213
- yaml_location = _THIS_FILE_LOCATION
214
- else:
215
-
216
- # The location was given as an argument
217
-
218
- yaml_location = pathlib.Path(location).resolve()
219
-
220
- _LOGGER.debug(f"yaml_location in Settings.load(location={location}) is {yaml_location}")
297
+ # Load all detected YAML documents, these are considered global settings
221
298
 
222
- # Load the YAML global document
299
+ yaml_locations = get_settings_locations(location, filename)
300
+ global_settings = load_global_settings(yaml_locations, filename, force)
223
301
 
224
- try:
225
- yaml_document_global = cls.read_configuration_file(
226
- str(yaml_location / filename), force=force
227
- )
228
- except FileNotFoundError as exc:
229
- raise SettingsError(
230
- f"Filename {filename} not found at location {yaml_location}."
231
- ) from exc
232
-
233
- # Check if there were any groups defined in the YAML document
234
-
235
- if not yaml_document_global:
236
- raise SettingsError(f"Empty YAML document {filename} at {yaml_location}.")
302
+ if not global_settings:
303
+ raise SettingsError(f"There were no global settings defined for {filename} at {yaml_locations}.")
237
304
 
238
305
  # Load the LOCAL settings YAML file
239
306
 
240
- local_settings = {}
241
-
242
307
  if add_local_settings:
243
- try:
244
- local_settings_location = get_local_settings()
245
- if local_settings_location:
246
- _LOGGER.debug(f"Using {local_settings_location} to update global settings.")
247
- try:
248
- yaml_document_local = cls.read_configuration_file(
249
- local_settings_location, force=force
250
- )
251
- if yaml_document_local is None:
252
- raise FileIsEmptyError()
253
- local_settings = AttributeDict(
254
- {name: value for name, value in yaml_document_local.items()}
255
- )
256
- except FileNotFoundError as exc:
257
- raise SettingsError(
258
- f"Local settings YAML file '{local_settings_location}' not found. "
259
- f"Check your environment variable {get_local_settings_env_name()}."
260
- ) from exc
261
- except FileIsEmptyError:
262
- _LOGGER.warning(f"Local settings YAML file '{local_settings_location}' is empty. "
263
- f"No local settings were loaded.")
264
- except KeyError:
265
- _LOGGER.debug(f"The environment variable {get_local_settings_env_name()} is not defined.")
308
+ local_settings = load_local_settings(force)
309
+ else:
310
+ local_settings = {}
266
311
 
267
312
  if group_name in (None, ""):
268
- global_settings = AttributeDict(
269
- {name: value for name, value in yaml_document_global.items()}
313
+ global_settings = attrdict(
314
+ {name: value for name, value in global_settings.items()},
315
+ label="Settings"
270
316
  )
271
317
  if add_local_settings:
272
318
  recursive_dict_update(global_settings, local_settings)
273
319
  return global_settings
274
320
 
275
- # Check if the requested group is defined in the YAML document
321
+ if group_name in global_settings:
322
+ include_global_settings = True
323
+ else:
324
+ include_global_settings = False
325
+ if group_name in local_settings:
326
+ include_local_settings = True
327
+ else:
328
+ include_local_settings = False
276
329
 
277
- if group_name not in yaml_document_global:
330
+ if not include_global_settings and not include_local_settings:
278
331
  raise SettingsError(
279
- f"Group name '{group_name}' is not defined in the YAML "
280
- f"document '{filename}' at '{yaml_location}."
332
+ f"Group name '{group_name}' is not defined in the global nor in the local settings."
281
333
  )
282
334
 
283
335
  # Check if the group has any settings
284
336
 
285
- if not yaml_document_global[group_name]:
286
- raise SettingsError(f"Empty group in YAML document {filename} at {yaml_location}.")
337
+ if include_global_settings and not global_settings[group_name]:
338
+ _LOGGER.warning(f"Empty group in YAML document {filename} for {group_name}.")
287
339
 
288
- group_settings = AttributeDict(
289
- {name: value for name, value in yaml_document_global[group_name].items()}
290
- )
340
+ if include_global_settings:
341
+ group_settings = attrdict(
342
+ {name: value for name, value in global_settings[group_name].items()},
343
+ label=group_name
344
+ )
345
+ else:
346
+ group_settings = attrdict(label=group_name)
291
347
 
292
- if add_local_settings and group_name in local_settings:
348
+ if add_local_settings and include_local_settings:
293
349
  recursive_dict_update(group_settings, local_settings[group_name])
294
350
 
295
351
  return group_settings
@@ -312,34 +368,15 @@ class Settings:
312
368
  trunc += " ..."
313
369
  msg += f" {field}: {trunc}\n"
314
370
 
315
- return msg
316
-
317
- @classmethod
318
- def set_profiling(cls, flag):
319
- cls.__profile = flag
320
-
321
- @classmethod
322
- def profiling(cls):
323
- return cls.__profile
371
+ return msg.rstrip()
324
372
 
325
- @classmethod
326
- def set_simulation_mode(cls, flag: bool):
327
- cls.__simulation = flag
328
-
329
- @classmethod
330
- def simulation_mode(cls) -> bool:
331
- return cls.__simulation
332
-
333
-
334
- ignore_m_warning('egse.settings')
335
-
336
- if __name__ == "__main__":
337
373
 
374
+ def main(args: list | None = None): # pragma: no cover
338
375
  # We provide convenience to inspect the settings by calling this module directly from Python.
339
376
  #
340
377
  # python -m egse.settings
341
378
  #
342
- # Use the '--help' option to see the what your choices are.
379
+ # Use the '--help' option to see what your choices are.
343
380
 
344
381
  logging.basicConfig(level=20)
345
382
 
@@ -352,7 +389,10 @@ if __name__ == "__main__":
352
389
  ),
353
390
  )
354
391
  parser.add_argument("--local", action="store_true", help="print only the local settings.")
355
- args = parser.parse_args()
392
+ parser.add_argument("--global", action="store_true",
393
+ help="print only the global settings, don't include local settings.")
394
+ parser.add_argument("--group", help="print only settings for this group")
395
+ args = parser.parse_args(args or [])
356
396
 
357
397
  # The following import will activate the pretty printing of the AttributeDict
358
398
  # through the __rich__ method.
@@ -362,13 +402,20 @@ if __name__ == "__main__":
362
402
  if args.local:
363
403
  location = get_local_settings()
364
404
  if location:
405
+ location = str(Path(location).expanduser().resolve())
365
406
  settings = Settings.load(filename=location)
366
407
  print(settings)
367
408
  print(f"Loaded from [purple]{location}.")
368
409
  else:
369
410
  print("[red]No local settings defined.")
370
411
  else:
371
- settings = Settings.load()
412
+ # if the global option is given we don't want to include local settings
413
+ add_local_settings = False if vars(args)["global"] else True
414
+
415
+ if args.group:
416
+ settings = Settings.load(args.group, add_local_settings=add_local_settings)
417
+ else:
418
+ settings = Settings.load(add_local_settings=add_local_settings)
372
419
  print(settings)
373
420
  print("[blue]Memoized locations:")
374
421
  locations = Settings.get_memoized_locations()
@@ -379,3 +426,9 @@ def get_site_id() -> str:
379
426
 
380
427
  site = Settings.load("SITE")
381
428
  return site.ID
429
+
430
+
431
+ # ignore_m_warning('egse.settings')
432
+
433
+ if __name__ == "__main__":
434
+ main()
egse/setup.py CHANGED
@@ -133,6 +133,8 @@ from rich.tree import Tree
133
133
  from egse.env import get_conf_repo_location
134
134
  from egse.env import get_conf_repo_location_env_name
135
135
  from egse.env import get_data_storage_location
136
+ from egse.env import has_conf_repo_location
137
+ from egse.env import print_env
136
138
  from egse.response import Failure
137
139
  from egse.env import get_conf_data_location
138
140
  from egse.system import format_datetime
@@ -227,7 +229,11 @@ def _load_yaml(resource_name: str):
227
229
 
228
230
 
229
231
  def _load_pandas(resource_name: str, separator: str):
230
- """ Find and return the content of the given files as a pandas DataFrame object.
232
+ """
233
+ Find and return the content of the given file as a pandas DataFrame object.
234
+
235
+ The file is loaded relative from the location of the configuration data
236
+ as defined by `get_conf_data_location()`.
231
237
 
232
238
  Args:
233
239
  - resource_name: Filename, preceded by "pandas//".
@@ -336,7 +342,7 @@ class NavigableDict(dict):
336
342
 
337
343
  """
338
344
 
339
- def __init__(self, head: dict = None):
345
+ def __init__(self, head: dict = None, label: str = None):
340
346
  """
341
347
  Args:
342
348
  head (dict): the original dictionary
@@ -344,6 +350,7 @@ class NavigableDict(dict):
344
350
  head = head or {}
345
351
  super().__init__(head)
346
352
  self.__dict__["_memoized"] = {}
353
+ self.__dict__["_label"] = label
347
354
 
348
355
  # By agreement, we only want the keys to be set as attributes if all keys are strings.
349
356
  # That way we enforce that always all keys are navigable, or none.
@@ -572,7 +579,7 @@ class NavigableDict(dict):
572
579
  return msg
573
580
 
574
581
  def __rich__(self) -> Tree:
575
- tree = Tree("NavigableDict", guide_style="dim")
582
+ tree = Tree(self.__dict__["_label"] or "NavigableDict", guide_style="dim")
576
583
  walk_dict_tree(self, tree, text_style="dark grey")
577
584
  return tree
578
585
 
@@ -636,8 +643,8 @@ class Setup(NavigableDict):
636
643
  """The Setup class represents a version of the configuration of the test facility, the
637
644
  test setup and the Camera Under Test (CUT)."""
638
645
 
639
- def __init__(self, nav_dict: NavigableDict | dict = None):
640
- super().__init__(nav_dict or {})
646
+ def __init__(self, nav_dict: NavigableDict | dict = None, label: str = None):
647
+ super().__init__(nav_dict or {}, label=label)
641
648
 
642
649
  @staticmethod
643
650
  def from_dict(my_dict):
@@ -651,7 +658,7 @@ class Setup(NavigableDict):
651
658
  >>> assert setup["ID"] == setup.ID == "my-setup-001"
652
659
 
653
660
  """
654
- return Setup(my_dict)
661
+ return Setup(my_dict, label="Setup")
655
662
 
656
663
  @staticmethod
657
664
  def from_yaml_string(yaml_content: str = None):
@@ -674,15 +681,16 @@ class Setup(NavigableDict):
674
681
  if "Setup" in setup_dict:
675
682
  setup_dict = setup_dict["Setup"]
676
683
 
677
- return Setup(setup_dict)
684
+ return Setup(setup_dict, label="Setup")
678
685
 
679
686
  @staticmethod
680
687
  @lru_cache
681
- def from_yaml_file(filename: Union[str, Path] = None):
688
+ def from_yaml_file(filename: Union[str, Path] = None, add_local_settings: bool = True):
682
689
  """Loads a Setup from the given YAML file.
683
690
 
684
691
  Args:
685
692
  filename (str): the path of the YAML file to be loaded
693
+ add_local_settings (bool): if local settings shall be loaded and override the settings from the YAML file.
686
694
 
687
695
  Returns:
688
696
  a Setup that was loaded from the given location.
@@ -692,9 +700,11 @@ class Setup(NavigableDict):
692
700
  if not filename:
693
701
  raise ValueError("Invalid argument to function: No filename or None given.")
694
702
 
695
- setup_dict = Settings.load("Setup", filename=filename, force=True)
703
+ # MODULE_LOGGER.info(f"Loading {filename}...")
696
704
 
697
- setup = Setup(setup_dict)
705
+ setup_dict = Settings.load("Setup", filename=filename, force=True, add_local_settings=add_local_settings)
706
+
707
+ setup = Setup(setup_dict, label="Setup")
698
708
  setup.set_private_attribute("_filename", filename)
699
709
  if setup_id := _parse_filename_for_setup_id(str(filename)):
700
710
  setup.set_private_attribute("_setup_id", setup_id)
@@ -911,18 +921,15 @@ def get_setup(setup_id: int = None):
911
921
 
912
922
  def _check_conditions_for_get_path_of_setup_file(site_id: str) -> Path:
913
923
  """
914
- Check some pre-conditions that need to be met before we try to determine the file path for
915
- the requested Setup file.
924
+ Check some pre-conditions that need to be met before we try to determine the
925
+ file path for the requested Setup file.
916
926
 
917
927
  The following checks are performed:
918
928
 
919
- * if the environment variable 'PLATO_CONF_REPO_LOCATION' is set
920
-
929
+ * if the environment variable '{PROJECT}_CONF_REPO_LOCATION' is set
921
930
  * if the directory specified in the env variable actually exists
922
-
923
931
  * if the folder with the Setups exists for the given site_id
924
932
 
925
-
926
933
  Args:
927
934
  site_id (str): the name of the test house
928
935
 
@@ -939,9 +946,12 @@ def _check_conditions_for_get_path_of_setup_file(site_id: str) -> Path:
939
946
 
940
947
  if not (repo_location := get_conf_repo_location()):
941
948
  raise LookupError(
942
- f"Environment variable doesn't exist, please define {repo_location_env} and try again."
949
+ f"Environment variable doesn't exist or points to an invalid location, please (re-)define"
950
+ f" {repo_location_env} and try again."
943
951
  )
944
952
 
953
+ print_env()
954
+
945
955
  repo_location = Path(repo_location)
946
956
  setup_location = repo_location / 'data' / site_id / 'conf'
947
957
 
@@ -962,11 +972,13 @@ def _check_conditions_for_get_path_of_setup_file(site_id: str) -> Path:
962
972
 
963
973
  def get_path_of_setup_file(setup_id: int, site_id: str) -> Path:
964
974
  """
965
- Returns the Path to the last Setup file for the given site_id. The last Setup file is the file
966
- with the largest setup_id number.
975
+ Returns the Path to the last Setup file for the given site_id. The last Setup
976
+ file is the file with the largest setup_id number.
967
977
 
968
- This function needs the environment variable <PROJECT>_CONF_REPO_LOCATION to be defined as the
969
- location of the repository with configuration data on your disk.
978
+ This function needs the environment variable <PROJECT>_CONF_REPO_LOCATION to
979
+ be defined as the location of the repository with configuration data on your
980
+ disk. If the repo is not defined, the configuration data location will be used
981
+ instead.
970
982
 
971
983
  Args:
972
984
  setup_id (int): the identifier for the requested Setup
@@ -984,7 +996,10 @@ def get_path_of_setup_file(setup_id: int, site_id: str) -> Path:
984
996
 
985
997
  """
986
998
 
987
- setup_location = _check_conditions_for_get_path_of_setup_file(site_id)
999
+ if not has_conf_repo_location():
1000
+ setup_location = Path(get_conf_data_location(site_id))
1001
+ else:
1002
+ setup_location = _check_conditions_for_get_path_of_setup_file(site_id)
988
1003
 
989
1004
  if setup_id:
990
1005
  files = list(setup_location.glob(f'SETUP_{site_id}_{setup_id:05d}_*.yaml'))
egse/system.py CHANGED
@@ -28,6 +28,7 @@ import socket
28
28
  import subprocess # For executing a shell command
29
29
  import sys
30
30
  import time
31
+ import warnings
31
32
  from collections import namedtuple
32
33
  from contextlib import contextmanager
33
34
  from pathlib import Path
@@ -512,6 +513,9 @@ class AttributeDict(dict):
512
513
  return self.__class__.__name__ + f"({{{sub_msg}{', ...' if len(self) > count else ''}}})"
513
514
 
514
515
 
516
+ attrdict = AttributeDict
517
+
518
+
515
519
  def walk_dict_tree(dictionary: dict, tree: Tree, text_style: str = "green"):
516
520
  for k, v in dictionary.items():
517
521
  if isinstance(v, dict):
@@ -931,22 +935,23 @@ def env_var(**kwargs):
931
935
 
932
936
  """
933
937
  saved_env = {}
934
- try:
935
- for k, v in kwargs.items():
936
- saved_env[k] = os.environ.get(k)
937
- if v is None:
938
- if k in os.environ:
939
- del os.environ[k]
940
- else:
941
- os.environ[k] = v
942
- yield
943
- finally:
944
- for k, v in saved_env.items():
945
- if v is None:
946
- if k in os.environ:
947
- del os.environ[k]
948
- else:
949
- os.environ[k] = v
938
+
939
+ for k, v in kwargs.items():
940
+ saved_env[k] = os.environ.get(k)
941
+ if v is None:
942
+ if k in os.environ:
943
+ del os.environ[k]
944
+ else:
945
+ os.environ[k] = v
946
+
947
+ yield
948
+
949
+ for k, v in saved_env.items():
950
+ if v is None:
951
+ if k in os.environ:
952
+ del os.environ[k]
953
+ else:
954
+ os.environ[k] = v
950
955
 
951
956
 
952
957
  def filter_by_attr(elements: Iterable, **attrs) -> List:
@@ -1120,7 +1125,7 @@ def is_namespace(module) -> bool:
1120
1125
  return False
1121
1126
 
1122
1127
 
1123
- def get_package_location(module) -> List[Path]:
1128
+ def get_package_location(module: str) -> List[Path]:
1124
1129
  """
1125
1130
  Retrieves the file system locations associated with a Python package.
1126
1131
 
@@ -1153,6 +1158,7 @@ def get_package_location(module) -> List[Path]:
1153
1158
  try:
1154
1159
  module = importlib.import_module(module)
1155
1160
  except TypeError:
1161
+ warnings.warn(f"The module is not found or is not valid: {module_name}.")
1156
1162
  return []
1157
1163
 
1158
1164
  if is_namespace(module):