cgse-common 2025.0.4__tar.gz → 2025.0.6__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 (39) hide show
  1. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/PKG-INFO +1 -1
  2. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/pyproject.toml +7 -3
  3. cgse_common-2025.0.6/src/cgse_common/__init__.py +0 -0
  4. {cgse_common-2025.0.4/src/egse → cgse_common-2025.0.6/src/cgse_common}/settings.yaml +4 -1
  5. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/control.py +0 -1
  6. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/decorators.py +13 -4
  7. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/env.py +223 -37
  8. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/plugin.py +68 -6
  9. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/protocol.py +1 -1
  10. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/response.py +2 -1
  11. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/services.py +4 -1
  12. cgse_common-2025.0.6/src/egse/settings.py +448 -0
  13. cgse_common-2025.0.6/src/egse/settings.yaml +5 -0
  14. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/setup.py +49 -26
  15. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/system.py +23 -17
  16. cgse_common-2025.0.4/src/egse/settings.py +0 -381
  17. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/.gitignore +0 -0
  18. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/README.md +0 -0
  19. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/bits.py +0 -0
  20. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/calibration.py +0 -0
  21. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/command.py +0 -0
  22. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/config.py +0 -0
  23. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/device.py +0 -0
  24. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/exceptions.py +0 -0
  25. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/hk.py +0 -0
  26. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/metrics.py +0 -0
  27. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/mixin.py +0 -0
  28. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/monitoring.py +0 -0
  29. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/observer.py +0 -0
  30. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/obsid.py +0 -0
  31. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/persistence.py +0 -0
  32. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/process.py +0 -0
  33. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/proxy.py +0 -0
  34. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/reload.py +0 -0
  35. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/resource.py +0 -0
  36. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/services.yaml +0 -0
  37. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/state.py +0 -0
  38. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/version.py +0 -0
  39. {cgse_common-2025.0.4 → cgse_common-2025.0.6}/src/egse/zmq_ser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgse-common
3
- Version: 2025.0.4
3
+ Version: 2025.0.6
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cgse-common"
3
- version = "2025.0.4"
3
+ version = "2025.0.6"
4
4
  description = "Software framework to support hardware testing"
