fmu-settings 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fmu-settings might be problematic. Click here for more details.
- fmu/__init__.py +6 -0
- fmu/settings/__init__.py +12 -0
- fmu/settings/_fmu_dir.py +337 -0
- fmu/settings/_init.py +131 -0
- fmu/settings/_logging.py +30 -0
- fmu/settings/_version.py +21 -0
- fmu/settings/models/__init__.py +5 -0
- fmu/settings/models/_enums.py +34 -0
- fmu/settings/models/_mappings.py +118 -0
- fmu/settings/models/project_config.py +49 -0
- fmu/settings/models/smda.py +90 -0
- fmu/settings/models/user_config.py +73 -0
- fmu/settings/py.typed +0 -0
- fmu/settings/resources/config_managers.py +211 -0
- fmu/settings/resources/managers.py +96 -0
- fmu/settings/types.py +20 -0
- fmu_settings-0.0.1.dist-info/METADATA +69 -0
- fmu_settings-0.0.1.dist-info/RECORD +21 -0
- fmu_settings-0.0.1.dist-info/WHEEL +5 -0
- fmu_settings-0.0.1.dist-info/licenses/LICENSE +674 -0
- fmu_settings-0.0.1.dist-info/top_level.txt +1 -0
fmu/__init__.py
ADDED
fmu/settings/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""The fmu-settings package."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from ._version import version
|
|
5
|
+
|
|
6
|
+
__version__ = version
|
|
7
|
+
except ImportError:
|
|
8
|
+
__version__ = version = "0.0.0"
|
|
9
|
+
|
|
10
|
+
from ._fmu_dir import ProjectFMUDirectory, find_nearest_fmu_directory, get_fmu_directory
|
|
11
|
+
|
|
12
|
+
__all__ = ["get_fmu_directory", "ProjectFMUDirectory", "find_nearest_fmu_directory"]
|
fmu/settings/_fmu_dir.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Main interface for working with .fmu directory."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Final, Self, TypeAlias, cast
|
|
5
|
+
|
|
6
|
+
from ._logging import null_logger
|
|
7
|
+
from .models.project_config import ProjectConfig
|
|
8
|
+
from .models.user_config import UserConfig
|
|
9
|
+
from .resources.config_managers import (
|
|
10
|
+
ProjectConfigManager,
|
|
11
|
+
UserConfigManager,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
logger: Final = null_logger(__name__)
|
|
15
|
+
|
|
16
|
+
FMUConfigManager: TypeAlias = ProjectConfigManager | UserConfigManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FMUDirectoryBase:
|
|
20
|
+
"""Provides access to a .fmu directory and operations on its contents."""
|
|
21
|
+
|
|
22
|
+
config: FMUConfigManager
|
|
23
|
+
|
|
24
|
+
def __init__(self: Self, base_path: str | Path) -> None:
|
|
25
|
+
"""Initializes access to a .fmu directory.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base_path: The directory containing the .fmu directory or one of its parent
|
|
29
|
+
dirs
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
FileExistsError: If .fmu exists but is not a directory
|
|
33
|
+
FileNotFoundError: If .fmu directory doesn't exist
|
|
34
|
+
PermissionError: If lacking permissions to read/write to the directory
|
|
35
|
+
"""
|
|
36
|
+
self.base_path = Path(base_path).resolve()
|
|
37
|
+
logger.debug(f"Initializing FMUDirectory from '{base_path}'")
|
|
38
|
+
|
|
39
|
+
fmu_dir = self.base_path / ".fmu"
|
|
40
|
+
if fmu_dir.exists():
|
|
41
|
+
if fmu_dir.is_dir():
|
|
42
|
+
self._path = fmu_dir
|
|
43
|
+
else:
|
|
44
|
+
raise FileExistsError(
|
|
45
|
+
f".fmu exists at {self.base_path} but is not a directory"
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
raise FileNotFoundError(f"No .fmu directory found at {self.base_path}")
|
|
49
|
+
|
|
50
|
+
logger.debug(f"Using .fmu directory at {self._path}")
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def path(self: Self) -> Path:
|
|
54
|
+
"""Returns the path to the .fmu directory."""
|
|
55
|
+
return self._path
|
|
56
|
+
|
|
57
|
+
def get_config_value(self: Self, key: str, default: Any = None) -> Any:
|
|
58
|
+
"""Gets a configuration value by key.
|
|
59
|
+
|
|
60
|
+
Supports dot notation for nested values (e.g., "foo.bar")
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key: The configuration key
|
|
64
|
+
default: Value to return if key is not found. Default None
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The configuration value or deafult
|
|
68
|
+
"""
|
|
69
|
+
return self.config.get(key, default)
|
|
70
|
+
|
|
71
|
+
def set_config_value(self: Self, key: str, value: Any) -> None:
|
|
72
|
+
"""Sets a configuration value by key.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
key: The configuration key
|
|
76
|
+
value: The value to set
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
FileNotFoundError: If config file doesn't exist
|
|
80
|
+
ValueError: If the updated config is invalid
|
|
81
|
+
"""
|
|
82
|
+
self.config.set(key, value)
|
|
83
|
+
|
|
84
|
+
def update_config(
|
|
85
|
+
self: Self, updates: dict[str, Any]
|
|
86
|
+
) -> ProjectConfig | UserConfig:
|
|
87
|
+
"""Updates multiple configuration values at once.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
updates: Dictionary of key-value pairs to update
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The updated *Config object
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
FileNotFoundError: If config file doesn't exist
|
|
97
|
+
ValueError: If the updates config is invalid
|
|
98
|
+
"""
|
|
99
|
+
return self.config.update(updates)
|
|
100
|
+
|
|
101
|
+
def get_file_path(self: Self, relative_path: str | Path) -> Path:
|
|
102
|
+
"""Gets the absolute path to a file within the .fmu directory.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
relative_path: Path relative to the .fmu directory
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Absolute path to the file
|
|
109
|
+
"""
|
|
110
|
+
return self.path / relative_path
|
|
111
|
+
|
|
112
|
+
def read_file(self, relative_path: str | Path) -> bytes:
|
|
113
|
+
"""Reads a file from the .fmu directory.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
relative_path: Path relative to the .fmu directory
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
File contents as bytes
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
FileNotFoundError: If the file doesn't exist
|
|
123
|
+
"""
|
|
124
|
+
file_path = self.get_file_path(relative_path)
|
|
125
|
+
return file_path.read_bytes()
|
|
126
|
+
|
|
127
|
+
def read_text_file(self, relative_path: str | Path, encoding: str = "utf-8") -> str:
|
|
128
|
+
"""Reads a text file from the .fmu directory.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
relative_path: Path relative to the .fmu directory
|
|
132
|
+
encoding: Text encoding to use. Default utf-8
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
File contents as string
|
|
136
|
+
"""
|
|
137
|
+
file_path = self.get_file_path(relative_path)
|
|
138
|
+
return file_path.read_text(encoding=encoding)
|
|
139
|
+
|
|
140
|
+
def write_file(self, relative_path: str | Path, data: bytes) -> None:
|
|
141
|
+
"""Writes bytes to a file in the .fmu directory.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
relative_path: Path relative to the .fmu directory
|
|
145
|
+
data: Bytes to write
|
|
146
|
+
"""
|
|
147
|
+
file_path = self.get_file_path(relative_path)
|
|
148
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
|
|
150
|
+
file_path.write_bytes(data)
|
|
151
|
+
logger.debug(f"Wrote {len(data)} bytes to {file_path}")
|
|
152
|
+
|
|
153
|
+
def write_text_file(
|
|
154
|
+
self, relative_path: str | Path, content: str, encoding: str = "utf-8"
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Writes text to a file in the .fmu directory.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
relative_path: Path relative to the .fmu directory
|
|
160
|
+
content: Text content to write
|
|
161
|
+
encoding: Text encoding to use. Default utf-8
|
|
162
|
+
"""
|
|
163
|
+
file_path = self.get_file_path(relative_path)
|
|
164
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
file_path.write_text(content, encoding=encoding)
|
|
167
|
+
logger.debug(f"Wrote text file to {file_path}")
|
|
168
|
+
|
|
169
|
+
def list_files(self, subdirectory: str | Path | None = None) -> list[Path]:
|
|
170
|
+
"""Lists files in the .fmu directory or a subdirectory.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
subdirectory: Optional subdirectory to list files from
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of Path objects for files (not directories)
|
|
177
|
+
"""
|
|
178
|
+
base = self.get_file_path(subdirectory) if subdirectory else self.path
|
|
179
|
+
if not base.exists():
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
return [p for p in base.iterdir() if p.is_file()]
|
|
183
|
+
|
|
184
|
+
def ensure_directory(self, relative_path: str | Path) -> Path:
|
|
185
|
+
"""Ensures a subdirectory exists in the .fmu directory.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
relative_path: Path relative to the .fmu directory
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Path to the directory
|
|
192
|
+
"""
|
|
193
|
+
dir_path = self.get_file_path(relative_path)
|
|
194
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
return dir_path
|
|
196
|
+
|
|
197
|
+
def file_exists(self, relative_path: str | Path) -> bool:
|
|
198
|
+
"""Checks if a file exists in the .fmu directory.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
relative_path: Path relative to the .fmu directory
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
True if the file exists, False otherwise
|
|
205
|
+
"""
|
|
206
|
+
return self.get_file_path(relative_path).exists()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ProjectFMUDirectory(FMUDirectoryBase):
|
|
210
|
+
config: ProjectConfigManager
|
|
211
|
+
|
|
212
|
+
def __init__(self, base_path: str | Path) -> None:
|
|
213
|
+
"""Initializes a project-based .fmu directory."""
|
|
214
|
+
self.config = ProjectConfigManager(self)
|
|
215
|
+
super().__init__(base_path)
|
|
216
|
+
|
|
217
|
+
def update_config(self: Self, updates: dict[str, Any]) -> ProjectConfig:
|
|
218
|
+
"""Updates multiple configuration values at once.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
updates: Dictionary of key-value pairs to update
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The updated ProjectConfig object
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
FileNotFoundError: If config file doesn't exist
|
|
228
|
+
ValueError: If the updates config is invalid
|
|
229
|
+
"""
|
|
230
|
+
return cast("ProjectConfig", super().update_config(updates))
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def find_fmu_directory(start_path: Path) -> Path | None:
|
|
234
|
+
"""Searches for a .fmu directory in start_path and its parents.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
start_path: The path to start searching from
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Path to the found .fmu directory or None if not found
|
|
241
|
+
"""
|
|
242
|
+
current = start_path
|
|
243
|
+
# Prevent symlink loops
|
|
244
|
+
visited = set()
|
|
245
|
+
|
|
246
|
+
while current not in visited:
|
|
247
|
+
visited.add(current)
|
|
248
|
+
fmu_dir = current / ".fmu"
|
|
249
|
+
|
|
250
|
+
# Do not include $HOME/.fmu in the search
|
|
251
|
+
if fmu_dir.is_dir() and current != Path.home():
|
|
252
|
+
return fmu_dir
|
|
253
|
+
|
|
254
|
+
# We hit root
|
|
255
|
+
if current == current.parent:
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
current = current.parent
|
|
259
|
+
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def find_nearest(cls: type[Self], start_path: str | Path = ".") -> Self:
|
|
264
|
+
"""Factory method to find and open the nearest .fmu directory.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
start_path: Path to start searching from. Default current working director
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
FMUDirectory instance
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
FileNotFoundError: If no .fmu directory is found
|
|
274
|
+
"""
|
|
275
|
+
start_path = Path(start_path).resolve()
|
|
276
|
+
fmu_dir_path = cls.find_fmu_directory(start_path)
|
|
277
|
+
if fmu_dir_path is None:
|
|
278
|
+
raise FileNotFoundError(f"No .fmu directory found at or above {start_path}")
|
|
279
|
+
return cls(fmu_dir_path.parent)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class UserFMUDirectory(FMUDirectoryBase):
|
|
283
|
+
config: UserConfigManager
|
|
284
|
+
|
|
285
|
+
def __init__(self) -> None:
|
|
286
|
+
"""Initializes a project-based .fmu directory."""
|
|
287
|
+
self.config = UserConfigManager(self)
|
|
288
|
+
super().__init__(Path.home())
|
|
289
|
+
|
|
290
|
+
def update_config(self: Self, updates: dict[str, Any]) -> UserConfig:
|
|
291
|
+
"""Updates multiple configuration values at once.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
updates: Dictionary of key-value pairs to update
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
The updated UserConfig object
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
FileNotFoundError: If config file doesn't exist
|
|
301
|
+
ValueError: If the updates config is invalid
|
|
302
|
+
"""
|
|
303
|
+
return cast("UserConfig", super().update_config(updates))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_fmu_directory(base_path: str | Path) -> ProjectFMUDirectory:
|
|
307
|
+
"""Initializes access to a .fmu directory.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
base_path: The directory containing the .fmu directory or one of its parent
|
|
311
|
+
dirs
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
FMUDirectory instance
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
FileExistsError: If .fmu exists but is not a directory
|
|
318
|
+
FileNotFoundError: If .fmu directory doesn't exist
|
|
319
|
+
PermissionError: If lacking permissions to read/write to the directory
|
|
320
|
+
|
|
321
|
+
"""
|
|
322
|
+
return ProjectFMUDirectory(base_path)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def find_nearest_fmu_directory(start_path: str | Path = ".") -> ProjectFMUDirectory:
|
|
326
|
+
"""Factory method to find and open the nearest .fmu directory.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
start_path: Path to start searching from. Default current working directory
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
FMUDirectory instance
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
FileNotFoundError: If no .fmu directory is found
|
|
336
|
+
"""
|
|
337
|
+
return ProjectFMUDirectory.find_nearest(start_path)
|
fmu/settings/_init.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Initializes the .fmu directory."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from textwrap import dedent
|
|
5
|
+
from typing import Any, Final
|
|
6
|
+
|
|
7
|
+
from ._fmu_dir import ProjectFMUDirectory, UserFMUDirectory
|
|
8
|
+
from ._logging import null_logger
|
|
9
|
+
from .models.project_config import ProjectConfig
|
|
10
|
+
|
|
11
|
+
logger: Final = null_logger(__name__)
|
|
12
|
+
|
|
13
|
+
_README = dedent("""\
|
|
14
|
+
This directory contains static configuration data for your FMU project.
|
|
15
|
+
|
|
16
|
+
You should *not* manually modify files within this directory. Doing so may
|
|
17
|
+
result in erroneous behavior or erroneous data in your FMU project.
|
|
18
|
+
|
|
19
|
+
Changes to data stored within this directory must happen through the FMU
|
|
20
|
+
Settings application.
|
|
21
|
+
|
|
22
|
+
Run `fmu-settings` to do this.
|
|
23
|
+
""")
|
|
24
|
+
|
|
25
|
+
_USER_README = dedent("""\
|
|
26
|
+
This directory contains static data and configuration elements used by some
|
|
27
|
+
components in FMU. It may also contains sensitive access tokens that should not be
|
|
28
|
+
shared with others.
|
|
29
|
+
|
|
30
|
+
You should *not* manually modify files within this directory. Doing so may
|
|
31
|
+
result in erroneous behavior by some FMU components.
|
|
32
|
+
|
|
33
|
+
Changes to data stored within this directory must happen through the FMU
|
|
34
|
+
Settings application.
|
|
35
|
+
|
|
36
|
+
Run `fmu-settings` to do this.
|
|
37
|
+
""")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _create_fmu_directory(base_path: Path) -> None:
|
|
41
|
+
"""Creates the .fmu directory.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
base_path: Base directory where .fmu should be created
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
FileNotFoundError: If base_path doesn't exist
|
|
48
|
+
FileExistsError: If .fmu exists
|
|
49
|
+
"""
|
|
50
|
+
logger.debug(f"Creating .fmu directory in '{base_path}'")
|
|
51
|
+
|
|
52
|
+
if not base_path.exists():
|
|
53
|
+
raise FileNotFoundError(
|
|
54
|
+
f"Base path '{base_path}' does not exist. Expected the root "
|
|
55
|
+
"directory of an FMU project."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
fmu_dir = base_path / ".fmu"
|
|
59
|
+
if fmu_dir.exists():
|
|
60
|
+
if fmu_dir.is_dir():
|
|
61
|
+
raise FileExistsError(f"{fmu_dir} already exists")
|
|
62
|
+
raise FileExistsError(f"{fmu_dir} exists but is not a directory")
|
|
63
|
+
|
|
64
|
+
fmu_dir.mkdir()
|
|
65
|
+
logger.debug(f"Created .fmu directory at '{fmu_dir}'")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def init_fmu_directory(
|
|
69
|
+
base_path: str | Path, config_data: ProjectConfig | dict[str, Any] | None = None
|
|
70
|
+
) -> ProjectFMUDirectory:
|
|
71
|
+
"""Creates and initializes a .fmu directory.
|
|
72
|
+
|
|
73
|
+
Also initializes a configuration file if configuration data is provided through the
|
|
74
|
+
function.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
base_path: Directory where .fmu should be created
|
|
78
|
+
config_data: Optional ProjectConfig instance or dictionary with configuration
|
|
79
|
+
data
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Instance of FMUDirectory
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
FileExistsError: If .fmu exists
|
|
86
|
+
FileNotFoundError: If base_path doesn't exist
|
|
87
|
+
PermissionError: If the user lacks permission to create directories
|
|
88
|
+
ValidationError: If config_data fails validationg
|
|
89
|
+
"""
|
|
90
|
+
logger.debug("Initializing .fmu directory")
|
|
91
|
+
base_path = Path(base_path)
|
|
92
|
+
|
|
93
|
+
_create_fmu_directory(base_path)
|
|
94
|
+
|
|
95
|
+
fmu_dir = ProjectFMUDirectory(base_path)
|
|
96
|
+
fmu_dir.write_text_file("README", _README)
|
|
97
|
+
|
|
98
|
+
fmu_dir.config.reset()
|
|
99
|
+
if config_data:
|
|
100
|
+
if isinstance(config_data, ProjectConfig):
|
|
101
|
+
config_dict = config_data.model_dump()
|
|
102
|
+
fmu_dir.update_config(config_dict)
|
|
103
|
+
elif isinstance(config_data, dict):
|
|
104
|
+
fmu_dir.update_config(config_data)
|
|
105
|
+
|
|
106
|
+
logger.debug(f"Successfully initialized .fmu directory at '{fmu_dir}'")
|
|
107
|
+
return fmu_dir
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def init_user_fmu_directory() -> UserFMUDirectory:
|
|
111
|
+
"""Creates and initializes a user's $HOME/.fmu directory.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Instance of FMUDirectory
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
FileExistsError: If .fmu exists
|
|
118
|
+
FileNotFoundError: If base_path doesn't exist
|
|
119
|
+
PermissionError: If the user lacks permission to create directories
|
|
120
|
+
ValidationError: If config_data fails validationg
|
|
121
|
+
"""
|
|
122
|
+
logger.debug("Initializing .fmu directory")
|
|
123
|
+
|
|
124
|
+
_create_fmu_directory(Path.home())
|
|
125
|
+
|
|
126
|
+
fmu_dir = UserFMUDirectory()
|
|
127
|
+
fmu_dir.write_text_file("README", _USER_README)
|
|
128
|
+
|
|
129
|
+
fmu_dir.config.reset()
|
|
130
|
+
logger.debug(f"Successfully initialized .fmu directory at '{fmu_dir}'")
|
|
131
|
+
return fmu_dir
|
fmu/settings/_logging.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Contains the logger that should be used throughout this package."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def null_logger(name: str) -> logging.Logger:
|
|
7
|
+
"""Create and return a logger with a NullHandler.
|
|
8
|
+
|
|
9
|
+
This function creates a logger for the specified name and attaches a
|
|
10
|
+
NullHandler to it. The NullHandler prevents logging messages from being
|
|
11
|
+
automatically output to the console or other default handlers. This is
|
|
12
|
+
particularly useful in library modules where you want to provide the
|
|
13
|
+
users of the library the flexibility to configure their own logging behavior.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
name: The name of the logger to be created. This is typically
|
|
17
|
+
the name of the module in which the logger is
|
|
18
|
+
created (e.g., using __name__).
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A logger object configured with a NullHandler.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
# In a library module
|
|
25
|
+
logger = null_logger(__name__)
|
|
26
|
+
logger.info("This info won't be logged to the console by default.")
|
|
27
|
+
"""
|
|
28
|
+
logger = logging.getLogger(name)
|
|
29
|
+
logger.addHandler(logging.NullHandler())
|
|
30
|
+
return logger
|
fmu/settings/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.0.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Contains enumerations used in this package."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MappingType(StrEnum):
|
|
7
|
+
"""The discriminator used between mappings.
|
|
8
|
+
|
|
9
|
+
Each of these types should have their own mapping class derived of some sort of
|
|
10
|
+
mapping.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
fault = "fault"
|
|
14
|
+
stratigraphy = "stratigraphy"
|
|
15
|
+
well = "well"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RelationType(StrEnum):
|
|
19
|
+
"""The kind of relation this mapping represents."""
|
|
20
|
+
|
|
21
|
+
alias = "alias"
|
|
22
|
+
child_to_parent = "child_to_parent"
|
|
23
|
+
equivalent = "equivalent"
|
|
24
|
+
fmu_to_target = "fmu_to_target"
|
|
25
|
+
predecessor_to_successor = "predecessor_to_successor"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DataEntrySource(StrEnum):
|
|
29
|
+
user = "user"
|
|
30
|
+
automated = "automated"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TargetSystem(StrEnum):
|
|
34
|
+
smda = "smda"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Contains models used for representing mappings within FMU."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, RootModel
|
|
8
|
+
|
|
9
|
+
from fmu.dataio._models.enums import Content # type: ignore
|
|
10
|
+
|
|
11
|
+
from ._enums import (
|
|
12
|
+
DataEntrySource,
|
|
13
|
+
MappingType,
|
|
14
|
+
RelationType,
|
|
15
|
+
TargetSystem,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Source(BaseModel):
|
|
20
|
+
name: str
|
|
21
|
+
data_entry_source: DataEntrySource
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseMapping(BaseModel):
|
|
25
|
+
"""The base mapping containing the fields all mappings should contain.
|
|
26
|
+
|
|
27
|
+
These fields will be contained in every individual mapping entry.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
source_system: str
|
|
31
|
+
target_system: TargetSystem
|
|
32
|
+
mapping_type: MappingType
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IdentifierMapping(BaseMapping):
|
|
36
|
+
"""Base class for a one-to-one or many-to-one mapping of identifiers.
|
|
37
|
+
|
|
38
|
+
This mapping represents takes some identifier from one source and correlates it to
|
|
39
|
+
an identifier in a target. Most often this target will be some official masterdata
|
|
40
|
+
store like SMDA.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
source_id: str
|
|
44
|
+
target_id: str
|
|
45
|
+
target_uuid: UUID
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class StratigraphyMapping(IdentifierMapping):
|
|
49
|
+
"""Represents a stratigraphy mapping.
|
|
50
|
+
|
|
51
|
+
This is a mapping from stratigraphic aliases identifiers to an official
|
|
52
|
+
identifier.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
mapping_type: Literal[MappingType.stratigraphy] = MappingType.stratigraphy
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class WellMapping(IdentifierMapping):
|
|
59
|
+
"""Represents a well mapping.
|
|
60
|
+
|
|
61
|
+
This is a mapping from well aliases identifiers to an official
|
|
62
|
+
identifier.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
mapping_type: Literal[MappingType.well] = MappingType.well
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FaultMapping(IdentifierMapping):
|
|
69
|
+
"""Represents a fault mapping.
|
|
70
|
+
|
|
71
|
+
This is a mapping from fault aliases identifiers to an official
|
|
72
|
+
identifier.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
mapping_type: Literal[MappingType.well]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class EntityReference(BaseModel):
|
|
79
|
+
"""Represents one entity we wish to related to naother entity.
|
|
80
|
+
|
|
81
|
+
This is typically an object exported by dataio.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name: str
|
|
85
|
+
uuid: UUID
|
|
86
|
+
content: Content
|
|
87
|
+
relative_path: Path
|
|
88
|
+
absolute_path: Path
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RelationshipMapping(BaseMapping):
|
|
92
|
+
"""Base class for a mapping that represents a relationship between two entities."""
|
|
93
|
+
|
|
94
|
+
source_entity: EntityReference
|
|
95
|
+
target_entity: EntityReference
|
|
96
|
+
relation_type: RelationType
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ParentChildMapping(BaseMapping):
|
|
100
|
+
"""A mapping between a child and their parent."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class HierarchicalMapping(RelationshipMapping):
|
|
104
|
+
"""A mapping that contains a hierarchy."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Mappings(BaseModel):
|
|
108
|
+
"""A list of mappings under a mappings key in metadata or in a file on disk."""
|
|
109
|
+
|
|
110
|
+
items: list[BaseMapping]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class OrderedMappings(Mappings):
|
|
114
|
+
"""Items in this list imply an ordering that is important in some context."""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
MappingFile = RootModel[Mappings]
|
|
118
|
+
"""Represents a list of mappings contained in a text file."""
|