fmu-settings 0.5.4__py3-none-any.whl → 0.6.0__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/settings/_fmu_dir.py CHANGED
@@ -4,12 +4,13 @@ from pathlib import Path
4
4
  from typing import TYPE_CHECKING, Any, Final, Self, TypeAlias, cast
5
5
 
6
6
  from ._logging import null_logger
7
+ from ._readme_texts import PROJECT_README_CONTENT, USER_README_CONTENT
7
8
  from ._resources.cache_manager import CacheManager
8
9
  from ._resources.config_managers import (
9
10
  ProjectConfigManager,
10
11
  UserConfigManager,
11
12
  )
12
- from ._resources.lock_manager import LockManager
13
+ from ._resources.lock_manager import DEFAULT_LOCK_TIMEOUT, LockManager
13
14
  from .models.project_config import ProjectConfig
14
15
  from .models.user_config import UserConfig
15
16
 
@@ -24,13 +25,22 @@ class FMUDirectoryBase:
24
25
  config: FMUConfigManager
25
26
  _lock: LockManager
26
27
  _cache_manager: CacheManager
27
-
28
- def __init__(self: Self, base_path: str | Path) -> None:
28
+ _README_CONTENT: str = ""
29
+
30
+ def __init__(
31
+ self: Self,
32
+ base_path: str | Path,
33
+ cache_revisions: int = CacheManager.MIN_REVISIONS,
34
+ *,
35
+ lock_timeout_seconds: int = DEFAULT_LOCK_TIMEOUT,
36
+ ) -> None:
29
37
  """Initializes access to a .fmu directory.
30
38
 
31
39
  Args:
32
40
  base_path: The directory containing the .fmu directory or one of its parent
33
41
  dirs
42
+ cache_revisions: Number of revisions to retain in the cache. Minimum is 5.
43
+ lock_timeout_seconds: Lock expiration time in seconds. Default 20 minutes.
34
44
 
35
45
  Raises:
36
46
  FileExistsError: If .fmu exists but is not a directory
@@ -39,8 +49,8 @@ class FMUDirectoryBase:
39
49
  """
40
50
  self.base_path = Path(base_path).resolve()
41
51
  logger.debug(f"Initializing FMUDirectory from '{base_path}'")
42
- self._lock = LockManager(self)
43
- self._cache_manager = CacheManager(self, max_revisions=5)
52
+ self._lock = LockManager(self, timeout_seconds=lock_timeout_seconds)
53
+ self._cache_manager = CacheManager(self, max_revisions=cache_revisions)
44
54
 
45
55
  fmu_dir = self.base_path / ".fmu"
46
56
  if fmu_dir.exists():
@@ -72,8 +82,15 @@ class FMUDirectoryBase:
72
82
 
73
83
  @cache_max_revisions.setter
74
84
  def cache_max_revisions(self: Self, value: int) -> None:
75
- """Update the retention limit for revision snapshots."""
76
- self._cache_manager.max_revisions = value
85
+ """Update the retention limit for revision snapshots.
86
+
87
+ Args:
88
+ value: The new maximum number of revisions to retain. Minimum value is 5.
89
+ Values below 5 are set to 5.
90
+ """
91
+ clamped_value = max(CacheManager.MIN_REVISIONS, value)
92
+ self._cache_manager.max_revisions = clamped_value
93
+ self.set_config_value("cache_max_revisions", clamped_value)
77
94
 
78
95
  def get_config_value(self: Self, key: str, default: Any = None) -> Any:
79
96
  """Gets a configuration value by key.
@@ -230,15 +247,59 @@ class FMUDirectoryBase:
230
247
  """
231
248
  return self.get_file_path(relative_path).exists()
232
249
 
