config-cli-gui 0.1.4__tar.gz → 0.1.6__tar.gz

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 (74) hide show
  1. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/HISTORY.md +72 -50
  2. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/PKG-INFO +1 -1
  3. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/README.md +5 -0
  4. config_cli_gui-0.1.6/config.yaml +51 -0
  5. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/usage/config.md +5 -5
  6. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/_version.py +16 -3
  7. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/cli.py +5 -5
  8. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/config.py +63 -98
  9. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/docs.py +9 -11
  10. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/gui.py +38 -37
  11. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/PKG-INFO +1 -1
  12. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/SOURCES.txt +1 -0
  13. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/cli/cli_example.py +5 -5
  14. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/config/config_example.py +28 -24
  15. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/core/logging.py +6 -6
  16. config_cli_gui-0.1.6/tests/example_project/gui/config.yaml +51 -0
  17. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/gui/gui_example.py +6 -6
  18. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/test_generic_cli.py +9 -9
  19. config_cli_gui-0.1.4/config.yaml +0 -51
  20. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/FUNDING.yml +0 -0
  21. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  23. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/actions/setup-environment/action.yml +0 -0
  25. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/dependabot.yml +0 -0
  26. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/init.sh +0 -0
  27. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/release_message.sh +0 -0
  28. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/update_funding.py +0 -0
  29. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/workflows/main.yml +0 -0
  30. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/workflows/release.yml +0 -0
  31. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.github/workflows/update_readme.yml +0 -0
  32. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.gitignore +0 -0
  33. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.idea/runConfigurations/config_generate.xml +0 -0
  34. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.pre-commit-config.yaml +0 -0
  35. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/.readthedocs.yaml +0 -0
  36. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/LICENSE +0 -0
  37. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/Makefile +0 -0
  38. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/.nav.yml +0 -0
  39. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/_static/img/favicon.png +0 -0
  40. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/_static/img/logo.png +0 -0
  41. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/css/custom.css +0 -0
  42. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/develop/contributing.md +0 -0
  43. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/develop/make_windows.md +0 -0
  44. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/develop/naming_convention.md +0 -0
  45. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/funding/funding.md +0 -0
  46. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/getting-started/install.md +0 -0
  47. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/getting-started/virtual-environment.md +0 -0
  48. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/index.md +0 -0
  49. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/docs/usage/cli.md +0 -0
  50. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/mkdocs.yml +0 -0
  51. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/pyproject.toml +0 -0
  52. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/scripts/show_filelist.ps1 +0 -0
  53. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/scripts/show_tree.ps1 +0 -0
  54. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/scripts/show_tree.py +0 -0
  55. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/scripts/update_readme.py +0 -0
  56. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/setup.cfg +0 -0
  57. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/__init__.py +0 -0
  58. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui/__init__.py +0 -0
  59. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
  60. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
  61. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/requires.txt +0 -0
  62. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/src/config_cli_gui.egg-info/top_level.txt +0 -0
  63. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/template.yml.url +0 -0
  64. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/__init__.py +0 -0
  65. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/__init__.py +0 -0
  66. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/__main__.py +0 -0
  67. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/cli/__init__.py +0 -0
  68. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/cli/__main__.py +0 -0
  69. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/config/__init__.py +0 -0
  70. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/core/__init__.py +0 -0
  71. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/core/base.py +0 -0
  72. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/gui/__init__.py +0 -0
  73. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/tests/example_project/gui/__main__.py +0 -0
  74. {config_cli_gui-0.1.4 → config_cli_gui-0.1.6}/uv.lock +0 -0
