esgvoc 1.1.2__py3-none-any.whl → 1.1.3__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 esgvoc might be problematic. Click here for more details.
- esgvoc/__init__.py +1 -1
- esgvoc/api/project_specs.py +24 -0
- esgvoc/apps/jsg/json_schema_generator.py +9 -7
- esgvoc/apps/jsg/templates/template.jinja +1 -1
- esgvoc/apps/test_cv/cv_tester.py +232 -31
- esgvoc/cli/clean.py +304 -0
- esgvoc/cli/config.py +226 -11
- esgvoc/cli/install.py +24 -0
- esgvoc/cli/main.py +4 -0
- esgvoc/cli/offline.py +269 -0
- esgvoc/cli/status.py +39 -7
- esgvoc/core/db/project_ingestion.py +6 -2
- esgvoc/core/repo_fetcher.py +87 -4
- esgvoc/core/service/__init__.py +4 -3
- esgvoc/core/service/configuration/config_manager.py +17 -16
- esgvoc/core/service/configuration/setting.py +162 -66
- esgvoc/core/service/data_merger.py +2 -1
- esgvoc/core/service/esg_voc.py +17 -19
- esgvoc/core/service/state.py +65 -27
- {esgvoc-1.1.2.dist-info → esgvoc-1.1.3.dist-info}/METADATA +1 -1
- {esgvoc-1.1.2.dist-info → esgvoc-1.1.3.dist-info}/RECORD +24 -22
- {esgvoc-1.1.2.dist-info → esgvoc-1.1.3.dist-info}/WHEEL +0 -0
- {esgvoc-1.1.2.dist-info → esgvoc-1.1.3.dist-info}/entry_points.txt +0 -0
- {esgvoc-1.1.2.dist-info → esgvoc-1.1.3.dist-info}/licenses/LICENSE.txt +0 -0
esgvoc/cli/offline.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Offline mode management CLI commands.
|
|
3
|
+
|
|
4
|
+
This module provides CLI commands for managing offline mode settings
|
|
5
|
+
for universe and project components.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from esgvoc.core.service import config_manager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def show(
|
|
21
|
+
component: str = typer.Argument(
|
|
22
|
+
None,
|
|
23
|
+
help="Component to show offline status for (universe or project name). Shows all if not specified."
|
|
24
|
+
),
|
|
25
|
+
config: str = typer.Option(
|
|
26
|
+
None,
|
|
27
|
+
"--config",
|
|
28
|
+
"-c",
|
|
29
|
+
help="Configuration name to use"
|
|
30
|
+
)
|
|
31
|
+
):
|
|
32
|
+
"""Show offline mode status for components."""
|
|
33
|
+
try:
|
|
34
|
+
if config:
|
|
35
|
+
settings = config_manager.get_config(config)
|
|
36
|
+
else:
|
|
37
|
+
settings = config_manager.get_active_config()
|
|
38
|
+
|
|
39
|
+
if component:
|
|
40
|
+
# Show specific component
|
|
41
|
+
if component == "universe":
|
|
42
|
+
if settings.universe.offline_mode:
|
|
43
|
+
console.print(f"[green]Universe is in offline mode[/green]")
|
|
44
|
+
else:
|
|
45
|
+
console.print(f"[yellow]Universe is in online mode[/yellow]")
|
|
46
|
+
elif component in settings.projects:
|
|
47
|
+
project = settings.projects[component]
|
|
48
|
+
if project.offline_mode:
|
|
49
|
+
console.print(f"[green]Project '{component}' is in offline mode[/green]")
|
|
50
|
+
else:
|
|
51
|
+
console.print(f"[yellow]Project '{component}' is in online mode[/yellow]")
|
|
52
|
+
else:
|
|
53
|
+
console.print(f"[red]Component '{component}' not found[/red]")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
else:
|
|
56
|
+
# Show all components
|
|
57
|
+
table = Table(title="Offline Mode Status")
|
|
58
|
+
table.add_column("Component", style="cyan")
|
|
59
|
+
table.add_column("Type", style="magenta")
|
|
60
|
+
table.add_column("Offline Mode", style="bold")
|
|
61
|
+
|
|
62
|
+
# Universe
|
|
63
|
+
status = "[green]✓ Enabled[/green]" if settings.universe.offline_mode else "[yellow]✗ Disabled[/yellow]"
|
|
64
|
+
table.add_row("Universe", "Universe", status)
|
|
65
|
+
|
|
66
|
+
# Projects
|
|
67
|
+
for project_name, project in settings.projects.items():
|
|
68
|
+
status = "[green]✓ Enabled[/green]" if project.offline_mode else "[yellow]✗ Disabled[/yellow]"
|
|
69
|
+
table.add_row(project_name, "Project", status)
|
|
70
|
+
|
|
71
|
+
console.print(table)
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
75
|
+
raise typer.Exit(1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command()
|
|
79
|
+
def enable(
|
|
80
|
+
component: str = typer.Argument(
|
|
81
|
+
None,
|
|
82
|
+
help="Component to enable offline mode for (universe or project name). If not specified, enables for all components."
|
|
83
|
+
),
|
|
84
|
+
config: str = typer.Option(
|
|
85
|
+
None,
|
|
86
|
+
"--config",
|
|
87
|
+
"-c",
|
|
88
|
+
help="Configuration name to use"
|
|
89
|
+
)
|
|
90
|
+
):
|
|
91
|
+
"""Enable offline mode for a component or all components if none specified."""
|
|
92
|
+
try:
|
|
93
|
+
if config:
|
|
94
|
+
settings = config_manager.get_config(config)
|
|
95
|
+
else:
|
|
96
|
+
settings = config_manager.get_active_config()
|
|
97
|
+
|
|
98
|
+
if component is None:
|
|
99
|
+
# Enable for all components
|
|
100
|
+
settings.universe.offline_mode = True
|
|
101
|
+
for project in settings.projects.values():
|
|
102
|
+
project.offline_mode = True
|
|
103
|
+
console.print(f"[green]✓ Enabled offline mode for all components[/green]")
|
|
104
|
+
elif component == "universe":
|
|
105
|
+
settings.universe.offline_mode = True
|
|
106
|
+
console.print(f"[green]✓ Enabled offline mode for universe[/green]")
|
|
107
|
+
elif component in settings.projects:
|
|
108
|
+
settings.projects[component].offline_mode = True
|
|
109
|
+
console.print(f"[green]✓ Enabled offline mode for project '{component}'[/green]")
|
|
110
|
+
else:
|
|
111
|
+
console.print(f"[red]Component '{component}' not found[/red]")
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
|
|
114
|
+
# Save the updated settings
|
|
115
|
+
if config:
|
|
116
|
+
# Use the correct format for saving
|
|
117
|
+
data = {
|
|
118
|
+
"universe": settings.universe.model_dump(),
|
|
119
|
+
"projects": [p.model_dump() for p in settings.projects.values()],
|
|
120
|
+
}
|
|
121
|
+
config_manager.save_config(data, config)
|
|
122
|
+
else:
|
|
123
|
+
config_manager.save_active_config(settings)
|
|
124
|
+
console.print(f"[blue]Configuration saved[/blue]")
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.command()
|
|
132
|
+
def disable(
|
|
133
|
+
component: str = typer.Argument(
|
|
134
|
+
None,
|
|
135
|
+
help="Component to disable offline mode for (universe or project name). If not specified, disables for all components."
|
|
136
|
+
),
|
|
137
|
+
config: str = typer.Option(
|
|
138
|
+
None,
|
|
139
|
+
"--config",
|
|
140
|
+
"-c",
|
|
141
|
+
help="Configuration name to use"
|
|
142
|
+
)
|
|
143
|
+
):
|
|
144
|
+
"""Disable offline mode for a component or all components if none specified."""
|
|
145
|
+
try:
|
|
146
|
+
if config:
|
|
147
|
+
settings = config_manager.get_config(config)
|
|
148
|
+
else:
|
|
149
|
+
settings = config_manager.get_active_config()
|
|
150
|
+
|
|
151
|
+
if component is None:
|
|
152
|
+
# Disable for all components
|
|
153
|
+
settings.universe.offline_mode = False
|
|
154
|
+
for project in settings.projects.values():
|
|
155
|
+
project.offline_mode = False
|
|
156
|
+
console.print(f"[yellow]✓ Disabled offline mode for all components[/yellow]")
|
|
157
|
+
elif component == "universe":
|
|
158
|
+
settings.universe.offline_mode = False
|
|
159
|
+
console.print(f"[yellow]✓ Disabled offline mode for universe[/yellow]")
|
|
160
|
+
elif component in settings.projects:
|
|
161
|
+
settings.projects[component].offline_mode = False
|
|
162
|
+
console.print(f"[yellow]✓ Disabled offline mode for project '{component}'[/yellow]")
|
|
163
|
+
else:
|
|
164
|
+
console.print(f"[red]Component '{component}' not found[/red]")
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
|
|
167
|
+
# Save the updated settings
|
|
168
|
+
if config:
|
|
169
|
+
# Use the correct format for saving
|
|
170
|
+
data = {
|
|
171
|
+
"universe": settings.universe.model_dump(),
|
|
172
|
+
"projects": [p.model_dump() for p in settings.projects.values()],
|
|
173
|
+
}
|
|
174
|
+
config_manager.save_config(data, config)
|
|
175
|
+
else:
|
|
176
|
+
config_manager.save_active_config(settings)
|
|
177
|
+
console.print(f"[blue]Configuration saved[/blue]")
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
181
|
+
raise typer.Exit(1)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@app.command()
|
|
185
|
+
def enable_all(
|
|
186
|
+
config: str = typer.Option(
|
|
187
|
+
None,
|
|
188
|
+
"--config",
|
|
189
|
+
"-c",
|
|
190
|
+
help="Configuration name to use"
|
|
191
|
+
)
|
|
192
|
+
):
|
|
193
|
+
"""Enable offline mode for all components."""
|
|
194
|
+
try:
|
|
195
|
+
if config:
|
|
196
|
+
settings = config_manager.get_config(config)
|
|
197
|
+
else:
|
|
198
|
+
settings = config_manager.get_active_config()
|
|
199
|
+
|
|
200
|
+
# Enable for universe
|
|
201
|
+
settings.universe.offline_mode = True
|
|
202
|
+
|
|
203
|
+
# Enable for all projects
|
|
204
|
+
for project in settings.projects.values():
|
|
205
|
+
project.offline_mode = True
|
|
206
|
+
|
|
207
|
+
# Save the updated settings
|
|
208
|
+
if config:
|
|
209
|
+
# Use the correct format for saving
|
|
210
|
+
data = {
|
|
211
|
+
"universe": settings.universe.model_dump(),
|
|
212
|
+
"projects": [p.model_dump() for p in settings.projects.values()],
|
|
213
|
+
}
|
|
214
|
+
config_manager.save_config(data, config)
|
|
215
|
+
else:
|
|
216
|
+
config_manager.save_active_config(settings)
|
|
217
|
+
|
|
218
|
+
console.print(f"[green]✓ Enabled offline mode for all components[/green]")
|
|
219
|
+
console.print(f"[blue]Configuration saved[/blue]")
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
223
|
+
raise typer.Exit(1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.command()
|
|
227
|
+
def disable_all(
|
|
228
|
+
config: str = typer.Option(
|
|
229
|
+
None,
|
|
230
|
+
"--config",
|
|
231
|
+
"-c",
|
|
232
|
+
help="Configuration name to use"
|
|
233
|
+
)
|
|
234
|
+
):
|
|
235
|
+
"""Disable offline mode for all components."""
|
|
236
|
+
try:
|
|
237
|
+
if config:
|
|
238
|
+
settings = config_manager.get_config(config)
|
|
239
|
+
else:
|
|
240
|
+
settings = config_manager.get_active_config()
|
|
241
|
+
|
|
242
|
+
# Disable for universe
|
|
243
|
+
settings.universe.offline_mode = False
|
|
244
|
+
|
|
245
|
+
# Disable for all projects
|
|
246
|
+
for project in settings.projects.values():
|
|
247
|
+
project.offline_mode = False
|
|
248
|
+
|
|
249
|
+
# Save the updated settings
|
|
250
|
+
if config:
|
|
251
|
+
# Use the correct format for saving
|
|
252
|
+
data = {
|
|
253
|
+
"universe": settings.universe.model_dump(),
|
|
254
|
+
"projects": [p.model_dump() for p in settings.projects.values()],
|
|
255
|
+
}
|
|
256
|
+
config_manager.save_config(data, config)
|
|
257
|
+
else:
|
|
258
|
+
config_manager.save_active_config(settings)
|
|
259
|
+
|
|
260
|
+
console.print(f"[yellow]✓ Disabled offline mode for all components[/yellow]")
|
|
261
|
+
console.print(f"[blue]Configuration saved[/blue]")
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
265
|
+
raise typer.Exit(1)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
app()
|
esgvoc/cli/status.py
CHANGED
|
@@ -22,26 +22,58 @@ def status():
|
|
|
22
22
|
"""
|
|
23
23
|
assert service.current_state is not None
|
|
24
24
|
service.current_state.get_state_summary()
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# Check for offline mode components and display summary
|
|
27
|
+
offline_components = []
|
|
28
|
+
if service.current_state.universe.offline_mode:
|
|
29
|
+
offline_components.append("universe")
|
|
30
|
+
for project_name, project in service.current_state.projects.items():
|
|
31
|
+
if project.offline_mode:
|
|
32
|
+
offline_components.append(project_name)
|
|
33
|
+
|
|
34
|
+
if offline_components:
|
|
35
|
+
console.print(f"[yellow]Offline mode enabled for: {', '.join(offline_components)}[/yellow]")
|
|
26
36
|
|
|
27
37
|
table = Table(show_header=False, show_lines=True)
|
|
28
38
|
|
|
29
|
-
table.add_row("", "Remote github repo", "Local repository", "Cache Database", style="bright_green")
|
|
39
|
+
table.add_row("", "Remote github repo", "Local repository", "Cache Database", "Offline Mode", style="bright_green")
|
|
40
|
+
|
|
41
|
+
# Universe row
|
|
42
|
+
universe_offline_status = "✓" if service.current_state.universe.offline_mode else "✗"
|
|
30
43
|
table.add_row(
|
|
31
44
|
"Universe path",
|
|
32
45
|
service.current_state.universe.github_repo,
|
|
33
46
|
service.current_state.universe.local_path,
|
|
34
47
|
service.current_state.universe.db_path,
|
|
48
|
+
universe_offline_status,
|
|
35
49
|
style="white",
|
|
36
50
|
)
|
|
37
51
|
table.add_row(
|
|
38
52
|
"Version",
|
|
39
|
-
service.current_state.universe.github_version,
|
|
40
|
-
service.current_state.universe.local_version,
|
|
41
|
-
service.current_state.universe.db_version,
|
|
53
|
+
service.current_state.universe.github_version or "N/A",
|
|
54
|
+
service.current_state.universe.local_version or "N/A",
|
|
55
|
+
service.current_state.universe.db_version or "N/A",
|
|
56
|
+
"",
|
|
42
57
|
style="bright_blue",
|
|
43
58
|
)
|
|
59
|
+
|
|
60
|
+
# Projects rows
|
|
44
61
|
for proj_name, proj in service.current_state.projects.items():
|
|
45
|
-
|
|
46
|
-
table.add_row(
|
|
62
|
+
proj_offline_status = "✓" if proj.offline_mode else "✗"
|
|
63
|
+
table.add_row(
|
|
64
|
+
f"{proj_name} path",
|
|
65
|
+
proj.github_repo,
|
|
66
|
+
proj.local_path,
|
|
67
|
+
proj.db_path,
|
|
68
|
+
proj_offline_status,
|
|
69
|
+
style="white"
|
|
70
|
+
)
|
|
71
|
+
table.add_row(
|
|
72
|
+
"Version",
|
|
73
|
+
proj.github_version or "N/A",
|
|
74
|
+
proj.local_version or "N/A",
|
|
75
|
+
proj.db_version or "N/A",
|
|
76
|
+
"",
|
|
77
|
+
style="bright_blue"
|
|
78
|
+
)
|
|
47
79
|
display(table)
|
|
@@ -138,15 +138,19 @@ def ingest_project(project_dir_path: Path, project_db_file_path: Path, git_hash:
|
|
|
138
138
|
project_specs_file_path = project_dir_path.joinpath(esgvoc.core.constants.PROJECT_SPECS_FILENAME)
|
|
139
139
|
drs_specs_file_path = project_dir_path.joinpath(esgvoc.core.constants.DRS_SPECS_FILENAME)
|
|
140
140
|
catalog_specs_file_path = project_dir_path.joinpath(esgvoc.core.constants.CATALOG_SPECS_FILENAME)
|
|
141
|
+
attr_specs_file_path = project_dir_path.joinpath(esgvoc.core.constants.ATTRIBUTES_SPECS_FILENAME)
|
|
141
142
|
try:
|
|
142
143
|
raw_project_specs = read_yaml_file(project_specs_file_path)
|
|
143
144
|
project_id = raw_project_specs[esgvoc.core.constants.PROJECT_ID_JSON_KEY]
|
|
144
145
|
raw_drs_specs = read_yaml_file(drs_specs_file_path)
|
|
145
146
|
project_specs = raw_project_specs
|
|
146
|
-
project_specs[
|
|
147
|
+
project_specs["drs_specs"] = raw_drs_specs
|
|
147
148
|
if catalog_specs_file_path.exists():
|
|
148
149
|
raw_catalog_specs = read_yaml_file(catalog_specs_file_path)
|
|
149
|
-
project_specs[
|
|
150
|
+
project_specs["catalog_specs"] = raw_catalog_specs
|
|
151
|
+
if attr_specs_file_path.exists():
|
|
152
|
+
raw_attr_specs = read_yaml_file(attr_specs_file_path)
|
|
153
|
+
project_specs["attr_specs"] = raw_attr_specs
|
|
150
154
|
except Exception as e:
|
|
151
155
|
msg = f"unable to read specs files in {project_dir_path}"
|
|
152
156
|
_LOGGER.fatal(msg)
|
esgvoc/core/repo_fetcher.py
CHANGED
|
@@ -64,9 +64,10 @@ class RepoFetcher:
|
|
|
64
64
|
DataFetcher is responsible for fetching data from external sources such as GitHub.
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
-
def __init__(self, base_url: str = "https://api.github.com", local_path: str = ".cache/repos"):
|
|
67
|
+
def __init__(self, base_url: str = "https://api.github.com", local_path: str = ".cache/repos", offline_mode: bool = False):
|
|
68
68
|
self.base_url = base_url
|
|
69
69
|
self.repo_dir = local_path
|
|
70
|
+
self.offline_mode = offline_mode
|
|
70
71
|
|
|
71
72
|
def fetch_repositories(self, user: str) -> List[GitHubRepository]:
|
|
72
73
|
"""
|
|
@@ -74,6 +75,9 @@ class RepoFetcher:
|
|
|
74
75
|
:param user: GitHub username
|
|
75
76
|
:return: List of GitHubRepository objects
|
|
76
77
|
"""
|
|
78
|
+
if self.offline_mode:
|
|
79
|
+
raise Exception("Cannot fetch repositories in offline mode")
|
|
80
|
+
|
|
77
81
|
url = f"{self.base_url}/users/{user}/repos"
|
|
78
82
|
response = requests.get(url)
|
|
79
83
|
|
|
@@ -93,6 +97,9 @@ class RepoFetcher:
|
|
|
93
97
|
:param repo: Repository name
|
|
94
98
|
:return: GitHubRepository object
|
|
95
99
|
"""
|
|
100
|
+
if self.offline_mode:
|
|
101
|
+
raise Exception("Cannot fetch repository details in offline mode")
|
|
102
|
+
|
|
96
103
|
url = f"{self.base_url}/repos/{owner}/{repo}"
|
|
97
104
|
response = requests.get(url)
|
|
98
105
|
|
|
@@ -113,6 +120,9 @@ class RepoFetcher:
|
|
|
113
120
|
:param branch: Branch name
|
|
114
121
|
:return: GitHubBranch object
|
|
115
122
|
"""
|
|
123
|
+
if self.offline_mode:
|
|
124
|
+
raise Exception("Cannot fetch branch details in offline mode")
|
|
125
|
+
|
|
116
126
|
url = f"{self.base_url}/repos/{owner}/{repo}/branches/{branch}"
|
|
117
127
|
response = requests.get(url)
|
|
118
128
|
|
|
@@ -133,6 +143,9 @@ class RepoFetcher:
|
|
|
133
143
|
:param branch: Branch name (default: 'main').
|
|
134
144
|
:return: List of directories in the repository.
|
|
135
145
|
"""
|
|
146
|
+
if self.offline_mode:
|
|
147
|
+
raise Exception("Cannot list directories in offline mode")
|
|
148
|
+
|
|
136
149
|
url = f"https://api.github.com/repos/{owner}/{repo}/contents/?ref={branch}"
|
|
137
150
|
response = requests.get(url)
|
|
138
151
|
response.raise_for_status() # Raise an error for bad responses
|
|
@@ -150,6 +163,9 @@ class RepoFetcher:
|
|
|
150
163
|
:param branch: Branch name (default: 'main').
|
|
151
164
|
:return: List of files in the specified directory.
|
|
152
165
|
"""
|
|
166
|
+
if self.offline_mode:
|
|
167
|
+
raise Exception("Cannot list files in offline mode")
|
|
168
|
+
|
|
153
169
|
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{directory}?ref={branch}"
|
|
154
170
|
response = requests.get(url)
|
|
155
171
|
response.raise_for_status() # Raise an error for bad responses
|
|
@@ -157,18 +173,24 @@ class RepoFetcher:
|
|
|
157
173
|
files = [item["name"] for item in contents if item["type"] == "file"]
|
|
158
174
|
return files
|
|
159
175
|
|
|
160
|
-
def clone_repository(self, owner: str, repo: str, branch: Optional[str] = None, local_path: str | None = None):
|
|
176
|
+
def clone_repository(self, owner: str, repo: str, branch: Optional[str] = None, local_path: str | None = None, shallow: bool = True):
|
|
161
177
|
"""
|
|
162
178
|
Clone a GitHub repository to a target directory.
|
|
163
179
|
:param owner: Repository owner
|
|
164
180
|
:param repo: Repository name
|
|
165
181
|
:param target_dir: The directory where the repository should be cloned.
|
|
166
182
|
:param branch: (Optional) The branch to clone. Clones the default branch if None.
|
|
183
|
+
:param shallow: (Optional) If True, performs a shallow clone with --depth 1. Default is True.
|
|
167
184
|
"""
|
|
185
|
+
if self.offline_mode:
|
|
186
|
+
raise Exception("Cannot clone repository in offline mode")
|
|
187
|
+
|
|
168
188
|
repo_url = f"https://github.com/{owner}/{repo}.git"
|
|
169
189
|
destination = local_path if local_path else f"{self.repo_dir}/{repo}"
|
|
170
190
|
|
|
171
191
|
command = ["git", "clone", repo_url, destination]
|
|
192
|
+
if shallow:
|
|
193
|
+
command.extend(["--depth", "1"])
|
|
172
194
|
if branch:
|
|
173
195
|
command.extend(["--branch", branch])
|
|
174
196
|
with redirect_stdout_to_log():
|
|
@@ -179,8 +201,63 @@ class RepoFetcher:
|
|
|
179
201
|
else:
|
|
180
202
|
current_work_dir = os.getcwd()
|
|
181
203
|
os.chdir(f"{destination}")
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
|
|
205
|
+
# Clean up any conflicted state first
|
|
206
|
+
try:
|
|
207
|
+
subprocess.run(["git", "reset", "--hard"], capture_output=True, check=False)
|
|
208
|
+
subprocess.run(["git", "clean", "-fd"], capture_output=True, check=False)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
# Check if the requested branch exists locally
|
|
213
|
+
try:
|
|
214
|
+
result = subprocess.run(
|
|
215
|
+
["git", "rev-parse", "--verify", f"refs/heads/{branch}"],
|
|
216
|
+
capture_output=True,
|
|
217
|
+
check=False
|
|
218
|
+
)
|
|
219
|
+
branch_exists_locally = result.returncode == 0
|
|
220
|
+
except Exception:
|
|
221
|
+
branch_exists_locally = False
|
|
222
|
+
|
|
223
|
+
if not branch_exists_locally and branch:
|
|
224
|
+
# If branch doesn't exist locally, we need to fetch it
|
|
225
|
+
# For shallow repos, we need to unshallow first or fetch the specific branch
|
|
226
|
+
try:
|
|
227
|
+
# Try to fetch the specific branch
|
|
228
|
+
subprocess.run(["git", "fetch", "origin", f"{branch}:{branch}"], check=True)
|
|
229
|
+
_LOGGER.debug(f"Fetched new branch {branch}")
|
|
230
|
+
except subprocess.CalledProcessError:
|
|
231
|
+
# If that fails, unshallow and try again
|
|
232
|
+
subprocess.run(["git", "fetch", "--unshallow"], check=True)
|
|
233
|
+
subprocess.run(["git", "fetch", "origin", f"{branch}:{branch}"], check=True)
|
|
234
|
+
_LOGGER.debug(f"Unshallowed repo and fetched branch {branch}")
|
|
235
|
+
|
|
236
|
+
# Switch to the requested branch if specified
|
|
237
|
+
if branch:
|
|
238
|
+
subprocess.run(["git", "checkout", branch], check=True)
|
|
239
|
+
_LOGGER.debug(f"Switched to branch {branch}")
|
|
240
|
+
|
|
241
|
+
# For shallow repos that switched branches, just ensure we have latest
|
|
242
|
+
# to avoid merge conflicts from different commit histories
|
|
243
|
+
if branch and not branch_exists_locally:
|
|
244
|
+
# We already fetched the branch, no need for additional reset
|
|
245
|
+
_LOGGER.debug(f"Switched to newly fetched branch {branch}")
|
|
246
|
+
else:
|
|
247
|
+
# Pull latest changes for normal updates
|
|
248
|
+
try:
|
|
249
|
+
subprocess.run(["git", "pull"], check=True)
|
|
250
|
+
except subprocess.CalledProcessError:
|
|
251
|
+
# If pull fails, try to fetch and reset
|
|
252
|
+
subprocess.run(["git", "fetch"], check=True)
|
|
253
|
+
# Check if remote tracking branch exists
|
|
254
|
+
try:
|
|
255
|
+
subprocess.run(["git", "rev-parse", f"origin/{branch}"], capture_output=True, check=True)
|
|
256
|
+
subprocess.run(["git", "reset", "--hard", f"origin/{branch}"], check=True)
|
|
257
|
+
_LOGGER.debug(f"Reset to origin/{branch} after pull failure")
|
|
258
|
+
except subprocess.CalledProcessError:
|
|
259
|
+
# Remote tracking branch doesn't exist, just continue
|
|
260
|
+
_LOGGER.debug(f"No remote tracking branch for {branch}, continuing")
|
|
184
261
|
os.chdir(current_work_dir)
|
|
185
262
|
|
|
186
263
|
except Exception as e:
|
|
@@ -188,11 +265,17 @@ class RepoFetcher:
|
|
|
188
265
|
|
|
189
266
|
def get_github_version_with_api(self, owner: str, repo: str, branch: str = "main"):
|
|
190
267
|
"""Fetch the latest commit version (or any other versioning scheme) from GitHub."""
|
|
268
|
+
if self.offline_mode:
|
|
269
|
+
raise Exception("Cannot get GitHub version in offline mode")
|
|
191
270
|
details = self.fetch_branch_details(owner, repo, branch)
|
|
192
271
|
return details.commit.get("sha")
|
|
193
272
|
|
|
194
273
|
def get_github_version(self, owner: str, repo: str, branch: str = "main"):
|
|
195
274
|
"""Fetch the latest commit version (or any other versioning scheme) from GitHub. with command git fetch"""
|
|
275
|
+
if self.offline_mode:
|
|
276
|
+
_LOGGER.debug("Cannot get GitHub version in offline mode")
|
|
277
|
+
return None
|
|
278
|
+
|
|
196
279
|
repo_url = f"https://github.com/{owner}/{repo}.git"
|
|
197
280
|
command = ["git", "ls-remote", repo_url, f"{self.repo_dir}/{repo}"]
|
|
198
281
|
if branch:
|
esgvoc/core/service/__init__.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_config_manager():
|
|
|
20
20
|
global config_manager
|
|
21
21
|
if config_manager is None:
|
|
22
22
|
|
|
23
|
-
config_manager = ConfigManager(ServiceSettings, app_name="esgvoc", app_author="ipsl", default_settings=ServiceSettings.
|
|
23
|
+
config_manager = ConfigManager(ServiceSettings, app_name="esgvoc", app_author="ipsl", default_settings=ServiceSettings._get_default_settings())
|
|
24
24
|
active_config_name= config_manager.get_active_config_name()
|
|
25
25
|
config_manager.data_config_dir = config_manager.data_dir / active_config_name
|
|
26
26
|
config_manager.data_config_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -29,9 +29,10 @@ def get_config_manager():
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def get_state():
|
|
32
|
-
global current_state
|
|
32
|
+
global current_state
|
|
33
33
|
if config_manager is not None:
|
|
34
|
-
|
|
34
|
+
service_settings = config_manager.get_active_config()
|
|
35
|
+
current_state = StateService(service_settings)
|
|
35
36
|
return current_state
|
|
36
37
|
|
|
37
38
|
# Singleton Access Function
|
|
@@ -11,16 +11,18 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
# Define a generic type for configuration
|
|
12
12
|
T = TypeVar("T", bound="ConfigSchema")
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
class ConfigSchema(Protocol):
|
|
15
16
|
"""Protocol for application-specific configuration classes."""
|
|
16
17
|
|
|
17
18
|
@classmethod
|
|
18
19
|
def load_from_file(cls, file_path: str): ...
|
|
19
|
-
|
|
20
|
+
|
|
20
21
|
def save_to_file(self, file_path: str): ...
|
|
21
22
|
|
|
23
|
+
|
|
22
24
|
class ConfigManager(Generic[T]):
|
|
23
|
-
def __init__(self, config_cls: Type[T], app_name: str, app_author: str, default_settings
|
|
25
|
+
def __init__(self, config_cls: Type[T], app_name: str, app_author: str, default_settings: dict | None = None):
|
|
24
26
|
"""
|
|
25
27
|
Initialize the configuration manager.
|
|
26
28
|
- config_cls: A class that implements `ConfigSchema` (e.g., ServiceSettings).
|
|
@@ -33,11 +35,10 @@ class ConfigManager(Generic[T]):
|
|
|
33
35
|
# Define standard paths
|
|
34
36
|
self.config_dir = Path(self.dirs.user_config_path).expanduser().resolve()
|
|
35
37
|
self.data_dir = Path(self.dirs.user_data_path).expanduser().resolve()
|
|
36
|
-
self.data_config_dir = None
|
|
38
|
+
self.data_config_dir = None # depends on loaded settings
|
|
37
39
|
|
|
38
40
|
self.cache_dir = Path(self.dirs.user_cache_path).expanduser().resolve()
|
|
39
41
|
|
|
40
|
-
|
|
41
42
|
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
42
43
|
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
43
44
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -80,7 +81,7 @@ class ConfigManager(Generic[T]):
|
|
|
80
81
|
active_config_name = registry["active"]
|
|
81
82
|
return Path(registry["configs"][active_config_name])
|
|
82
83
|
|
|
83
|
-
def get_config(self, config_name:str) -> T:
|
|
84
|
+
def get_config(self, config_name: str) -> T:
|
|
84
85
|
"""Load the configuration as an instance of the given config schema."""
|
|
85
86
|
registry = self._load_toml(self.registry_path)
|
|
86
87
|
if config_name not in registry["configs"]:
|
|
@@ -92,9 +93,14 @@ class ConfigManager(Generic[T]):
|
|
|
92
93
|
def get_active_config(self) -> T:
|
|
93
94
|
"""Load the active configuration as an instance of the given config schema."""
|
|
94
95
|
active_config_path = self._get_active_config_path()
|
|
96
|
+
active_config_name = self.get_active_config_name()
|
|
97
|
+
|
|
98
|
+
settings = self.config_cls.load_from_file(str(active_config_path))
|
|
99
|
+
# Set the config name if the settings support it (duck typing)
|
|
100
|
+
if hasattr(settings, 'set_config_name'):
|
|
101
|
+
settings.set_config_name(active_config_name)
|
|
102
|
+
return settings
|
|
95
103
|
|
|
96
|
-
return self.config_cls.load_from_file(str(active_config_path))
|
|
97
|
-
|
|
98
104
|
def get_active_config_name(self) -> str:
|
|
99
105
|
"""Retrieve the config name from the registry"""
|
|
100
106
|
registry = self._load_toml(self.registry_path)
|
|
@@ -102,7 +108,7 @@ class ConfigManager(Generic[T]):
|
|
|
102
108
|
|
|
103
109
|
def save_config(self, config_data: dict, name: str | None = None) -> None:
|
|
104
110
|
"""Save the modified configuration to the corresponding file and update the registry."""
|
|
105
|
-
|
|
111
|
+
|
|
106
112
|
if name:
|
|
107
113
|
# If a name is provided, save the configuration with that name
|
|
108
114
|
config_path = self.config_dir / f"{name}.toml"
|
|
@@ -119,11 +125,11 @@ class ConfigManager(Generic[T]):
|
|
|
119
125
|
# If no name is provided, give the user a default name, like "user_config"
|
|
120
126
|
default_name = "user_config"
|
|
121
127
|
config_path = self.config_dir / f"{default_name}.toml"
|
|
122
|
-
|
|
128
|
+
|
|
123
129
|
# Check if the user_config already exists, if so, warn them
|
|
124
130
|
if config_path.exists():
|
|
125
131
|
logger.warning(f"{default_name}.toml already exists. Overwriting with the new config.")
|
|
126
|
-
|
|
132
|
+
|
|
127
133
|
# Save the configuration with the default name
|
|
128
134
|
self._save_toml(config_path, config_data)
|
|
129
135
|
|
|
@@ -147,7 +153,7 @@ class ConfigManager(Generic[T]):
|
|
|
147
153
|
logger.error(f"Config '{config_name}' not found in registry.")
|
|
148
154
|
raise ValueError(f"Config '{config_name}' not found in registry.")
|
|
149
155
|
registry["active"] = config_name
|
|
150
|
-
|
|
156
|
+
|
|
151
157
|
self._save_toml(self.registry_path, registry)
|
|
152
158
|
logger.info(f"Switched to configuration: {config_name}")
|
|
153
159
|
|
|
@@ -181,8 +187,3 @@ class ConfigManager(Generic[T]):
|
|
|
181
187
|
if registry["active"] not in registry["configs"]:
|
|
182
188
|
self.switch_config("default")
|
|
183
189
|
logger.info("active configuration doesnot exist anymore : Switch to default configuration")
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|