fmu-settings 0.5.0__tar.gz → 0.5.2__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.
Potentially problematic release.
This version of fmu-settings might be problematic. Click here for more details.
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/PKG-INFO +1 -1
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_global_config.py +17 -4
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_resources/lock_manager.py +2 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_resources/pydantic_resource_manager.py +45 -45
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_version.py +3 -3
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu_settings.egg-info/PKG-INFO +1 -1
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_global_config.py +82 -23
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_resources/test_lock_manager.py +15 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.coveragerc +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.github/pull_request_template.md +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.github/workflows/ci.yml +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.github/workflows/codeql.yml +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.github/workflows/publish.yml +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/.gitignore +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/CONTRIBUTING.md +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/LICENSE +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/README.md +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/SECURITY.md +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/pyproject.toml +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/setup.cfg +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/__init__.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/__init__.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_fmu_dir.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_init.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_logging.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_resources/__init__.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_resources/config_managers.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/__init__.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/_enums.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/_mappings.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/lock_info.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/project_config.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/models/user_config.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/py.typed +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/types.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu_settings.egg-info/SOURCES.txt +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu_settings.egg-info/dependency_links.txt +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu_settings.egg-info/requires.txt +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu_settings.egg-info/top_level.txt +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/conftest.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_fmu_dir.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_init.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_resources/test_project_config.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_resources/test_resource_managers.py +0 -0
- {fmu_settings-0.5.0 → fmu_settings-0.5.2}/tests/test_resources/test_user_config.py +0 -0
|
@@ -145,16 +145,18 @@ def load_global_configuration_if_present(
|
|
|
145
145
|
fmu_load: Whether or not to load in the custom 'fmu' format. Default False.
|
|
146
146
|
|
|
147
147
|
Returns:
|
|
148
|
-
GlobalConfiguration instance or None.
|
|
148
|
+
GlobalConfiguration instance or None if file cannot be loaded.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValidationError: If the file is loaded but has invalid schema.
|
|
149
152
|
"""
|
|
150
153
|
loader = "fmu" if fmu_load else "standard"
|
|
151
154
|
try:
|
|
152
155
|
global_variables_dict = yaml_load(path, loader=loader)
|
|
153
156
|
global_config = GlobalConfiguration.model_validate(global_variables_dict)
|
|
154
157
|
logger.debug(f"Global variables at {path} has valid settings data")
|
|
155
|
-
except ValidationError
|
|
156
|
-
|
|
157
|
-
return None
|
|
158
|
+
except ValidationError:
|
|
159
|
+
raise
|
|
158
160
|
except Exception as e:
|
|
159
161
|
logger.debug(
|
|
160
162
|
f"Failed to load global variables at {path}: {type(e).__name__}: {e}"
|
|
@@ -173,6 +175,9 @@ def _find_global_variables_file(paths: list[Path]) -> GlobalConfiguration | None
|
|
|
173
175
|
|
|
174
176
|
Returns:
|
|
175
177
|
A validated GlobalConfiguration or None.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValidationError: If a file is found but has invalid schema.
|
|
176
181
|
"""
|
|
177
182
|
for path in paths:
|
|
178
183
|
if not path.exists():
|
|
@@ -204,6 +209,9 @@ def _find_global_config_file(paths: list[Path]) -> GlobalConfiguration | None:
|
|
|
204
209
|
|
|
205
210
|
Returns:
|
|
206
211
|
A validated GlobalConfiguration or None.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
ValidationError: If a file is found but has invalid schema.
|
|
207
215
|
"""
|
|
208
216
|
for path in paths:
|
|
209
217
|
if not path.exists():
|
|
@@ -241,6 +249,11 @@ def find_global_config(
|
|
|
241
249
|
|
|
242
250
|
Returns:
|
|
243
251
|
A valid GlobalConfiguration instance, or None.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
ValidationError: If a configuration file is found but has invalid schema.
|
|
255
|
+
InvalidGlobalConfigurationError: If strict=True and configuration contains
|
|
256
|
+
disallowed content (e.g., Drogon data).
|
|
244
257
|
"""
|
|
245
258
|
base_path = Path(base_path)
|
|
246
259
|
|
|
@@ -194,6 +194,8 @@ class LockManager(PydanticResourceManager[LockInfo]):
|
|
|
194
194
|
LockError: If we don't hold the lock or it's invalid
|
|
195
195
|
"""
|
|
196
196
|
if not self.exists:
|
|
197
|
+
if self.is_acquired():
|
|
198
|
+
self.release()
|
|
197
199
|
raise LockError("Cannot refresh: lock file does not exist")
|
|
198
200
|
|
|
199
201
|
lock_info = self._safe_load()
|
{fmu_settings-0.5.0 → fmu_settings-0.5.2}/src/fmu/settings/_resources/pydantic_resource_manager.py
RENAMED
|
@@ -112,21 +112,21 @@ class PydanticResourceManager(Generic[PydanticResource]):
|
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticResource]):
|
|
115
|
-
"""Manages the .fmu
|
|
115
|
+
"""Manages the .fmu resource file."""
|
|
116
116
|
|
|
117
117
|
def __init__(
|
|
118
|
-
self: Self, fmu_dir: FMUDirectoryBase,
|
|
118
|
+
self: Self, fmu_dir: FMUDirectoryBase, resource: type[MutablePydanticResource]
|
|
119
119
|
) -> None:
|
|
120
|
-
"""Initializes the
|
|
121
|
-
super().__init__(fmu_dir,
|
|
120
|
+
"""Initializes the resource manager."""
|
|
121
|
+
super().__init__(fmu_dir, resource)
|
|
122
122
|
|
|
123
123
|
def _get_dot_notation_key(
|
|
124
|
-
self: Self,
|
|
124
|
+
self: Self, resource_dict: dict[str, Any], key: str, default: Any = None
|
|
125
125
|
) -> Any:
|
|
126
126
|
"""Sets the value to a dot-notation key.
|
|
127
127
|
|
|
128
128
|
Args:
|
|
129
|
-
|
|
129
|
+
resource_dict: The resource dictionary we are modifying (by reference)
|
|
130
130
|
key: The key to set
|
|
131
131
|
default: Value to return if key is not found. Default None
|
|
132
132
|
|
|
@@ -134,7 +134,7 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
134
134
|
The value or default
|
|
135
135
|
"""
|
|
136
136
|
parts = key.split(".")
|
|
137
|
-
value =
|
|
137
|
+
value = resource_dict
|
|
138
138
|
for part in parts:
|
|
139
139
|
if isinstance(value, dict) and part in value:
|
|
140
140
|
value = value[part]
|
|
@@ -144,28 +144,28 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
144
144
|
return value
|
|
145
145
|
|
|
146
146
|
def get(self: Self, key: str, default: Any = None) -> Any:
|
|
147
|
-
"""Gets a
|
|
147
|
+
"""Gets a resource value by key.
|
|
148
148
|
|
|
149
149
|
Supports dot notation for nested values (e.g., "foo.bar")
|
|
150
150
|
|
|
151
151
|
Args:
|
|
152
|
-
key: The
|
|
152
|
+
key: The resource key
|
|
153
153
|
default: Value to return if key is not found. Default None
|
|
154
154
|
|
|
155
155
|
Returns:
|
|
156
|
-
The
|
|
156
|
+
The resource value or default
|
|
157
157
|
"""
|
|
158
158
|
try:
|
|
159
|
-
|
|
159
|
+
resource = self.load()
|
|
160
160
|
|
|
161
161
|
if "." in key:
|
|
162
|
-
return self._get_dot_notation_key(
|
|
162
|
+
return self._get_dot_notation_key(resource.model_dump(), key, default)
|
|
163
163
|
|
|
164
|
-
if hasattr(
|
|
165
|
-
return getattr(
|
|
164
|
+
if hasattr(resource, key):
|
|
165
|
+
return getattr(resource, key)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
return
|
|
167
|
+
resource_dict = resource.model_dump()
|
|
168
|
+
return resource_dict.get(key, default)
|
|
169
169
|
except FileNotFoundError as e:
|
|
170
170
|
raise FileNotFoundError(
|
|
171
171
|
f"Resource file for '{self.__class__.__name__}' not found "
|
|
@@ -173,17 +173,17 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
173
173
|
) from e
|
|
174
174
|
|
|
175
175
|
def _set_dot_notation_key(
|
|
176
|
-
self: Self,
|
|
176
|
+
self: Self, resource_dict: dict[str, Any], key: str, value: Any
|
|
177
177
|
) -> None:
|
|
178
178
|
"""Sets the value to a dot-notation key.
|
|
179
179
|
|
|
180
180
|
Args:
|
|
181
|
-
|
|
181
|
+
resource_dict: The resource dictionary we are modifying (by reference)
|
|
182
182
|
key: The key to set
|
|
183
183
|
value: The value to set
|
|
184
184
|
"""
|
|
185
185
|
parts = key.split(".")
|
|
186
|
-
target =
|
|
186
|
+
target = resource_dict
|
|
187
187
|
|
|
188
188
|
for part in parts[:-1]:
|
|
189
189
|
if part not in target or not isinstance(target[part], dict):
|
|
@@ -193,27 +193,27 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
193
193
|
target[parts[-1]] = value
|
|
194
194
|
|
|
195
195
|
def set(self: Self, key: str, value: Any) -> None:
|
|
196
|
-
"""Sets a
|
|
196
|
+
"""Sets a resource value by key.
|
|
197
197
|
|
|
198
198
|
Args:
|
|
199
|
-
key: The
|
|
199
|
+
key: The resource key
|
|
200
200
|
value: The value to set
|
|
201
201
|
|
|
202
202
|
Raises:
|
|
203
|
-
FileNotFoundError: If
|
|
204
|
-
ValueError: If the updated
|
|
203
|
+
FileNotFoundError: If resource file doesn't exist
|
|
204
|
+
ValueError: If the updated resource is invalid
|
|
205
205
|
"""
|
|
206
206
|
try:
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
resource = self.load()
|
|
208
|
+
resource_dict = resource.model_dump()
|
|
209
209
|
|
|
210
210
|
if "." in key:
|
|
211
|
-
self._set_dot_notation_key(
|
|
211
|
+
self._set_dot_notation_key(resource_dict, key, value)
|
|
212
212
|
else:
|
|
213
|
-
|
|
213
|
+
resource_dict[key] = value
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
self.save(
|
|
215
|
+
updated_resource = resource.model_validate(resource_dict)
|
|
216
|
+
self.save(updated_resource)
|
|
217
217
|
except ValidationError as e:
|
|
218
218
|
raise ValueError(
|
|
219
219
|
f"Invalid value set for '{self.__class__.__name__}' with "
|
|
@@ -226,31 +226,31 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
226
226
|
) from e
|
|
227
227
|
|
|
228
228
|
def update(self: Self, updates: dict[str, Any]) -> MutablePydanticResource:
|
|
229
|
-
"""Updates multiple
|
|
229
|
+
"""Updates multiple resource values at once.
|
|
230
230
|
|
|
231
231
|
Args:
|
|
232
232
|
updates: Dictionary of key-value pairs to update
|
|
233
233
|
|
|
234
234
|
Returns:
|
|
235
|
-
The updated
|
|
235
|
+
The updated Resource object
|
|
236
236
|
|
|
237
237
|
Raises:
|
|
238
|
-
FileNotFoundError: If
|
|
239
|
-
ValueError: If the updates
|
|
238
|
+
FileNotFoundError: If resource file doesn't exist
|
|
239
|
+
ValueError: If the updates resource is invalid
|
|
240
240
|
"""
|
|
241
241
|
try:
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
resource = self.load()
|
|
243
|
+
resource_dict = resource.model_dump()
|
|
244
244
|
|
|
245
245
|
flat_updates = {k: v for k, v in updates.items() if "." not in k}
|
|
246
|
-
|
|
246
|
+
resource_dict.update(flat_updates)
|
|
247
247
|
|
|
248
248
|
for key, value in updates.items():
|
|
249
249
|
if "." in key:
|
|
250
|
-
self._set_dot_notation_key(
|
|
250
|
+
self._set_dot_notation_key(resource_dict, key, value)
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
self.save(
|
|
252
|
+
updated_resource = resource.model_validate(resource_dict)
|
|
253
|
+
self.save(updated_resource)
|
|
254
254
|
except ValidationError as e:
|
|
255
255
|
raise ValueError(
|
|
256
256
|
f"Invalid value set for '{self.__class__.__name__}' with "
|
|
@@ -262,14 +262,14 @@ class MutablePydanticResourceManager(PydanticResourceManager[MutablePydanticReso
|
|
|
262
262
|
f"at: '{self.path}' when setting updates {updates}"
|
|
263
263
|
) from e
|
|
264
264
|
|
|
265
|
-
return
|
|
265
|
+
return updated_resource
|
|
266
266
|
|
|
267
267
|
def reset(self: Self) -> MutablePydanticResource:
|
|
268
|
-
"""Resets the
|
|
268
|
+
"""Resets the resources to defaults.
|
|
269
269
|
|
|
270
270
|
Returns:
|
|
271
|
-
The new default
|
|
271
|
+
The new default resource object
|
|
272
272
|
"""
|
|
273
|
-
|
|
274
|
-
self.save(
|
|
275
|
-
return
|
|
273
|
+
resource = self.model_class.reset()
|
|
274
|
+
self.save(resource)
|
|
275
|
+
return resource
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.5.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 5,
|
|
31
|
+
__version__ = version = '0.5.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 5, 2)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g4490dbb9d'
|
|
@@ -13,6 +13,7 @@ from fmu.datamodels.fmu_results.global_configuration import (
|
|
|
13
13
|
GlobalConfiguration,
|
|
14
14
|
StratigraphyElement,
|
|
15
15
|
)
|
|
16
|
+
from pydantic import ValidationError
|
|
16
17
|
|
|
17
18
|
from fmu.settings._global_config import (
|
|
18
19
|
InvalidGlobalConfigurationError,
|
|
@@ -291,29 +292,51 @@ def test_validate_global_config_strict_stratigraphy_names(
|
|
|
291
292
|
|
|
292
293
|
|
|
293
294
|
@pytest.mark.parametrize("fmu_load", [True, False])
|
|
294
|
-
def
|
|
295
|
+
def test_load_global_configuration_raises_on_invalid_yaml_structure(
|
|
295
296
|
fmu_load: bool, tmp_path: Path
|
|
296
297
|
) -> None:
|
|
297
|
-
"""Tests
|
|
298
|
+
"""Tests that ValidationError is raised for invalid YAML structure."""
|
|
298
299
|
config_path = tmp_path / "global_config.yml"
|
|
299
300
|
with open(config_path, "w") as f:
|
|
300
301
|
f.write("foo=bar")
|
|
301
302
|
|
|
302
|
-
|
|
303
|
+
with pytest.raises(ValidationError):
|
|
304
|
+
load_global_configuration_if_present(config_path, fmu_load=fmu_load)
|
|
303
305
|
|
|
304
306
|
|
|
305
307
|
@pytest.mark.parametrize("fmu_load", [True, False])
|
|
306
|
-
def
|
|
308
|
+
def test_load_global_configuration_raises_on_missing_required_fields(
|
|
307
309
|
fmu_load: bool,
|
|
308
310
|
tmp_path: Path,
|
|
309
311
|
global_variables_with_masterdata: dict[str, Any],
|
|
310
312
|
) -> None:
|
|
311
|
-
"""Tests
|
|
313
|
+
"""Tests that ValidationError is raised for missing required fields."""
|
|
312
314
|
config_path = tmp_path / "global_config.yml"
|
|
313
315
|
del global_variables_with_masterdata["masterdata"]
|
|
314
316
|
with open(config_path, "w") as f:
|
|
315
317
|
yaml.safe_dump(global_variables_with_masterdata, f)
|
|
316
318
|
|
|
319
|
+
with pytest.raises(ValidationError):
|
|
320
|
+
load_global_configuration_if_present(config_path, fmu_load=fmu_load)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize("fmu_load", [True, False])
|
|
324
|
+
def test_load_global_configuration_returns_none_on_file_not_found(
|
|
325
|
+
fmu_load: bool, tmp_path: Path
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Tests that None is returned when file doesn't exist."""
|
|
328
|
+
config_path = tmp_path / "non_existent_file.yml"
|
|
329
|
+
assert load_global_configuration_if_present(config_path, fmu_load=fmu_load) is None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.mark.parametrize("fmu_load", [True, False])
|
|
333
|
+
def test_load_global_configuration_returns_none_on_yaml_parse_error(
|
|
334
|
+
fmu_load: bool, tmp_path: Path
|
|
335
|
+
) -> None:
|
|
336
|
+
"""Tests that None is returned on YAML parsing errors."""
|
|
337
|
+
config_path = tmp_path / "invalid.yml"
|
|
338
|
+
with open(config_path, "w") as f:
|
|
339
|
+
f.write("key: [unclosed list")
|
|
317
340
|
assert load_global_configuration_if_present(config_path, fmu_load=fmu_load) is None
|
|
318
341
|
|
|
319
342
|
|
|
@@ -327,10 +350,23 @@ def test_find_global_config_file_not_there(tmp_path: Path) -> None:
|
|
|
327
350
|
assert _find_global_config_file([tmp_path / "dne"]) is None
|
|
328
351
|
|
|
329
352
|
|
|
330
|
-
def
|
|
331
|
-
|
|
353
|
+
def test_find_global_config_file_malformed_raises_validation_error(
|
|
354
|
+
tmp_path: Path,
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Tests that malformed global config file raises ValidationError."""
|
|
332
357
|
with open(tmp_path / "global_master_config.yml", "w") as f:
|
|
333
358
|
f.write("foo: bar")
|
|
359
|
+
with pytest.raises(ValidationError):
|
|
360
|
+
_find_global_config_file([tmp_path])
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_find_global_config_file_skips_invalid_yaml_and_continues(
|
|
364
|
+
tmp_path: Path,
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Tests that function skips files with YAML parse errors and continues."""
|
|
367
|
+
with open(tmp_path / "global_config.yml", "w") as f:
|
|
368
|
+
f.write("key: [unclosed list")
|
|
369
|
+
|
|
334
370
|
assert _find_global_config_file([tmp_path]) is None
|
|
335
371
|
|
|
336
372
|
|
|
@@ -363,32 +399,55 @@ def test_find_global_variables_file_not_there(tmp_path: Path) -> None:
|
|
|
363
399
|
assert _find_global_variables_file([tmp_path / "dne"]) is None
|
|
364
400
|
|
|
365
401
|
|
|
366
|
-
def
|
|
367
|
-
|
|
402
|
+
def test_find_global_variables_file_malformed_raises_validation_error(
|
|
403
|
+
tmp_path: Path,
|
|
404
|
+
) -> None:
|
|
405
|
+
"""Tests that malformed global variables file raises ValidationError."""
|
|
368
406
|
with open(tmp_path / "global_variables.yml", "w") as f:
|
|
369
407
|
f.write("foo: bar")
|
|
370
|
-
|
|
408
|
+
with pytest.raises(ValidationError):
|
|
409
|
+
_find_global_variables_file([tmp_path])
|
|
371
410
|
|
|
372
411
|
|
|
373
|
-
def
|
|
374
|
-
|
|
412
|
+
def test_find_global_variables_file_returns_none_when_not_found(
|
|
413
|
+
fmuconfig_with_output: Path,
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Tests that None is returned when no global variables file exists."""
|
|
375
416
|
tmp_path = fmuconfig_with_output
|
|
376
417
|
some_dir = tmp_path / "some_dir"
|
|
377
418
|
some_dir.mkdir()
|
|
378
|
-
some_file = some_dir / "some_file"
|
|
379
|
-
some_file.touch()
|
|
380
419
|
does_not_exist = tmp_path / "bad"
|
|
381
|
-
assert _find_global_variables_file([does_not_exist, some_dir
|
|
420
|
+
assert _find_global_variables_file([does_not_exist, some_dir]) is None
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_find_global_variables_file_skips_invalid_yaml_and_continues(
|
|
424
|
+
tmp_path: Path,
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Tests that function skips files with YAML parse errors and continues."""
|
|
427
|
+
invalid_yaml_file = tmp_path / "global_variables.yml"
|
|
428
|
+
with open(invalid_yaml_file, "w") as f:
|
|
429
|
+
f.write("key: [unclosed list")
|
|
430
|
+
|
|
431
|
+
assert _find_global_variables_file([tmp_path]) is None
|
|
432
|
+
|
|
382
433
|
|
|
434
|
+
def test_find_global_variables_file_raises_on_empty_file(
|
|
435
|
+
fmuconfig_with_output: Path,
|
|
436
|
+
) -> None:
|
|
437
|
+
"""Tests that ValidationError is raised for empty/invalid file."""
|
|
438
|
+
tmp_path = fmuconfig_with_output
|
|
439
|
+
some_file = tmp_path / "some_file"
|
|
440
|
+
some_file.touch()
|
|
441
|
+
with pytest.raises(ValidationError):
|
|
442
|
+
_find_global_variables_file([some_file])
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def test_find_global_variables_file_returns_valid_config(
|
|
446
|
+
fmuconfig_with_output: Path,
|
|
447
|
+
) -> None:
|
|
448
|
+
"""Tests finding a valid global variables file in fmuconfig."""
|
|
383
449
|
assert isinstance(
|
|
384
|
-
_find_global_variables_file(
|
|
385
|
-
[
|
|
386
|
-
does_not_exist,
|
|
387
|
-
some_dir,
|
|
388
|
-
some_file,
|
|
389
|
-
fmuconfig_with_output / "fmuconfig/output",
|
|
390
|
-
]
|
|
391
|
-
),
|
|
450
|
+
_find_global_variables_file([fmuconfig_with_output / "fmuconfig/output"]),
|
|
392
451
|
GlobalConfiguration,
|
|
393
452
|
)
|
|
394
453
|
|
|
@@ -649,3 +649,18 @@ def test_ensure_can_write_foreign_lock(fmu_dir: ProjectFMUDirectory) -> None:
|
|
|
649
649
|
pytest.raises(PermissionError, match="Cannot write to .fmu directory"),
|
|
650
650
|
):
|
|
651
651
|
lock.ensure_can_write()
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def test_manual_delete_invalidates_lock_file_on_refresh(
|
|
655
|
+
fmu_dir: ProjectFMUDirectory,
|
|
656
|
+
) -> None:
|
|
657
|
+
"""Tests that if a user deletes anothers lock it's invalidated on a refresh."""
|
|
658
|
+
fmu_dir._lock.acquire()
|
|
659
|
+
assert fmu_dir._lock.is_acquired() is True
|
|
660
|
+
|
|
661
|
+
# Someone deletes the lock
|
|
662
|
+
fmu_dir._lock.path.unlink()
|
|
663
|
+
|
|
664
|
+
with pytest.raises(LockError, match="Cannot refresh: lock file does not exist"):
|
|
665
|
+
fmu_dir._lock.refresh()
|
|
666
|
+
assert fmu_dir._lock.is_acquired() is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|