esgvoc 2.0.2__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.
- esgvoc/__init__.py +3 -0
- esgvoc/api/__init__.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/__init__.py +66 -0
- esgvoc/api/data_descriptors/EMD_models/arrangement.py +21 -0
- esgvoc/api/data_descriptors/EMD_models/calendar.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/cell_variable_type.py +20 -0
- esgvoc/api/data_descriptors/EMD_models/component_type.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/coordinate.py +52 -0
- esgvoc/api/data_descriptors/EMD_models/grid_mapping.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_region.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_type.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_computational_grid.py +56 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_grid_cells.py +230 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_subgrid.py +41 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_units.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/model.py +139 -0
- esgvoc/api/data_descriptors/EMD_models/model_component.py +115 -0
- esgvoc/api/data_descriptors/EMD_models/reference.py +61 -0
- esgvoc/api/data_descriptors/EMD_models/resolution.py +48 -0
- esgvoc/api/data_descriptors/EMD_models/temporal_refinement.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/truncation_method.py +17 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_computational_grid.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_coordinate.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_units.py +19 -0
- esgvoc/api/data_descriptors/__init__.py +159 -0
- esgvoc/api/data_descriptors/activity.py +72 -0
- esgvoc/api/data_descriptors/archive.py +5 -0
- esgvoc/api/data_descriptors/area_label.py +30 -0
- esgvoc/api/data_descriptors/branded_suffix.py +30 -0
- esgvoc/api/data_descriptors/branded_variable.py +21 -0
- esgvoc/api/data_descriptors/citation_url.py +5 -0
- esgvoc/api/data_descriptors/contact.py +5 -0
- esgvoc/api/data_descriptors/conventions.py +28 -0
- esgvoc/api/data_descriptors/creation_date.py +18 -0
- esgvoc/api/data_descriptors/data_descriptor.py +127 -0
- esgvoc/api/data_descriptors/data_specs_version.py +25 -0
- esgvoc/api/data_descriptors/date.py +5 -0
- esgvoc/api/data_descriptors/directory_date.py +22 -0
- esgvoc/api/data_descriptors/drs_specs.py +38 -0
- esgvoc/api/data_descriptors/experiment.py +215 -0
- esgvoc/api/data_descriptors/forcing_index.py +21 -0
- esgvoc/api/data_descriptors/frequency.py +48 -0
- esgvoc/api/data_descriptors/further_info_url.py +5 -0
- esgvoc/api/data_descriptors/grid.py +43 -0
- esgvoc/api/data_descriptors/horizontal_label.py +20 -0
- esgvoc/api/data_descriptors/initialization_index.py +27 -0
- esgvoc/api/data_descriptors/institution.py +80 -0
- esgvoc/api/data_descriptors/known_branded_variable.py +75 -0
- esgvoc/api/data_descriptors/license.py +31 -0
- esgvoc/api/data_descriptors/member_id.py +9 -0
- esgvoc/api/data_descriptors/mip_era.py +26 -0
- esgvoc/api/data_descriptors/model_component.py +32 -0
- esgvoc/api/data_descriptors/models_test/models.py +17 -0
- esgvoc/api/data_descriptors/nominal_resolution.py +50 -0
- esgvoc/api/data_descriptors/obs_type.py +5 -0
- esgvoc/api/data_descriptors/organisation.py +22 -0
- esgvoc/api/data_descriptors/physics_index.py +21 -0
- esgvoc/api/data_descriptors/product.py +16 -0
- esgvoc/api/data_descriptors/publication_status.py +5 -0
- esgvoc/api/data_descriptors/realization_index.py +24 -0
- esgvoc/api/data_descriptors/realm.py +16 -0
- esgvoc/api/data_descriptors/regex.py +5 -0
- esgvoc/api/data_descriptors/region.py +35 -0
- esgvoc/api/data_descriptors/resolution.py +7 -0
- esgvoc/api/data_descriptors/source.py +120 -0
- esgvoc/api/data_descriptors/source_type.py +5 -0
- esgvoc/api/data_descriptors/sub_experiment.py +5 -0
- esgvoc/api/data_descriptors/table.py +28 -0
- esgvoc/api/data_descriptors/temporal_label.py +20 -0
- esgvoc/api/data_descriptors/time_range.py +17 -0
- esgvoc/api/data_descriptors/title.py +5 -0
- esgvoc/api/data_descriptors/tracking_id.py +67 -0
- esgvoc/api/data_descriptors/variable.py +56 -0
- esgvoc/api/data_descriptors/variant_label.py +25 -0
- esgvoc/api/data_descriptors/vertical_label.py +20 -0
- esgvoc/api/project_specs.py +143 -0
- esgvoc/api/projects.py +1253 -0
- esgvoc/api/py.typed +0 -0
- esgvoc/api/pydantic_handler.py +146 -0
- esgvoc/api/report.py +127 -0
- esgvoc/api/search.py +171 -0
- esgvoc/api/universe.py +434 -0
- esgvoc/apps/__init__.py +6 -0
- esgvoc/apps/cmor_tables/__init__.py +7 -0
- esgvoc/apps/cmor_tables/cvs_table.py +948 -0
- esgvoc/apps/drs/__init__.py +0 -0
- esgvoc/apps/drs/constants.py +2 -0
- esgvoc/apps/drs/generator.py +429 -0
- esgvoc/apps/drs/report.py +540 -0
- esgvoc/apps/drs/validator.py +312 -0
- esgvoc/apps/ga/__init__.py +104 -0
- esgvoc/apps/ga/example_usage.py +315 -0
- esgvoc/apps/ga/models/__init__.py +47 -0
- esgvoc/apps/ga/models/netcdf_header.py +306 -0
- esgvoc/apps/ga/models/validator.py +491 -0
- esgvoc/apps/ga/test_ga.py +161 -0
- esgvoc/apps/ga/validator.py +277 -0
- esgvoc/apps/jsg/json_schema_generator.py +341 -0
- esgvoc/apps/jsg/templates/template.jinja +241 -0
- esgvoc/apps/test_cv/README.md +214 -0
- esgvoc/apps/test_cv/__init__.py +0 -0
- esgvoc/apps/test_cv/cv_tester.py +1611 -0
- esgvoc/apps/test_cv/example_usage.py +216 -0
- esgvoc/apps/vr/__init__.py +12 -0
- esgvoc/apps/vr/build_variable_registry.py +71 -0
- esgvoc/apps/vr/example_usage.py +60 -0
- esgvoc/apps/vr/vr_app.py +333 -0
- esgvoc/cli/clean.py +304 -0
- esgvoc/cli/cmor.py +46 -0
- esgvoc/cli/config.py +1300 -0
- esgvoc/cli/drs.py +267 -0
- esgvoc/cli/find.py +138 -0
- esgvoc/cli/get.py +155 -0
- esgvoc/cli/install.py +41 -0
- esgvoc/cli/main.py +60 -0
- esgvoc/cli/offline.py +269 -0
- esgvoc/cli/status.py +79 -0
- esgvoc/cli/test_cv.py +258 -0
- esgvoc/cli/valid.py +147 -0
- esgvoc/core/constants.py +17 -0
- esgvoc/core/convert.py +0 -0
- esgvoc/core/data_handler.py +206 -0
- esgvoc/core/db/__init__.py +3 -0
- esgvoc/core/db/connection.py +40 -0
- esgvoc/core/db/models/mixins.py +25 -0
- esgvoc/core/db/models/project.py +102 -0
- esgvoc/core/db/models/universe.py +98 -0
- esgvoc/core/db/project_ingestion.py +231 -0
- esgvoc/core/db/universe_ingestion.py +172 -0
- esgvoc/core/exceptions.py +33 -0
- esgvoc/core/logging_handler.py +26 -0
- esgvoc/core/repo_fetcher.py +345 -0
- esgvoc/core/service/__init__.py +41 -0
- esgvoc/core/service/configuration/config_manager.py +196 -0
- esgvoc/core/service/configuration/setting.py +363 -0
- esgvoc/core/service/data_merger.py +634 -0
- esgvoc/core/service/esg_voc.py +77 -0
- esgvoc/core/service/resolver_config.py +56 -0
- esgvoc/core/service/state.py +324 -0
- esgvoc/core/service/string_heuristics.py +98 -0
- esgvoc/core/service/term_cache.py +108 -0
- esgvoc/core/service/uri_resolver.py +133 -0
- esgvoc-2.0.2.dist-info/METADATA +82 -0
- esgvoc-2.0.2.dist-info/RECORD +147 -0
- esgvoc-2.0.2.dist-info/WHEEL +4 -0
- esgvoc-2.0.2.dist-info/entry_points.txt +2 -0
- esgvoc-2.0.2.dist-info/licenses/LICENSE.txt +519 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
from typing import ClassVar, Dict, Optional
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import toml
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from platformdirs import PlatformDirs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_path_to_absolute(relative_path: Optional[str], config_name: Optional[str] = None) -> Optional[str]:
|
|
10
|
+
"""
|
|
11
|
+
Convert a relative path to an absolute path without modifying the original.
|
|
12
|
+
This is used for internal path resolution only.
|
|
13
|
+
"""
|
|
14
|
+
if relative_path is None:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
path_obj = Path(relative_path)
|
|
18
|
+
|
|
19
|
+
if path_obj.is_absolute():
|
|
20
|
+
return str(path_obj.resolve())
|
|
21
|
+
|
|
22
|
+
# Handle dot-relative paths (./... or ../..) relative to current working directory
|
|
23
|
+
if relative_path.startswith("."):
|
|
24
|
+
return str((Path.cwd() / relative_path).resolve())
|
|
25
|
+
|
|
26
|
+
# Handle plain relative paths using PlatformDirs with config name (default behavior)
|
|
27
|
+
dirs = PlatformDirs("esgvoc", "ipsl")
|
|
28
|
+
base_path = Path(dirs.user_data_path).expanduser().resolve()
|
|
29
|
+
if config_name:
|
|
30
|
+
base_path = base_path / config_name
|
|
31
|
+
return str(base_path / relative_path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ProjectSettings(BaseModel):
|
|
35
|
+
project_name: str
|
|
36
|
+
github_repo: str
|
|
37
|
+
branch: Optional[str] = "main"
|
|
38
|
+
local_path: Optional[str] = None
|
|
39
|
+
db_path: Optional[str] = None
|
|
40
|
+
offline_mode: bool = False
|
|
41
|
+
_config_name: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
def set_config_name(self, config_name: str):
|
|
44
|
+
"""Set the config name for path resolution."""
|
|
45
|
+
self._config_name = config_name
|
|
46
|
+
|
|
47
|
+
def get_absolute_local_path(self) -> Optional[str]:
|
|
48
|
+
"""Get the absolute local path without modifying the stored value."""
|
|
49
|
+
return resolve_path_to_absolute(self.local_path, self._config_name)
|
|
50
|
+
|
|
51
|
+
def get_absolute_db_path(self) -> Optional[str]:
|
|
52
|
+
"""Get the absolute db path without modifying the stored value."""
|
|
53
|
+
return resolve_path_to_absolute(self.db_path, self._config_name)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UniverseSettings(BaseModel):
|
|
57
|
+
github_repo: str
|
|
58
|
+
branch: Optional[str] = None
|
|
59
|
+
local_path: Optional[str] = None
|
|
60
|
+
db_path: Optional[str] = None
|
|
61
|
+
offline_mode: bool = False
|
|
62
|
+
_config_name: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
def set_config_name(self, config_name: str):
|
|
65
|
+
"""Set the config name for path resolution."""
|
|
66
|
+
self._config_name = config_name
|
|
67
|
+
|
|
68
|
+
def get_absolute_local_path(self) -> Optional[str]:
|
|
69
|
+
"""Get the absolute local path without modifying the stored value."""
|
|
70
|
+
return resolve_path_to_absolute(self.local_path, self._config_name)
|
|
71
|
+
|
|
72
|
+
def get_absolute_db_path(self) -> Optional[str]:
|
|
73
|
+
"""Get the absolute db path without modifying the stored value."""
|
|
74
|
+
return resolve_path_to_absolute(self.db_path, self._config_name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ServiceSettings(BaseModel):
|
|
78
|
+
universe: UniverseSettings
|
|
79
|
+
projects: Dict[str, ProjectSettings] = Field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
def set_config_name(self, config_name: str):
|
|
82
|
+
"""Set the config name for all settings components."""
|
|
83
|
+
self.universe.set_config_name(config_name)
|
|
84
|
+
for project_settings in self.projects.values():
|
|
85
|
+
project_settings.set_config_name(config_name)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _get_default_base_path() -> Path:
|
|
89
|
+
"""Get the default base path for data storage using PlatformDirs."""
|
|
90
|
+
dirs = PlatformDirs("esgvoc", "ipsl")
|
|
91
|
+
return Path(dirs.user_data_path).expanduser().resolve()
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _get_default_project_configs(cls) -> dict[str, dict]:
|
|
95
|
+
"""Generate default project configurations with relative paths."""
|
|
96
|
+
return {
|
|
97
|
+
"cmip6": {
|
|
98
|
+
"project_name": "cmip6",
|
|
99
|
+
"github_repo": "https://github.com/WCRP-CMIP/CMIP6_CVs",
|
|
100
|
+
"branch": "esgvoc",
|
|
101
|
+
"local_path": "repos/CMIP6_CVs",
|
|
102
|
+
"db_path": "dbs/cmip6.sqlite",
|
|
103
|
+
"offline_mode": False,
|
|
104
|
+
},
|
|
105
|
+
"cmip6plus": {
|
|
106
|
+
"project_name": "cmip6plus",
|
|
107
|
+
"github_repo": "https://github.com/WCRP-CMIP/CMIP6Plus_CVs",
|
|
108
|
+
"branch": "esgvoc",
|
|
109
|
+
"local_path": "repos/CMIP6Plus_CVs",
|
|
110
|
+
"db_path": "dbs/cmip6plus.sqlite",
|
|
111
|
+
"offline_mode": False,
|
|
112
|
+
},
|
|
113
|
+
"input4mip": {
|
|
114
|
+
"project_name": "input4mip",
|
|
115
|
+
"github_repo": "https://github.com/PCMDI/input4MIPs_CVs",
|
|
116
|
+
"branch": "esgvoc",
|
|
117
|
+
"local_path": "repos/Input4MIP_CVs",
|
|
118
|
+
"db_path": "dbs/input4mips.sqlite",
|
|
119
|
+
"offline_mode": False,
|
|
120
|
+
},
|
|
121
|
+
"obs4ref": {
|
|
122
|
+
"project_name": "obs4ref",
|
|
123
|
+
"github_repo": "https://github.com/Climate-REF/Obs4REF_CVs",
|
|
124
|
+
"branch": "main",
|
|
125
|
+
"local_path": "repos/obs4REF_CVs",
|
|
126
|
+
"db_path": "dbs/obs4ref.sqlite",
|
|
127
|
+
"offline_mode": False,
|
|
128
|
+
},
|
|
129
|
+
"cordex-cmip6": {
|
|
130
|
+
"project_name": "cordex-cmip6",
|
|
131
|
+
"github_repo": "https://github.com/WCRP-CORDEX/cordex-cmip6-cv",
|
|
132
|
+
"branch": "esgvoc",
|
|
133
|
+
"local_path": "repos/cordex-cmip6-cv",
|
|
134
|
+
"db_path": "dbs/cordex-cmip6.sqlite",
|
|
135
|
+
"offline_mode": False,
|
|
136
|
+
},
|
|
137
|
+
"cmip7": {
|
|
138
|
+
"project_name": "cmip7",
|
|
139
|
+
"github_repo": "https://github.com/WCRP-CMIP/CMIP7-CVs",
|
|
140
|
+
"branch": "esgvoc",
|
|
141
|
+
"local_path": "repos/CMIP7-CVs",
|
|
142
|
+
"db_path": "dbs/cmip7.sqlite",
|
|
143
|
+
"offline_mode": False,
|
|
144
|
+
},
|
|
145
|
+
"emd": {
|
|
146
|
+
"project_name": "emd",
|
|
147
|
+
"github_repo": "https://github.com/WCRP-CMIP/Essential-Model-Documentation",
|
|
148
|
+
"branch": "esgvoc",
|
|
149
|
+
"local_path": "repos/Essential-Model-Documentation",
|
|
150
|
+
"db_path": "dbs/emd.sqlite",
|
|
151
|
+
"offline_mode": False,
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def _get_default_settings(cls) -> dict:
|
|
157
|
+
"""Generate default settings with relative paths."""
|
|
158
|
+
project_configs = cls._get_default_project_configs()
|
|
159
|
+
return {
|
|
160
|
+
"universe": {
|
|
161
|
+
"github_repo": "https://github.com/WCRP-CMIP/WCRP-universe",
|
|
162
|
+
"branch": "esgvoc",
|
|
163
|
+
"local_path": "repos/WCRP-universe",
|
|
164
|
+
"db_path": "dbs/universe.sqlite",
|
|
165
|
+
"offline_mode": False,
|
|
166
|
+
},
|
|
167
|
+
"projects": [
|
|
168
|
+
project_configs["cmip6"],
|
|
169
|
+
project_configs["cmip6plus"],
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# 🔹 Properties that provide access to the dynamic configurations
|
|
174
|
+
@property
|
|
175
|
+
def DEFAULT_PROJECT_CONFIGS(self) -> Dict[str, dict]:
|
|
176
|
+
return self._get_default_project_configs()
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def DEFAULT_SETTINGS(self) -> dict:
|
|
180
|
+
return self._get_default_settings()
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def load_from_file(cls, file_path: str) -> "ServiceSettings":
|
|
184
|
+
"""Load configuration from a TOML file, falling back to defaults if necessary."""
|
|
185
|
+
try:
|
|
186
|
+
data = toml.load(file_path)
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
data = cls._get_default_settings().copy() # Use defaults if the file is missing
|
|
189
|
+
|
|
190
|
+
projects = {p["project_name"]: ProjectSettings(**p) for p in data.pop("projects", [])}
|
|
191
|
+
return cls(universe=UniverseSettings(**data["universe"]), projects=projects)
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def load_default(cls) -> "ServiceSettings":
|
|
195
|
+
"""Load default settings."""
|
|
196
|
+
return cls.load_from_dict(cls._get_default_settings())
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def load_from_dict(cls, config_data: dict) -> "ServiceSettings":
|
|
200
|
+
"""Load configuration from a dictionary."""
|
|
201
|
+
projects = {p["project_name"]: ProjectSettings(**p) for p in config_data.get("projects", [])}
|
|
202
|
+
return cls(universe=UniverseSettings(**config_data["universe"]), projects=projects)
|
|
203
|
+
|
|
204
|
+
def save_to_file(self, file_path: str):
|
|
205
|
+
"""Save the configuration to a TOML file."""
|
|
206
|
+
data = {
|
|
207
|
+
"universe": self.universe.model_dump(),
|
|
208
|
+
"projects": [p.model_dump() for p in self.projects.values()],
|
|
209
|
+
}
|
|
210
|
+
with open(file_path, "w") as f:
|
|
211
|
+
toml.dump(data, f)
|
|
212
|
+
|
|
213
|
+
def dump(self) -> dict:
|
|
214
|
+
"""Return the configuration as a dictionary."""
|
|
215
|
+
return {
|
|
216
|
+
"universe": self.universe.model_dump(),
|
|
217
|
+
"projects": [p.model_dump() for p in self.projects.values()],
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# 🔹 NEW: Project management methods
|
|
221
|
+
|
|
222
|
+
def add_project_from_default(self, project_name: str) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
Add a project using its default configuration.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
project_name: Name of the project to add (must exist in DEFAULT_PROJECT_CONFIGS)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
bool: True if project was added, False if it already exists or is unknown
|
|
231
|
+
"""
|
|
232
|
+
if project_name in self.projects:
|
|
233
|
+
return False # Project already exists
|
|
234
|
+
|
|
235
|
+
default_configs = self._get_default_project_configs()
|
|
236
|
+
if project_name not in default_configs:
|
|
237
|
+
raise ValueError(f"Unknown project '{project_name}'. Available defaults: {list(default_configs.keys())}")
|
|
238
|
+
|
|
239
|
+
config = default_configs[project_name].copy()
|
|
240
|
+
self.projects[project_name] = ProjectSettings(**config)
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
def add_project_custom(self, project_config: dict) -> bool:
|
|
244
|
+
"""
|
|
245
|
+
Add a project with custom configuration.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
project_config: Dictionary containing project configuration
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
bool: True if project was added, False if it already exists
|
|
252
|
+
"""
|
|
253
|
+
project_settings = ProjectSettings(**project_config)
|
|
254
|
+
project_name = project_settings.project_name
|
|
255
|
+
|
|
256
|
+
if project_name in self.projects:
|
|
257
|
+
return False # Project already exists
|
|
258
|
+
|
|
259
|
+
self.projects[project_name] = project_settings
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
def remove_project(self, project_name: str) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Remove a project from the configuration.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
project_name: Name of the project to remove
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
|
|
271
|
+
bool: True if project was removed, False if it didn't exist
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
if project_name in self.projects:
|
|
275
|
+
del self.projects[project_name]
|
|
276
|
+
return True
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def update_project(self, project_name: str, **kwargs) -> bool:
|
|
280
|
+
"""
|
|
281
|
+
Update specific fields of an existing project.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
project_name: Name of the project to update
|
|
285
|
+
**kwargs: Fields to update
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
bool: True if project was updated, False if it doesn't exist
|
|
289
|
+
"""
|
|
290
|
+
if project_name not in self.projects:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
# Handle boolean conversion for offline_mode if present
|
|
294
|
+
if "offline_mode" in kwargs:
|
|
295
|
+
if isinstance(kwargs["offline_mode"], str):
|
|
296
|
+
kwargs["offline_mode"] = kwargs["offline_mode"].lower() in ("true", "1", "yes", "on")
|
|
297
|
+
|
|
298
|
+
# Get current config and update with new values
|
|
299
|
+
current_config = self.projects[project_name].model_dump()
|
|
300
|
+
current_config.update(kwargs)
|
|
301
|
+
|
|
302
|
+
# Recreate the ProjectSettings with updated config
|
|
303
|
+
self.projects[project_name] = ProjectSettings(**current_config)
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
def get_available_default_projects(self) -> list[str]:
|
|
307
|
+
"""Return list of available default project names."""
|
|
308
|
+
return list(self._get_default_project_configs().keys())
|
|
309
|
+
|
|
310
|
+
def has_project(self, project_name: str) -> bool:
|
|
311
|
+
"""Check if a project exists in the current configuration."""
|
|
312
|
+
return project_name in self.projects
|
|
313
|
+
|
|
314
|
+
def get_project(self, project_name: str) -> Optional[ProjectSettings]:
|
|
315
|
+
"""Get a specific project configuration."""
|
|
316
|
+
return self.projects.get(project_name)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# 🔹 Usage Examples
|
|
320
|
+
def main():
|
|
321
|
+
# Create default settings (only cmip6 and cmip6plus)
|
|
322
|
+
settings = ServiceSettings.load_default()
|
|
323
|
+
# ['cmip6', 'cmip6plus']
|
|
324
|
+
print(f"Default projects: {list(settings.projects.keys())}")
|
|
325
|
+
|
|
326
|
+
# See what other projects are available to add
|
|
327
|
+
available = settings.get_available_default_projects()
|
|
328
|
+
# ['cmip6', 'cmip6plus', 'input4mip', 'obs4mip']
|
|
329
|
+
print(f"Available default projects: {available}")
|
|
330
|
+
|
|
331
|
+
# Add optional projects when needed
|
|
332
|
+
added_input4mip = settings.add_project_from_default("input4mip")
|
|
333
|
+
print(f"Added input4mip: {added_input4mip}")
|
|
334
|
+
|
|
335
|
+
added_obs4mip = settings.add_project_from_default("obs4mip")
|
|
336
|
+
print(f"Added obs4mip: {added_obs4mip}")
|
|
337
|
+
|
|
338
|
+
print(f"Projects after adding optional ones: {list(settings.projects.keys())}")
|
|
339
|
+
|
|
340
|
+
# Remove a project if no longer needed
|
|
341
|
+
removed = settings.remove_project("obs4mip")
|
|
342
|
+
print(f"Removed obs4mip: {removed}")
|
|
343
|
+
print(f"Projects after removal: {list(settings.projects.keys())}")
|
|
344
|
+
|
|
345
|
+
# Try to add a custom project
|
|
346
|
+
custom_project = {
|
|
347
|
+
"project_name": "my_custom_project",
|
|
348
|
+
"github_repo": "https://github.com/me/my-project",
|
|
349
|
+
"branch": "develop",
|
|
350
|
+
"local_path": "repos/my_project",
|
|
351
|
+
"db_path": "dbs/my_project.sqlite",
|
|
352
|
+
}
|
|
353
|
+
added_custom = settings.add_project_custom(custom_project)
|
|
354
|
+
print(f"Added custom project: {added_custom}")
|
|
355
|
+
print(f"Final projects: {list(settings.projects.keys())}")
|
|
356
|
+
|
|
357
|
+
# Update a project
|
|
358
|
+
updated = settings.update_project("my_custom_project", branch="main", db_path="dbs/updated.sqlite")
|
|
359
|
+
print(f"Updated custom project: {updated}")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
main()
|