python-template-project 0.2.0__py3-none-any.whl → 0.2.1__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.
- python_template_project/_version.py +16 -3
- python_template_project/cli/cli.py +13 -12
- python_template_project/config/config.py +40 -60
- python_template_project/core/logging.py +9 -9
- python_template_project/gui/gui.py +8 -9
- {python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/METADATA +3 -4
- python_template_project-0.2.1.dist-info/RECORD +23 -0
- {python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/top_level.txt +0 -1
- generic_config_cli_gui/__init__.py +0 -0
- generic_config_cli_gui/cli_generator.py +0 -177
- generic_config_cli_gui/config_framework.py +0 -362
- generic_config_cli_gui/gui_generator.py +0 -225
- python_template_project-0.2.0.dist-info/RECORD +0 -27
- {python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/WHEEL +0 -0
- {python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/entry_points.txt +0 -0
- {python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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__ = [
|
|
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.2.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
31
|
+
__version__ = version = '0.2.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -5,7 +5,8 @@ This file uses the CliGenerator from the generic config framework.
|
|
|
5
5
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from config_cli_gui.cli import CliGenerator
|
|
9
|
+
|
|
9
10
|
from python_template_project.config.config import ConfigParameterManager
|
|
10
11
|
from python_template_project.core.base import BaseGPXProcessor
|
|
11
12
|
from python_template_project.core.logging import initialize_logging
|
|
@@ -22,19 +23,19 @@ def validate_config(config: ConfigParameterManager, logger) -> bool:
|
|
|
22
23
|
True if configuration is valid, False otherwise
|
|
23
24
|
"""
|
|
24
25
|
# Get CLI category and check required parameters
|
|
25
|
-
cli_category = config.
|
|
26
|
+
cli_category = config.cli
|
|
26
27
|
if not cli_category:
|
|
27
28
|
logger.error("No CLI configuration found")
|
|
28
29
|
return False
|
|
29
30
|
|
|
30
31
|
# Check if input parameter exists and has a value
|
|
31
|
-
input_param = getattr(cli_category, "input"
|
|
32
|
-
if not input_param or not input_param.
|
|
32
|
+
input_param = getattr(cli_category, "input")
|
|
33
|
+
if not input_param or not input_param.value:
|
|
33
34
|
logger.error("Input is required")
|
|
34
35
|
return False
|
|
35
36
|
|
|
36
37
|
# Check if input file exists
|
|
37
|
-
input_path = Path(input_param.
|
|
38
|
+
input_path = Path(input_param.value)
|
|
38
39
|
if not input_path.exists():
|
|
39
40
|
logger.error(f"File not found: {input_path}")
|
|
40
41
|
return False
|
|
@@ -67,15 +68,15 @@ def run_main_processing(config: ConfigParameterManager) -> int:
|
|
|
67
68
|
return 1
|
|
68
69
|
|
|
69
70
|
# Get CLI parameters
|
|
70
|
-
cli_category = config.
|
|
71
|
-
input_file = cli_category.input.
|
|
72
|
-
output_file = cli_category.output.
|
|
73
|
-
min_dist = cli_category.min_dist.
|
|
74
|
-
extract_waypoints = cli_category.extract_waypoints.
|
|
71
|
+
cli_category = config.cli
|
|
72
|
+
input_file = cli_category.input.value
|
|
73
|
+
output_file = cli_category.output.value
|
|
74
|
+
min_dist = cli_category.min_dist.value
|
|
75
|
+
extract_waypoints = cli_category.extract_waypoints.value
|
|
75
76
|
|
|
76
77
|
# Get app parameters
|
|
77
|
-
app_category = config.
|
|
78
|
-
date_format = app_category.date_format.
|
|
78
|
+
app_category = config.app
|
|
79
|
+
date_format = app_category.date_format.value if app_category else "%Y-%m-%d"
|
|
79
80
|
|
|
80
81
|
logger.info(f"Processing input: {input_file}")
|
|
81
82
|
|
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
"""Central configuration management for
|
|
1
|
+
"""Central configuration management for config-cli-gui project.
|
|
2
2
|
|
|
3
3
|
This module provides a single source of truth for all configuration parameters
|
|
4
4
|
organized in categories (CLI, App, GUI). It can generate config files, CLI modules,
|
|
5
5
|
and documentation from the parameter definitions.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
CliConfigCategory,
|
|
11
|
-
ConfigManager,
|
|
12
|
-
ConfigParameter,
|
|
13
|
-
DocumentationGenerator,
|
|
14
|
-
)
|
|
8
|
+
from config_cli_gui.config import ConfigCategory, ConfigManager, ConfigParameter
|
|
9
|
+
from config_cli_gui.docs import DocumentationGenerator
|
|
15
10
|
|
|
16
11
|
|
|
17
|
-
class CliConfig(
|
|
12
|
+
class CliConfig(ConfigCategory):
|
|
18
13
|
"""CLI-specific configuration parameters."""
|
|
19
14
|
|
|
20
15
|
def get_category_name(self) -> str:
|
|
@@ -23,44 +18,43 @@ class CliConfig(CliConfigCategory):
|
|
|
23
18
|
# Positional argument
|
|
24
19
|
input: ConfigParameter = ConfigParameter(
|
|
25
20
|
name="input",
|
|
26
|
-
|
|
27
|
-
type_=str,
|
|
21
|
+
value="",
|
|
28
22
|
help="Path to input (file or folder)",
|
|
29
23
|
required=True,
|
|
30
|
-
|
|
24
|
+
is_cli=True,
|
|
31
25
|
)
|
|
32
26
|
|
|
33
27
|
# Optional CLI arguments
|
|
34
28
|
output: ConfigParameter = ConfigParameter(
|
|
35
29
|
name="output",
|
|
36
|
-
|
|
37
|
-
type_=str,
|
|
30
|
+
value="",
|
|
38
31
|
help="Path to output destination",
|
|
32
|
+
is_cli=True,
|
|
39
33
|
)
|
|
40
34
|
|
|
41
35
|
min_dist: ConfigParameter = ConfigParameter(
|
|
42
36
|
name="min_dist",
|
|
43
|
-
|
|
44
|
-
type_=int,
|
|
37
|
+
value=20,
|
|
45
38
|
help="Maximum distance between two waypoints",
|
|
39
|
+
is_cli=True,
|
|
46
40
|
)
|
|
47
41
|
|
|
48
42
|
extract_waypoints: ConfigParameter = ConfigParameter(
|
|
49
43
|
name="extract_waypoints",
|
|
50
|
-
|
|
51
|
-
type_=bool,
|
|
44
|
+
value=True,
|
|
52
45
|
help="Extract starting points of each track as waypoint",
|
|
46
|
+
is_cli=True,
|
|
53
47
|
)
|
|
54
48
|
|
|
55
49
|
elevation: ConfigParameter = ConfigParameter(
|
|
56
50
|
name="elevation",
|
|
57
|
-
|
|
58
|
-
type_=bool,
|
|
51
|
+
value=True,
|
|
59
52
|
help="Include elevation data in waypoints",
|
|
53
|
+
is_cli=True,
|
|
60
54
|
)
|
|
61
55
|
|
|
62
56
|
|
|
63
|
-
class AppConfig(
|
|
57
|
+
class AppConfig(ConfigCategory):
|
|
64
58
|
"""Application-specific configuration parameters."""
|
|
65
59
|
|
|
66
60
|
def get_category_name(self) -> str:
|
|
@@ -68,64 +62,56 @@ class AppConfig(BaseConfigCategory):
|
|
|
68
62
|
|
|
69
63
|
date_format: ConfigParameter = ConfigParameter(
|
|
70
64
|
name="date_format",
|
|
71
|
-
|
|
72
|
-
type_=str,
|
|
65
|
+
value="%Y-%m-%d",
|
|
73
66
|
help="Date format to use",
|
|
74
67
|
)
|
|
75
68
|
|
|
76
69
|
log_level: ConfigParameter = ConfigParameter(
|
|
77
70
|
name="log_level",
|
|
78
|
-
|
|
79
|
-
type_=str,
|
|
71
|
+
value="INFO",
|
|
80
72
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
81
73
|
help="Logging level for the application",
|
|
82
74
|
)
|
|
83
75
|
|
|
84
76
|
log_file_max_size: ConfigParameter = ConfigParameter(
|
|
85
77
|
name="log_file_max_size",
|
|
86
|
-
|
|
87
|
-
type_=int,
|
|
78
|
+
value=10,
|
|
88
79
|
help="Maximum log file size in MB before rotation",
|
|
89
80
|
)
|
|
90
81
|
|
|
91
82
|
log_backup_count: ConfigParameter = ConfigParameter(
|
|
92
83
|
name="log_backup_count",
|
|
93
|
-
|
|
94
|
-
type_=int,
|
|
84
|
+
value=5,
|
|
95
85
|
help="Number of backup log files to keep",
|
|
96
86
|
)
|
|
97
87
|
|
|
98
88
|
log_format: ConfigParameter = ConfigParameter(
|
|
99
89
|
name="log_format",
|
|
100
|
-
|
|
101
|
-
type_=str,
|
|
90
|
+
value="detailed",
|
|
102
91
|
choices=["simple", "detailed", "json"],
|
|
103
92
|
help="Log message format style",
|
|
104
93
|
)
|
|
105
94
|
|
|
106
95
|
max_workers: ConfigParameter = ConfigParameter(
|
|
107
96
|
name="max_workers",
|
|
108
|
-
|
|
109
|
-
type_=int,
|
|
97
|
+
value=4,
|
|
110
98
|
help="Maximum number of worker threads",
|
|
111
99
|
)
|
|
112
100
|
|
|
113
101
|
enable_file_logging: ConfigParameter = ConfigParameter(
|
|
114
102
|
name="enable_file_logging",
|
|
115
|
-
|
|
116
|
-
type_=bool,
|
|
103
|
+
value=True,
|
|
117
104
|
help="Enable logging to file",
|
|
118
105
|
)
|
|
119
106
|
|
|
120
107
|
enable_console_logging: ConfigParameter = ConfigParameter(
|
|
121
108
|
name="enable_console_logging",
|
|
122
|
-
|
|
123
|
-
type_=bool,
|
|
109
|
+
value=True,
|
|
124
110
|
help="Enable logging to console",
|
|
125
111
|
)
|
|
126
112
|
|
|
127
113
|
|
|
128
|
-
class GuiConfig(
|
|
114
|
+
class GuiConfig(ConfigCategory):
|
|
129
115
|
"""GUI-specific configuration parameters."""
|
|
130
116
|
|
|
131
117
|
def get_category_name(self) -> str:
|
|
@@ -133,44 +119,38 @@ class GuiConfig(BaseConfigCategory):
|
|
|
133
119
|
|
|
134
120
|
theme: ConfigParameter = ConfigParameter(
|
|
135
121
|
name="theme",
|
|
136
|
-
|
|
137
|
-
type_=str,
|
|
122
|
+
value="light",
|
|
138
123
|
choices=["light", "dark", "auto"],
|
|
139
124
|
help="GUI theme setting",
|
|
140
125
|
)
|
|
141
126
|
|
|
142
127
|
window_width: ConfigParameter = ConfigParameter(
|
|
143
128
|
name="window_width",
|
|
144
|
-
|
|
145
|
-
type_=int,
|
|
129
|
+
value=800,
|
|
146
130
|
help="Default window width",
|
|
147
131
|
)
|
|
148
132
|
|
|
149
133
|
window_height: ConfigParameter = ConfigParameter(
|
|
150
134
|
name="window_height",
|
|
151
|
-
|
|
152
|
-
type_=int,
|
|
135
|
+
value=600,
|
|
153
136
|
help="Default window height",
|
|
154
137
|
)
|
|
155
138
|
|
|
156
139
|
log_window_height: ConfigParameter = ConfigParameter(
|
|
157
140
|
name="log_window_height",
|
|
158
|
-
|
|
159
|
-
type_=int,
|
|
141
|
+
value=200,
|
|
160
142
|
help="Height of the log window in pixels",
|
|
161
143
|
)
|
|
162
144
|
|
|
163
145
|
auto_scroll_log: ConfigParameter = ConfigParameter(
|
|
164
146
|
name="auto_scroll_log",
|
|
165
|
-
|
|
166
|
-
type_=bool,
|
|
147
|
+
value=True,
|
|
167
148
|
help="Automatically scroll to newest log entries",
|
|
168
149
|
)
|
|
169
150
|
|
|
170
151
|
max_log_lines: ConfigParameter = ConfigParameter(
|
|
171
152
|
name="max_log_lines",
|
|
172
|
-
|
|
173
|
-
type_=int,
|
|
153
|
+
value=1000,
|
|
174
154
|
help="Maximum number of log lines to keep in GUI",
|
|
175
155
|
)
|
|
176
156
|
|
|
@@ -178,14 +158,14 @@ class GuiConfig(BaseConfigCategory):
|
|
|
178
158
|
class ConfigParameterManager(ConfigManager): # Inherit from ConfigManager
|
|
179
159
|
"""Main configuration manager that handles all parameter categories."""
|
|
180
160
|
|
|
161
|
+
cli: CliConfig
|
|
162
|
+
app: AppConfig
|
|
163
|
+
gui: GuiConfig
|
|
164
|
+
|
|
181
165
|
def __init__(self, config_file: str | None = None, **kwargs):
|
|
182
166
|
# Erst den Parent initialisieren
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
# Dann die Kategorien hinzufügen
|
|
186
|
-
self.add_category("cli", CliConfig())
|
|
187
|
-
self.add_category("app", AppConfig())
|
|
188
|
-
self.add_category("gui", GuiConfig())
|
|
167
|
+
categories = (CliConfig(), AppConfig(), GuiConfig())
|
|
168
|
+
super().__init__(categories, config_file, **kwargs)
|
|
189
169
|
|
|
190
170
|
|
|
191
171
|
def main():
|
|
@@ -194,14 +174,14 @@ def main():
|
|
|
194
174
|
default_cli_doc: str = "../../docs/usage/cli.md"
|
|
195
175
|
default_config_doc: str = "../../docs/usage/config.md"
|
|
196
176
|
config_manager = ConfigParameterManager()
|
|
197
|
-
|
|
198
|
-
|
|
177
|
+
doc_gen = DocumentationGenerator(config_manager)
|
|
178
|
+
doc_gen.generate_default_config_file(output_file=default_config)
|
|
199
179
|
print(f"Generated: {default_config}")
|
|
200
180
|
|
|
201
|
-
|
|
181
|
+
doc_gen.generate_config_markdown_doc(output_file=default_config_doc)
|
|
202
182
|
print(f"Generated: {default_config_doc}")
|
|
203
183
|
|
|
204
|
-
|
|
184
|
+
doc_gen.generate_cli_markdown_doc(output_file=default_cli_doc)
|
|
205
185
|
print(f"Generated: {default_cli_doc}")
|
|
206
186
|
|
|
207
187
|
|
|
@@ -12,7 +12,7 @@ import logging.handlers
|
|
|
12
12
|
import sys
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
-
from
|
|
15
|
+
from python_template_project.config.config import ConfigParameterManager
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class GuiLogHandler(logging.Handler):
|
|
@@ -39,7 +39,7 @@ class GuiLogHandler(logging.Handler):
|
|
|
39
39
|
class LoggerManager:
|
|
40
40
|
"""Manages all logging configuration and handlers."""
|
|
41
41
|
|
|
42
|
-
def __init__(self, config:
|
|
42
|
+
def __init__(self, config: ConfigParameterManager):
|
|
43
43
|
self.config = config
|
|
44
44
|
self.logger = logging.getLogger("python_template_project")
|
|
45
45
|
self.gui_handler = None
|
|
@@ -53,7 +53,7 @@ class LoggerManager:
|
|
|
53
53
|
self.logger.handlers.clear()
|
|
54
54
|
|
|
55
55
|
# Set log level from config
|
|
56
|
-
log_level = getattr(logging, self.config.
|
|
56
|
+
log_level = getattr(logging, self.config.app.log_level.value.upper())
|
|
57
57
|
self.logger.setLevel(log_level)
|
|
58
58
|
|
|
59
59
|
# Create formatters
|
|
@@ -142,15 +142,15 @@ class LoggerManager:
|
|
|
142
142
|
self.logger.setLevel(log_level)
|
|
143
143
|
|
|
144
144
|
# Update config
|
|
145
|
-
self.config.
|
|
145
|
+
self.config.app.log_level.value = level.upper()
|
|
146
146
|
|
|
147
147
|
def log_config_summary(self):
|
|
148
148
|
"""Log current configuration summary."""
|
|
149
149
|
self.logger.info("=== Configuration Summary ===")
|
|
150
|
-
self.logger.info(f"Log level: {self.config.
|
|
151
|
-
self.logger.info(f"Input: {self.config.
|
|
152
|
-
self.logger.info(f"Output: {self.config.
|
|
153
|
-
self.logger.info(f"Max workers: {self.config.
|
|
150
|
+
self.logger.info(f"Log level: {self.config.app.log_level.value}")
|
|
151
|
+
self.logger.info(f"Input: {self.config.cli.input.value}")
|
|
152
|
+
self.logger.info(f"Output: {self.config.cli.output.value}")
|
|
153
|
+
self.logger.info(f"Max workers: {self.config.app.max_workers.value}")
|
|
154
154
|
self.logger.info("==============================")
|
|
155
155
|
|
|
156
156
|
|
|
@@ -158,7 +158,7 @@ class LoggerManager:
|
|
|
158
158
|
_logger_manager = None
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
def initialize_logging(config:
|
|
161
|
+
def initialize_logging(config: ConfigParameterManager) -> LoggerManager:
|
|
162
162
|
"""Initialize the global logging system.
|
|
163
163
|
|
|
164
164
|
Args:
|
|
@@ -17,7 +17,8 @@ from functools import partial
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from tkinter import filedialog, messagebox, ttk
|
|
19
19
|
|
|
20
|
-
from
|
|
20
|
+
from config_cli_gui.gui import SettingsDialogGenerator
|
|
21
|
+
|
|
21
22
|
from python_template_project.config.config import ConfigParameterManager
|
|
22
23
|
from python_template_project.core.base import BaseGPXProcessor
|
|
23
24
|
from python_template_project.core.logging import (
|
|
@@ -241,9 +242,7 @@ class MainGui:
|
|
|
241
242
|
|
|
242
243
|
# Log level selector
|
|
243
244
|
ttk.Label(log_controls, text="Log Level:").pack(side=tk.LEFT, padx=(10, 5))
|
|
244
|
-
self.log_level_var = tk.StringVar(
|
|
245
|
-
value=self.config_manager.get_category("app").log_level.default
|
|
246
|
-
)
|
|
245
|
+
self.log_level_var = tk.StringVar(value=self.config_manager.app.log_level.value)
|
|
247
246
|
log_level_combo = ttk.Combobox(
|
|
248
247
|
log_controls,
|
|
249
248
|
textvariable=self.log_level_var,
|
|
@@ -456,10 +455,10 @@ class MainGui:
|
|
|
456
455
|
# Create and run project
|
|
457
456
|
project = BaseGPXProcessor(
|
|
458
457
|
files_to_process, # Pass selected files
|
|
459
|
-
self.config_manager.
|
|
460
|
-
self.config_manager.
|
|
461
|
-
self.config_manager.
|
|
462
|
-
self.config_manager.
|
|
458
|
+
self.config_manager.cli.output.value,
|
|
459
|
+
self.config_manager.cli.min_dist.value,
|
|
460
|
+
self.config_manager.app.date_format.value,
|
|
461
|
+
self.config_manager.cli.elevation.value,
|
|
463
462
|
self.logger,
|
|
464
463
|
)
|
|
465
464
|
# implement switch case for different processing modes
|
|
@@ -506,7 +505,7 @@ class MainGui:
|
|
|
506
505
|
if dialog.result == "ok":
|
|
507
506
|
self.logger.info("Settings updated successfully")
|
|
508
507
|
# Update log level selector if it changed
|
|
509
|
-
self.log_level_var.set(self.config_manager.
|
|
508
|
+
self.log_level_var.set(self.config_manager.app.log_level.value)
|
|
510
509
|
|
|
511
510
|
def _open_help(self):
|
|
512
511
|
"""Open help documentation in browser."""
|
{python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/METADATA
RENAMED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-template-project
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Feature-rich Python project template designed for robustness and ease of use.
|
|
5
5
|
Author: pamagister
|
|
6
6
|
Requires-Python: <3.12,>=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
+
Requires-Dist: config-cli-gui>=0.2.0
|
|
9
10
|
Requires-Dist: fastkml>=1.1.0
|
|
10
11
|
Requires-Dist: gpxpy>=1.6.2
|
|
11
|
-
Requires-Dist: pydantic>=2.11.7
|
|
12
|
-
Requires-Dist: pyyaml>=6.0.2
|
|
13
12
|
Requires-Dist: shapely>=2.1.1
|
|
14
13
|
Requires-Dist: srtm-py>=0.3.7
|
|
15
14
|
Provides-Extra: dev
|
|
@@ -62,7 +61,7 @@ Getting started on developing your own project based on this template
|
|
|
62
61
|
|
|
63
62
|
2. **Give a name to your project**
|
|
64
63
|
For example: `my-python-project`
|
|
65
|
-
*(Hyphens may
|
|
64
|
+
*(Hyphens may be used as project name; they are converted during renaming internally to underscores for packages.)*
|
|
66
65
|
|
|
67
66
|
3. **Set write permissions**
|
|
68
67
|
Go to: `Repository -> Settings -> Actions -> General -> Workflow permissions`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
firewall_handler.py,sha256=qT0dInkeKHulpCQD2x3HH7_lMxz4lwdkIPxEvBSl3No,7336
|
|
3
|
+
main.py,sha256=d6ZpnMxcPBf860gQmuYwPVJg1di6r2CR6gSvFLkAZkk,4448
|
|
4
|
+
python_template_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
python_template_project/__main__.py,sha256=JS28LiHoU9Xo4c_RvyLBTDKHqdZRRytJLj6qyrg26mQ,195
|
|
6
|
+
python_template_project/_version.py,sha256=vYqoJTG51NOUmYyL0xt8asRK8vUT4lGAdal_EZ59mvw,704
|
|
7
|
+
python_template_project/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
python_template_project/cli/__main__.py,sha256=qnGkoNNbhnvLTA24X8YTEDxRqDbNEAzgjp9Ki_JYIhs,184
|
|
9
|
+
python_template_project/cli/cli.py,sha256=L5UiEeLzgYbbbKeWPKCVG7i509ou9mi6yNMc2K2UG_c,4201
|
|
10
|
+
python_template_project/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
python_template_project/config/config.py,sha256=oTub_qmH4CbQQc7L354qJE5N3CRhajr0o8XULAUvzA8,5359
|
|
12
|
+
python_template_project/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
python_template_project/core/base.py,sha256=3S_DSPgPom0wCS_tgdEaNpWub0r8GlytmjKtXlIypRA,28045
|
|
14
|
+
python_template_project/core/logging.py,sha256=b5Z0g7XVj4fy7nYVfWMTSz8pV6tcXrNEJ6Y7pNJKfWA,6932
|
|
15
|
+
python_template_project/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
python_template_project/gui/__main__.py,sha256=5AaiBxNCAKWcuSQFcQ_lsEQfEsXm6BA5-i5MScdf30c,184
|
|
17
|
+
python_template_project/gui/gui.py,sha256=8jmwvrJlyez66cnkIoosq65KAx75WmAfwJUUiluh6TY,21647
|
|
18
|
+
python_template_project-0.2.1.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
19
|
+
python_template_project-0.2.1.dist-info/METADATA,sha256=515IGLkUpYAHUiCaHrIsh_FXJd4H6RojXZmLQ8_RZos,7924
|
|
20
|
+
python_template_project-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
python_template_project-0.2.1.dist-info/entry_points.txt,sha256=LmgMiqxIVRdD7gkktVBBR0CR4AhkugqlgifuglkPhtI,148
|
|
22
|
+
python_template_project-0.2.1.dist-info/top_level.txt,sha256=O-Yg0tzEwgH58MhdcYWpJsgZSUPg6SBbOUwlmVHZo3A,55
|
|
23
|
+
python_template_project-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# generic_config_cli_gui/cli.py
|
|
2
|
-
"""Generic CLI generator for configuration framework."""
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import traceback
|
|
6
|
-
from collections.abc import Callable
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from generic_config_cli_gui.config_framework import ConfigManager
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class CliGenerator:
|
|
13
|
-
"""Generates CLI interface from ConfigManager."""
|
|
14
|
-
|
|
15
|
-
def __init__(self, config_manager: ConfigManager, app_name: str = "app"):
|
|
16
|
-
self.config_manager = config_manager
|
|
17
|
-
self.app_name = app_name
|
|
18
|
-
|
|
19
|
-
def create_argument_parser(self, description: str = None) -> argparse.ArgumentParser:
|
|
20
|
-
"""Create argument parser from configuration."""
|
|
21
|
-
if description is None:
|
|
22
|
-
description = f"Command line interface for {self.app_name}"
|
|
23
|
-
|
|
24
|
-
parser = argparse.ArgumentParser(
|
|
25
|
-
description=description,
|
|
26
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
# Config file argument
|
|
30
|
-
parser.add_argument(
|
|
31
|
-
"--config",
|
|
32
|
-
default=None,
|
|
33
|
-
help="Path to configuration file (JSON or YAML)",
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
# Verbose/quiet options for log level override
|
|
37
|
-
parser.add_argument(
|
|
38
|
-
"-v", "--verbose", action="store_true", help="Enable verbose logging (DEBUG level)"
|
|
39
|
-
)
|
|
40
|
-
parser.add_argument(
|
|
41
|
-
"-q", "--quiet", action="store_true", help="Enable quiet mode (WARNING level only)"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
# Get CLI parameters
|
|
45
|
-
cli_params = self.config_manager.get_cli_parameters()
|
|
46
|
-
|
|
47
|
-
# Generate arguments from CLI config parameters
|
|
48
|
-
for param in cli_params:
|
|
49
|
-
if param.required and param.cli_arg is None:
|
|
50
|
-
# Positional argument
|
|
51
|
-
parser.add_argument(param.name, help=param.help)
|
|
52
|
-
else:
|
|
53
|
-
# Optional argument
|
|
54
|
-
kwargs = {
|
|
55
|
-
"default": argparse.SUPPRESS,
|
|
56
|
-
"help": f"{param.help} (default: {param.default})",
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# Handle different parameter types
|
|
60
|
-
if param.choices and param.type_ != bool:
|
|
61
|
-
kwargs["choices"] = param.choices
|
|
62
|
-
|
|
63
|
-
if param.type_ == int:
|
|
64
|
-
kwargs["type"] = int
|
|
65
|
-
elif param.type_ == float:
|
|
66
|
-
kwargs["type"] = float
|
|
67
|
-
elif param.type_ == bool:
|
|
68
|
-
kwargs["action"] = "store_true" if not param.default else "store_false"
|
|
69
|
-
kwargs["help"] = f"{param.help} (default: {param.default})"
|
|
70
|
-
elif param.type_ == str:
|
|
71
|
-
kwargs["type"] = str
|
|
72
|
-
|
|
73
|
-
parser.add_argument(param.cli_arg, **kwargs)
|
|
74
|
-
|
|
75
|
-
return parser
|
|
76
|
-
|
|
77
|
-
def create_config_overrides(self, args: argparse.Namespace) -> dict[str, Any]:
|
|
78
|
-
"""Create configuration overrides from CLI arguments."""
|
|
79
|
-
cli_params = self.config_manager.get_cli_parameters()
|
|
80
|
-
overrides = {}
|
|
81
|
-
|
|
82
|
-
for param in cli_params:
|
|
83
|
-
if hasattr(args, param.name):
|
|
84
|
-
arg_value = getattr(args, param.name)
|
|
85
|
-
# Add CLI category prefix for override system
|
|
86
|
-
overrides[f"cli__{param.name}"] = arg_value
|
|
87
|
-
|
|
88
|
-
# Handle log level overrides from verbose/quiet flags
|
|
89
|
-
if hasattr(args, "verbose") and args.verbose:
|
|
90
|
-
overrides["app__log_level"] = "DEBUG"
|
|
91
|
-
elif hasattr(args, "quiet") and args.quiet:
|
|
92
|
-
overrides["app__log_level"] = "WARNING"
|
|
93
|
-
|
|
94
|
-
return overrides
|
|
95
|
-
|
|
96
|
-
def run_cli(
|
|
97
|
-
self,
|
|
98
|
-
main_function: Callable[[ConfigManager], int],
|
|
99
|
-
description: str = None,
|
|
100
|
-
validator: Callable[[ConfigManager], bool] = None,
|
|
101
|
-
) -> int:
|
|
102
|
-
"""Run the CLI application with error handling.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
main_function: Function that takes ConfigManager and returns exit code
|
|
106
|
-
description: CLI description
|
|
107
|
-
validator: Optional function to validate configuration
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
Exit code
|
|
111
|
-
"""
|
|
112
|
-
logger = None
|
|
113
|
-
|
|
114
|
-
try:
|
|
115
|
-
# Parse command line arguments
|
|
116
|
-
parser = self.create_argument_parser(description)
|
|
117
|
-
args = parser.parse_args()
|
|
118
|
-
|
|
119
|
-
# Create configuration overrides from CLI arguments
|
|
120
|
-
cli_overrides = self.create_config_overrides(args)
|
|
121
|
-
|
|
122
|
-
# Create new config manager with overrides
|
|
123
|
-
config_file = args.config if hasattr(args, "config") and args.config else None
|
|
124
|
-
updated_config = ConfigManager(config_file=config_file, **cli_overrides)
|
|
125
|
-
|
|
126
|
-
# Copy categories from original config manager
|
|
127
|
-
for name, category in self.config_manager._categories.items():
|
|
128
|
-
updated_config.add_category(name, category)
|
|
129
|
-
|
|
130
|
-
# Apply overrides again after copying categories
|
|
131
|
-
updated_config._apply_kwargs(cli_overrides)
|
|
132
|
-
|
|
133
|
-
# Try to get logger if logging is configured
|
|
134
|
-
try:
|
|
135
|
-
from .logging import get_logger
|
|
136
|
-
|
|
137
|
-
logger = get_logger(f"{self.app_name}.cli")
|
|
138
|
-
logger.info(f"Starting {self.app_name} CLI")
|
|
139
|
-
logger.debug(f"Command line arguments: {vars(args)}")
|
|
140
|
-
except ImportError:
|
|
141
|
-
pass
|
|
142
|
-
|
|
143
|
-
# Validate configuration if validator provided
|
|
144
|
-
if validator and not validator(updated_config):
|
|
145
|
-
if logger:
|
|
146
|
-
logger.error("Configuration validation failed")
|
|
147
|
-
else:
|
|
148
|
-
print("Configuration validation failed")
|
|
149
|
-
return 1
|
|
150
|
-
|
|
151
|
-
# Run main function
|
|
152
|
-
return main_function(updated_config)
|
|
153
|
-
|
|
154
|
-
except FileNotFoundError as e:
|
|
155
|
-
if logger:
|
|
156
|
-
logger.error(f"File not found: {e}")
|
|
157
|
-
logger.debug("Full traceback:", exc_info=True)
|
|
158
|
-
else:
|
|
159
|
-
print(f"Error: {e}")
|
|
160
|
-
traceback.print_exc()
|
|
161
|
-
return 1
|
|
162
|
-
|
|
163
|
-
except KeyboardInterrupt:
|
|
164
|
-
if logger:
|
|
165
|
-
logger.warning("Process interrupted by user")
|
|
166
|
-
else:
|
|
167
|
-
print("Process interrupted by user")
|
|
168
|
-
return 130
|
|
169
|
-
|
|
170
|
-
except Exception as e:
|
|
171
|
-
if logger:
|
|
172
|
-
logger.error(f"Unexpected error: {e}")
|
|
173
|
-
logger.debug("Full traceback:", exc_info=True)
|
|
174
|
-
else:
|
|
175
|
-
print(f"Unexpected error: {e}")
|
|
176
|
-
traceback.print_exc()
|
|
177
|
-
return 1
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
# config_framework/core.py
|
|
2
|
-
"""Generic configuration framework for Python applications."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from textwrap import dedent
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
import yaml
|
|
12
|
-
from pydantic import BaseModel
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class ConfigParameter:
|
|
17
|
-
"""Represents a single configuration parameter with all its metadata."""
|
|
18
|
-
|
|
19
|
-
name: str
|
|
20
|
-
default: Any
|
|
21
|
-
type_: type
|
|
22
|
-
choices: list[str | bool] = None
|
|
23
|
-
help: str = ""
|
|
24
|
-
cli_arg: str = None
|
|
25
|
-
required: bool = False
|
|
26
|
-
category: str = "general"
|
|
27
|
-
|
|
28
|
-
def __post_init__(self):
|
|
29
|
-
if self.cli_arg is None and not self.required:
|
|
30
|
-
self.cli_arg = f"--{self.name}"
|
|
31
|
-
if self.type_ is bool and self.choices is None:
|
|
32
|
-
self.choices = [True, False]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class BaseConfigCategory(BaseModel, ABC):
|
|
36
|
-
"""Base class for configuration categories."""
|
|
37
|
-
|
|
38
|
-
@abstractmethod
|
|
39
|
-
def get_category_name(self) -> str:
|
|
40
|
-
"""Return the category name for this configuration group."""
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
def get_parameters(self) -> list[ConfigParameter]:
|
|
44
|
-
"""Get all ConfigParameter objects from this category."""
|
|
45
|
-
parameters = []
|
|
46
|
-
for field_name in self.model_fields:
|
|
47
|
-
param = getattr(self, field_name)
|
|
48
|
-
if isinstance(param, ConfigParameter):
|
|
49
|
-
param.category = self.get_category_name()
|
|
50
|
-
parameters.append(param)
|
|
51
|
-
return parameters
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class CliConfigCategory(BaseConfigCategory):
|
|
55
|
-
"""Base class for CLI-specific configuration parameters."""
|
|
56
|
-
|
|
57
|
-
def get_category_name(self) -> str:
|
|
58
|
-
return "cli"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class ConfigManager:
|
|
62
|
-
"""Generic configuration manager that can handle multiple configuration categories."""
|
|
63
|
-
|
|
64
|
-
def __init__(self, config_file: str = None, **kwargs):
|
|
65
|
-
"""Initialize configuration manager.
|
|
66
|
-
|
|
67
|
-
Args:
|
|
68
|
-
config_file: Path to configuration file (JSON or YAML)
|
|
69
|
-
**kwargs: Override parameters in format category__parameter
|
|
70
|
-
"""
|
|
71
|
-
self._categories: dict[str, BaseConfigCategory] = {}
|
|
72
|
-
self._cli_category_name: str = None
|
|
73
|
-
|
|
74
|
-
# Load from file if provided
|
|
75
|
-
if config_file:
|
|
76
|
-
self.load_from_file(config_file)
|
|
77
|
-
|
|
78
|
-
# Override with provided kwargs
|
|
79
|
-
self._apply_kwargs(kwargs)
|
|
80
|
-
|
|
81
|
-
def add_category(self, name: str, category: BaseConfigCategory):
|
|
82
|
-
"""Add a configuration category.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
name: Name of the category (e.g., 'cli', 'app', 'gui')
|
|
86
|
-
category: Configuration category instance
|
|
87
|
-
"""
|
|
88
|
-
self._categories[name] = category
|
|
89
|
-
if isinstance(category, CliConfigCategory):
|
|
90
|
-
self._cli_category_name = name
|
|
91
|
-
|
|
92
|
-
def get_category(self, name: str) -> BaseConfigCategory:
|
|
93
|
-
"""Get a configuration category by name."""
|
|
94
|
-
return self._categories.get(name)
|
|
95
|
-
|
|
96
|
-
def get_cli_category(self) -> BaseConfigCategory | None:
|
|
97
|
-
"""Get the CLI configuration category."""
|
|
98
|
-
if self._cli_category_name:
|
|
99
|
-
return self._categories[self._cli_category_name]
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
def _apply_kwargs(self, kwargs: dict[str, Any]):
|
|
103
|
-
"""Apply keyword arguments to override configuration values."""
|
|
104
|
-
for key, value in kwargs.items():
|
|
105
|
-
if "__" in key:
|
|
106
|
-
category_name, param_name = key.split("__", 1)
|
|
107
|
-
if category_name in self._categories:
|
|
108
|
-
category = self._categories[category_name]
|
|
109
|
-
if hasattr(category, param_name):
|
|
110
|
-
param = getattr(category, param_name)
|
|
111
|
-
if isinstance(param, ConfigParameter):
|
|
112
|
-
param.default = value
|
|
113
|
-
|
|
114
|
-
def load_from_file(self, config_file: str):
|
|
115
|
-
"""Load configuration from JSON or YAML file."""
|
|
116
|
-
config_path = Path(config_file)
|
|
117
|
-
if not config_path.exists():
|
|
118
|
-
raise FileNotFoundError(f"Configuration file not found: {config_file}")
|
|
119
|
-
|
|
120
|
-
with open(config_path, "r", encoding="utf-8") as f:
|
|
121
|
-
if config_path.suffix.lower() in [".yml", ".yaml"]:
|
|
122
|
-
config_data = yaml.safe_load(f)
|
|
123
|
-
else:
|
|
124
|
-
config_data = json.load(f)
|
|
125
|
-
|
|
126
|
-
# Apply loaded configuration
|
|
127
|
-
for category_name, category_data in config_data.items():
|
|
128
|
-
if category_name in self._categories:
|
|
129
|
-
category = self._categories[category_name]
|
|
130
|
-
for param_name, param_value in category_data.items():
|
|
131
|
-
if hasattr(category, param_name):
|
|
132
|
-
param = getattr(category, param_name)
|
|
133
|
-
if isinstance(param, ConfigParameter):
|
|
134
|
-
param.default = param_value
|
|
135
|
-
|
|
136
|
-
def save_to_file(self, config_file: str, format_: str = "auto"):
|
|
137
|
-
"""Save current configuration to file."""
|
|
138
|
-
config_path = Path(config_file)
|
|
139
|
-
config_data = self.to_dict()
|
|
140
|
-
|
|
141
|
-
# Determine format
|
|
142
|
-
if format_ == "auto":
|
|
143
|
-
format_ = "yaml" if config_path.suffix.lower() in [".yml", ".yaml"] else "json"
|
|
144
|
-
|
|
145
|
-
# Ensure directory exists
|
|
146
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
-
|
|
148
|
-
with open(config_path, "w", encoding="utf-8") as f:
|
|
149
|
-
if format_ == "yaml":
|
|
150
|
-
yaml.dump(config_data, f, default_flow_style=False, indent=2)
|
|
151
|
-
else:
|
|
152
|
-
json.dump(config_data, f, indent=2)
|
|
153
|
-
|
|
154
|
-
def to_dict(self) -> dict[str, Any]:
|
|
155
|
-
"""Convert configuration to dictionary."""
|
|
156
|
-
result = {}
|
|
157
|
-
for category_name, category in self._categories.items():
|
|
158
|
-
category_dict = {}
|
|
159
|
-
for param in category.get_parameters():
|
|
160
|
-
category_dict[param.name] = param.default
|
|
161
|
-
result[category_name] = category_dict
|
|
162
|
-
return result
|
|
163
|
-
|
|
164
|
-
def get_all_parameters(self) -> list[ConfigParameter]:
|
|
165
|
-
"""Get all parameters from all categories."""
|
|
166
|
-
parameters = []
|
|
167
|
-
for category in self._categories.values():
|
|
168
|
-
parameters.extend(category.get_parameters())
|
|
169
|
-
return parameters
|
|
170
|
-
|
|
171
|
-
def get_cli_parameters(self) -> list[ConfigParameter]:
|
|
172
|
-
"""Get only CLI parameters."""
|
|
173
|
-
cli_category = self.get_cli_category()
|
|
174
|
-
if cli_category:
|
|
175
|
-
return cli_category.get_parameters()
|
|
176
|
-
return []
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
class DocumentationGenerator:
|
|
180
|
-
"""Generates documentation and configuration files from ConfigManager."""
|
|
181
|
-
|
|
182
|
-
def __init__(self, config_manager: ConfigManager):
|
|
183
|
-
self.config_manager = config_manager
|
|
184
|
-
|
|
185
|
-
def generate_config_markdown_doc(self, output_file: str):
|
|
186
|
-
"""Generate Markdown documentation for all configuration parameters."""
|
|
187
|
-
|
|
188
|
-
def pad(s, width):
|
|
189
|
-
return s + " " * (width - len(s))
|
|
190
|
-
|
|
191
|
-
markdown_content = dedent("""
|
|
192
|
-
# Configuration Parameters
|
|
193
|
-
|
|
194
|
-
These parameters are available to configure the behavior of your application.
|
|
195
|
-
The parameters in the cli category can be accessed via the command line interface.
|
|
196
|
-
|
|
197
|
-
""").lstrip()
|
|
198
|
-
|
|
199
|
-
for category_name, category in self.config_manager._categories.items():
|
|
200
|
-
markdown_content += f'## Category "{category_name}"\n\n'
|
|
201
|
-
|
|
202
|
-
# Collect all parameters for this category
|
|
203
|
-
rows = []
|
|
204
|
-
header = ["Name", "Type", "Description", "Default", "Choices"]
|
|
205
|
-
|
|
206
|
-
for param in category.get_parameters():
|
|
207
|
-
name = param.name
|
|
208
|
-
typ = param.type_.__name__
|
|
209
|
-
desc = param.help
|
|
210
|
-
default = repr(param.default)
|
|
211
|
-
choices = str(param.choices) if param.choices else "-"
|
|
212
|
-
|
|
213
|
-
rows.append((name, typ, desc, default, choices))
|
|
214
|
-
|
|
215
|
-
if not rows:
|
|
216
|
-
continue
|
|
217
|
-
|
|
218
|
-
# Calculate column widths
|
|
219
|
-
all_rows = [header] + rows
|
|
220
|
-
widths = [max(len(str(col)) for col in column) for column in zip(*all_rows)]
|
|
221
|
-
|
|
222
|
-
# Create Markdown table
|
|
223
|
-
table = (
|
|
224
|
-
"| "
|
|
225
|
-
+ " | ".join(pad(h, w) for h, w in zip(header, widths))
|
|
226
|
-
+ " |\n"
|
|
227
|
-
+ "|-"
|
|
228
|
-
+ "-|-".join("-" * w for w in widths)
|
|
229
|
-
+ "-|\n"
|
|
230
|
-
)
|
|
231
|
-
for row in rows:
|
|
232
|
-
table += "| " + " | ".join(pad(str(col), w) for col, w in zip(row, widths)) + " |\n"
|
|
233
|
-
|
|
234
|
-
markdown_content += table + "\n"
|
|
235
|
-
|
|
236
|
-
# Write to file
|
|
237
|
-
Path(output_file).parent.mkdir(parents=True, exist_ok=True)
|
|
238
|
-
with open(output_file, "w", encoding="utf-8") as f:
|
|
239
|
-
f.write(markdown_content)
|
|
240
|
-
|
|
241
|
-
def generate_default_config_file(self, output_file: str):
|
|
242
|
-
"""Generate a default configuration file with all parameters and descriptions."""
|
|
243
|
-
output_path = Path(output_file)
|
|
244
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
245
|
-
|
|
246
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
247
|
-
f.write("# Configuration File\n")
|
|
248
|
-
f.write("# This file was auto-generated. Modify as needed.\n\n")
|
|
249
|
-
|
|
250
|
-
for category_name, category in self.config_manager._categories.items():
|
|
251
|
-
f.write(f"# {category_name.upper()} Configuration\n")
|
|
252
|
-
f.write(f"{category_name}:\n")
|
|
253
|
-
|
|
254
|
-
for param in category.get_parameters():
|
|
255
|
-
f.write(f" # {param.help}\n")
|
|
256
|
-
if param.choices:
|
|
257
|
-
f.write(f" # Choices: {param.choices}\n")
|
|
258
|
-
f.write(f" # Type: {param.type_.__name__}\n")
|
|
259
|
-
f.write(f" {param.name}: {repr(param.default)}\n\n")
|
|
260
|
-
|
|
261
|
-
f.write("\n")
|
|
262
|
-
|
|
263
|
-
def generate_cli_markdown_doc(self, output_file: str, app_name: str = "app"):
|
|
264
|
-
"""Generate Markdown CLI documentation."""
|
|
265
|
-
cli_params = self.config_manager.get_cli_parameters()
|
|
266
|
-
|
|
267
|
-
if not cli_params:
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
rows = []
|
|
271
|
-
required_params = []
|
|
272
|
-
optional_params = []
|
|
273
|
-
|
|
274
|
-
for param in cli_params:
|
|
275
|
-
cli_arg = f"`--{param.name}`" if not param.required else f"`{param.name}`"
|
|
276
|
-
typ = param.type_.__name__
|
|
277
|
-
desc = param.help
|
|
278
|
-
default = (
|
|
279
|
-
"*required*"
|
|
280
|
-
if param.required or param.default in (None, "")
|
|
281
|
-
else repr(param.default)
|
|
282
|
-
)
|
|
283
|
-
choices = str(param.choices) if param.choices else "-"
|
|
284
|
-
|
|
285
|
-
rows.append((cli_arg, typ, desc, default, choices))
|
|
286
|
-
if default == "*required*":
|
|
287
|
-
required_params.append(param)
|
|
288
|
-
else:
|
|
289
|
-
optional_params.append(param)
|
|
290
|
-
|
|
291
|
-
# Generate table
|
|
292
|
-
def pad(s, width):
|
|
293
|
-
return s + " " * (width - len(s))
|
|
294
|
-
|
|
295
|
-
widths = [max(len(str(col)) for col in column) for column in zip(*rows)]
|
|
296
|
-
header = ["Option", "Type", "Description", "Default", "Choices"]
|
|
297
|
-
|
|
298
|
-
table = (
|
|
299
|
-
"| "
|
|
300
|
-
+ " | ".join(pad(h, w) for h, w in zip(header, widths))
|
|
301
|
-
+ " |\n"
|
|
302
|
-
+ "|-"
|
|
303
|
-
+ "-|-".join("-" * w for w in widths)
|
|
304
|
-
+ "-|\n"
|
|
305
|
-
)
|
|
306
|
-
for row in rows:
|
|
307
|
-
table += "| " + " | ".join(pad(str(col), w) for col, w in zip(row, widths)) + " |\n"
|
|
308
|
-
|
|
309
|
-
# Generate examples
|
|
310
|
-
examples = []
|
|
311
|
-
required_arg = required_params[0].name if required_params else "example.input"
|
|
312
|
-
|
|
313
|
-
examples.append(
|
|
314
|
-
dedent(
|
|
315
|
-
f"""
|
|
316
|
-
### 1. Basic usage
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
python -m {app_name} {required_arg}
|
|
320
|
-
```
|
|
321
|
-
"""
|
|
322
|
-
)
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
# Add more examples with optional parameters
|
|
326
|
-
for i, param in enumerate(optional_params[:3], 2):
|
|
327
|
-
if param.name in ["verbose", "quiet"]:
|
|
328
|
-
continue
|
|
329
|
-
example_value = param.choices[0] if param.choices else param.default
|
|
330
|
-
examples.append(
|
|
331
|
-
dedent(f"""
|
|
332
|
-
### {i}. With {param.name} parameter
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
python -m {app_name} --{param.name} {example_value} {required_arg}
|
|
336
|
-
```
|
|
337
|
-
""")
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
markdown = dedent(
|
|
341
|
-
f"""
|
|
342
|
-
# Command Line Interface
|
|
343
|
-
|
|
344
|
-
Command line options for {app_name}
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
python -m {app_name} [OPTIONS] {required_arg if required_params else ""}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
## Options
|
|
351
|
-
|
|
352
|
-
{table}
|
|
353
|
-
|
|
354
|
-
## Examples
|
|
355
|
-
|
|
356
|
-
{"".join(examples)}
|
|
357
|
-
"""
|
|
358
|
-
).strip()
|
|
359
|
-
|
|
360
|
-
Path(output_file).parent.mkdir(parents=True, exist_ok=True)
|
|
361
|
-
with open(output_file, "w", encoding="utf-8") as f:
|
|
362
|
-
f.write(markdown)
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
# config_framework/gui.py
|
|
2
|
-
"""Generic GUI settings dialog generator for configuration framework."""
|
|
3
|
-
|
|
4
|
-
import tkinter as tk
|
|
5
|
-
from tkinter import messagebox, ttk
|
|
6
|
-
|
|
7
|
-
from generic_config_cli_gui.config_framework import ConfigManager, ConfigParameter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ToolTip:
|
|
11
|
-
"""Create a tooltip for a given widget."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, widget, text="widget info"):
|
|
14
|
-
self.widget = widget
|
|
15
|
-
self.text = text
|
|
16
|
-
self.tipwindow = None
|
|
17
|
-
self.id = None
|
|
18
|
-
self.x = self.y = 0
|
|
19
|
-
|
|
20
|
-
self.widget.bind("<Enter>", self.on_enter)
|
|
21
|
-
self.widget.bind("<Leave>", self.on_leave)
|
|
22
|
-
|
|
23
|
-
def on_enter(self, event=None):
|
|
24
|
-
self.show_tooltip()
|
|
25
|
-
|
|
26
|
-
def on_leave(self, event=None):
|
|
27
|
-
self.hide_tooltip()
|
|
28
|
-
|
|
29
|
-
def show_tooltip(self):
|
|
30
|
-
if self.tipwindow or not self.text:
|
|
31
|
-
return
|
|
32
|
-
x, y, cx, cy = self.widget.bbox("insert")
|
|
33
|
-
x = x + self.widget.winfo_rootx() + 25
|
|
34
|
-
y = y + cy + self.widget.winfo_rooty() + 25
|
|
35
|
-
self.tipwindow = tw = tk.Toplevel(self.widget)
|
|
36
|
-
tw.wm_overrideredirect(True)
|
|
37
|
-
tw.wm_geometry(f"+{x}+{y}")
|
|
38
|
-
label = tk.Label(
|
|
39
|
-
tw,
|
|
40
|
-
text=self.text,
|
|
41
|
-
justify=tk.LEFT,
|
|
42
|
-
background="#ffffe0",
|
|
43
|
-
relief=tk.SOLID,
|
|
44
|
-
borderwidth=1,
|
|
45
|
-
font=("tahoma", "8", "normal"),
|
|
46
|
-
)
|
|
47
|
-
label.pack(ipadx=1)
|
|
48
|
-
|
|
49
|
-
def hide_tooltip(self):
|
|
50
|
-
tw = self.tipwindow
|
|
51
|
-
self.tipwindow = None
|
|
52
|
-
if tw:
|
|
53
|
-
tw.destroy()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class SettingsDialogGenerator:
|
|
57
|
-
"""Generates settings dialog from ConfigManager."""
|
|
58
|
-
|
|
59
|
-
def __init__(self, config_manager: ConfigManager):
|
|
60
|
-
self.config_manager = config_manager
|
|
61
|
-
|
|
62
|
-
def create_settings_dialog(
|
|
63
|
-
self, parent, title="Settings", config_file="config.yaml"
|
|
64
|
-
) -> "GenericSettingsDialog":
|
|
65
|
-
"""Create a settings dialog for the configuration."""
|
|
66
|
-
return GenericSettingsDialog(parent, self.config_manager, title, config_file)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class GenericSettingsDialog:
|
|
70
|
-
"""Generic settings dialog for ConfigManager."""
|
|
71
|
-
|
|
72
|
-
def __init__(
|
|
73
|
-
self, parent, config_manager: ConfigManager, title="Settings", config_file="config.yaml"
|
|
74
|
-
):
|
|
75
|
-
self.parent = parent
|
|
76
|
-
self.config_manager = config_manager
|
|
77
|
-
self.config_file = config_file
|
|
78
|
-
self.result = None
|
|
79
|
-
self.widgets = {}
|
|
80
|
-
|
|
81
|
-
# Create dialog window
|
|
82
|
-
self.dialog = tk.Toplevel(parent)
|
|
83
|
-
self.dialog.title(title)
|
|
84
|
-
self.dialog.geometry("600x500")
|
|
85
|
-
self.dialog.transient(parent)
|
|
86
|
-
self.dialog.grab_set()
|
|
87
|
-
|
|
88
|
-
# Center the dialog
|
|
89
|
-
self.dialog.geometry(f"+{int(parent.winfo_rootx() + 50)}+{int(parent.winfo_rooty() + 50)}")
|
|
90
|
-
|
|
91
|
-
self._create_widgets()
|
|
92
|
-
|
|
93
|
-
# Handle window closing
|
|
94
|
-
self.dialog.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
95
|
-
|
|
96
|
-
def _create_widgets(self):
|
|
97
|
-
"""Create the settings dialog widgets."""
|
|
98
|
-
# Main frame
|
|
99
|
-
main_frame = ttk.Frame(self.dialog)
|
|
100
|
-
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
101
|
-
|
|
102
|
-
# Create notebook for tabs
|
|
103
|
-
self.notebook = ttk.Notebook(main_frame)
|
|
104
|
-
self.notebook.pack(fill=tk.BOTH, expand=True)
|
|
105
|
-
|
|
106
|
-
# Create tabs for each configuration category
|
|
107
|
-
for category_name, category in self.config_manager._categories.items():
|
|
108
|
-
self._create_category_tab(category_name, category)
|
|
109
|
-
|
|
110
|
-
# Button frame
|
|
111
|
-
button_frame = ttk.Frame(main_frame)
|
|
112
|
-
button_frame.pack(fill=tk.X, pady=(10, 0))
|
|
113
|
-
|
|
114
|
-
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=(5, 0))
|
|
115
|
-
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT)
|
|
116
|
-
|
|
117
|
-
def _create_category_tab(self, category_name: str, category):
|
|
118
|
-
"""Create a tab for a configuration category."""
|
|
119
|
-
# Create tab frame
|
|
120
|
-
tab_frame = ttk.Frame(self.notebook)
|
|
121
|
-
self.notebook.add(tab_frame, text=category_name.title())
|
|
122
|
-
|
|
123
|
-
# Create scrollable frame
|
|
124
|
-
canvas = tk.Canvas(tab_frame)
|
|
125
|
-
scrollbar = ttk.Scrollbar(tab_frame, orient="vertical", command=canvas.yview)
|
|
126
|
-
scrollable_frame = ttk.Frame(canvas)
|
|
127
|
-
|
|
128
|
-
scrollable_frame.bind(
|
|
129
|
-
"<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
|
133
|
-
canvas.configure(yscrollcommand=scrollbar.set)
|
|
134
|
-
|
|
135
|
-
# Add parameters
|
|
136
|
-
self._add_category_parameters(scrollable_frame, category_name, category)
|
|
137
|
-
|
|
138
|
-
canvas.pack(side="left", fill="both", expand=True)
|
|
139
|
-
scrollbar.pack(side="right", fill="y")
|
|
140
|
-
|
|
141
|
-
def _add_category_parameters(self, parent, category_name: str, category):
|
|
142
|
-
"""Add parameter widgets for a specific category."""
|
|
143
|
-
row = 0
|
|
144
|
-
parameters = category.get_parameters()
|
|
145
|
-
|
|
146
|
-
for param in parameters:
|
|
147
|
-
if param.required:
|
|
148
|
-
# Skip required parameters as they are not configurable in GUI
|
|
149
|
-
continue
|
|
150
|
-
|
|
151
|
-
# Create label
|
|
152
|
-
label = ttk.Label(parent, text=f"{param.name}:")
|
|
153
|
-
label.grid(row=row, column=0, sticky="w", padx=5, pady=2)
|
|
154
|
-
|
|
155
|
-
# Create appropriate widget based on parameter type
|
|
156
|
-
widget = self._create_parameter_widget(parent, param)
|
|
157
|
-
widget.grid(row=row, column=1, sticky="ew", padx=5, pady=2)
|
|
158
|
-
|
|
159
|
-
# Add tooltip
|
|
160
|
-
ToolTip(label, param.help)
|
|
161
|
-
ToolTip(widget, param.help)
|
|
162
|
-
|
|
163
|
-
# Store widget reference
|
|
164
|
-
self.widgets[f"{category_name}__{param.name}"] = widget
|
|
165
|
-
|
|
166
|
-
row += 1
|
|
167
|
-
|
|
168
|
-
# Configure column weights
|
|
169
|
-
parent.columnconfigure(1, weight=1)
|
|
170
|
-
|
|
171
|
-
def _create_parameter_widget(self, parent, param: ConfigParameter):
|
|
172
|
-
"""Create appropriate widget for parameter type."""
|
|
173
|
-
if param.type_ == bool:
|
|
174
|
-
# Checkbox for boolean values
|
|
175
|
-
var = tk.BooleanVar(value=param.default)
|
|
176
|
-
widget = ttk.Checkbutton(parent, variable=var)
|
|
177
|
-
widget.var = var
|
|
178
|
-
return widget
|
|
179
|
-
|
|
180
|
-
elif param.choices and param.type_ != bool:
|
|
181
|
-
# Combobox for choices
|
|
182
|
-
var = tk.StringVar(value=str(param.default))
|
|
183
|
-
widget = ttk.Combobox(parent, textvariable=var, values=param.choices, state="readonly")
|
|
184
|
-
widget.var = var
|
|
185
|
-
return widget
|
|
186
|
-
|
|
187
|
-
elif param.type_ == int:
|
|
188
|
-
# Spinbox for integers
|
|
189
|
-
var = tk.IntVar(value=param.default)
|
|
190
|
-
widget = ttk.Spinbox(parent, from_=-999999, to=999999, textvariable=var)
|
|
191
|
-
widget.var = var
|
|
192
|
-
return widget
|
|
193
|
-
|
|
194
|
-
else: # str or other types
|
|
195
|
-
# Entry for strings
|
|
196
|
-
var = tk.StringVar(value=str(param.default))
|
|
197
|
-
widget = ttk.Entry(parent, textvariable=var)
|
|
198
|
-
widget.var = var
|
|
199
|
-
return widget
|
|
200
|
-
|
|
201
|
-
def _on_ok(self):
|
|
202
|
-
"""Handle OK button click."""
|
|
203
|
-
try:
|
|
204
|
-
# Update configuration with widget values
|
|
205
|
-
overrides = {}
|
|
206
|
-
for key, widget in self.widgets.items():
|
|
207
|
-
value = widget.var.get()
|
|
208
|
-
overrides[key] = value
|
|
209
|
-
|
|
210
|
-
# Apply overrides to config manager
|
|
211
|
-
self.config_manager._apply_kwargs(overrides)
|
|
212
|
-
|
|
213
|
-
# Save to file
|
|
214
|
-
self.config_manager.save_to_file(self.config_file)
|
|
215
|
-
|
|
216
|
-
self.result = "ok"
|
|
217
|
-
self.dialog.destroy()
|
|
218
|
-
|
|
219
|
-
except Exception as e:
|
|
220
|
-
messagebox.showerror("Error", f"Failed to save configuration: {e}")
|
|
221
|
-
|
|
222
|
-
def _on_cancel(self):
|
|
223
|
-
"""Handle Cancel button click."""
|
|
224
|
-
self.result = "cancel"
|
|
225
|
-
self.dialog.destroy()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
firewall_handler.py,sha256=qT0dInkeKHulpCQD2x3HH7_lMxz4lwdkIPxEvBSl3No,7336
|
|
3
|
-
main.py,sha256=d6ZpnMxcPBf860gQmuYwPVJg1di6r2CR6gSvFLkAZkk,4448
|
|
4
|
-
generic_config_cli_gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
generic_config_cli_gui/cli_generator.py,sha256=9eN8Lyv3d6bY8LlOoHyAeFAqu1IQFQcQm-zpZ_CjsS4,6396
|
|
6
|
-
generic_config_cli_gui/config_framework.py,sha256=0DttxYz_tnB_qJ6wM-KMwiGXsQzCEr9UbDh84DpxdGM,12514
|
|
7
|
-
generic_config_cli_gui/gui_generator.py,sha256=nB8pRp1eihOG2Cfiz58nY1M6bHfVMGhirW-9NkmhteQ,7628
|
|
8
|
-
python_template_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
python_template_project/__main__.py,sha256=JS28LiHoU9Xo4c_RvyLBTDKHqdZRRytJLj6qyrg26mQ,195
|
|
10
|
-
python_template_project/_version.py,sha256=iB5DfB5V6YB5Wo4JmvS-txT42QtmGaWcWp3udRT7zCI,511
|
|
11
|
-
python_template_project/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
python_template_project/cli/__main__.py,sha256=qnGkoNNbhnvLTA24X8YTEDxRqDbNEAzgjp9Ki_JYIhs,184
|
|
13
|
-
python_template_project/cli/cli.py,sha256=j7LliSLjrntcjDJ5uWpVu8RxriA67xNnytLJYfsrWys,4284
|
|
14
|
-
python_template_project/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
python_template_project/config/config.py,sha256=nI4Lh2VC4wHnDnJ8e1T0cuzbv1FDMkpGKmtMJ0pMBFY,5804
|
|
16
|
-
python_template_project/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
python_template_project/core/base.py,sha256=3S_DSPgPom0wCS_tgdEaNpWub0r8GlytmjKtXlIypRA,28045
|
|
18
|
-
python_template_project/core/logging.py,sha256=kfoEWppFh2ihQMFcSXpb4A2mYUnJdYxwXrYScCCAhZo,7015
|
|
19
|
-
python_template_project/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
python_template_project/gui/__main__.py,sha256=5AaiBxNCAKWcuSQFcQ_lsEQfEsXm6BA5-i5MScdf30c,184
|
|
21
|
-
python_template_project/gui/gui.py,sha256=bh51l_TPt-Oa6zbV9WMJJFJPQKW_-Kz1M-TyCJ3ppao,21794
|
|
22
|
-
python_template_project-0.2.0.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
23
|
-
python_template_project-0.2.0.dist-info/METADATA,sha256=dhQLBOZC2Z94kIji4GZaTYiO_LYLOlxnUXbVVDSilhs,7948
|
|
24
|
-
python_template_project-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
python_template_project-0.2.0.dist-info/entry_points.txt,sha256=LmgMiqxIVRdD7gkktVBBR0CR4AhkugqlgifuglkPhtI,148
|
|
26
|
-
python_template_project-0.2.0.dist-info/top_level.txt,sha256=xtUN1Sz5ZjTDsvgfGQpSu4sxObl1HOHgzgb4JAQ1MtA,78
|
|
27
|
-
python_template_project-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
{python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_template_project-0.2.0.dist-info → python_template_project-0.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|