cgse-common 2025.0.5__py3-none-any.whl → 2025.0.7__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.
- cgse_common/__init__.py +0 -0
- cgse_common/settings.yaml +10 -0
- {cgse_common-2025.0.5.dist-info → cgse_common-2025.0.7.dist-info}/METADATA +1 -1
- {cgse_common-2025.0.5.dist-info → cgse_common-2025.0.7.dist-info}/RECORD +12 -10
- cgse_common-2025.0.7.dist-info/entry_points.txt +5 -0
- egse/decorators.py +13 -4
- egse/env.py +21 -9
- egse/plugin.py +68 -6
- egse/settings.py +168 -154
- egse/settings.yaml +4 -6
- egse/setup.py +13 -5
- cgse_common-2025.0.5.dist-info/entry_points.txt +0 -2
- {cgse_common-2025.0.5.dist-info → cgse_common-2025.0.7.dist-info}/WHEEL +0 -0
cgse_common/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
PACKAGES:
|
|
2
|
+
CGSE_COMMON: Common classes, functions, decorators, etc. for the CGSE
|
|
3
|
+
|
|
4
|
+
SITE:
|
|
5
|
+
ID: XXXX # The SITE ID shall be filled in by the local settings file
|
|
6
|
+
SSH_SERVER: localhost # The IP address of the SSH server on your site
|
|
7
|
+
SSH_PORT: 22 # The TCP/IP port on which the SSH server is listening
|
|
8
|
+
|
|
9
|
+
PROCESS:
|
|
10
|
+
METRICS_INTERVAL: 10
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
cgse_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cgse_common/settings.yaml,sha256=PS8HOoxbhwVoQ7zzDtZyhx25RZB6SGq3uXNuJTgtRIw,443
|
|
1
3
|
egse/bits.py,sha256=1DPcC5R-8w-KLfbr2WLx-4Ezfwm6jpRMz-E5QfQ_YKw,11138
|
|
2
4
|
egse/calibration.py,sha256=DZ-LgEwamTWDukMqTr2JLCPt3M1lw6W0SfpHQfG7cXg,8989
|
|
3
5
|
egse/command.py,sha256=su35PU_0Bru1Es4up1lJFps9FTQpJ7xcxl9Y4w8suxA,22880
|
|
4
6
|
egse/config.py,sha256=Ib1Ddt0wFwRrioBvMs7Md-wgEEPdK3eaXCt0H7HJEow,14888
|
|
5
7
|
egse/control.py,sha256=cH0EA5mqaS3nQc35x1Sa0o2iM2Q4_hsrak6GOdcRMMU,12793
|
|
6
|
-
egse/decorators.py,sha256=
|
|
8
|
+
egse/decorators.py,sha256=YDoFNn7Fe-voKX2iui85POciXuLs0P5rW1TNtxpejUM,13629
|
|
7
9
|
egse/device.py,sha256=SIMROwB9YdNdFowTcZp1HW_el8LWVWDJ5tDUxs58YuY,8500
|
|
8
|
-
egse/env.py,sha256=
|
|
10
|
+
egse/env.py,sha256=IzT_DYTHKlqtXDR8fWI9n7xj7KnL58UE6_mZUCdQMss,28279
|
|
9
11
|
egse/exceptions.py,sha256=Tc1xqUrWxV6SXxc9xB9viK_O-GVa_SpzqZUVEhZkwzA,1801
|
|
10
12
|
egse/hk.py,sha256=5WMOGlx043XmEPj7ML1TkDzA8ZaTYLD-i5OcNUxE268,31163
|
|
11
13
|
egse/metrics.py,sha256=cZKMEe3OTT2uomj7vXjEl54JD0CStfEC4nCgS6U5YSM,3794
|
|
@@ -14,7 +16,7 @@ egse/monitoring.py,sha256=-pwXqPoiNKzQYKQSpKddFlaPkCTJZYdxvG1d2MBN3l0,3033
|
|
|
14
16
|
egse/observer.py,sha256=6faaLHqgpOQs_oEvdBygQ5HF7mGneKJEfyQEFUFA5VY,1069
|
|
15
17
|
egse/obsid.py,sha256=-HPuHApZrr3Nj1J2-qqnIiE814C-gm4FSHdM2afKdRY,5883
|
|
16
18
|
egse/persistence.py,sha256=Lx6LMJ1-dh8N43XF7tTM6RwD0sSETiGQ9DNqug-G-zQ,2160
|
|
17
|
-
egse/plugin.py,sha256=
|
|
19
|
+
egse/plugin.py,sha256=Vuy4WkymGEt-_fibUIoVxdF1pcCS3FaXwtiYhUIdjcE,4820
|
|
18
20
|
egse/process.py,sha256=mQ2ojeL_9oE_QkMJlQDPd1290z0j2mOrGXrlrWtOtzI,16615
|
|
19
21
|
egse/protocol.py,sha256=G1HTU7xfS4xE1Oi1diDOTWQUTkPLyuooawWCOY6p-b0,23828
|
|
20
22
|
egse/proxy.py,sha256=pMKdnF62SXm0quLoKfgvK9GFkH2mLMB5fWNrZenfqQQ,18100
|
|
@@ -23,14 +25,14 @@ egse/resource.py,sha256=VoB7BVrQULT_SJ1XioDzB59-uH47nUcN-KNVLvFxiFE,15163
|
|
|
23
25
|
egse/response.py,sha256=WFtWftovrmmn92NW4mhJjibMtCWteZmAzNuPLVsV-lg,2716
|
|
24
26
|
egse/services.py,sha256=rGS_5mSUFDw8wISEU_nuS9P9_XNtHvpViD9oSH9FqKo,7709
|
|
25
27
|
egse/services.yaml,sha256=p8QBF56zLI21iJ9skt65VlNz4rIqRoFfBTZxOIUZCZ4,1853
|
|
26
|
-
egse/settings.py,sha256=
|
|
27
|
-
egse/settings.yaml,sha256=
|
|
28
|
-
egse/setup.py,sha256=
|
|
28
|
+
egse/settings.py,sha256=lmIfg3lwIuHrI8JL5qfiAaXHjblC87_W0c2mjbj-vRA,15562
|
|
29
|
+
egse/settings.yaml,sha256=mz9O2QqmiptezsMvxJRLhnC1ROwIHENX0nbnhMaXUpE,190
|
|
30
|
+
egse/setup.py,sha256=xUbESIwXhFS86xTxltrTnKALWUs-wZrkmpxvfS7oqx0,44031
|
|
29
31
|
egse/state.py,sha256=ekcCZu_DZKkKYn-5iWG7ij7Aif2WYMNVs5h3cia-cVc,5352
|
|
30
32
|
egse/system.py,sha256=KCXz8eH3PNwLaB7ww3OCkK_tQoaTYhh2FEwX1mvN8RQ,48996
|
|
31
33
|
egse/version.py,sha256=-EMuiSn2eEp8QJX6csmBEu1m2yGqlXHJj2Hj49w6a2k,6217
|
|
32
34
|
egse/zmq_ser.py,sha256=2-nwVUBWZ3vvosKNmlWobHJrIJA2HlM3V5a63Gz2JY0,1819
|
|
33
|
-
cgse_common-2025.0.
|
|
34
|
-
cgse_common-2025.0.
|
|
35
|
-
cgse_common-2025.0.
|
|
36
|
-
cgse_common-2025.0.
|
|
35
|
+
cgse_common-2025.0.7.dist-info/METADATA,sha256=JBawsbH6qJsHziZvcp34fA6bI2wMy9kxLDBmneeVXTk,2574
|
|
36
|
+
cgse_common-2025.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
37
|
+
cgse_common-2025.0.7.dist-info/entry_points.txt,sha256=MtsCYBzfuc3afzsjbdGJ-TZRSS8rhof5fI9w86_nGkQ,91
|
|
38
|
+
cgse_common-2025.0.7.dist-info/RECORD,,
|
egse/decorators.py
CHANGED
|
@@ -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
|
-
"""
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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)
|
egse/env.py
CHANGED
|
@@ -47,7 +47,7 @@ __all__ = [
|
|
|
47
47
|
"get_conf_repo_location_env_name",
|
|
48
48
|
"get_data_storage_location",
|
|
49
49
|
"get_data_storage_location_env_name",
|
|
50
|
-
"
|
|
50
|
+
"get_local_settings_path",
|
|
51
51
|
"get_local_settings_env_name",
|
|
52
52
|
"get_log_file_location",
|
|
53
53
|
"get_log_file_location_env_name",
|
|
@@ -455,18 +455,30 @@ def set_local_settings(path: str | Path | None):
|
|
|
455
455
|
_env.set('LOCAL_SETTINGS', path)
|
|
456
456
|
|
|
457
457
|
|
|
458
|
-
def
|
|
459
|
-
"""
|
|
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
|
+
"""
|
|
460
467
|
|
|
461
468
|
local_settings = _env.get("LOCAL_SETTINGS")
|
|
462
469
|
|
|
463
|
-
if
|
|
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():
|
|
464
476
|
warnings.warn(
|
|
465
|
-
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, "
|
|
466
478
|
f"the local settings for your project will not be loaded."
|
|
467
479
|
)
|
|
468
480
|
|
|
469
|
-
return local_settings
|
|
481
|
+
return local_settings
|
|
470
482
|
|
|
471
483
|
|
|
472
484
|
def has_conf_repo_location() -> bool:
|
|
@@ -552,7 +564,7 @@ def print_env():
|
|
|
552
564
|
console.print(f" {get_log_file_location_env_name():{col_width}s}: {get_log_file_location()}")
|
|
553
565
|
console.print(f" {get_conf_data_location_env_name():{col_width}s}: {get_conf_data_location()}")
|
|
554
566
|
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}: {
|
|
567
|
+
console.print(f" {get_local_settings_env_name():{col_width}s}: {get_local_settings_path()}")
|
|
556
568
|
|
|
557
569
|
|
|
558
570
|
@contextlib.contextmanager
|
|
@@ -726,8 +738,8 @@ def main(args: list | None = None): # pragma: no cover
|
|
|
726
738
|
rich.print(f" get_log_file_location() = [red]{exc}[/]")
|
|
727
739
|
|
|
728
740
|
try:
|
|
729
|
-
rich.print(f" {
|
|
730
|
-
location =
|
|
741
|
+
rich.print(f" {get_local_settings_path() = }", flush=True, end="")
|
|
742
|
+
location = get_local_settings_path()
|
|
731
743
|
if location is None or not Path(location).exists():
|
|
732
744
|
rich.print(" [red]⟶ ERROR: The local settings file is not defined or doesn't exist![/]")
|
|
733
745
|
else:
|
egse/plugin.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
)
|
egse/settings.py
CHANGED
|
@@ -3,9 +3,16 @@ The Settings class handles user and configuration settings that are provided in
|
|
|
3
3
|
a [`YAML`](http://yaml.org) file.
|
|
4
4
|
|
|
5
5
|
The idea is that settings are grouped by components or any arbitrary grouping that makes sense for
|
|
6
|
-
the application or for the user.
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
the application or for the user. Settings are also modular and provided by each package by means
|
|
7
|
+
of entry-points.The Settings class can read from different YAML files.
|
|
8
|
+
|
|
9
|
+
By default, settings are loaded from a file called `settings.yaml`, but this can be changed in the entry-point
|
|
10
|
+
definition.
|
|
11
|
+
|
|
12
|
+
The yaml configuration files are provided as entry-points by the packages that provided an entry-point
|
|
13
|
+
group 'cgse.settings'. The Settings dictionary (attrdict) is constructed from the configuration YAML
|
|
14
|
+
files from each of the packages. Settings can be overwritten by the next package configuration file.
|
|
15
|
+
So, make sure the group names in each package configuration file are unique.
|
|
9
16
|
|
|
10
17
|
The YAML file is read and the configuration parameters for the given group are
|
|
11
18
|
available as instance variables of the returned class.
|
|
@@ -23,11 +30,10 @@ The intended use is as follows:
|
|
|
23
30
|
else:
|
|
24
31
|
raise RMAPError("Attempt to access outside the RMAP memory map.")
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
The above code reads the settings from the default YAML file for a group called ``DSI``.
|
|
33
|
+
The above code reads the settings from the default YAML file for a group called `DSI`.
|
|
28
34
|
The settings will then be available as variables of the returned class, in this case
|
|
29
|
-
|
|
30
|
-
if a configuration parameter is defined like this:
|
|
35
|
+
`dsi_settings`. The returned class is and behaves also like a dictionary, so you can
|
|
36
|
+
check if a configuration parameter is defined like this:
|
|
31
37
|
|
|
32
38
|
if "DSI_FEE_IP_ADDRESS" not in dsi_settings:
|
|
33
39
|
# define the IP address of the DSI
|
|
@@ -46,12 +52,12 @@ The YAML section for the above code looks like this:
|
|
|
46
52
|
RMAP_BASE_ADDRESS: 0x00000000 # The start of the RMAP memory map managed by the FEE
|
|
47
53
|
RMAP_MEMORY_SIZE: 4096 # The size of the RMAP memory map managed by the FEE
|
|
48
54
|
|
|
49
|
-
When you want to read settings from another YAML file, specify the
|
|
50
|
-
If that file is located at a specific location, also use the
|
|
55
|
+
When you want to read settings from another YAML file, specify the `filename=` keyword.
|
|
56
|
+
If that file is located at a specific location, also use the `location=` keyword.
|
|
51
57
|
|
|
52
58
|
my_settings = Settings.load(filename="user.yaml", location="/Users/JohnDoe")
|
|
53
59
|
|
|
54
|
-
The above code will read the
|
|
60
|
+
The above code will read the YAML file from the given location and not from the entry-points.
|
|
55
61
|
|
|
56
62
|
"""
|
|
57
63
|
from __future__ import annotations
|
|
@@ -63,12 +69,9 @@ from typing import Any
|
|
|
63
69
|
|
|
64
70
|
import yaml # This module is provided by the pip package PyYaml - pip install pyyaml
|
|
65
71
|
|
|
66
|
-
from egse.env import get_local_settings
|
|
67
72
|
from egse.env import get_local_settings_env_name
|
|
68
|
-
from egse.
|
|
73
|
+
from egse.env import get_local_settings_path
|
|
69
74
|
from egse.system import attrdict
|
|
70
|
-
from egse.system import get_package_location
|
|
71
|
-
from egse.system import ignore_m_warning
|
|
72
75
|
from egse.system import recursive_dict_update
|
|
73
76
|
|
|
74
77
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -80,22 +83,6 @@ class SettingsError(Exception):
|
|
|
80
83
|
pass
|
|
81
84
|
|
|
82
85
|
|
|
83
|
-
def is_defined(cls, name):
|
|
84
|
-
return hasattr(cls, name)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def get_attr_value(cls, name, default=None):
|
|
88
|
-
try:
|
|
89
|
-
return getattr(cls, name)
|
|
90
|
-
except AttributeError:
|
|
91
|
-
return default
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def set_attr_value(cls, name, value):
|
|
95
|
-
if hasattr(cls, name):
|
|
96
|
-
raise KeyError(f"Overwriting setting {name} with {value}, was {hasattr(cls, name)}")
|
|
97
|
-
|
|
98
|
-
|
|
99
86
|
# Fix the problem: YAML loads 5e-6 as string and not a number
|
|
100
87
|
# https://stackoverflow.com/questions/30458977/yaml-loads-5e-6-as-string-and-not-a-number
|
|
101
88
|
|
|
@@ -112,91 +99,117 @@ SAFE_LOADER.add_implicit_resolver(
|
|
|
112
99
|
list(u'-+0123456789.'))
|
|
113
100
|
|
|
114
101
|
|
|
115
|
-
def
|
|
102
|
+
def load_settings_file(path: Path, filename: str, force: bool = False) -> attrdict:
|
|
103
|
+
"""
|
|
104
|
+
Loads the YAML configuration file that is located at `path / filename`.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
- path (PATH): the folder where the YAML file is located
|
|
108
|
+
- filename (str): the name of the YAML configuration file
|
|
109
|
+
- force (bool): force reloading, i.e. don't use the cached information
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
A SettingsError when the configuration file doesn't exist or cannot be found.
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
A SettingsError when there was an error reading the configuration file.
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
Returns:
|
|
117
|
+
A dictionary (attrdict) with all the settings from the given file.
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
Note that, in case of an empty configuration file, and empty dictionary
|
|
120
|
+
is returned and a warning message is issued.
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
yaml_document = read_configuration_file(path / filename, force=force)
|
|
124
|
+
settings = attrdict(
|
|
125
|
+
{name: value for name, value in yaml_document.items()}
|
|
126
|
+
)
|
|
127
|
+
except FileNotFoundError as exc:
|
|
128
|
+
raise SettingsError(
|
|
129
|
+
f"The Settings YAML file '{filename}' is not found at {path!s}. "
|
|
130
|
+
) from exc
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
_LOGGER.
|
|
132
|
+
if not settings:
|
|
133
|
+
_LOGGER.warning(
|
|
134
|
+
f"The Settings YAML file '{filename}' at {path!s} is empty. "
|
|
135
|
+
f"No local settings were loaded, an empty dictionary is returned.")
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
return settings
|
|
130
138
|
|
|
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
139
|
|
|
137
|
-
|
|
140
|
+
def load_global_settings(entry_point: str = 'cgse.settings', force: bool = False) -> attrdict:
|
|
141
|
+
"""
|
|
142
|
+
Loads the settings that are defined by the given entry_point. The entry-points are defined in the
|
|
143
|
+
`pyproject.toml` files of the packages that export their global settings.
|
|
138
144
|
|
|
145
|
+
Args:
|
|
146
|
+
- entry_point (str): the name of the entry-point group [default: 'cgse.settings']
|
|
147
|
+
- force (bool): force reloading the settings, i.e. ignore the cache
|
|
139
148
|
|
|
140
|
-
|
|
149
|
+
Returns:
|
|
150
|
+
A dictionary (attrdict) containing a collection of all the settings exported by the packages
|
|
151
|
+
through the given entry-point.
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
"""
|
|
154
|
+
from egse.plugin import get_file_infos
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
) from exc
|
|
156
|
+
ep_settings = get_file_infos(entry_point)
|
|
157
|
+
|
|
158
|
+
global_settings = attrdict(label="Settings")
|
|
159
|
+
|
|
160
|
+
for ep_name, (path, filename) in ep_settings.items():
|
|
161
|
+
settings = load_settings_file(path, filename, force)
|
|
162
|
+
recursive_dict_update(global_settings, settings)
|
|
152
163
|
|
|
153
164
|
return global_settings
|
|
154
165
|
|
|
155
166
|
|
|
156
|
-
def load_local_settings(force: bool = False):
|
|
167
|
+
def load_local_settings(force: bool = False) -> attrdict:
|
|
168
|
+
"""
|
|
169
|
+
Loads the local settings file that is defined from the environment variable <PROJECT>_LOCAL_SETTINGS.
|
|
170
|
+
|
|
171
|
+
This function might return an empty dictionary when
|
|
157
172
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
local_settings_location = get_local_settings()
|
|
173
|
+
- the local settings YAML file is empty
|
|
174
|
+
- the local settings environment variable is not defined.
|
|
161
175
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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})")
|
|
176
|
+
in both cases a warning message is logged.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
A dictionary (attrdict) with all local settings.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
A SettingsError when the local settings YAML file is not found. Check the <PROJECT>_LOCAL_SETTINGS
|
|
183
|
+
environment variable.
|
|
184
|
+
"""
|
|
185
|
+
local_settings = attrdict()
|
|
186
|
+
|
|
187
|
+
local_settings_path = get_local_settings_path()
|
|
188
|
+
|
|
189
|
+
if local_settings_path:
|
|
190
|
+
path = Path(local_settings_path)
|
|
191
|
+
local_settings = load_settings_file(path.parent, path.name, force)
|
|
184
192
|
|
|
185
193
|
return local_settings
|
|
186
194
|
|
|
187
195
|
|
|
188
|
-
def read_configuration_file(filename:
|
|
196
|
+
def read_configuration_file(filename: Path, *, force=False):
|
|
189
197
|
"""
|
|
190
198
|
Read the YAML input configuration file. The configuration file is only read
|
|
191
199
|
once and memoized as load optimization.
|
|
192
200
|
|
|
193
201
|
Args:
|
|
194
|
-
filename (
|
|
195
|
-
force (bool): force reloading the file
|
|
202
|
+
filename (Path): the fully qualified filename of the YAML file
|
|
203
|
+
force (bool): force reloading the file, even when it was memoized
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
A SettingsError when there was an error reading the YAML file.
|
|
196
207
|
|
|
197
208
|
Returns:
|
|
198
209
|
a dictionary containing all the configuration settings from the YAML file.
|
|
199
210
|
"""
|
|
211
|
+
filename = str(filename)
|
|
212
|
+
|
|
200
213
|
if force or not Settings.is_memoized(filename):
|
|
201
214
|
|
|
202
215
|
_LOGGER.debug(f"Parsing YAML configuration file {filename}.")
|
|
@@ -261,94 +274,95 @@ class Settings:
|
|
|
261
274
|
def profiling(cls):
|
|
262
275
|
return cls.__profile
|
|
263
276
|
|
|
264
|
-
@
|
|
265
|
-
def
|
|
277
|
+
@staticmethod
|
|
278
|
+
def _load_all(
|
|
279
|
+
entry_point: str = 'cgse.settings',
|
|
280
|
+
add_local_settings: bool = False,
|
|
281
|
+
force: bool = False
|
|
282
|
+
) -> attrdict:
|
|
266
283
|
"""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
The default YAML file is 'settings.yaml' and is located in the same directory
|
|
271
|
-
as the settings module.
|
|
272
|
-
|
|
273
|
-
About the ``location`` keyword several options are available.
|
|
274
|
-
|
|
275
|
-
* when no location is given, i.e. ``location=None``, the YAML settings file is searched for
|
|
276
|
-
at the same location as the settings module.
|
|
277
|
-
|
|
278
|
-
* when a relative location is given, the YAML settings file is searched for relative to the
|
|
279
|
-
current working directory.
|
|
280
|
-
|
|
281
|
-
* when an absolute location is given, that location is used 'as is'.
|
|
284
|
+
Loads all settings from all package with the entry point 'cgse.settings'
|
|
285
|
+
"""
|
|
286
|
+
global_settings = load_global_settings(entry_point, force)
|
|
282
287
|
|
|
283
|
-
|
|
284
|
-
group_name (str): the name of one of the main groups from the YAML file
|
|
285
|
-
filename (str): the name of the YAML file to read
|
|
286
|
-
location (str, Path): the path to the location of the YAML file
|
|
287
|
-
force (bool): force reloading the file
|
|
288
|
-
add_local_settings (bool): update the Settings with site specific local settings
|
|
288
|
+
# Load the LOCAL settings YAML file
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
if add_local_settings:
|
|
291
|
+
local_settings = load_local_settings(force)
|
|
292
|
+
recursive_dict_update(global_settings, local_settings)
|
|
292
293
|
|
|
293
|
-
|
|
294
|
-
a SettingsError when the group is not defined in the YAML file.
|
|
295
|
-
"""
|
|
294
|
+
return global_settings
|
|
296
295
|
|
|
297
|
-
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _load_group(
|
|
298
|
+
group_name: str,
|
|
299
|
+
entry_point: str = 'cgse.settings',
|
|
300
|
+
add_local_settings: bool = False,
|
|
301
|
+
force: bool = False
|
|
302
|
+
) -> attrdict:
|
|
298
303
|
|
|
299
|
-
|
|
300
|
-
global_settings = load_global_settings(yaml_locations, filename, force)
|
|
304
|
+
global_settings = load_global_settings(entry_point, force)
|
|
301
305
|
|
|
302
|
-
|
|
303
|
-
raise SettingsError(f"There were no global settings defined for {filename} at {yaml_locations}.")
|
|
306
|
+
group_settings = attrdict(label=group_name)
|
|
304
307
|
|
|
305
|
-
|
|
308
|
+
if group_name in global_settings:
|
|
309
|
+
group_settings = attrdict(
|
|
310
|
+
{name: value for name, value in global_settings[group_name].items()},
|
|
311
|
+
label=group_name
|
|
312
|
+
)
|
|
306
313
|
|
|
307
314
|
if add_local_settings:
|
|
308
315
|
local_settings = load_local_settings(force)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if group_name in (None, ""):
|
|
313
|
-
global_settings = attrdict(
|
|
314
|
-
{name: value for name, value in global_settings.items()},
|
|
315
|
-
label="Settings"
|
|
316
|
-
)
|
|
317
|
-
if add_local_settings:
|
|
318
|
-
recursive_dict_update(global_settings, local_settings)
|
|
319
|
-
return global_settings
|
|
320
|
-
|
|
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
|
|
316
|
+
if group_name in local_settings:
|
|
317
|
+
recursive_dict_update(group_settings, local_settings[group_name])
|
|
329
318
|
|
|
330
|
-
if not
|
|
319
|
+
if not group_settings:
|
|
331
320
|
raise SettingsError(
|
|
332
321
|
f"Group name '{group_name}' is not defined in the global nor in the local settings."
|
|
333
322
|
)
|
|
334
323
|
|
|
335
|
-
|
|
324
|
+
return group_settings
|
|
336
325
|
|
|
337
|
-
|
|
338
|
-
|
|
326
|
+
@staticmethod
|
|
327
|
+
def _load_one(location: str, filename: str, force=False) -> attrdict:
|
|
339
328
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
329
|
+
return load_settings_file(Path(location).expanduser(), filename, force)
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def load(
|
|
333
|
+
cls,
|
|
334
|
+
group_name=None, filename="settings.yaml", location=None,
|
|
335
|
+
*, add_local_settings=True, force=False
|
|
336
|
+
):
|
|
337
|
+
"""
|
|
338
|
+
Load the settings for the given group. When no group is provided, the
|
|
339
|
+
complete configuration is returned.
|
|
347
340
|
|
|
348
|
-
|
|
349
|
-
|
|
341
|
+
The Settings are loaded from entry-points that are defined in each of the
|
|
342
|
+
packages that provide a Settings file.
|
|
350
343
|
|
|
351
|
-
|
|
344
|
+
If a location is explicitly provided, the Settings will be loaded from that
|
|
345
|
+
location, using the given filename or the default (which is settings.yaml).
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
group_name (str): the name of one of the main groups from the YAML file
|
|
349
|
+
filename (str): the name of the YAML file to read [default=settings.yaml]
|
|
350
|
+
location (str, Path): the path to the location of the YAML file
|
|
351
|
+
force (bool): force reloading the file
|
|
352
|
+
add_local_settings (bool): update the Settings with site specific local settings
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
a dynamically created class with the configuration parameters as instance variables.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
a SettingsError when the group is not defined in the YAML file.
|
|
359
|
+
"""
|
|
360
|
+
if group_name:
|
|
361
|
+
return cls._load_group(group_name, add_local_settings=add_local_settings, force=force)
|
|
362
|
+
elif location:
|
|
363
|
+
return cls._load_one(location=location, filename=filename, force=force)
|
|
364
|
+
else:
|
|
365
|
+
return cls._load_all(add_local_settings=add_local_settings, force=force)
|
|
352
366
|
|
|
353
367
|
@classmethod
|
|
354
368
|
def to_string(cls):
|
|
@@ -400,7 +414,7 @@ def main(args: list | None = None): # pragma: no cover
|
|
|
400
414
|
from rich import print
|
|
401
415
|
|
|
402
416
|
if args.local:
|
|
403
|
-
location =
|
|
417
|
+
location = get_local_settings_path()
|
|
404
418
|
if location:
|
|
405
419
|
location = str(Path(location).expanduser().resolve())
|
|
406
420
|
settings = Settings.load(filename=location)
|
egse/settings.yaml
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
SSH_SERVER: localhost # The IP address of the SSH server on your site
|
|
4
|
-
SSH_PORT: 22 # The TCP/IP port on which the SSH server is listening
|
|
1
|
+
# This is a stub file. Don't add any settings to this file, use the settings.yaml file
|
|
2
|
+
# in your projects module, like e.g. `cgse_common/settings.yaml` for this module.
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
SITE:
|
|
5
|
+
ID: XXXX
|
egse/setup.py
CHANGED
|
@@ -120,6 +120,7 @@ import logging
|
|
|
120
120
|
import os
|
|
121
121
|
import re
|
|
122
122
|
import textwrap
|
|
123
|
+
import warnings
|
|
123
124
|
from functools import lru_cache
|
|
124
125
|
from pathlib import Path
|
|
125
126
|
from typing import Any
|
|
@@ -130,13 +131,14 @@ import rich
|
|
|
130
131
|
import yaml
|
|
131
132
|
from rich.tree import Tree
|
|
132
133
|
|
|
134
|
+
from egse.env import get_conf_data_location
|
|
133
135
|
from egse.env import get_conf_repo_location
|
|
134
136
|
from egse.env import get_conf_repo_location_env_name
|
|
135
137
|
from egse.env import get_data_storage_location
|
|
136
138
|
from egse.env import has_conf_repo_location
|
|
137
139
|
from egse.env import print_env
|
|
138
140
|
from egse.response import Failure
|
|
139
|
-
from egse.
|
|
141
|
+
from egse.settings import read_configuration_file
|
|
140
142
|
from egse.system import format_datetime
|
|
141
143
|
from egse.system import sanity_check
|
|
142
144
|
from egse.system import walk_dict_tree
|
|
@@ -220,8 +222,8 @@ def _load_yaml(resource_name: str):
|
|
|
220
222
|
[in_dir, fn] = parts if len(parts) > 1 else [None, parts[0]]
|
|
221
223
|
conf_location = get_conf_data_location()
|
|
222
224
|
try:
|
|
223
|
-
yaml_location = Path(conf_location) / in_dir
|
|
224
|
-
content = NavigableDict(Settings.load(
|
|
225
|
+
yaml_location = Path(conf_location) / in_dir
|
|
226
|
+
content = NavigableDict(Settings.load(location=yaml_location, filename=fn, add_local_settings=False))
|
|
225
227
|
except (TypeError, SettingsError) as exc:
|
|
226
228
|
raise ValueError(
|
|
227
229
|
f"Couldn't load resource '{resource_name}' from default {conf_location=}") from exc
|
|
@@ -695,14 +697,20 @@ class Setup(NavigableDict):
|
|
|
695
697
|
Returns:
|
|
696
698
|
a Setup that was loaded from the given location.
|
|
697
699
|
"""
|
|
698
|
-
from egse.settings import Settings
|
|
699
700
|
|
|
700
701
|
if not filename:
|
|
701
702
|
raise ValueError("Invalid argument to function: No filename or None given.")
|
|
702
703
|
|
|
703
704
|
# MODULE_LOGGER.info(f"Loading {filename}...")
|
|
704
705
|
|
|
705
|
-
setup_dict =
|
|
706
|
+
setup_dict = read_configuration_file(filename, force=True)
|
|
707
|
+
if setup_dict == {}:
|
|
708
|
+
warnings.warn(f"Empty Setup file: {filename!s}")
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
setup_dict = setup_dict["Setup"]
|
|
712
|
+
except KeyError:
|
|
713
|
+
warnings.warn(f"Setup file doesn't have a 'Setup' group: {filename!s}")
|
|
706
714
|
|
|
707
715
|
setup = Setup(setup_dict, label="Setup")
|
|
708
716
|
setup.set_private_attribute("_filename", filename)
|
|
File without changes
|