250
+ def restore(self: Self) -> None:
251
+ """Attempt to reconstruct missing .fmu files from in-memory state."""
252
+ if not self.path.exists():
253
+ self.path.mkdir(parents=True, exist_ok=True)
254
+ logger.info("Recreated missing .fmu directory at %s", self.path)
255
+
256
+ readme_path = self.get_file_path("README")
257
+ if self._README_CONTENT and not readme_path.exists():
258
+ self.write_text_file("README", self._README_CONTENT)
259
+ logger.info("Restored README at %s", readme_path)
260
+
261
+ config_path = self.config.path
262
+ if not config_path.exists():
263
+ cached_model = getattr(self.config, "_cache", None)
264
+ if cached_model is not None:
265
+ self.config.save(cached_model)
266
+ logger.info("Restored config.json from cached model at %s", config_path)
267
+ else:
268
+ self.config.reset()
269
+ logger.info("Restored config.json from defaults at %s", config_path)
270
+
233
271
 
234
272
  class ProjectFMUDirectory(FMUDirectoryBase):
235
273
  if TYPE_CHECKING:
236
274
  config: ProjectConfigManager
237
275
 
238
- def __init__(self, base_path: str | Path) -> None:
239
- """Initializes a project-based .fmu directory."""
276
+ _README_CONTENT: str = PROJECT_README_CONTENT
277
+
278
+ def __init__(
279
+ self: Self,
280
+ base_path: str | Path,
281
+ *,
282
+ lock_timeout_seconds: int = DEFAULT_LOCK_TIMEOUT,
283
+ ) -> None:
284
+ """Initializes a project-based .fmu directory.
285
+
286
+ Args:
287
+ base_path: Project directory containing the .fmu folder.
288
+ lock_timeout_seconds: Lock expiration time in seconds. Default 20 minutes.
289
+ """
240
290
  self.config = ProjectConfigManager(self)
241
- super().__init__(base_path)
291
+ super().__init__(
292
+ base_path,
293
+ CacheManager.MIN_REVISIONS,
294
+ lock_timeout_seconds=lock_timeout_seconds,
295
+ )
296
+ try:
297
+ max_revisions = self.config.get(
298
+ "cache_max_revisions", CacheManager.MIN_REVISIONS
299
+ )
300
+ self._cache_manager.max_revisions = max_revisions
301
+ except FileNotFoundError:
302
+ pass
242
303
 
243
304
  def update_config(self: Self, updates: dict[str, Any]) -> ProjectConfig:
244
305
  """Updates multiple configuration values at once.
@@ -309,10 +370,31 @@ class UserFMUDirectory(FMUDirectoryBase):
309
370
  if TYPE_CHECKING:
310
371
  config: UserConfigManager
311
372
 
312
- def __init__(self) -> None:
313
- """Initializes a project-based .fmu directory."""
373
+ _README_CONTENT: str = USER_README_CONTENT
374
+
375
+ def __init__(
376
+ self: Self,
377
+ *,
378
+ lock_timeout_seconds: int = DEFAULT_LOCK_TIMEOUT,
379
+ ) -> None:
380
+ """Initializes a user .fmu directory.
381
+
382
+ Args:
383
+ lock_timeout_seconds: Lock expiration time in seconds. Default 20 minutes.
384
+ """
314
385
  self.config = UserConfigManager(self)
315
- super().__init__(Path.home())
386
+ super().__init__(
387
+ Path.home(),
388
+ CacheManager.MIN_REVISIONS,
389
+ lock_timeout_seconds=lock_timeout_seconds,
390
+ )
391
+ try:
392
+ max_revisions = self.config.get(
393
+ "cache_max_revisions", CacheManager.MIN_REVISIONS
394
+ )
395
+ self._cache_manager.max_revisions = max_revisions
396
+ except FileNotFoundError:
397
+ pass
316
398
 
317
399
  def update_config(self: Self, updates: dict[str, Any]) -> UserConfig:
318
400
  """Updates multiple configuration values at once.
fmu/settings/_init.py CHANGED
@@ -1,43 +1,18 @@
1
1
  """Initializes the .fmu directory."""
2
2
 
3
3
  from pathlib import Path
4
- from textwrap import dedent
5
4
  from typing import Any, Final