@@ -1,50 +1,72 @@
1
- Changelog
2
- =========
3
-
4
-
5
- (unreleased)
6
- ------------
7
- - Update README.md from docs/index.md. [github-actions]
8
- - Feat #5: extensive renaming and adjustments. [Paul Magister]
9
- - Feat #5: extensive renaming. [Paul Magister]
10
- - Feat #5: extensive renaming. [Paul Magister]
11
-
12
-
13
- 0.1.2 (2025-06-23)
14
- ------------------
15
- - Feat #5: improve dialog. [Paul Magister]
16
- - Feat #5: Bugfig: use specific dialog for path parameters. [Paul
17
- Magister]
18
- - Fix #4: fix test. [Paul Magister]
19
- - Fix #4: write yaml file with nicely with comments. [Paul Magister]
20
- - Fix #4: add comments to the yaml. [Paul Magister]
21
- - Fix #4: make new special datatypes work (Color, Path): correct
22
- load/save lifecycle. [Paul Magister]
23
- - Feat #3: some advanced parameter types as example. [Paul Magister]
24
- - Feat #3: delete build workflow since it is not necessary for the lib.
25
- [Paul Magister]
26
- - Feat #3: add verbose and quiet doc to auto generated config. [Paul
27
- Magister]
28
-
29
-
30
- 0.1.1 (2025-06-22)
31
- ------------------
32
- - Feat #3: avoid type_ [Paul Magister]
33
- - Feat #3: Improved paramter types: [Paul Magister]
34
- - Feat #3: Improved paramter types: * move example project to tests *
35
- remove type_ * add improved widgets to gui_generator.py * separate
36
- module for docs_generator.py. [Paul Magister]
37
-
38
-
39
- 0.1.0 (2025-06-22)
40
- ------------------
41
- - Remove unnecessary example files and deps. [Paul Magister]
42
- - Update README.md from docs/index.md. [github-actions]
43
- - Fix doc: formatting. [Paul Magister]
44
-
45
-
46
- 0.0.2 (2025-06-22)
47
- ------------------
48
- - Remove _version.py. [Paul Magister]
49
-
50
-
1
+ Changelog
2
+ =========
3
+
4
+
5
+ 0.1.5 (2025-11-12)
6
+ ------------------
7
+ - Docs: Update HISTORY.md for release 0.1.5. [paul]
8
+ - #8 switch to value instead of default. [paul]
9
+ - #8 adjust doc: how to develop. [paul]
10
+ - #8 allow direct access to attributes without get_category #8. [paul]
11
+
12
+
13
+ 0.1.4 (2025-06-23)
14
+ ------------------
15
+ - Feat #5: extensive renaming. [Paul Magister]
16
+
17
+
18
+ 0.1.3 (2025-06-23)
19
+ ------------------
20
+ - Update README.md from docs/index.md. [github-actions]
21
+ - Feat #5: extensive renaming and adjustments. [Paul Magister]
22
+ - Feat #5: extensive renaming. [Paul Magister]
23
+ - Feat #5: extensive renaming. [Paul Magister]
24
+
25
+
26
+ 0.1.2 (2025-06-23)
27
+ ------------------
28
+ - Feat #5: improve dialog. [Paul Magister]
29
+ - Feat #5: Bugfig: use specific dialog for path parameters. [Paul
30
+ Magister]
31
+ - Fix #4: fix test. [Paul Magister]
32
+ - Fix #4: write yaml file with nicely with comments. [Paul Magister]
33
+ - Fix #4: add comments to the yaml. [Paul Magister]
34
+ - Fix #4: make new special datatypes work (Color, Path): correct
35
+ load/save lifecycle. [Paul Magister]
36
+ - Feat #3: some advanced parameter types as example. [Paul Magister]
37
+ - Feat #3: delete build workflow since it is not necessary for the lib.
38
+ [Paul Magister]
39
+ - Feat #3: add verbose and quiet doc to auto generated config. [Paul
40
+ Magister]
41
+
42
+
43
+ 0.1.1 (2025-06-22)
44
+ ------------------
45
+ - Feat #3: avoid type_ [Paul Magister]
46
+ - Feat #3: Improved paramter types: [Paul Magister]
47
+ - Feat #3: Improved paramter types: * move example project to tests *
48
+ remove type_ * add improved widgets to gui_generator.py * separate
49
+ module for docs_generator.py. [Paul Magister]
50
+
51
+
52
+ 0.1.0 (2025-06-22)
53
+ ------------------
54
+ - Remove unnecessary example files and deps. [Paul Magister]
55
+ - Update README.md from docs/index.md. [github-actions]
56
+ - Fix doc: formatting. [Paul Magister]
57
+
58
+
59
+ 0.0.2 (2025-06-22)
60
+ ------------------
61
+ - Remove _version.py. [Paul Magister]
62
+
63
+
64
+ 0.0.1 (2025-06-22)
65
+ ------------------
66
+ - Fix fmt. [Paul Magister]
67
+ - Doc update. [Paul Magister]
68
+ - First actual commit: cleanup. [Paul Magister]
69
+ - ✅ Project renamed from template. [github-actions[bot]]
70
+ - Initial commit. [Paul Magister]
71
+
72
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: config-cli-gui
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Feature-rich Python project template for config-cli-gui.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.10
@@ -30,6 +30,11 @@ You can install `config-cli-gui` using pip:
30
30
  pip install config-cli-gui
