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.

@@ -0,0 +1,49 @@
1
+ """The model for config.json."""
2
+
3
+ import getpass
4
+ from datetime import UTC, datetime
5
+ from typing import Self
6
+ from uuid import UUID # noqa TC003
7
+
8
+ from pydantic import AwareDatetime, BaseModel, Field
9
+
10
+ from fmu.settings import __version__
11
+ from fmu.settings.types import ResettableBaseModel, VersionStr # noqa TC001
12
+
13
+ from .smda import Smda
14
+
15
+
16
+ class Masterdata(BaseModel):
17
+ """The ``masterdata`` block contains information related to masterdata.
18
+
19
+ Currently, SMDA holds the masterdata.
20
+ """
21
+
22
+ smda: Smda | None = Field(default=None)
23
+ """Block containing SMDA-related attributes. See :class:`Smda`."""
24
+
25
+
26
+ class ProjectConfig(ResettableBaseModel):
27
+ """The configuration file in a .fmu directory.
28
+
29
+ Stored as config.json.
30
+ """
31
+
32
+ version: VersionStr
33
+ created_at: AwareDatetime
34
+ created_by: str
35
+ masterdata: Masterdata
36
+
37
+ @classmethod
38
+ def reset(cls: type[Self]) -> Self:
39
+ """Resets the configuration to defaults.
40
+
41
+ Returns:
42
+ The new default Config object
43
+ """
44
+ return cls(
45
+ version=__version__,
46
+ created_at=datetime.now(UTC),
47
+ created_by=getpass.getuser(),
48
+ masterdata=Masterdata(),
49
+ )
@@ -0,0 +1,90 @@
1
+ """Models for SMDA masterdata."""
2
+
3
+ from uuid import UUID
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from fmu.settings.types import VersionStr # noqa TC001
8
+
9
+
10
+ class SmdaIdentifier(BaseModel):
11
+ """The identifier for something known to SMDA."""
12
+
13
+ identifier: str
14
+ """Identifier known to SMDA."""
15
+
16
+ uuid: UUID
17
+ """Identifier known to SMDA."""
18
+
19
+
20
+ class CountryItem(SmdaIdentifier):
21
+ """A single country in the list of countries known to SMDA."""
22
+
23
+ identifier: str = Field(examples=["Norway"])
24
+ """Identifier known to SMDA."""
25
+
26
+ uuid: UUID = Field(examples=["15ce3b84-766f-4c93-9050-b154861f9100"])
27
+ """Identifier known to SMDA."""
28
+
29
+
30
+ class FieldItem(SmdaIdentifier):
31
+ """A single field in the list of fields known to SMDA."""
32
+
33
+ identifier: str = Field(examples=["OseFax"])
34
+ """Identifier known to SMDA."""
35
+
36
+ uuid: UUID = Field(examples=["15ce3b84-766f-4c93-9050-b154861f9100"])
37
+ """Identifier known to SMDA."""
38
+
39
+
40
+ class CoordinateSystem(SmdaIdentifier):
41
+ """Contains the coordinate system known to SMDA."""
42
+
43
+ identifier: str = Field(examples=["ST_WGS84_UTM37N_P32637"])
44
+ """Identifier known to SMDA."""
45
+
46
+ uuid: UUID = Field(examples=["15ce3b84-766f-4c93-9050-b154861f9100"])
47
+ """Identifier known to SMDA."""
48
+
49
+
50
+ class StratigraphicColumn(SmdaIdentifier):
51
+ """Contains the stratigraphic column known to SMDA."""
52
+
53
+ identifier: str = Field(examples=["DROGON_2020"])
54
+ """Identifier known to SMDA."""
55
+
56
+ uuid: UUID = Field(examples=["15ce3b84-766f-4c93-9050-b154861f9100"])
57
+ """Identifier known to SMDA."""
58
+
59
+
60
+ class DiscoveryItem(BaseModel):
61
+ """A single discovery in the list of discoveries known to SMDA."""
62
+
63
+ short_identifier: str = Field(examples=["SomeDiscovery"])
64
+ """Identifier known to SMDA."""
65
+
66
+ uuid: UUID = Field(examples=["15ce3b84-766f-4c93-9050-b154861f9100"])
67
+ """Identifier known to SMDA."""
68
+
69
+
70
+ class Smda(BaseModel):
71
+ """Contains SMDA-related attributes."""
72
+
73
+ coordinate_system: CoordinateSystem
74
+ """Reference to coordinate system known to SMDA. See :class:`CoordinateSystem`."""
75
+
76
+ country: list[CountryItem]
77
+ """A list referring to countries known to SMDA. First item is primary.
78
+ See :class:`CountryItem`."""
79
+
80
+ discovery: list[DiscoveryItem]
81
+ """A list referring to discoveries known to SMDA. First item is primary.
82
+ See :class:`DiscoveryItem`."""
83
+
84
+ field: list[FieldItem]
85
+ """A list referring to fields known to SMDA. First item is primary.
86
+ See :class:`FieldItem`."""
87
+
88
+ stratigraphic_column: StratigraphicColumn
89
+ """Reference to stratigraphic column known to SMDA.
90
+ See :class:`StratigraphicColumn`."""
@@ -0,0 +1,73 @@
1
+ """The model for config.json."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import UTC, datetime
6
+ from pathlib import Path
7
+ from typing import Annotated, Self
8
+ from uuid import UUID # noqa TC003
9
+
10
+ import annotated_types
11
+ from pydantic import AwareDatetime, BaseModel, SecretStr, field_serializer
12
+
13
+ from fmu.settings import __version__
14
+ from fmu.settings.types import ResettableBaseModel, VersionStr # noqa TC001
15
+
16
+ RecentDirectories = Annotated[set[Path], annotated_types.Len(0, 5)]
17
+
18
+
19
+ class UserAPIKeys(BaseModel):
20
+ """Known API keys stored in a user config."""
21
+
22
+ smda_subscription: SecretStr | None = None
23
+
24
+ @field_serializer("smda_subscription", when_used="json")
25
+ def dump_secret(self, v: SecretStr | None) -> str | None:
26
+ """Write the secret string value when serializing to json."""
27
+ if v is None:
28
+ return None
29
+ return v.get_secret_value()
30
+
31
+
32
+ class UserConfig(ResettableBaseModel):
33
+ """The configuration file in a $HOME/.fmu directory.
34
+
35
+ Stored as config.json.
36
+ """
37
+
38
+ version: VersionStr
39
+ created_at: AwareDatetime
40
+ user_api_keys: UserAPIKeys
41
+ recent_directories: RecentDirectories
42
+
43
+ @classmethod
44
+ def reset(cls: type[Self]) -> Self:
45
+ """Resets the model to an initial state."""
46
+ return cls(
47
+ version=__version__,
48
+ created_at=datetime.now(UTC),
49
+ user_api_keys=UserAPIKeys(),
50
+ recent_directories=set(),
51
+ )
52
+
53
+ def obfuscate_secrets(self: Self) -> Self:
54
+ """Returns a copy of the model with obfuscated secrets.
55
+
56
+ If an API Key is:
57
+
58
+ key: SecretStr = SecretStr("secret")
59
+
60
+ we may want to serialize it to JSON as:
61
+
62
+ {key:"********"}
63
+
64
+ so that we do not serialize the actual value of the secret when, for example,
65
+ returning the user configuration from an API.
66
+ """
67
+ config_dict = self.model_dump()
68
+ # Overwrite secret keys with obfuscated keys
69
+ for k, v in config_dict["user_api_keys"].items():
70
+ if v is not None:
71
+ # Convert SecretStr("*********") to "*********"
72
+ config_dict["user_api_keys"][k] = str(v)
73
+ return self.model_validate(config_dict)
fmu/settings/py.typed ADDED
File without changes
@@ -0,0 +1,211 @@
1
+ """The generic configuration file in a .fmu directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any, Final, Self, TypeVar
7
+ from uuid import UUID # noqa TC003
8
+
9
+ from pydantic import ValidationError
10
+
11
+ from fmu.settings._logging import null_logger
12
+ from fmu.settings.models.project_config import ProjectConfig
13
+ from fmu.settings.models.user_config import UserConfig
14
+ from fmu.settings.types import ResettableBaseModel, VersionStr # noqa TC001
15
+
16
+ from .managers import PydanticResourceManager
17
+
18
+ if TYPE_CHECKING:
19
+ # Avoid circular dependency for type hint in __init__ only
20
+ from fmu.settings._fmu_dir import (
21
+ FMUDirectoryBase,
22
+ ProjectFMUDirectory,
23
+ UserFMUDirectory,
24
+ )
25
+
26
+ logger: Final = null_logger(__name__)
27
+
28
+ T = TypeVar("T", bound=ResettableBaseModel)
29
+
30
+
31
+ class ConfigManager(PydanticResourceManager[T]):
32
+ """Manages the .fmu configuration file."""
33
+
34
+ def __init__(self: Self, fmu_dir: FMUDirectoryBase, config: type[T]) -> None:
35
+ """Initializes the Config resource manager."""
36
+ super().__init__(fmu_dir, config)
37
+
38
+ @property
39
+ def relative_path(self: Self) -> Path:
40
+ """Returns the relative path to the config file."""
41
+ return Path("config.json")
42
+
43
+ def _get_dot_notation_key(
44
+ self: Self, config_dict: dict[str, Any], key: str, default: Any = None
45
+ ) -> Any:
46
+ """Sets the value to a dot-notation key.
47
+
48
+ Args:
49
+ config_dict: The configuration dictionary we are modifying (by reference)
50
+ key: The key to set
51
+ default: Value to return if key is not found. Default None
52
+
53
+ Returns:
54
+ The value or default
55
+ """
56
+ parts = key.split(".")
57
+ value = config_dict
58
+ for part in parts:
59
+ if isinstance(value, dict) and part in value:
60
+ value = value[part]
61
+ else:
62
+ return default
63
+
64
+ return value
65
+
66
+ def get(self: Self, key: str, default: Any = None) -> Any:
67
+ """Gets a configuration value by key.
68
+
69
+ Supports dot notation for nested values (e.g., "foo.bar")
70
+
71
+ Args:
72
+ key: The configuration key
73
+ default: Value to return if key is not found. Default None
74
+
75
+ Returns:
76
+ The configuration value or deafult
77
+ """
78
+ try:
79
+ config = self.load()
80
+
81
+ if "." in key:
82
+ return self._get_dot_notation_key(config.model_dump(), key, default)
83
+
84
+ if hasattr(config, key):
85
+ return getattr(config, key)
86
+
87
+ config_dict = config.model_dump()
88
+ return config_dict.get(key, default)
89
+ except FileNotFoundError as e:
90
+ raise FileNotFoundError(
91
+ f"Resource file for '{self.__class__.__name__}' not found "
92
+ f"at: '{self.path}' when getting key {key}"
93
+ ) from e
94
+
95
+ def _set_dot_notation_key(
96
+ self: Self, config_dict: dict[str, Any], key: str, value: Any
97
+ ) -> None:
98
+ """Sets the value to a dot-notation key.
99
+
100
+ Args:
101
+ config_dict: The configuration dictionary we are modifying (by reference)
102
+ key: The key to set
103
+ value: The value to set
104
+ """
105
+ parts = key.split(".")
106
+ target = config_dict
107
+
108
+ for part in parts[:-1]:
109
+ if part not in target or not isinstance(target[part], dict):
110
+ target[part] = {}
111
+ target = target[part]
112
+
113
+ target[parts[-1]] = value
114
+
115
+ def set(self: Self, key: str, value: Any) -> None:
116
+ """Sets a configuration value by key.
117
+
118
+ Args:
119
+ key: The configuration key
120
+ value: The value to set
121
+
122
+ Raises:
123
+ FileNotFoundError: If config file doesn't exist
124
+ ValueError: If the updated config is invalid
125
+ """
126
+ try:
127
+ config = self.load()
128
+ config_dict = config.model_dump()
129
+
130
+ if "." in key:
131
+ self._set_dot_notation_key(config_dict, key, value)
132
+ else:
133
+ config_dict[key] = value
134
+
135
+ updated_config = config.model_validate(config_dict)
136
+ self.save(updated_config)
137
+ except ValidationError as e:
138
+ raise ValueError(
139
+ f"Invalid value set for '{self.__class__.__name__}' with "
140
+ f"key '{key}', value '{value}': '{e}"
141
+ ) from e
142
+ except FileNotFoundError as e:
143
+ raise FileNotFoundError(
144
+ f"Resource file for '{self.__class__.__name__}' not found "
145
+ f"at: '{self.path}' when setting key {key}"
146
+ ) from e
147
+
148
+ def update(self: Self, updates: dict[str, Any]) -> T:
149
+ """Updates multiple configuration values at once.
150
+
151
+ Args:
152
+ updates: Dictionary of key-value pairs to update
153
+
154
+ Returns:
155
+ The updated Config object
156
+
157
+ Raises:
158
+ FileNotFoundError: If config file doesn't exist
159
+ ValueError: If the updates config is invalid
160
+ """
161
+ try:
162
+ config = self.load()
163
+ config_dict = config.model_dump()
164
+
165
+ flat_updates = {k: v for k, v in updates.items() if "." not in k}
166
+ config_dict.update(flat_updates)
167
+
168
+ for key, value in updates.items():
169
+ if "." in key:
170
+ self._set_dot_notation_key(config_dict, key, value)
171
+
172
+ updated_config = config.model_validate(config_dict)
173
+ self.save(updated_config)
174
+ except ValidationError as e:
175
+ raise ValueError(
176
+ f"Invalid value set for '{self.__class__.__name__}' with "
177
+ f"updates '{updates}': '{e}"
178
+ ) from e
179
+ except FileNotFoundError as e:
180
+ raise FileNotFoundError(
181
+ f"Resource file for '{self.__class__.__name__}' not found "
182
+ f"at: '{self.path}' when setting updates {updates}"
183
+ ) from e
184
+
185
+ return updated_config
186
+
187
+ def reset(self: Self) -> T:
188
+ """Resets the configuration to defaults.
189
+
190
+ Returns:
191
+ The new default config object
192
+ """
193
+ config = self.model_class.reset()
194
+ self.save(config)
195
+ return config
196
+
197
+
198
+ class ProjectConfigManager(ConfigManager[ProjectConfig]):
199
+ """Manages the .fmu configuration file in a project."""
200
+
201
+ def __init__(self: Self, fmu_dir: ProjectFMUDirectory) -> None:
202
+ """Initializes the ProjectConfig resource manager."""
203
+ super().__init__(fmu_dir, ProjectConfig)
204
+
205
+
206
+ class UserConfigManager(ConfigManager[UserConfig]):
207
+ """Manages the .fmu configuration file in a user's home directory."""
208
+
209
+ def __init__(self: Self, fmu_dir: UserFMUDirectory) -> None:
210
+ """Initializes the UserConfig resource manager."""
211
+ super().__init__(fmu_dir, UserConfig)
@@ -0,0 +1,96 @@
1
+ """Contains the base class used for interacting with resources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import TYPE_CHECKING, Generic, Self, TypeVar
7
+
8
+ from pydantic import BaseModel, ValidationError
9
+
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
12
+
13
+ # Avoid circular dependency for type hint in __init__ only
14
+ from fmu.settings._fmu_dir import FMUDirectoryBase
15
+
16
+ T = TypeVar("T", bound=BaseModel)
17
+
18
+
19
+ class PydanticResourceManager(Generic[T]):
20
+ """Base class for managing resources represented by Pydantic models."""
21
+
22
+ def __init__(self: Self, fmu_dir: FMUDirectoryBase, model_class: type[T]) -> None:
23
+ """Initializes the resource manager.
24
+
25
+ Args:
26
+ fmu_dir: The FMUDirectory instance
27
+ model_class: The Pydantic model class this manager handles
28
+ """
29
+ self.fmu_dir = fmu_dir
30
+ self.model_class = model_class
31
+ self._cache: T | None = None
32
+
33
+ @property
34
+ def relative_path(self: Self) -> Path:
35
+ """Returns the path to the resource file _inside_ the .fmu directory.
36
+
37
+ Must be implemented by subclasses.
38
+ """
39
+ raise NotImplementedError
40
+
41
+ @property
42
+ def path(self: Self) -> Path:
43
+ """Returns the full path to the resource file."""
44
+ return self.fmu_dir.get_file_path(self.relative_path)
45
+
46
+ @property
47
+ def exists(self: Self) -> bool:
48
+ """Returns whether or not the resource exists."""
49
+ return self.path.exists()
50
+
51
+ def load(self: Self, force: bool = False) -> T:
52
+ """Loads the resources from disk and validates it as a Pydantic model.
53
+
54
+ Args:
55
+ force: Force a re-read even if the file is already cached
56
+
57
+ Returns:
58
+ Validated Pydantic model
59
+
60
+ Raises:
61
+ FileNotFoundError: If the resource file doesn't exist
62
+ ValidationError: If the data doesn't match the model schema
63
+ """
64
+ if self._cache is None or force:
65
+ if not self.exists:
66
+ raise FileNotFoundError(
67
+ f"Resource file for '{self.__class__.__name__}' not found "
68
+ f"at: '{self.path}'"
69
+ )
70
+
71
+ try:
72
+ content = self.fmu_dir.read_text_file(self.relative_path)
73
+ data = json.loads(content)
74
+ self._cache = self.model_class.model_validate(data)
75
+ except ValidationError as e:
76
+ raise ValueError(
77
+ f"Invalid content in resource file for '{self.__class__.__name__}: "
78
+ f"'{e}"
79
+ ) from e
80
+ except json.JSONDecodeError as e:
81
+ raise ValueError(
82
+ f"Invalid JSON in resource file for '{self.__class__.__name__}': "
83
+ f"'{e}'"
84
+ ) from e
85
+
86
+ return self._cache
87
+
88
+ def save(self: Self, model: T) -> None:
89
+ """Save the Pydantic model to disk.
90
+
91
+ Args:
92
+ model: Validated Pydantic model instance
93
+ """
94
+ json_data = model.model_dump_json(by_alias=True, indent=2)
95
+ self.fmu_dir.write_text_file(self.relative_path, json_data)
96
+ self._cache = model
fmu/settings/types.py ADDED
@@ -0,0 +1,20 @@
1
+ """Type annotations used in Pydantic models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Self, TypeAlias
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ VersionStr: TypeAlias = Annotated[
10
+ str, Field(pattern=r"(\d+(\.\d+){0,2}|\d+\.\d+\.[a-z0-9]+\+[a-z0-9.]+)")
11
+ ]
12
+
13
+
14
+ class ResettableBaseModel(BaseModel):
15
+ """A Pydantic BaseModel that implements reset()."""
16
+
17
+ @classmethod
18
+ def reset(cls) -> Self:
19
+ """Resets the model to an initial state."""
20
+ raise NotImplementedError
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: fmu-settings
3
+ Version: 0.0.1
4
+ Summary: A library for managing FMU settings
5
+ Author-email: Equinor <fg-fmu_atlas@equinor.com>
6
+ License: GPL-3.0
7
+ Project-URL: Homepage, https://github.com/equinor/fmu-settings
8
+ Project-URL: Repository, https://github.com/equinor/fmu-settings
9
+ Project-URL: Documentation, https://github.com/equinor/fmu-settings
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Classifier: Topic :: Utilities
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Natural Language :: English
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: annotated_types
21
+ Requires-Dist: fmu-dataio
22
+ Requires-Dist: pydantic
23
+ Provides-Extra: dev
24
+ Requires-Dist: mypy; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-cov; extra == "dev"
27
+ Requires-Dist: pytest-mock; extra == "dev"
28
+ Requires-Dist: pytest-xdist; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # fmu-settings
33
+
34
+ [![ci](https://github.com/equinor/fmu-settings/actions/workflows/ci.yml/badge.svg)](https://github.com/equinor/fmu-settings/actions/workflows/ci.yml)
35
+
36
+ **fmu-settings** is a package to manage and interface with `.fmu/`
37
+ directories, where the FMU settings are contained.
38
+
39
+ ## Developing
40
+
41
+ Clone and install into a virtual environment.
42
+
43
+ ```sh
44
+ git clone git@github.com:equinor/fmu-settings.git
45
+ cd fmu-settings
46
+ # Create or source virtual/Komodo env
47
+ pip install -U pip
48
+ pip install -e ".[dev]"
49
+ # Make a feature branch for your changes
50
+ git checkout -b some-feature-branch
51
+ ```
52
+
53
+ Run the tests with
54
+
55
+ ```sh
56
+ pytest -n auto tests
57
+ ```
58
+
59
+ Ensure your changes will pass the various linters before making a pull
60
+ request. It is expected that all code will be typed and validated with
61
+ mypy.
62
+
63
+ ```sh
64
+ ruff check
65
+ ruff format --check
66
+ mypy src tests
67
+ ```
68
+
69
+ See the [contributing document](CONTRIBUTING.md) for more.
@@ -0,0 +1,21 @@
1
+ fmu/__init__.py,sha256=htx6HlMme77I6pZ8U256-2B2cMJuELsu3JN3YM2Efh4,144
2
+ fmu/settings/__init__.py,sha256=x96dVVR-2n2lYD84LGbL7W8l3-r7W_0reUTKZlE7S34,331
3
+ fmu/settings/_fmu_dir.py,sha256=-w3cB0_2WCKYkXTmoOQtZHI_fHfCDbnzEtTF_lcYod8,10572
4
+ fmu/settings/_init.py,sha256=5CT7tV2XHz5wuLh97XozyLiKpwogrsfjpxm2dpn7KWE,4097
5
+ fmu/settings/_logging.py,sha256=nEdmZlNCBsB1GfDmFMKCjZmeuRp3CRlbz1EYUemc95Y,1104
6
+ fmu/settings/_version.py,sha256=vgltXBYF55vNcC2regxjGN0_cbebmm8VgcDdQaDapWQ,511
7
+ fmu/settings/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fmu/settings/types.py,sha256=aeXEsznBTT1YRRY_LSRqK1j2gmMmyLYYTGYl3a9fweU,513
9
+ fmu/settings/models/__init__.py,sha256=lRlXgl55ba2upmDzdvzx8N30JMq2Osnm8aa_xxTZn8A,112
10
+ fmu/settings/models/_enums.py,sha256=SQUZ-2mQcTx4F0oefPFfuQzMKsKTSFSB-wq_CH7TBRE,734
11
+ fmu/settings/models/_mappings.py,sha256=uaSAE_0y88Gvv-vM3VR5xx7yuXn9mio_8V3YC1t9wLI,2836
12
+ fmu/settings/models/project_config.py,sha256=vImpk_rPrMEn3G9NZj83Recq9YzqAABQS7ZVUlBa0vM,1207
13
+ fmu/settings/models/smda.py,sha256=nQ3-EI2VDRappwHgipLLXnWbEVQCsLrtJn14lES2Q6g,2639
14
+ fmu/settings/models/user_config.py,sha256=JhMeSmWcE4GrBRkM_D5QVnUbRKfVy_XakHeKqJrYxvE,2217
15
+ fmu/settings/resources/config_managers.py,sha256=4rA2xXS8BoNkM-RCfhJ6FNiwRGW_jTckZzpT_llkqlY,6852
16
+ fmu/settings/resources/managers.py,sha256=t4Rp6MOSIq85iKfDHZLJxeGRj8S3SKanWHWri0p9wV8,3161
17
+ fmu_settings-0.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
18
+ fmu_settings-0.0.1.dist-info/METADATA,sha256=_Jt3s4v_3G-lhNAd4S8ocOYZ0_KoRQEdCRCIvaQqO68,2020
19
+ fmu_settings-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ fmu_settings-0.0.1.dist-info/top_level.txt,sha256=Z-FIY3pxn0UK2Wxi9IJ7fKoLSraaxuNGi1eokiE0ShM,4
21
+ fmu_settings-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+