diffsage 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. diffsage/__init__.py +0 -0
  2. diffsage/__main__.py +4 -0
  3. diffsage/cli.py +38 -0
  4. diffsage/commands/__init__.py +0 -0
  5. diffsage/commands/ask.py +62 -0
  6. diffsage/commands/auth.py +153 -0
  7. diffsage/commands/commit.py +125 -0
  8. diffsage/commands/config.py +216 -0
  9. diffsage/commands/doctor.py +18 -0
  10. diffsage/config/__init__.py +0 -0
  11. diffsage/config/defaults.py +20 -0
  12. diffsage/config/file_loader.py +21 -0
  13. diffsage/config/loader.py +8 -0
  14. diffsage/config/paths.py +61 -0
  15. diffsage/config/resolver.py +118 -0
  16. diffsage/config/schema.py +53 -0
  17. diffsage/config/scope.py +8 -0
  18. diffsage/config/settings.py +9 -0
  19. diffsage/exceptions/__init__.py +34 -0
  20. diffsage/exceptions/base.py +32 -0
  21. diffsage/exceptions/credentials.py +8 -0
  22. diffsage/exceptions/git.py +9 -0
  23. diffsage/exceptions/provider.py +17 -0
  24. diffsage/git/__init__.py +0 -0
  25. diffsage/git/client.py +155 -0
  26. diffsage/logging/__init__.py +0 -0
  27. diffsage/logging/formatter.py +0 -0
  28. diffsage/logging/logger.py +41 -0
  29. diffsage/models/__init__.py +0 -0
  30. diffsage/models/commit_message.py +9 -0
  31. diffsage/models/config.py +16 -0
  32. diffsage/models/credentials.py +8 -0
  33. diffsage/models/doctor.py +13 -0
  34. diffsage/models/git.py +32 -0
  35. diffsage/models/provider.py +20 -0
  36. diffsage/parsers/commit_message_parser.py +43 -0
  37. diffsage/prompts/__init__.py +0 -0
  38. diffsage/providers/__init__.py +0 -0
  39. diffsage/providers/base.py +8 -0
  40. diffsage/providers/factory.py +14 -0
  41. diffsage/providers/gemini_provider.py +77 -0
  42. diffsage/services/__init__.py +0 -0
  43. diffsage/services/ai_service.py +63 -0
  44. diffsage/services/commit_service.py +38 -0
  45. diffsage/services/config_service.py +109 -0
  46. diffsage/services/credentials_service.py +61 -0
  47. diffsage/services/doctor_service.py +47 -0
  48. diffsage/services/editor_service.py +75 -0
  49. diffsage/services/git_service.py +20 -0
  50. diffsage/services/prompt_service.py +60 -0
  51. diffsage/storage/__init__.py +0 -0
  52. diffsage/storage/base.py +0 -0
  53. diffsage/storage/config_repository.py +85 -0
  54. diffsage/storage/credentials_repository.py +121 -0
  55. diffsage/ui/__init__.py +0 -0
  56. diffsage/ui/ask_view.py +10 -0
  57. diffsage/ui/auth_view.py +51 -0
  58. diffsage/ui/base.py +15 -0
  59. diffsage/ui/commit_view.py +100 -0
  60. diffsage/ui/config_view.py +58 -0
  61. diffsage/ui/doctor_view.py +39 -0
  62. diffsage-1.2.0.dist-info/METADATA +471 -0
  63. diffsage-1.2.0.dist-info/RECORD +67 -0
  64. diffsage-1.2.0.dist-info/WHEEL +5 -0
  65. diffsage-1.2.0.dist-info/entry_points.txt +2 -0
  66. diffsage-1.2.0.dist-info/licenses/LICENSE +21 -0
  67. diffsage-1.2.0.dist-info/top_level.txt +1 -0