5
5
  authors = [
6
6
  {name = "IVS KU Leuven"}
@@ -36,10 +36,13 @@ dependencies = [
36
36
  [project.entry-points."cgse.version"]
37
37
  cgse-common = 'egse'
38
38
 
39
+ [project.entry-points."cgse.settings"]
40
+ cgse-common = "cgse_common:settings.yaml"
41
+
39
42
  [tool.pytest.ini_options]
40
43
  pythonpath = "src"
41
44
  testpaths = ["tests"]
42
- addopts = "-ra --cov --cov-report html"
45
+ addopts = "-ra --cov --cov-branch --cov-report html"
43
46
  filterwarnings = [
44
47
  "ignore::DeprecationWarning"
45
48
  ]
@@ -47,6 +50,7 @@ filterwarnings = [
47
50
  [tool.coverage.run]
48
51
  omit = [
49
52
  "tests/*",
53
+ "conftest.py",
50
54
  ]
51
55
 
52
56
  [tool.hatch.build.targets.sdist]
@@ -59,7 +63,7 @@ exclude = [
59
63
  ]
60
64
 
61
65
  [tool.hatch.build.targets.wheel]
62
- packages = ["src/egse"]
66
+ packages = ["src/egse", "src/cgse-common"]
63
67
 
64
68
  [tool.ruff]
65
69
  line-length = 120
File without changes
@@ -1,5 +1,8 @@
1
+ PACKAGES:
2
+ CGSE_COMMON: Common classes, functions, decorators, etc. for the CGSE
3
+
1
4
  SITE:
2
- ID: XXX
5
+ ID: XXXX # The SITE ID shall be filled in by the local settings file
3
6
  SSH_SERVER: localhost # The IP address of the SSH server on your site
4
7
  SSH_PORT: 22 # The TCP/IP port on which the SSH server is listening
5
8
 
@@ -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
 
@@ -10,6 +10,8 @@ import warnings
10
10
  from typing import Callable
11
11
  from typing import Optional
12
12
 
13
+ import rich
14
+
13
15
  from egse.settings import Settings
14
16
  from egse.system import get_caller_info
15
17
 
@@ -227,7 +229,14 @@ def profile_func(output_file=None, sort_by='cumulative', lines_to_print=None, st
227
229
 
228
230
 
229
231
  def profile(func):
230
- """Print the function signature and return value"""
232
+ """
233
+ Prints the function signature and return value to stdout.
234
+
235
+ This function checks the `Settings.profiling()` value and only prints out
236
+ profiling information if this returns True.
237
+
238
+ Profiling can be activated with `Settings.set_profiling(True)`.
239
+ """
231
240
  if not hasattr(profile, "counter"):
232
241
  profile.counter = 0
233
242
 
@@ -240,10 +249,10 @@ def profile(func):
240
249
  signature = ", ".join(args_repr + kwargs_repr)
241
250
  caller = get_caller_info(level=2)
242
251
  prefix = f"PROFILE[{profile.counter}]: "
243
- _LOGGER.info(f"{prefix}Calling {func.__name__}({signature})")
244
- _LOGGER.info(f"{prefix} from {caller.filename} at {caller.lineno}.")
252
+ rich.print(f"{prefix}Calling {func.__name__}({signature})")
253
+ rich.print(f"{prefix} from {caller.filename} at {caller.lineno}.")
245
254
  value = func(*args, **kwargs)
246
- _LOGGER.info(f"{prefix}{func.__name__!r} returned {value!r}")
255
+ rich.print(f"{prefix}{func.__name__!r} returned {value!r}")
247
256
  profile.counter -= 1
248
257
  else:
249
258
  value = func(*args, **kwargs)
@@ -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_path",
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}."
@@ -385,18 +455,35 @@ def set_local_settings(path: str | Path):
385
455
  _env.set('LOCAL_SETTINGS', path)
386
456
 
387
457
 
388
- def get_local_settings() -> str:
389
- """Returns the fully qualified filename of the local settings YAML file."""
458
+ def get_local_settings_path() -> str or None:
459
+ """
460
+ Returns the fully qualified filename of the local settings YAML file. When the local settings environment
461
+ variable is not defined or is an empty string, None is returned.
462
+
463
+ Warnings:
464
+ - When the local settings environment variable is not defined, or
465
+ - when the path defined by the environment variable doesn't exist.
466
+ """
390
467
 
391
468
  local_settings = _env.get("LOCAL_SETTINGS")
392
469
 
393
- if local_settings and not Path(local_settings).exists():
470
+ if not local_settings:
471
+ warnings.warn(f"The local settings environment variable '{get_local_settings_env_name()}' "
472
+ f"is not defined or is an empty string.")
473
+ return None
474
+
475
+ if not Path(local_settings).exists():
394
476
  warnings.warn(
395
- f"The local settings '{local_settings}' doesn't exist. As a result, "
477
+ f"The local settings path '{local_settings}' doesn't exist. As a result, "
396
478
  f"the local settings for your project will not be loaded."
397
479
  )
398
480
 
399
- return local_settings or None
481
+ return local_settings
482
+
483
+
484
+ def has_conf_repo_location() -> bool:
485
+ location = _env.get("CONF_REPO_LOCATION")
486
+ return True if location else False
400
487
 
401
488
 
402
489
  def get_conf_repo_location_env_name() -> str:
@@ -405,28 +492,50 @@ def get_conf_repo_location_env_name() -> str:
405
492
  return f"{project}_CONF_REPO_LOCATION"
406
493
 
407
494
 
408
- def get_conf_repo_location() -> str:
409
- """Returns the fully qualified name of the location of the repository with configuration and calibration data."""
495
+ def get_conf_repo_location() -> str | None:
496
+ """
497
+ Returns the fully qualified name of the location of the repository with
498
+ configuration and calibration data.
499
+
500
+ Returns None if no environment variable was defined or if the location doesn't exist.
501
+ In both cases a Warning is issued.
502
+ """
410
503
 
411
504
  location = _env.get("CONF_REPO_LOCATION")
412
505
 
413
- if location and not Path(location).exists():
506
+ if location in (None, NoValue()):
507
+ warnings.warn(
508
+ f"The environment variable for the configuration data repository is "
509
+ f"not defined ({get_conf_repo_location_env_name()})."
510
+ )
511
+ return None
512
+
513
+ if not Path(location).exists():
414
514
  warnings.warn(f"The location of the configuration data repository doesn't exist: {location}.")
515
+ return None
415
516
 
416
- return location or None
517
+ return location
417
518
 
418
519
 
419
- def set_conf_repo_location(location: str):
520
+ def set_conf_repo_location(location: str | Path | None):
420
521
  """
421
522
  Sets the environment variable and the internal representation to the given value.
422
523
 
524
+ When the location is None, the environment variable will be unset and its internal
525
+ representation will be NoValue().
526
+
423
527
  Warnings:
424
528
  Issues a warning when the given location doesn't exist.
425
529
  """
426
530
 
427
531
  env_name = get_conf_repo_location_env_name()
428
532
 
429
- # Check if location exists and is readable
533
+ if location is None:
534
+ if env_name in os.environ:
535
+ del os.environ[env_name]
536
+ _env.set("CONF_REPO_LOCATION", None)
537
+ return
538
+
430
539
  if not Path(location).exists():
431
540
  warnings.warn(
432
541
  f"The location you provided for the environment variable {env_name} doesn't exist: {location}."
@@ -436,7 +545,69 @@ def set_conf_repo_location(location: str):
436
545
  _env.set('CONF_REPO_LOCATION', location)
437
546
 
438
547
 
439
- ignore_m_warning('egse.env')
548
+ def print_env():
549
+ """
550
+ Prints out the mandatory and known environment variables at the time of the
551
+ function call. The function and lineno is also printed for information.
552
+ """
553
+ col_width = 30
554
+
555
+ console = Console(width=200)
556
+
557
+ with warnings.catch_warnings():
558
+ warnings.simplefilter("ignore")
559
+ caller_info = get_caller_info(level=2)
560
+ console.print(f"[b]Environment as in {caller_info.filename}:{caller_info.lineno}[/]")
561
+ console.print(f" {'PROJECT':{col_width}s}: {get_project_name()}")
562
+ console.print(f" {'SITE_ID':{col_width}s}: {get_site_id()}")
563
+ console.print(f" {get_data_storage_location_env_name():{col_width}s}: {get_data_storage_location()}")
564
+ console.print(f" {get_log_file_location_env_name():{col_width}s}: {get_log_file_location()}")
565
+ console.print(f" {get_conf_data_location_env_name():{col_width}s}: {get_conf_data_location()}")
566
+ console.print(f" {get_conf_repo_location_env_name():{col_width}s}: {get_conf_repo_location()}")
567
+ console.print(f" {get_local_settings_env_name():{col_width}s}: {get_local_settings_path()}")
568
+
569
+
570
+ @contextlib.contextmanager
571
+ def env_var(**kwargs):
572
+ """
573
+ Context manager to run some code that need alternate settings for environment variables.
574
+ This will automatically initialize the CGSE environment upon entry and re-initialize upon exit.
575
+
576
+ NOTE: This context manager is different from the one from `egse.system` because of the CGSE environment changes.
577
+
578
+ Args:
579
+ **kwargs: dictionary with environment variables that are needed
580
+
581
+ Examples:
582
+
583
+ >>> from egse.env import env_var
584
+ >>> with env_var(PLATO_DATA_STORAGE_LOCATION="/Users/rik/data"):
585
+ ... # do stuff that needs these alternate setting
586
+ ... pass
587
+
588
+ """
589
+ saved_env = {}
590
+
591
+ for k, v in kwargs.items():
592
+ saved_env[k] = os.environ.get(k)
593
+ if v is None:
594
+ if k in os.environ:
595
+ del os.environ[k]
596
+ else:
597
+ os.environ[k] = v
598
+
599
+ initialize()
600
+
601
+ yield
602
+
603
+ for k, v in saved_env.items():
604
+ if v is None:
605
+ if k in os.environ:
606
+ del os.environ[k]
607
+ else:
608
+ os.environ[k] = v
609
+
610
+ initialize()
440
611
 
441
612
 
442
613
  def main(args: list | None = None): # pragma: no cover
@@ -517,8 +688,10 @@ def main(args: list | None = None): # pragma: no cover
517
688
  location = get_data_storage_location()
518
689
  if not Path(location).exists():
519
690
  if args.mkdir:
520
- rich.print(f" [green]⟶ Creating data storage location: {location}[/]")
691
+ rich.print(f" [green]⟶ Creating data storage location: {location} (+ daily + obs)[/]")
521
692
  Path(location).mkdir(parents=True)
693
+ (Path(location) / "daily").mkdir()
694
+ (Path(location) / "obs").mkdir()
522
695
  else:
523
696
  rich.print(" [red]⟶ ERROR: The data storage location doesn't exist![/]")
524
697
  else:
@@ -540,6 +713,16 @@ def main(args: list | None = None): # pragma: no cover
540
713
  except ValueError as exc:
541
714
  rich.print(f" get_conf_data_location() = [red]{exc}[/]")
542
715
 
716
+ try:
717
+ rich.print(f" {get_conf_repo_location() = }", flush=True, end="")
718
+ location = get_conf_repo_location()
719
+ if location is None or not Path(location).exists():
720
+ rich.print(" [red]⟶ ERROR: The configuration repository location doesn't exist![/]")
721
+ else:
722
+ rich.print()
723
+ except ValueError as exc:
724
+ rich.print(f" get_conf_repo_location() = [red]{exc}[/]")
725
+
543
726
  try:
544
727
  rich.print(f" {get_log_file_location() = }", flush=True, end="")
545
728
  location = get_log_file_location()
@@ -555,8 +738,8 @@ def main(args: list | None = None): # pragma: no cover
555
738
  rich.print(f" get_log_file_location() = [red]{exc}[/]")
556
739
 
557
740
  try:
558
- rich.print(f" {get_local_settings() = }", flush=True, end="")
559
- location = get_local_settings()
741
+ rich.print(f" {get_local_settings_path() = }", flush=True, end="")
742
+ location = get_local_settings_path()
560
743
  if location is None or not Path(location).exists():
561
744
  rich.print(" [red]⟶ ERROR: The local settings file is not defined or doesn't exist![/]")
562
745
  else:
@@ -623,6 +806,9 @@ def main(args: list | None = None): # pragma: no cover
623
806
  # PLATO_COMMON_EGSE_PATH - YES
624
807
 
625
808
 
809
+ ignore_m_warning('egse.env')
810
+
811
+
626
812
  if __name__ == "__main__":
627
813
  import sys
628
814
  main(sys.argv[1:])
@@ -1,34 +1,96 @@
1
+ """
2
+ This module provides function to load plugins and settings from entry-points.
3
+ """
4
+ __all__ = [
5
+ "load_plugins",
6
+ "get_file_infos",
7
+ "entry_points",
8
+ ]
1
9
  import logging
2
10
  import os
3
11
  import sys
4
12
  import textwrap
5
13
  import traceback
14
+ from importlib.metadata import EntryPoint
15
+ from pathlib import Path
6
16
 
7
17
  import click
8
18
  import rich
9
19
 
10
- LOGGER = logging.getLogger(__name__)
20
+ _LOGGER = logging.getLogger(__name__)
11
21
 
12
22
 
13
- def entry_points(name: str):
23
+ def entry_points(name: str) -> set[EntryPoint]:
24
+ """
25
+ Returns a set with all entry-points for the given group name.
26
+
27
+ When the name is not known as an entry-point group, an empty set will be returned.
28
+ """
29
+
14
30
  import importlib.metadata
15
31
 
16
32
  try:
17
33
  x = importlib.metadata.entry_points()[name]
18
34
  return {ep for ep in x} # use of set here to remove duplicates
19
35
  except KeyError:
20
- return []
36
+ return set()
21
37
 
22
38
 
23
39
  def load_plugins(entry_point: str) -> dict:
24
-
40
+ """
41
+ Returns a dictionary with plugins loaded. The keys are the names of the entry-points,
42
+ the values are the loaded modules or objects.
43
+ """
25
44
  eps = {}
26
45
  for ep in entry_points(entry_point):
27
46
  try:
28
47
  eps[ep.name] = ep.load()
29
48
  except Exception as exc:
30
49
  eps[ep.name] = None
31
- LOGGER.error(f"Couldn't load entry point: {exc}")
50
+ _LOGGER.error(f"Couldn't load entry point: {exc}")
51
+
52
+ return eps
53
+
54
+
55
+ def get_file_infos(entry_point: str) -> dict[str, tuple[Path, str]]:
56
+ """
57
+ Returns a dictionary with location and filename of all the entries found for
58
+ the given entry-point name.
59
+
60
+ The entry-points are interpreted as follows: <name> = "<module>:<filename>" where
61
+
62
+ - <name> is the name of the entry-point given in the pyproject.toml file
63
+ - <module> is a valid module name that can be imported and from which the location can be determined.
64
+ - <filename> is the name of the target file, e.g. a YAML file
65
+
66
+ As an example, for the `cgse-common` settings, the following entry in the `pyproject.toml`:
67
+
68
+ [project.entry-points."cgse.settings"]
69
+ cgse-common = "cgse_common:settings.yaml"
70
+
71
+ Note that the module name for this entry point has an underscore instead of a dash.
72
+
73
+ Return:
74
+ A dictionary with the entry point name as the key and a tuple (location, filename) as the value.
75
+ """
76
+ from egse.system import get_module_location
77
+
78
+ eps = dict()
79
+
80
+ for ep in entry_points(entry_point):
81
+ try:
82
+ path = get_module_location(ep.module)
83
+
84
+ if path is None:
85
+ _LOGGER.error(
86
+ f"The entry-point '{ep.name}' is ill defined. The module part doesn't exist or is a "
87
+ f"namespace. No settings are loaded for this entry-point."
88
+ )
89
+ else:
90
+ eps[ep.name] = (path, ep.attr)
91
+
92
+ except Exception as exc:
93
+ _LOGGER.error(f"The entry point '{ep.name}' is ill defined: {exc}")
32
94
 
33
95
  return eps
34
96
 
@@ -77,7 +139,7 @@ class BrokenCommand(click.Command):
77
139
  self.help = textwrap.dedent(
78
140
  f"""\
79
141
  Warning: entry point could not be loaded. Contact its author for help.
80
-
142
+
81
143
  {traceback.format_exc()}
82
144
  """
83
145
  )
@@ -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