31
31
  ```
32
32
 
33
+ ## Contribution
34
+
35
+ Refer to this how-to in the referenced project for getting started to install and develop on this project:
36
+ https://github.com/pamagister/python-template-project
37
+
33
38
  ---
34
39
 
35
40
  ## ✨ Features
@@ -0,0 +1,51 @@
1
+ app:
2
+ # Date format to use | type=str, default value=%Y-%m-%d
3
+ date_format: '%Y-%m-%d'
4
+ # Enable logging to console | type=bool, default value=True
5
+ enable_console_logging: true
6
+ # Enable logging to file | type=bool, default value=True
7
+ enable_file_logging: true
8
+ # Number of backup log files to keep | type=int, default value=5
9
+ log_backup_count: 5
10
+ # Maximum log file size in MB before rotation | type=int, default value=10
11
+ log_file_max_size: 10
12
+ # Log message format style | type=str, default value=detailed
13
+ log_format: detailed
14
+ # Logging level for the application | type=str, default value=INFO
15
+ log_level: INFO
16
+ # Maximum number of worker threads | type=int, default value=4
17
+ max_workers: 4
18
+ cli:
19
+ # Include elevation data in waypoints | type=bool, default value=True [CLI]
20
+ elevation: true
21
+ # Extract starting points of each track as waypoint | type=bool, default value=True [CLI]
22
+ extract_waypoints: true
23
+ # Path to input (file or folder) | type=str, default value= [CLI]
24
+ input: ''
25
+ # Maximum distance between two waypoints | type=int, default value=20 [CLI]
26
+ min_dist: 20
27
+ # Path to output destination | type=str, default value= [CLI]
28
+ output: ''
29
+ gui:
30
+ # Automatically scroll to the newest log entries | type=bool, default value=True
31
+ auto_scroll_log: true
32
+ # Height of the log window in pixels | type=int, default value=200
33
+ log_window_height: 200
34
+ # Maximum number of log lines to keep in GUI | type=int, default value=1000
35
+ max_log_lines: 1000
36
+ # GUI theme setting | type=str, default value=light
37
+ theme: light
38
+ # Default window height | type=int, default value=600
39
+ window_height: 600
40
+ # Default window width | type=int, default value=800
41
+ window_width: 800
42
+ misc:
43
+ # Color setting for the application | type=Color, default value=#ff0000
44
+ some_color:
45
+ - 255
46
+ - 0
47
+ - 0
48
+ # Date setting for the application | type=datetime, default value=2025-11-12 22:24:14.121460
49
+ some_date: '2025-11-12T22:24:14.121460'
50
+ # Path to the file to use | type=PosixPath, default value=some_file.txt
51
+ some_file: some_file.txt
@@ -39,9 +39,9 @@ The parameters in the cli category can be accessed via the command line interfac
39
39
 
40
40
  ## Category "misc"
41
41
 
42
- | Name | Type | Description | Default | Choices |
43
- |------------|-------------|-----------------------------------|--------------------------------------------------|---------|
44
- | some_file | WindowsPath | Path to the file to use | WindowsPath('some_file.txt') | - |
45
- | some_color | Color | Color setting for the application | Color(255, 0, 0) | - |
46
- | some_date | datetime | Date setting for the application | datetime.datetime(2025, 6, 24, 0, 19, 57, 95774) | - |
42
+ | Name | Type | Description | Default | Choices |
43
+ |------------|-----------|-----------------------------------|-----------------------------------------------------|---------|
44
+ | some_file | PosixPath | Path to the file to use | PosixPath('some_file.txt') | - |
45
+ | some_color | Color | Color setting for the application | Color(255, 0, 0) | - |
46
+ | some_date | datetime | Date setting for the application | datetime.datetime(2025, 11, 12, 22, 24, 14, 121460) | - |
47
47
 
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.1.4'
21
- __version_tuple__ = version_tuple = (0, 1, 4)
31
+ __version__ = version = '0.1.6'
32
+ __version_tuple__ = version_tuple = (0, 1, 6)
33
+
34
+ __commit_id__ = commit_id = 'gd42f29151'
@@ -46,7 +46,7 @@ class CliGenerator:
46
46
 
47
47
  # Generate arguments from CLI config parameters
48
48
  for param in cli_params:
49
- param_type = type(param.default)
49
+ param_type = type(param.value)
50
50
 
51
51
  if param.required and param.cli_arg is None:
52
52
  # Positional argument
@@ -54,8 +54,8 @@ class CliGenerator:
54
54
  else:
55
55
  # Optional argument
56
56
  kwargs = {
57
- "default": argparse.SUPPRESS,
58
- "help": f"{param.help} (default: {param.default})",
57
+ "value": argparse.SUPPRESS,
58
+ "help": f"{param.help} (value: {param.value})",
59
59
  }
60
60
 
61
61
  # Handle different parameter types
@@ -67,8 +67,8 @@ class CliGenerator:
67
67
  elif param_type == float:
68
68
  kwargs["type"] = float
69
69
  elif param_type == bool:
70
- kwargs["action"] = "store_true" if not param.default else "store_false"
71
- kwargs["help"] = f"{param.help} (default: {param.default})"
70
+ kwargs["action"] = "store_true" if not param.value else "store_false"
71
+ kwargs["help"] = f"{param.help} (value: {param.value})"
72
72
  elif param_type == str:
73
73
  kwargs["type"] = str
74
74
 
@@ -48,7 +48,7 @@ class ConfigParameter:
48
48
  """Represents a single configuration parameter with all its metadata."""
49
49
 
50
50
  name: str
51
- default: Any
51
+ value: Any
52
52
  choices: list | tuple | None = None
53
53
  help: str = ""
54
54
  cli_arg: str = None
@@ -59,13 +59,13 @@ class ConfigParameter:
59
59
  def __post_init__(self):
60
60
  if self.is_cli and self.cli_arg is None and not self.required:
61
61
  self.cli_arg = f"--{self.name}"
62
- if isinstance(self.default, bool) and self.choices is None:
62
+ if isinstance(self.value, bool) and self.choices is None:
63
63
  self.choices = [True, False]
64
64
 
65
65
  @property
66
66
  def type_(self) -> type:
67
- """Get the type from the default value."""
68
- return type(self.default)
67
+ """Get the type from the value."""
68
+ return type(self.value)
69
69
 
70
70
 
71
71
  class ConfigCategory(BaseModel, ABC):
@@ -73,14 +73,13 @@ class ConfigCategory(BaseModel, ABC):
73
73
 
74
74
  @abstractmethod
75
75
  def get_category_name(self) -> str:
76
- """Return the category name for this configuration group."""
77
76
  pass
78
77
 
79
78
  def get_parameters(self) -> list[ConfigParameter]:
80
79
  """Get all ConfigParameter objects from this category."""
81
80
  parameters = []
82
81
  for field_name in self.__class__.model_fields:
83
- param = getattr(self, field_name)
82
+ param = self.model_fields[field_name].default
84
83
  if isinstance(param, ConfigParameter):
85
84
  param.category = self.get_category_name()
86
85
  parameters.append(param)
@@ -99,19 +98,18 @@ class ConfigManager:
99
98
  """
100
99
  self._categories: dict[str, ConfigCategory] = {}
101
100
 
101
+ # Register categories and make accessible as attributes
102
102
  for category in categories:
103
- if isinstance(category, ConfigCategory):
104
- self.add_category(category.get_category_name(), category)
105
- else:
103
+ if not isinstance(category, ConfigCategory):
106
104
  raise TypeError(
107
- f"Category must be an instance of BaseConfigCategory, got {type(category)}"
105
+ f"Category must be an instance of ConfigCategory, got {type(category)}"
108
106
  )
107
+ name = category.get_category_name()
108
+ self.add_category(name, category)
109
109
 
110
- # Load from file if provided
111
110
  if config_file:
112
111
  self.load_from_file(config_file)
113
112
 
114
- # Override with provided kwargs
115
113
  self._apply_kwargs(kwargs)
116
114
 
117
115
  def add_category(self, name: str, category: ConfigCategory):
@@ -122,58 +120,46 @@ class ConfigManager:
122
120
  category: Configuration category instance
123
121
  """
124
122
  self._categories[name] = category
123
+ setattr(self, name, category)
125
124
 
126
125
  def get_category(self, name: str) -> ConfigCategory:
127
- """Get a configuration category by name."""
128
126
  return self._categories.get(name)
129
127
 
130
128
  def _apply_kwargs(self, kwargs: dict[str, Any]):
131
- """Apply keyword arguments to override configuration values."""
129
+ """Apply keyword overrides: category__param=value"""
132
130
  for key, value in kwargs.items():
133
131
  if "__" in key:
134
132
  category_name, param_name = key.split("__", 1)
135
133
  if category_name in self._categories:
136
134
  category = self._categories[category_name]
137
135
  if hasattr(category, param_name):
138
- param = getattr(category, param_name)
139
- if isinstance(param, ConfigParameter):
140
- param.default = value
136
+ setattr(category, param_name, value)
141
137
 
142
138
  def load_from_file(self, config_file: str):
143
- """Load configuration from JSON or YAML file."""
144
- config_path = Path(config_file)
145
- if not config_path.exists():
139
+ path = Path(config_file)
140
+ if not path.exists():
146
141
  raise FileNotFoundError(f"Configuration file not found: {config_file}")
147
-
148
- with open(config_path, "r", encoding="utf-8") as f:
149
- if config_path.suffix.lower() in [".yml", ".yaml"]:
150
- config_data = yaml.safe_load(f)
151
- else:
152
- config_data = json.load(f)
153
-
154
- # Store loaded data for later application
155
- self._apply_config_data(config_data)
156
-
157
- def _apply_config_data(self, _loaded_config_data):
158
- """Apply configuration data to categories."""
159
-
160
- # Apply loaded configuration
161
- for category_name, category_data in _loaded_config_data.items():
162
- if category_name in self._categories:
163
- category = self._categories[category_name]
164
- for param_name, param_value in category_data.items():
165
- if hasattr(category, param_name):
166
- param = getattr(category, param_name)
167
- if isinstance(param, ConfigParameter):
168
- # Handle special types
169
- if isinstance(param.default, Color) and isinstance(param_value, list):
170
- param.default = Color.from_list(param_value)
171
- elif isinstance(param.default, Path):
172
- param.default = Path(param_value)
173
- elif isinstance(param.default, datetime):
174
- param.default = datetime.fromisoformat(param_value)
175
- else:
176
- param.default = param_value
142
+ with open(path, "r", encoding="utf-8") as f:
143
+ data = yaml.safe_load(f) if path.suffix in [".yml", ".yaml"] else json.load(f)
144
+ self._apply_config_data(data)
145
+
146
+ def _apply_config_data(self, data: dict):
147
+ for category_name, category_data in data.items():
148
+ category = self._categories.get(category_name)
149
+ if not category:
150
+ continue
151
+ for param_name, param_value in category_data.items():
152
+ if hasattr(category, param_name):
153
+ param = category.model_fields.get(param_name).default
154
+ if isinstance(param, ConfigParameter):
155
+ default_value = getattr(category, param_name)
156
+ if isinstance(default_value, Color) and isinstance(param_value, list):
157
+ param_value = Color.from_list(param_value)
158
+ elif isinstance(default_value, Path):
159
+ param_value = Path(param_value)
160
+ elif isinstance(default_value, datetime):
161
+ param_value = datetime.fromisoformat(param_value)
162
+ setattr(getattr(self, category_name), param_name, param_value)
177
163
 
178
164
  def save_to_file(self, config_file: str, format_: str = "auto"):
179
165
  """Save current configuration to file with enhanced YAML formatting and comments.
@@ -182,34 +168,28 @@ class ConfigManager:
182
168
  config_file (str): The path to the configuration file.
183
169
  format_ (str): The format to save the file in ('auto', 'json', 'yaml').
184
170
  """
185
- config_path = Path(config_file)
186
- config_data = self.to_dict()
171
+ path = Path(config_file)
172
+ data = self.to_dict()
187
173
 
188
- # Determine format
189
174
  if format_ == "auto":
190
- format_ = "yaml" if config_path.suffix.lower() in [".yml", ".yaml"] else "json"
191
-
192
- # Ensure directory exists
193
- config_path.parent.mkdir(parents=True, exist_ok=True)
175
+ format_ = "yaml" if path.suffix in [".yml", ".yaml"] else "json"
194
176
 
195
- with open(config_path, "w", encoding="utf-8") as f:
177
+ path.parent.mkdir(parents=True, exist_ok=True)
178
+ with open(path, "w", encoding="utf-8") as f:
196
179
  if format_ == "yaml":
197
- yaml.dump(config_data, f, default_flow_style=False, indent=2)
180
+ yaml.dump(data, f, indent=2)
198
181
  else:
199
- json.dump(config_data, f, indent=2)
182
+ json.dump(data, f, indent=2)
200
183
 
201
- # Append comments for YAML files
202
184
  if format_ == "yaml":
203
- self._append_comments_to_yaml(config_path)
185
+ self._append_comments_to_yaml(path)
204
186
 
205
187
  def to_dict(self) -> dict[str, Any]:
206
- """Convert configuration to dictionary."""
207
188
  result = {}
208
- for category_name, category in self._categories.items():
189
+ for name, category in self._categories.items():
209
190
  category_dict = {}
210
191
  for param in category.get_parameters():
211
- value = param.default
212
- # Handle special types for serialization
192
+ value = getattr(category, param.name).value
213
193
  if isinstance(value, Color):
214
194
  value = value.to_list()
215
195
  elif isinstance(value, Path):
@@ -217,64 +197,49 @@ class ConfigManager:
217
197
  elif isinstance(value, datetime):
218
198
  value = value.isoformat()
219
199
  category_dict[param.name] = value
220
- result[category_name] = category_dict
200
+ result[name] = category_dict
221
201
  return result
222
202
 
223
203
  def get_all_parameters(self) -> list[ConfigParameter]:
224
- """Get all parameters from all categories."""
225
- parameters = []
226
- for category in self._categories.values():
227
- parameters.extend(category.get_parameters())
228
- return parameters
204
+ return [p for c in self._categories.values() for p in c.get_parameters()]
229
205
 
230
206
  def get_cli_parameters(self) -> list[ConfigParameter]:
231
- """Get parameters that are CLI-enabled."""
232
- cli_parameters = []
233
- for category in self._categories.values():
234
- for param in category.get_parameters():
235
- if param.is_cli:
236
- cli_parameters.append(param)
237
- return cli_parameters
207
+ return [p for p in self.get_all_parameters() if p.is_cli]
238
208
 
239
- def _append_comments_to_yaml(self, config_path: Path):
209
+ def _append_comments_to_yaml(self, path: Path):
240
210
  """Appends comments to a YAML file based on ConfigParameter metadata.
241
211
 
242
212
  Args:
243
213
  config_path (Path): The path to the YAML configuration file.
244
214
  """
245
- lines = config_path.read_text(encoding="utf-8").splitlines()
215
+
216
+ lines = path.read_text(encoding="utf-8").splitlines()
246
217
  new_lines = []
247
218
  all_parameters = {param.name: param for param in self.get_all_parameters()}
248
219
  current_category = None
249
220
 
250
221
  for line in lines:
251
- stripped_line = line.strip()
252
- # Check for category (e.g., 'app:')
253
- # A category should end with ':', not start with '#', and not be indented.
222
+ stripped = line.strip()
254
223
  if (
255
- stripped_line.endswith(":")
256
- and not stripped_line.startswith("#")
257
- and line.startswith(stripped_line)
224
+ stripped.endswith(":")
225
+ and not stripped.startswith("#")
226
+ and line.startswith(stripped)
258
227
  ):
259
- current_category = stripped_line[:-1]
228
+ current_category = stripped[:-1]
260
229
  new_lines.append(line)
261
230
  else:
262
- # Check for parameter (e.g., ' date_format: '%Y-%m-%d'')
263
- # This needs to handle cases where the value spans multiple lines
264
- parts = stripped_line.split(":", 1)
265
- if len(parts) > 1: # This line might be a parameter definition
231
+ parts = stripped.split(":", 1)
232
+ if len(parts) > 1:
266
233
  param_name = parts[0].strip()
267
234
  if param_name in all_parameters:
268
235
  param = all_parameters[param_name]
269
- # Ensure the parameter belongs to the current category
270
- # and is not a sub-item of a multi-line value
271
236
  if current_category and param.category == current_category:
272
- comment_indent = " " * (len(line) - len(stripped_line))
237
+ indent = " " * (len(line) - len(stripped))
273
238
  comment = (
274
- f"{comment_indent}# {param.help} | "
275
- f"type={type(param.default).__name__}, default={param.default}"
239
+ f"{indent}# {param.help} | "
240
+ f"type={type(param.value).__name__}, default value={param.value}"
276
241
  f"{' [CLI]' if param.is_cli else ''}"
277
242
  )
278
243
  new_lines.append(comment)
279
244
  new_lines.append(line)
280
- config_path.write_text("\n".join(new_lines), encoding="utf-8")
245
+ path.write_text("\n".join(new_lines), encoding="utf-8")
@@ -33,12 +33,12 @@ class DocumentationGenerator:
33
33
 
34
34
  for param in category.get_parameters():
35
35
  name = param.name
36
- typ = type(param.default).__name__
36
+ typ = type(param.value).__name__
37
37
  desc = param.help
38
- default = repr(param.default)
38
+ value = repr(param.value)
39
39
  choices = str(param.choices) if param.choices else "-"
40
40
 
41
- rows.append((name, typ, desc, default, choices))
41
+ rows.append((name, typ, desc, value, choices))
42
42
 
43
43
  if not rows:
44
44
  continue
@@ -85,17 +85,15 @@ class DocumentationGenerator:
85
85
 
86
86
  for param in cli_params:
87
87
  cli_arg = f"`--{param.name}`" if not param.required else f"`{param.name}`"
88
- typ = type(param.default).__name__
88
+ typ = type(param.value).__name__
89
89
  desc = param.help
90
- default = (
91
- "*required*"
92
- if param.required or param.default in (None, "")
93
- else repr(param.default)
90
+ value = (
91
+ "*required*" if param.required or param.value in (None, "") else repr(param.value)
94
92
  )
95
93
  choices = str(param.choices) if param.choices else "-"
96
94
 
97
- rows.append((cli_arg, typ, desc, default, choices))
98
- if default == "*required*":
95
+ rows.append((cli_arg, typ, desc, value, choices))
96
+ if value == "*required*":
99
97
  required_params.append(param)
100
98
  else:
101
99
  optional_params.append(param)
@@ -165,7 +163,7 @@ class DocumentationGenerator:
165
163
  for i, param in enumerate(optional_params[:3], 4):
166
164
  if param.name in ["verbose", "quiet"]:
167
165
  continue
168
- example_value = param.choices[0] if param.choices else param.default
166
+ example_value = param.choices[0] if param.choices else param.value
169
167
  examples.append(
170
168
  dedent(f"""
171
169
  ### {i}. With {param.name} parameter