diffsage/__init__.py ADDED
File without changes
diffsage/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from diffsage.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
diffsage/cli.py ADDED
@@ -0,0 +1,38 @@
1
+ import logging
2
+
3
+ import typer
4
+
5
+ from diffsage.commands.ask import ask
6
+ from diffsage.commands.auth import app as auth_app
7
+ from diffsage.commands.commit import commit
8
+ from diffsage.commands.config import app as config_app
9
+ from diffsage.commands.doctor import doctor
10
+ from diffsage.config.loader import load_settings
11
+ from diffsage.logging.logger import configure_logging
12
+
13
+ app = typer.Typer(help="DiffSage: AI-aware Git workflow toolkit")
14
+
15
+
16
+ @app.callback()
17
+ def main() -> None:
18
+ """
19
+ Initialize DiffSage.
20
+ """
21
+ settings = load_settings()
22
+ configure_logging(
23
+ getattr(
24
+ logging,
25
+ settings.log_level.upper(),
26
+ logging.INFO,
27
+ )
28
+ )
29
+
30
+
31
+ app.command()(doctor)
32
+ app.command()(ask)
33
+ app.command()(commit)
34
+ app.add_typer(
35
+ config_app,
36
+ name="config",
37
+ )
38
+ app.add_typer(auth_app, name="auth")
File without changes
@@ -0,0 +1,62 @@
1
+ from diffsage.config.loader import load_settings
2
+ from diffsage.config.paths import get_credentials_path
3
+ from diffsage.exceptions import (
4
+ AuthenticationError,
5
+ ConfigError,
6
+ CredentialNotFoundError,
7
+ ProviderUnavailableError,
8
+ )
9
+ from diffsage.logging.logger import get_logger
10
+ from diffsage.services.ai_service import AIService
11
+ from diffsage.services.credentials_service import CredentialService
12
+ from diffsage.storage.credentials_repository import CredentialsRepository
13
+ from diffsage.ui.ask_view import AskView
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ def ask(prompt: str) -> None:
19
+ """Ask the configured AI provider a question"""
20
+
21
+ logger.info("Running ask command")
22
+ view = AskView()
23
+
24
+ try:
25
+ settings = load_settings()
26
+
27
+ credential_path = get_credentials_path()
28
+ repository = CredentialsRepository(credential_path)
29
+ credential_service = CredentialService(repository)
30
+
31
+ service = AIService(
32
+ settings,
33
+ credential_service,
34
+ )
35
+
36
+ response = service.ask(prompt)
37
+ view.show_response(response)
38
+
39
+ except AuthenticationError as e:
40
+ logger.warning(str(e))
41
+ view.show_error(str(e))
42
+ raise SystemExit(1) from None
43
+
44
+ except CredentialNotFoundError as e:
45
+ logger.warning(str(e))
46
+ view.show_error(str(e))
47
+ raise SystemExit(1) from None
48
+
49
+ except ProviderUnavailableError as e:
50
+ logger.warning(str(e))
51
+ view.show_error(str(e))
52
+ raise SystemExit(1) from None
53
+
54
+ except ConfigError as e:
55
+ logger.warning(str(e))
56
+ view.show_error(str(e))
57
+ raise SystemExit(1) from None
58
+
59
+ except Exception:
60
+ logger.exception("Unexpected error while executing ask command.")
61
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
62
+ raise SystemExit(1) from None
@@ -0,0 +1,153 @@
1
+ import typer
2
+
3
+ from diffsage.config.paths import get_credentials_path
4
+ from diffsage.logging.logger import get_logger
5
+ from diffsage.services.credentials_service import CredentialService
6
+ from diffsage.storage.credentials_repository import CredentialsRepository
7
+ from diffsage.ui.auth_view import AuthView
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ app = typer.Typer(
12
+ help="DiffSage credential management",
13
+ invoke_without_command=False,
14
+ )
15
+
16
+
17
+ @app.command("set")
18
+ def set_credential(
19
+ provider: str,
20
+ api_key: str,
21
+ name: str = typer.Option(
22
+ "default",
23
+ "--name",
24
+ help="Credential profile name.",
25
+ ),
26
+ ) -> None:
27
+ """Store a DiffSage provider credential."""
28
+
29
+ view = AuthView()
30
+
31
+ try:
32
+ path = get_credentials_path()
33
+ repository = CredentialsRepository(path)
34
+ service = CredentialService(repository)
35
+
36
+ service.set_credential(
37
+ provider,
38
+ api_key,
39
+ name,
40
+ )
41
+
42
+ view.show_success("Credential saved.")
43
+
44
+ except ValueError as e:
45
+ logger.warning(str(e))
46
+ view.show_error(str(e))
47
+ raise SystemExit(1) from None
48
+
49
+ except Exception:
50
+ logger.exception("Unexpected error while executing auth command.")
51
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
52
+ raise SystemExit(1) from None
53
+
54
+
55
+ @app.command("get")
56
+ def get_credential(
57
+ provider: str,
58
+ name: str = typer.Option(
59
+ "default",
60
+ "--name",
61
+ help="Credential profile name.",
62
+ ),
63
+ ) -> None:
64
+ """Get a DiffSage provider credential."""
65
+
66
+ view = AuthView()
67
+
68
+ try:
69
+ path = get_credentials_path()
70
+ repository = CredentialsRepository(path)
71
+ service = CredentialService(repository)
72
+
73
+ credential = service.get_credential(
74
+ provider,
75
+ name,
76
+ )
77
+
78
+ if credential is None:
79
+ view.show_error(f"Credential not found for provider '{provider}' and profile '{name}'.")
80
+ raise SystemExit(1)
81
+
82
+ view.show_credential(credential)
83
+
84
+ except ValueError as e:
85
+ logger.warning(str(e))
86
+ view.show_error(str(e))
87
+ raise SystemExit(1) from None
88
+
89
+ except Exception:
90
+ logger.exception("Unexpected error while executing auth command.")
91
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
92
+ raise SystemExit(1) from None
93
+
94
+
95
+ @app.command("list")
96
+ def list_credentials() -> None:
97
+ """List configured DiffSage credentials."""
98
+
99
+ view = AuthView()
100
+
101
+ try:
102
+ path = get_credentials_path()
103
+ repository = CredentialsRepository(path)
104
+ service = CredentialService(repository)
105
+
106
+ credentials = service.list_credentials()
107
+
108
+ view.show_credentials(credentials)
109
+
110
+ except Exception:
111
+ logger.exception("Unexpected error while executing auth command.")
112
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
113
+ raise SystemExit(1) from None
114
+
115
+
116
+ @app.command("unset")
117
+ def unset_credential(
118
+ provider: str,
119
+ name: str = typer.Option(
120
+ "default",
121
+ "--name",
122
+ help="Credential profile name.",
123
+ ),
124
+ ) -> None:
125
+ """Unset DiffSage provider credential."""
126
+
127
+ view = AuthView()
128
+
129
+ try:
130
+ path = get_credentials_path()
131
+ repository = CredentialsRepository(path)
132
+ service = CredentialService(repository)
133
+
134
+ deleted = service.delete_credential(
135
+ provider,
136
+ name,
137
+ )
138
+
139
+ if not deleted:
140
+ view.show_error(f"Credential not found for provider '{provider}' and profile '{name}'.")
141
+ raise SystemExit(1)
142
+
143
+ view.show_success("Credential removed.")
144
+
145
+ except ValueError as e:
146
+ logger.warning(str(e))
147
+ view.show_error(str(e))
148
+ raise SystemExit(1) from None
149
+
150
+ except Exception:
151
+ logger.exception("Unexpected error while executing auth command.")
152
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
153
+ raise SystemExit(1) from None
@@ -0,0 +1,125 @@
1
+ from diffsage.config.loader import load_settings
2
+ from diffsage.config.paths import get_credentials_path
3
+ from diffsage.exceptions import (
4
+ ConfigError,
5
+ CredentialNotFoundError,
6
+ NoStagedChangesError,
7
+ NotGitRepositoryError,
8
+ ProviderError,
9
+ )
10
+ from diffsage.git.client import GitClient
11
+ from diffsage.logging.logger import get_logger
12
+ from diffsage.parsers.commit_message_parser import CommitMessageParser
13
+ from diffsage.services.ai_service import AIService
14
+ from diffsage.services.commit_service import CommitService
15
+ from diffsage.services.credentials_service import CredentialService
16
+ from diffsage.services.editor_service import EditorService
17
+ from diffsage.services.git_service import GitService
18
+ from diffsage.services.prompt_service import PromptService
19
+ from diffsage.storage.credentials_repository import CredentialsRepository
20
+ from diffsage.ui.commit_view import CommitView
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ def generate_message(commit_service: CommitService) -> str:
26
+ return commit_service.generate_commit_message()
27
+
28
+
29
+ def commit() -> None:
30
+ """Generate a conventional commit message"""
31
+
32
+ try:
33
+ git_client = GitClient()
34
+ view = CommitView()
35
+ editor = EditorService()
36
+
37
+ settings = load_settings()
38
+
39
+ credential_path = get_credentials_path()
40
+ credential_repository = CredentialsRepository(credential_path)
41
+ credential_service = CredentialService(credential_repository)
42
+
43
+ git_service = GitService(git_client)
44
+ ai_service = AIService(
45
+ settings,
46
+ credential_service,
47
+ )
48
+ prompt_service = PromptService()
49
+
50
+ commit_service = CommitService(
51
+ git_client,
52
+ git_service,
53
+ prompt_service,
54
+ ai_service,
55
+ )
56
+
57
+ with view.generating():
58
+ raw_message = generate_message(commit_service)
59
+ commit_message = CommitMessageParser.parse(raw_message)
60
+
61
+ view.show_generated()
62
+ view.show_commit(commit_message)
63
+
64
+ while True:
65
+ choice = view.prompt_action()
66
+
67
+ if choice in ("", "y"):
68
+ logger.info("User selected commit.")
69
+ git_client.commit(raw_message)
70
+ view.show_success(raw_message)
71
+ break
72
+
73
+ if choice == "e":
74
+ logger.info("User selected edit.")
75
+ raw_message = editor.edit(raw_message)
76
+ commit_message = CommitMessageParser.parse(raw_message)
77
+ view.show_commit(commit_message)
78
+ continue
79
+
80
+ if choice == "r":
81
+ logger.info("User selected regenerate.")
82
+ with view.generating():
83
+ raw_message = generate_message(commit_service)
84
+ commit_message = CommitMessageParser.parse(raw_message)
85
+
86
+ view.show_generated()
87
+ view.show_commit(commit_message)
88
+ continue
89
+
90
+ if choice == "n":
91
+ logger.info("User cancelled commit.")
92
+ view.show_cancelled()
93
+ break
94
+
95
+ view.show_invalid_option()
96
+
97
+ except NotGitRepositoryError:
98
+ logger.info("Command aborted: not a Git repository.")
99
+ view.show_not_git_repository()
100
+ raise SystemExit(1) from None
101
+
102
+ except NoStagedChangesError:
103
+ logger.info("Command aborted: no staged changes.")
104
+ view.show_no_staged_changes()
105
+ raise SystemExit(1) from None
106
+
107
+ except CredentialNotFoundError as e:
108
+ logger.warning(str(e))
109
+ view.show_error(str(e))
110
+ raise SystemExit(1) from None
111
+
112
+ except ProviderError as e:
113
+ logger.warning(str(e))
114
+ view.show_error(str(e))
115
+ raise SystemExit(1) from None
116
+
117
+ except ConfigError as e:
118
+ logger.warning(str(e))
119
+ view.show_error(str(e))
120
+ raise SystemExit(1) from None
121
+
122
+ except Exception:
123
+ logger.exception("Unexpected error while executing commit command.")
124
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
125
+ raise SystemExit(1) from None
@@ -0,0 +1,216 @@
1
+ import typer
2
+
3
+ from diffsage.config.loader import load_settings
4
+ from diffsage.config.resolver import get_local_config_path, resolve_config_path
5
+ from diffsage.config.scope import ConfigScope
6
+ from diffsage.exceptions import InvalidConfigurationValueError, UnknownConfigurationKeyError
7
+ from diffsage.logging.logger import get_logger
8
+ from diffsage.services.config_service import ConfigService
9
+ from diffsage.storage.config_repository import ConfigRepository
10
+ from diffsage.ui.config_view import ConfigView
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ app = typer.Typer(help="DiffSage configuration", invoke_without_command=False)
15
+
16
+
17
+ def _resolve_scope(*, local: bool, global_: bool) -> ConfigScope:
18
+ if local and global_:
19
+ raise typer.BadParameter("Cannot specify both --local and --global.")
20
+
21
+ if local:
22
+ return ConfigScope.LOCAL
23
+
24
+ return ConfigScope.GLOBAL
25
+
26
+
27
+ @app.command("list")
28
+ def list_config(
29
+ local: bool = typer.Option(False, "--local"), global_: bool = typer.Option(False, "--global")
30
+ ) -> None:
31
+ """List the current DiffSage configuration."""
32
+
33
+ view = ConfigView()
34
+
35
+ try:
36
+ settings = load_settings()
37
+ scope = _resolve_scope(
38
+ local=local,
39
+ global_=global_,
40
+ )
41
+
42
+ if local or global_:
43
+ path = resolve_config_path(scope)
44
+
45
+ repository = ConfigRepository(path)
46
+ service = ConfigService(settings, repository)
47
+
48
+ report = service.get_configuration_raw()
49
+
50
+ else:
51
+ path = get_local_config_path()
52
+
53
+ repository = ConfigRepository(path)
54
+ service = ConfigService(settings, repository)
55
+
56
+ report = service.get_configuration()
57
+
58
+ view.show_path(path)
59
+ view.show_configuration(report)
60
+
61
+ except typer.BadParameter as e:
62
+ logger.warning(str(e))
63
+ view.show_error(str(e))
64
+ raise SystemExit(1) from None
65
+
66
+ except Exception:
67
+ logger.exception("Unexpected error while executing config command.")
68
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
69
+ raise SystemExit(1) from None
70
+
71
+
72
+ @app.command("get")
73
+ def get_config(
74
+ key: str,
75
+ local: bool = typer.Option(False, "--local"),
76
+ global_: bool = typer.Option(False, "--global"),
77
+ ) -> None:
78
+ """Get the value of a DiffSage configuration."""
79
+
80
+ view = ConfigView()
81
+
82
+ try:
83
+ settings = load_settings()
84
+
85
+ if local or global_:
86
+ scope = _resolve_scope(
87
+ local=local,
88
+ global_=global_,
89
+ )
90
+
91
+ path = resolve_config_path(scope)
92
+ repository = ConfigRepository(path)
93
+ service = ConfigService(settings, repository)
94
+
95
+ report = service.get_configuration_value_raw(key.strip().lower())
96
+
97
+ else:
98
+ path = get_local_config_path()
99
+ repository = ConfigRepository(path)
100
+ service = ConfigService(settings, repository)
101
+
102
+ report = service.get_value(key.strip().lower())
103
+
104
+ view.show_path(path)
105
+ view.show_value(report)
106
+
107
+ except UnknownConfigurationKeyError as e:
108
+ logger.warning(str(e))
109
+ view.show_error(str(e))
110
+ raise SystemExit(1) from None
111
+
112
+ except typer.BadParameter as e:
113
+ logger.warning(str(e))
114
+ view.show_error(str(e))
115
+ raise SystemExit(1) from None
116
+
117
+ except Exception:
118
+ logger.exception("Unexpected error while executing config command.")
119
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
120
+ raise SystemExit(1) from None
121
+
122
+
123
+ @app.command("set")
124
+ def set_config(
125
+ key: str,
126
+ value: str,
127
+ local: bool = typer.Option(
128
+ False, "--local", help="Write to the repository's local configuration."
129
+ ),
130
+ global_: bool = typer.Option(
131
+ False, "--global", help="Write to the repository's global configuration."
132
+ ),
133
+ ) -> None:
134
+ """Set the value of a DiffSage configuration."""
135
+
136
+ view = ConfigView()
137
+
138
+ try:
139
+ settings = load_settings()
140
+
141
+ scope = _resolve_scope(local=local, global_=global_)
142
+ path = resolve_config_path(scope)
143
+ repository = ConfigRepository(path)
144
+
145
+ service = ConfigService(settings, repository)
146
+
147
+ report = service.set_value(key.strip().lower(), value)
148
+
149
+ view.show_success(f"{scope.value.capitalize()} configuration updated.")
150
+ view.show_path(path)
151
+ view.show_configuration(report)
152
+
153
+ except UnknownConfigurationKeyError as e:
154
+ logger.warning(str(e))
155
+ view.show_error(str(e))
156
+ raise SystemExit(1) from None
157
+
158
+ except InvalidConfigurationValueError as e:
159
+ logger.warning(str(e))
160
+ view.show_error(str(e))
161
+ raise SystemExit(1) from None
162
+
163
+ except typer.BadParameter as e:
164
+ logger.warning(str(e))
165
+ view.show_error(str(e))
166
+ raise SystemExit(1) from None
167
+
168
+ except Exception:
169
+ logger.exception("Unexpected error while executing config command.")
170
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
171
+ raise SystemExit(1) from None
172
+
173
+
174
+ @app.command("unset")
175
+ def unset_config(
176
+ key: str,
177
+ local: bool = typer.Option(
178
+ False, "--local", help="Write to the repository's local configuration."
179
+ ),
180
+ global_: bool = typer.Option(
181
+ False, "--global", help="Write to the repository's global configuration."
182
+ ),
183
+ ) -> None:
184
+ """Remove a DiffSage configuration value."""
185
+
186
+ view = ConfigView()
187
+
188
+ try:
189
+ settings = load_settings()
190
+
191
+ scope = _resolve_scope(local=local, global_=global_)
192
+ path = resolve_config_path(scope)
193
+ repository = ConfigRepository(path)
194
+
195
+ service = ConfigService(settings, repository)
196
+
197
+ report = service.unset_value(key.strip().lower())
198
+
199
+ view.show_success(f"{scope.value.capitalize()} configuration removed.")
200
+ view.show_path(path)
201
+ view.show_configuration(report)
202
+
203
+ except UnknownConfigurationKeyError as e:
204
+ logger.warning(str(e))
205
+ view.show_error(str(e))
206
+ raise SystemExit(1) from None
207
+
208
+ except typer.BadParameter as e:
209
+ logger.warning(str(e))
210
+ view.show_error(str(e))
211
+ raise SystemExit(1) from None
212
+
213
+ except Exception:
214
+ logger.exception("Unexpected error while executing config command.")
215
+ view.show_error("An unexpected error occurred. Please check the log file for more details.")
216
+ raise SystemExit(1) from None
@@ -0,0 +1,18 @@
1
+ from diffsage.logging.logger import get_logger
2
+ from diffsage.services.doctor_service import DoctorService
3
+ from diffsage.ui.doctor_view import DoctorView
4
+
5
+ logger = get_logger(__name__)
6
+
7
+
8
+ def doctor() -> None:
9
+ """Run environment diagnostics"""
10
+
11
+ logger.info("Running doctor command.")
12
+
13
+ service = DoctorService()
14
+ view = DoctorView()
15
+
16
+ report = service.run()
17
+
18
+ view.show_report(report)
File without changes
@@ -0,0 +1,20 @@
1
+ from diffsage.config.schema import (
2
+ AIConfig,
3
+ DiffSageConfig,
4
+ LoggingConfig,
5
+ NetworkConfig,
6
+ )
7
+
8
+ DEFAULT_CONFIG = DiffSageConfig(
9
+ ai=AIConfig(
10
+ provider="gemini",
11
+ model="gemini-3.5-flash-lite",
12
+ ),
13
+ network=NetworkConfig(
14
+ timeout=30,
15
+ max_retries=3,
16
+ ),
17
+ logging=LoggingConfig(
18
+ level="INFO",
19
+ ),
20
+ )
@@ -0,0 +1,21 @@
1
+ import tomllib
2
+ from pathlib import Path
3
+
4
+ from diffsage.config.schema import PartialDiffSageConfig
5
+ from diffsage.exceptions import ConfigError
6
+
7
+
8
+ def load_config(path: Path) -> PartialDiffSageConfig:
9
+ """Load a configuration file."""
10
+
11
+ try:
12
+ with path.open("rb") as file:
13
+ data = tomllib.load(file)
14
+
15
+ return PartialDiffSageConfig.model_validate(data)
16
+
17
+ except FileNotFoundError as error:
18
+ raise ConfigError(f"Configuration file not found: {path}") from error
19
+
20
+ except tomllib.TOMLDecodeError as error:
21
+ raise ConfigError(f"Invalid TOML syntax: {path}") from error
@@ -0,0 +1,8 @@
1
+ from diffsage.config.resolver import resolve_settings
2
+ from diffsage.config.settings import Settings
3
+
4
+
5
+ def load_settings() -> Settings:
6
+ """Load application settings."""
7
+
8
+ return resolve_settings()