6
5
 
7
6
  from fmu.datamodels.fmu_results.global_configuration import GlobalConfiguration
8
7
 
9
8
  from ._fmu_dir import ProjectFMUDirectory, UserFMUDirectory
10
9
  from ._logging import null_logger
10
+ from ._readme_texts import PROJECT_README_CONTENT, USER_README_CONTENT
11
+ from ._resources.lock_manager import DEFAULT_LOCK_TIMEOUT
11
12
  from .models.project_config import ProjectConfig
12
13
 
13
14
  logger: Final = null_logger(__name__)
14
15
 
15
- _README = dedent("""\
16
- This directory contains static configuration data for your FMU project.
17
-
18
- You should *not* manually modify files within this directory. Doing so may
19
- result in erroneous behavior or erroneous data in your FMU project.
20
-
21
- Changes to data stored within this directory must happen through the FMU
22
- Settings application.
23
-
24
- Run `fmu-settings` to do this.
25
- """)
26
-
27
- _USER_README = dedent("""\
28
- This directory contains static data and configuration elements used by some
29
- components in FMU. It may also contains sensitive access tokens that should not be
30
- shared with others.
31
-
32
- You should *not* manually modify files within this directory. Doing so may
33
- result in erroneous behavior by some FMU components.
34
-
35
- Changes to data stored within this directory must happen through the FMU
36
- Settings application.
37
-
38
- Run `fmu-settings` to do this.
39
- """)
40
-
41
16
 
42
17
  def _create_fmu_directory(base_path: Path) -> None:
43
18
  """Creates the .fmu directory.
@@ -71,6 +46,8 @@ def init_fmu_directory(
71
46
  base_path: str | Path,
72
47
  config_data: ProjectConfig | dict[str, Any] | None = None,
73
48
  global_config: GlobalConfiguration | None = None,
49
+ *,
50
+ lock_timeout_seconds: int = DEFAULT_LOCK_TIMEOUT,
74
51
  ) -> ProjectFMUDirectory:
75
52
  """Creates and initializes a .fmu directory.
76
53
 
