esgvoc 1.1.1__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/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
- # display(service.state_service.table())
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
- table.add_row(f"{proj_name} path", proj.github_repo, proj.local_path, proj.db_path, style="white")
46
- table.add_row("Version", proj.github_version, proj.local_version, proj.db_version, style="bright_blue")
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['drs_specs'] = raw_drs_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['catalog_specs'] = raw_catalog_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)
@@ -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
- command = ["git", "pull"]
183
- subprocess.run(command, check=True)
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:
@@ -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.DEFAULT_SETTINGS)
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
- current_state = StateService(config_manager.get_active_config())
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 : dict | None = None ):
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 # depends on loaded settings
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
-