@@ -83,6 +60,7 @@ def init_fmu_directory(
83
60
  data.
84
61
  global_config: Optional GlobaConfiguration instance with existing global config
85
62
  data.
63
+ lock_timeout_seconds: Lock expiration time in seconds. Default 20 minutes.
86
64
 
87
65
  Returns:
88
66
  Instance of FMUDirectory
@@ -98,8 +76,11 @@ def init_fmu_directory(
98
76
 
99
77
  _create_fmu_directory(base_path)
100
78
 
101
- fmu_dir = ProjectFMUDirectory(base_path)
102
- fmu_dir.write_text_file("README", _README)
79
+ fmu_dir = ProjectFMUDirectory(
80
+ base_path,
81
+ lock_timeout_seconds=lock_timeout_seconds,
82
+ )
83
+ fmu_dir.write_text_file("README", PROJECT_README_CONTENT)
103
84
 
104
85
  fmu_dir.config.reset()
105
86
  if config_data:
@@ -115,9 +96,15 @@ def init_fmu_directory(
115
96
  return fmu_dir
116
97
 
117
98
 
118
- def init_user_fmu_directory() -> UserFMUDirectory:
99
+ def init_user_fmu_directory(
100
+ *,
101
+ lock_timeout_seconds: int = DEFAULT_LOCK_TIMEOUT,
102
+ ) -> UserFMUDirectory:
119
103
  """Creates and initializes a user's $HOME/.fmu directory.
120
104
 
105
+ Args:
106
+ lock_timeout_seconds: Lock expiration time in seconds. Default 20 minutes.
107
+
121
108
  Returns:
122
109
  Instance of FMUDirectory
123
110
 
@@ -131,8 +118,8 @@ def init_user_fmu_directory() -> UserFMUDirectory:
131
118
 
132
119
  _create_fmu_directory(Path.home())
133
120
 
134
- fmu_dir = UserFMUDirectory()
135
- fmu_dir.write_text_file("README", _USER_README)
121
+ fmu_dir = UserFMUDirectory(lock_timeout_seconds=lock_timeout_seconds)
122
+ fmu_dir.write_text_file("README", USER_README_CONTENT)
136
123
 
137
124
  fmu_dir.config.reset()
138
125
  logger.debug(f"Successfully initialized .fmu directory at '{fmu_dir}'")
@@ -0,0 +1,34 @@
1
+ """Shared README content for .fmu directories."""
2
+
3
+ from textwrap import dedent
4
+ from typing import Final
5
+
6
+ PROJECT_README_CONTENT: Final[str] = dedent(
7
+ """\
8
+ This directory contains static configuration data for your FMU project.
9
+
10
+ You should *not* manually modify files within this directory. Doing so may
11
+ result in erroneous behavior or erroneous data in your FMU project.
12
+
13
+ Changes to data stored within this directory must happen through the FMU
14
+ Settings application.
15
+
16
+ Run `fmu-settings` to do this.
17
+ """
18
+ )
19
+
20
+ USER_README_CONTENT: Final[str] = dedent(
21
+ """\
22
+ This directory contains static data and configuration elements used by some
23
+ components in FMU. It may also contains sensitive access tokens that should not be
24
+ shared with others.
25
+
26
+ You should *not* manually modify files within this directory. Doing so may
27
+ result in erroneous behavior by some FMU components.
28
+
29
+ Changes to data stored within this directory must happen through the FMU
30
+ Settings application.
31
+
32
+ Run `fmu-settings` to do this.
33
+ """
34
+ )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from datetime import UTC, datetime
6
6
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Final, Self
7
+ from typing import TYPE_CHECKING, ClassVar, Final, Self
8
8
  from uuid import uuid4
9
9
 
10
10
  from fmu.settings._logging import null_logger
@@ -25,6 +25,8 @@ _CACHEDIR_TAG_CONTENT: Final = (
25
25
  class CacheManager:
26
26
  """Stores complete file revisions under the `.fmu/cache` tree."""
27
27
 
28
+ MIN_REVISIONS: ClassVar[int] = 5
29
+
28
30
  def __init__(
29
31
  self: Self,
30
32
  fmu_dir: FMUDirectoryBase,
@@ -35,10 +37,11 @@ class CacheManager:
35
37
  Args:
36
38
  fmu_dir: The FMUDirectory instance.
37
39
  max_revisions: Maximum number of revisions to retain. Default is 5.
40
+ Values below 5 are set to 5.
38
41
  """
39
42
  self._fmu_dir = fmu_dir
40
43
  self._cache_root = Path("cache")
41
- self._max_revisions = max(0, max_revisions)
44
+ self._max_revisions = max(self.MIN_REVISIONS, max_revisions)
42
45
 
43
46
  @property
44
47
  def max_revisions(self: Self) -> int:
@@ -47,8 +50,13 @@ class CacheManager:
47
50
 
48
51
  @max_revisions.setter
49
52
  def max_revisions(self: Self, value: int) -> None:
50
- """Update the per-resource revision retention."""
51
- self._max_revisions = max(0, value)
53
+ """Update the per-resource revision retention.
54
+
55
+ Args:
56
+ value: The new maximum number of revisions. Minimum value is 5.
57
+ Values below 5 are set to 5.
58
+ """
59
+ self._max_revisions = max(self.MIN_REVISIONS, value)
52
60
 
53
61
  def store_revision(
54
62
  self: Self,
@@ -65,12 +73,8 @@ class CacheManager:
65
73
  encoding: Encoding used when persisting the snapshot. Defaults to UTF-8.
66
74
 
67
75
  Returns:
68
- Absolute filesystem path to the stored snapshot, or ``None`` if caching is
69
- disabled (``max_revisions`` equals zero).
76
+ Absolute filesystem path to the stored snapshot.
70
77
  """
71
- if self.max_revisions == 0:
72
- return None
73
-
74
78
  resource_file_path = Path(resource_file_path)
75
79
  cache_dir = self._ensure_resource_cache_dir(resource_file_path)
76
80
  snapshot_name = self._snapshot_filename(resource_file_path)
fmu/settings/_version.py CHANGED
@@ -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.4'
32
- __version_tuple__ = version_tuple = (0, 5, 4)
31
+ __version__ = version = '0.6.0'
32
+ __version_tuple__ = version_tuple = (0, 6, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -23,6 +23,7 @@ class ProjectConfig(ResettableBaseModel):
23
23
  masterdata: Masterdata | None = Field(default=None)
24
24
  model: Model | None = Field(default=None)
25
25
  access: Access | None = Field(default=None)
26
+ cache_max_revisions: int = Field(default=5, ge=5)
26
27
 
27
28
  @classmethod
28
29
  def reset(cls: type[Self]) -> Self:
@@ -38,4 +39,5 @@ class ProjectConfig(ResettableBaseModel):
38
39
  masterdata=None,
39
40
  model=None,
40
41
  access=None,
42
+ cache_max_revisions=5,
41
43
  )
@@ -10,6 +10,7 @@ import annotated_types
10
10
  from pydantic import (
11
11
  AwareDatetime,
12
12
  BaseModel,
13
+ Field,
13
14
  SecretStr,
14
15
  field_serializer,
15
16
  field_validator,
@@ -42,6 +43,7 @@ class UserConfig(ResettableBaseModel):
42
43
 
43
44
  version: VersionStr
44
45
  created_at: AwareDatetime
46
+ cache_max_revisions: int = Field(default=5, ge=5)
45
47
  user_api_keys: UserAPIKeys
46
48
  recent_project_directories: RecentProjectDirectories
47
49
 
@@ -51,6 +53,7 @@ class UserConfig(ResettableBaseModel):
51
53
  return cls(
52
54
  version=__version__,
53
55
  created_at=datetime.now(UTC),
56
+ cache_max_revisions=5,
54
57
  user_api_keys=UserAPIKeys(),
55
58
  recent_project_directories=[],
56
59
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmu-settings
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Summary: A library for managing FMU settings
5
5
  Author-email: Equinor <fg-fmu_atlas@equinor.com>
6
6
  License: GPL-3.0
@@ -1,14 +1,15 @@
1
1
  fmu/__init__.py,sha256=htx6HlMme77I6pZ8U256-2B2cMJuELsu3JN3YM2Efh4,144
2
2
  fmu/settings/__init__.py,sha256=CkEE7al_uBCQO1lxBKN5LzyCwzzH5Aq6kkEIR7f-zTw,336
3
- fmu/settings/_fmu_dir.py,sha256=J0K6CWlkk3Z5ojyOZaUDYQadx0_FiCyKNn1ZGs79h1o,11592
3
+ fmu/settings/_fmu_dir.py,sha256=RfDE-CMcRsFP1Xhv2uXDXpjNEQ4sPnpTLAZgRq5YCNQ,14676
4
4
  fmu/settings/_global_config.py,sha256=C0_o99OhOc49ynz4h6ygbbHHH8OOI5lcVFr-9FCwD0c,9331
5
- fmu/settings/_init.py,sha256=ucueS0BlEsM3MkX7IaRISloH4vF7-_ZKSphrORbHgJ4,4381
5
+ fmu/settings/_init.py,sha256=rgt5aZou54RiVAOegC4kzoigJcZefPY4Kv6ke7OAIxI,4014
6
6
  fmu/settings/_logging.py,sha256=nEdmZlNCBsB1GfDmFMKCjZmeuRp3CRlbz1EYUemc95Y,1104
7
- fmu/settings/_version.py,sha256=OrfVZdCDQ-QC6dUnxdROooJjwvLfeDMedTBstpAdSBU,704
7
+ fmu/settings/_readme_texts.py,sha256=vVU_MTSZhN91z_Y16pCmA3fcCWxkWPF2aR1KfuI6Jwo,1067
8
+ fmu/settings/_version.py,sha256=MAYWefOLb6kbIRub18WSzK6ggSjz1LNLy9aDRlX9Ea4,704
8
9
  fmu/settings/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
10
  fmu/settings/types.py,sha256=aeXEsznBTT1YRRY_LSRqK1j2gmMmyLYYTGYl3a9fweU,513
10
11
  fmu/settings/_resources/__init__.py,sha256=LHYR_F7lNGdv8N6R3cEwds5CJQpkOthXFqsEs24vgF8,118
11
- fmu/settings/_resources/cache_manager.py,sha256=TCVefkQEZuEdrzS0yOs7K-fCMouU4fctRtEvj4Du28M,5656
12
+ fmu/settings/_resources/cache_manager.py,sha256=LDCmPy06DdF7Uo_QJrCm9cCpT9cBMOIUEuvKR2YSDQQ,5787
12
13
  fmu/settings/_resources/config_managers.py,sha256=QcCLlSw8KdJKrkhGax5teFJzjgQG3ym7Ljs1DykjFbc,1570
13
14
  fmu/settings/_resources/lock_manager.py,sha256=3h1wRhDvn1xYkKoPs4I1e-6PO8Ctu8StdS7Nh5DW5O4,10508
14
15
  fmu/settings/_resources/pydantic_resource_manager.py,sha256=iCLO0xzTPhcYyhCoafKf7DAxjIDv8VBQIqRl-HYEDok,9472
@@ -16,10 +17,10 @@ fmu/settings/models/__init__.py,sha256=lRlXgl55ba2upmDzdvzx8N30JMq2Osnm8aa_xxTZn
16
17
  fmu/settings/models/_enums.py,sha256=SQUZ-2mQcTx4F0oefPFfuQzMKsKTSFSB-wq_CH7TBRE,734
17
18
  fmu/settings/models/_mappings.py,sha256=Z4Ex7MtmajBr6FjaNzmwDRwtJlaZZ8YKh9NDmZHRKPI,2832
18
19
  fmu/settings/models/lock_info.py,sha256=-oHDF9v9bDLCoFvEg4S6XXYLeo19zRAZ8HynCv75VWg,711
19
- fmu/settings/models/project_config.py,sha256=pxb54JmpXNMVAFUu_yJ89dNrYEk6hrPuFfFUpf84Jh0,1099
20
- fmu/settings/models/user_config.py,sha256=dWFTcZY6UnEgNTuGqB-izraJ657PecsW0e0Nt9GBDhI,2666
21
- fmu_settings-0.5.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
22
- fmu_settings-0.5.4.dist-info/METADATA,sha256=kysnXaWPfT7_e-lqxRIEOgbdirMo-VvzbcdNeFztyzA,2116
23
- fmu_settings-0.5.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- fmu_settings-0.5.4.dist-info/top_level.txt,sha256=Z-FIY3pxn0UK2Wxi9IJ7fKoLSraaxuNGi1eokiE0ShM,4
25
- fmu_settings-0.5.4.dist-info/RECORD,,
20
+ fmu/settings/models/project_config.py,sha256=uja8F956m76ZrtAzuWogLySgb3seYmoTQp5xJayBqyE,1188
21
+ fmu/settings/models/user_config.py,sha256=zl8-_UCvP9eygCuXksLo3eR9BmhPoeCRRn2Sn7ABsZY,2766
22
+ fmu_settings-0.6.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
23
+ fmu_settings-0.6.0.dist-info/METADATA,sha256=sw3f_PxM4GOfCsIYrSaQAbCg8ybxwecTAoLQ6-eFZKQ,2116
24
+ fmu_settings-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ fmu_settings-0.6.0.dist-info/top_level.txt,sha256=Z-FIY3pxn0UK2Wxi9IJ7fKoLSraaxuNGi1eokiE0ShM,4
26
+ fmu_settings-0.6.0.dist-info/RECORD,,