pomera-ai-commander 1.1.1 → 1.2.2
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.
- package/LICENSE +21 -21
- package/README.md +105 -680
- package/bin/pomera-ai-commander.js +62 -62
- package/core/__init__.py +65 -65
- package/core/app_context.py +482 -482
- package/core/async_text_processor.py +421 -421
- package/core/backup_manager.py +655 -655
- package/core/backup_recovery_manager.py +1199 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_directory.py +549 -0
- package/core/data_validator.py +1066 -1066
- package/core/database_connection_manager.py +744 -744
- package/core/database_curl_settings_manager.py +608 -608
- package/core/database_promera_ai_settings_manager.py +446 -446
- package/core/database_schema.py +411 -411
- package/core/database_schema_manager.py +395 -395
- package/core/database_settings_manager.py +1507 -1507
- package/core/database_settings_manager_interface.py +456 -456
- package/core/dialog_manager.py +734 -734
- package/core/diff_utils.py +239 -0
- package/core/efficient_line_numbers.py +540 -510
- package/core/error_handler.py +746 -746
- package/core/error_service.py +431 -431
- package/core/event_consolidator.py +511 -511
- package/core/mcp/__init__.py +43 -43
- package/core/mcp/find_replace_diff.py +334 -0
- package/core/mcp/protocol.py +288 -288
- package/core/mcp/schema.py +251 -251
- package/core/mcp/server_stdio.py +299 -299
- package/core/mcp/tool_registry.py +2699 -2345
- package/core/memento.py +275 -0
- package/core/memory_efficient_text_widget.py +711 -711
- package/core/migration_manager.py +914 -914
- package/core/migration_test_suite.py +1085 -1085
- package/core/migration_validator.py +1143 -1143
- package/core/optimized_find_replace.py +714 -714
- package/core/optimized_pattern_engine.py +424 -424
- package/core/optimized_search_highlighter.py +552 -552
- package/core/performance_monitor.py +674 -674
- package/core/persistence_manager.py +712 -712
- package/core/progressive_stats_calculator.py +632 -632
- package/core/regex_pattern_cache.py +529 -529
- package/core/regex_pattern_library.py +350 -350
- package/core/search_operation_manager.py +434 -434
- package/core/settings_defaults_registry.py +1087 -1087
- package/core/settings_integrity_validator.py +1111 -1111
- package/core/settings_serializer.py +557 -557
- package/core/settings_validator.py +1823 -1823
- package/core/smart_stats_calculator.py +709 -709
- package/core/statistics_update_manager.py +619 -619
- package/core/stats_config_manager.py +858 -858
- package/core/streaming_text_handler.py +723 -723
- package/core/task_scheduler.py +596 -596
- package/core/update_pattern_library.py +168 -168
- package/core/visibility_monitor.py +596 -596
- package/core/widget_cache.py +498 -498
- package/mcp.json +51 -61
- package/migrate_data.py +127 -0
- package/package.json +64 -57
- package/pomera.py +7883 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +33 -0
- package/scripts/Dockerfile.alpine +43 -0
- package/scripts/Dockerfile.gui-test +54 -0
- package/scripts/Dockerfile.linux +43 -0
- package/scripts/Dockerfile.test-linux +80 -0
- package/scripts/Dockerfile.ubuntu +39 -0
- package/scripts/README.md +53 -0
- package/scripts/build-all.bat +113 -0
- package/scripts/build-docker.bat +53 -0
- package/scripts/build-docker.sh +55 -0
- package/scripts/build-optimized.bat +101 -0
- package/scripts/build.sh +78 -0
- package/scripts/docker-compose.test.yml +27 -0
- package/scripts/docker-compose.yml +32 -0
- package/scripts/postinstall.js +62 -0
- package/scripts/requirements-minimal.txt +33 -0
- package/scripts/test-linux-simple.bat +28 -0
- package/scripts/validate-release-workflow.py +450 -0
- package/tools/__init__.py +4 -4
- package/tools/ai_tools.py +2891 -2891
- package/tools/ascii_art_generator.py +352 -352
- package/tools/base64_tools.py +183 -183
- package/tools/base_tool.py +511 -511
- package/tools/case_tool.py +308 -308
- package/tools/column_tools.py +395 -395
- package/tools/cron_tool.py +884 -884
- package/tools/curl_history.py +600 -600
- package/tools/curl_processor.py +1207 -1207
- package/tools/curl_settings.py +502 -502
- package/tools/curl_tool.py +5467 -5467
- package/tools/diff_viewer.py +1817 -1072
- package/tools/email_extraction_tool.py +248 -248
- package/tools/email_header_analyzer.py +425 -425
- package/tools/extraction_tools.py +250 -250
- package/tools/find_replace.py +2289 -1750
- package/tools/folder_file_reporter.py +1463 -1463
- package/tools/folder_file_reporter_adapter.py +480 -480
- package/tools/generator_tools.py +1216 -1216
- package/tools/hash_generator.py +255 -255
- package/tools/html_tool.py +656 -656
- package/tools/jsonxml_tool.py +729 -729
- package/tools/line_tools.py +419 -419
- package/tools/markdown_tools.py +561 -561
- package/tools/mcp_widget.py +1417 -1417
- package/tools/notes_widget.py +978 -973
- package/tools/number_base_converter.py +372 -372
- package/tools/regex_extractor.py +571 -571
- package/tools/slug_generator.py +310 -310
- package/tools/sorter_tools.py +458 -458
- package/tools/string_escape_tool.py +392 -392
- package/tools/text_statistics_tool.py +365 -365
- package/tools/text_wrapper.py +430 -430
- package/tools/timestamp_converter.py +421 -421
- package/tools/tool_loader.py +710 -710
- package/tools/translator_tools.py +522 -522
- package/tools/url_link_extractor.py +261 -261
- package/tools/url_parser.py +204 -204
- package/tools/whitespace_tools.py +355 -355
- package/tools/word_frequency_counter.py +146 -146
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/app_context.cpython-313.pyc +0 -0
- package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
- package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
- package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
- package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/error_service.cpython-313.pyc +0 -0
- package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
- package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
- package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
- package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
- package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
- package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
- package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
- package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
- package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
- package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
- package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
- package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
package/core/app_context.py
CHANGED
|
@@ -1,482 +1,482 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Application Context - Dependency injection container.
|
|
3
|
-
|
|
4
|
-
This module provides a centralized container for application dependencies,
|
|
5
|
-
enabling better testability, modularity, and clear visibility of dependencies.
|
|
6
|
-
|
|
7
|
-
Author: Pomera AI Commander Team
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from dataclasses import dataclass, field
|
|
11
|
-
from typing import Optional, Any, Dict, Callable, TypeVar, Generic
|
|
12
|
-
import logging
|
|
13
|
-
import weakref
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
T = TypeVar('T')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ServiceNotFoundError(Exception):
|
|
20
|
-
"""Raised when a requested service is not registered."""
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ServiceAlreadyRegisteredError(Exception):
|
|
25
|
-
"""Raised when attempting to register a service that already exists."""
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dataclass
|
|
30
|
-
class ServiceDescriptor:
|
|
31
|
-
"""
|
|
32
|
-
Descriptor for a registered service.
|
|
33
|
-
|
|
34
|
-
Attributes:
|
|
35
|
-
name: Service name/identifier
|
|
36
|
-
instance: The service instance (or None if lazy)
|
|
37
|
-
factory: Factory function for lazy instantiation
|
|
38
|
-
singleton: Whether to cache the instance
|
|
39
|
-
description: Human-readable description
|
|
40
|
-
"""
|
|
41
|
-
name: str
|
|
42
|
-
instance: Any = None
|
|
43
|
-
factory: Optional[Callable[[], Any]] = None
|
|
44
|
-
singleton: bool = True
|
|
45
|
-
description: str = ""
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def is_lazy(self) -> bool:
|
|
49
|
-
"""Check if this is a lazy-loaded service."""
|
|
50
|
-
return self.factory is not None and self.instance is None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass
|
|
54
|
-
class AppContext:
|
|
55
|
-
"""
|
|
56
|
-
Container for application dependencies.
|
|
57
|
-
|
|
58
|
-
This allows for:
|
|
59
|
-
- Easy testing with mock dependencies
|
|
60
|
-
- Clear visibility of application dependencies
|
|
61
|
-
- Centralized dependency management
|
|
62
|
-
- Lazy loading of services
|
|
63
|
-
|
|
64
|
-
Usage:
|
|
65
|
-
# Create context
|
|
66
|
-
ctx = AppContext()
|
|
67
|
-
|
|
68
|
-
# Register services
|
|
69
|
-
ctx.register('logger', logger_instance)
|
|
70
|
-
ctx.register_lazy('database', lambda: DatabaseConnection())
|
|
71
|
-
|
|
72
|
-
# Get services
|
|
73
|
-
logger = ctx.get('logger')
|
|
74
|
-
db = ctx.get('database') # Created on first access
|
|
75
|
-
|
|
76
|
-
# Or use typed properties for common services
|
|
77
|
-
ctx.logger # Returns logger if registered
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
# Core services (typed for IDE support)
|
|
81
|
-
logger: Optional[logging.Logger] = None
|
|
82
|
-
settings_manager: Any = None
|
|
83
|
-
dialog_manager: Any = None
|
|
84
|
-
error_service: Any = None
|
|
85
|
-
|
|
86
|
-
# Processing services
|
|
87
|
-
async_processor: Any = None
|
|
88
|
-
stats_calculator: Any = None
|
|
89
|
-
event_consolidator: Any = None
|
|
90
|
-
|
|
91
|
-
# Tool management
|
|
92
|
-
tool_loader: Any = None
|
|
93
|
-
widget_cache: Any = None
|
|
94
|
-
|
|
95
|
-
# UI references (set after UI creation)
|
|
96
|
-
root_window: Any = None
|
|
97
|
-
|
|
98
|
-
# Internal service registry
|
|
99
|
-
_services: Dict[str, ServiceDescriptor] = field(default_factory=dict)
|
|
100
|
-
_initialized: bool = False
|
|
101
|
-
|
|
102
|
-
def __post_init__(self):
|
|
103
|
-
"""Initialize the service registry."""
|
|
104
|
-
self._services = {}
|
|
105
|
-
self._initialized = True
|
|
106
|
-
|
|
107
|
-
def register(self,
|
|
108
|
-
name: str,
|
|
109
|
-
instance: Any,
|
|
110
|
-
description: str = "",
|
|
111
|
-
overwrite: bool = False) -> 'AppContext':
|
|
112
|
-
"""
|
|
113
|
-
Register a service instance.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
name: Service name/identifier
|
|
117
|
-
instance: The service instance
|
|
118
|
-
description: Human-readable description
|
|
119
|
-
overwrite: Allow overwriting existing service
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
Self for method chaining
|
|
123
|
-
|
|
124
|
-
Raises:
|
|
125
|
-
ServiceAlreadyRegisteredError: If service exists and overwrite=False
|
|
126
|
-
"""
|
|
127
|
-
if name in self._services and not overwrite:
|
|
128
|
-
raise ServiceAlreadyRegisteredError(
|
|
129
|
-
f"Service '{name}' is already registered. Use overwrite=True to replace."
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
self._services[name] = ServiceDescriptor(
|
|
133
|
-
name=name,
|
|
134
|
-
instance=instance,
|
|
135
|
-
description=description
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# Also set typed attribute if it exists
|
|
139
|
-
if hasattr(self, name) and name != '_services':
|
|
140
|
-
setattr(self, name, instance)
|
|
141
|
-
|
|
142
|
-
return self
|
|
143
|
-
|
|
144
|
-
def register_lazy(self,
|
|
145
|
-
name: str,
|
|
146
|
-
factory: Callable[[], Any],
|
|
147
|
-
singleton: bool = True,
|
|
148
|
-
description: str = "",
|
|
149
|
-
overwrite: bool = False) -> 'AppContext':
|
|
150
|
-
"""
|
|
151
|
-
Register a lazy-loaded service.
|
|
152
|
-
|
|
153
|
-
The factory function is called on first access.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
name: Service name/identifier
|
|
157
|
-
factory: Factory function that creates the service
|
|
158
|
-
singleton: If True, cache the instance after first creation
|
|
159
|
-
description: Human-readable description
|
|
160
|
-
overwrite: Allow overwriting existing service
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
Self for method chaining
|
|
164
|
-
"""
|
|
165
|
-
if name in self._services and not overwrite:
|
|
166
|
-
raise ServiceAlreadyRegisteredError(
|
|
167
|
-
f"Service '{name}' is already registered. Use overwrite=True to replace."
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
self._services[name] = ServiceDescriptor(
|
|
171
|
-
name=name,
|
|
172
|
-
factory=factory,
|
|
173
|
-
singleton=singleton,
|
|
174
|
-
description=description
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
return self
|
|
178
|
-
|
|
179
|
-
def get(self, name: str, default: Any = None) -> Any:
|
|
180
|
-
"""
|
|
181
|
-
Get a registered service.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
name: Service name/identifier
|
|
185
|
-
default: Default value if service not found
|
|
186
|
-
|
|
187
|
-
Returns:
|
|
188
|
-
The service instance, or default if not found
|
|
189
|
-
"""
|
|
190
|
-
if name not in self._services:
|
|
191
|
-
# Check typed attributes
|
|
192
|
-
if hasattr(self, name):
|
|
193
|
-
value = getattr(self, name)
|
|
194
|
-
if value is not None:
|
|
195
|
-
return value
|
|
196
|
-
return default
|
|
197
|
-
|
|
198
|
-
descriptor = self._services[name]
|
|
199
|
-
|
|
200
|
-
# Return existing instance
|
|
201
|
-
if descriptor.instance is not None:
|
|
202
|
-
return descriptor.instance
|
|
203
|
-
|
|
204
|
-
# Lazy instantiation
|
|
205
|
-
if descriptor.factory is not None:
|
|
206
|
-
instance = descriptor.factory()
|
|
207
|
-
|
|
208
|
-
if descriptor.singleton:
|
|
209
|
-
descriptor.instance = instance
|
|
210
|
-
# Also set typed attribute if it exists
|
|
211
|
-
if hasattr(self, name) and name != '_services':
|
|
212
|
-
setattr(self, name, instance)
|
|
213
|
-
|
|
214
|
-
return instance
|
|
215
|
-
|
|
216
|
-
return default
|
|
217
|
-
|
|
218
|
-
def get_required(self, name: str) -> Any:
|
|
219
|
-
"""
|
|
220
|
-
Get a required service (raises if not found).
|
|
221
|
-
|
|
222
|
-
Args:
|
|
223
|
-
name: Service name/identifier
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
The service instance
|
|
227
|
-
|
|
228
|
-
Raises:
|
|
229
|
-
ServiceNotFoundError: If service is not registered
|
|
230
|
-
"""
|
|
231
|
-
result = self.get(name)
|
|
232
|
-
if result is None:
|
|
233
|
-
raise ServiceNotFoundError(f"Required service '{name}' not found")
|
|
234
|
-
return result
|
|
235
|
-
|
|
236
|
-
def has(self, name: str) -> bool:
|
|
237
|
-
"""
|
|
238
|
-
Check if a service is registered.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
name: Service name/identifier
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
True if service is registered
|
|
245
|
-
"""
|
|
246
|
-
if name in self._services:
|
|
247
|
-
return True
|
|
248
|
-
if hasattr(self, name):
|
|
249
|
-
return getattr(self, name) is not None
|
|
250
|
-
return False
|
|
251
|
-
|
|
252
|
-
def unregister(self, name: str) -> bool:
|
|
253
|
-
"""
|
|
254
|
-
Unregister a service.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
name: Service name/identifier
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
True if service was removed, False if not found
|
|
261
|
-
"""
|
|
262
|
-
if name in self._services:
|
|
263
|
-
del self._services[name]
|
|
264
|
-
if hasattr(self, name) and name != '_services':
|
|
265
|
-
setattr(self, name, None)
|
|
266
|
-
return True
|
|
267
|
-
return False
|
|
268
|
-
|
|
269
|
-
def list_services(self) -> Dict[str, Dict[str, Any]]:
|
|
270
|
-
"""
|
|
271
|
-
List all registered services.
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
Dictionary with service info
|
|
275
|
-
"""
|
|
276
|
-
result = {}
|
|
277
|
-
|
|
278
|
-
for name, descriptor in self._services.items():
|
|
279
|
-
result[name] = {
|
|
280
|
-
'registered': True,
|
|
281
|
-
'instantiated': descriptor.instance is not None,
|
|
282
|
-
'lazy': descriptor.is_lazy,
|
|
283
|
-
'singleton': descriptor.singleton,
|
|
284
|
-
'description': descriptor.description
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return result
|
|
288
|
-
|
|
289
|
-
def is_initialized(self) -> bool:
|
|
290
|
-
"""
|
|
291
|
-
Check if essential services are initialized.
|
|
292
|
-
|
|
293
|
-
Returns:
|
|
294
|
-
True if logger and settings_manager are available
|
|
295
|
-
"""
|
|
296
|
-
return (
|
|
297
|
-
self.logger is not None and
|
|
298
|
-
self.settings_manager is not None
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
def clear(self) -> None:
|
|
302
|
-
"""Clear all registered services."""
|
|
303
|
-
self._services.clear()
|
|
304
|
-
|
|
305
|
-
# Reset typed attributes
|
|
306
|
-
self.logger = None
|
|
307
|
-
self.settings_manager = None
|
|
308
|
-
self.dialog_manager = None
|
|
309
|
-
self.error_service = None
|
|
310
|
-
self.async_processor = None
|
|
311
|
-
self.stats_calculator = None
|
|
312
|
-
self.event_consolidator = None
|
|
313
|
-
self.tool_loader = None
|
|
314
|
-
self.widget_cache = None
|
|
315
|
-
self.root_window = None
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
class AppContextBuilder:
|
|
319
|
-
"""
|
|
320
|
-
Builder for creating AppContext with proper initialization order.
|
|
321
|
-
|
|
322
|
-
Usage:
|
|
323
|
-
context = (AppContextBuilder()
|
|
324
|
-
.with_logger(logger)
|
|
325
|
-
.with_settings_manager(settings_mgr)
|
|
326
|
-
.with_dialog_manager(dialog_mgr)
|
|
327
|
-
.with_error_service(error_svc)
|
|
328
|
-
.build())
|
|
329
|
-
"""
|
|
330
|
-
|
|
331
|
-
def __init__(self):
|
|
332
|
-
self._context = AppContext()
|
|
333
|
-
|
|
334
|
-
def with_logger(self, logger: logging.Logger) -> 'AppContextBuilder':
|
|
335
|
-
"""Add logger to context."""
|
|
336
|
-
self._context.logger = logger
|
|
337
|
-
self._context.register('logger', logger, 'Application logger')
|
|
338
|
-
return self
|
|
339
|
-
|
|
340
|
-
def with_settings_manager(self, manager: Any) -> 'AppContextBuilder':
|
|
341
|
-
"""Add settings manager to context."""
|
|
342
|
-
self._context.settings_manager = manager
|
|
343
|
-
self._context.register('settings_manager', manager, 'Settings manager')
|
|
344
|
-
return self
|
|
345
|
-
|
|
346
|
-
def with_dialog_manager(self, manager: Any) -> 'AppContextBuilder':
|
|
347
|
-
"""Add dialog manager to context."""
|
|
348
|
-
self._context.dialog_manager = manager
|
|
349
|
-
self._context.register('dialog_manager', manager, 'Dialog manager')
|
|
350
|
-
return self
|
|
351
|
-
|
|
352
|
-
def with_error_service(self, service: Any) -> 'AppContextBuilder':
|
|
353
|
-
"""Add error service to context."""
|
|
354
|
-
self._context.error_service = service
|
|
355
|
-
self._context.register('error_service', service, 'Error handling service')
|
|
356
|
-
return self
|
|
357
|
-
|
|
358
|
-
def with_async_processor(self, processor: Any) -> 'AppContextBuilder':
|
|
359
|
-
"""Add async processor to context."""
|
|
360
|
-
self._context.async_processor = processor
|
|
361
|
-
self._context.register('async_processor', processor, 'Async text processor')
|
|
362
|
-
return self
|
|
363
|
-
|
|
364
|
-
def with_stats_calculator(self, calculator: Any) -> 'AppContextBuilder':
|
|
365
|
-
"""Add stats calculator to context."""
|
|
366
|
-
self._context.stats_calculator = calculator
|
|
367
|
-
self._context.register('stats_calculator', calculator, 'Statistics calculator')
|
|
368
|
-
return self
|
|
369
|
-
|
|
370
|
-
def with_event_consolidator(self, consolidator: Any) -> 'AppContextBuilder':
|
|
371
|
-
"""Add event consolidator to context."""
|
|
372
|
-
self._context.event_consolidator = consolidator
|
|
373
|
-
self._context.register('event_consolidator', consolidator, 'Event consolidator')
|
|
374
|
-
return self
|
|
375
|
-
|
|
376
|
-
def with_tool_loader(self, loader: Any) -> 'AppContextBuilder':
|
|
377
|
-
"""Add tool loader to context."""
|
|
378
|
-
self._context.tool_loader = loader
|
|
379
|
-
self._context.register('tool_loader', loader, 'Tool loader')
|
|
380
|
-
return self
|
|
381
|
-
|
|
382
|
-
def with_widget_cache(self, cache: Any) -> 'AppContextBuilder':
|
|
383
|
-
"""Add widget cache to context."""
|
|
384
|
-
self._context.widget_cache = cache
|
|
385
|
-
self._context.register('widget_cache', cache, 'Widget cache')
|
|
386
|
-
return self
|
|
387
|
-
|
|
388
|
-
def with_root_window(self, window: Any) -> 'AppContextBuilder':
|
|
389
|
-
"""Add root window reference to context."""
|
|
390
|
-
self._context.root_window = window
|
|
391
|
-
self._context.register('root_window', window, 'Root Tkinter window')
|
|
392
|
-
return self
|
|
393
|
-
|
|
394
|
-
def with_service(self,
|
|
395
|
-
name: str,
|
|
396
|
-
instance: Any,
|
|
397
|
-
description: str = "") -> 'AppContextBuilder':
|
|
398
|
-
"""Add a custom service to context."""
|
|
399
|
-
self._context.register(name, instance, description)
|
|
400
|
-
return self
|
|
401
|
-
|
|
402
|
-
def with_lazy_service(self,
|
|
403
|
-
name: str,
|
|
404
|
-
factory: Callable[[], Any],
|
|
405
|
-
singleton: bool = True,
|
|
406
|
-
description: str = "") -> 'AppContextBuilder':
|
|
407
|
-
"""Add a lazy-loaded service to context."""
|
|
408
|
-
self._context.register_lazy(name, factory, singleton, description)
|
|
409
|
-
return self
|
|
410
|
-
|
|
411
|
-
def build(self) -> AppContext:
|
|
412
|
-
"""
|
|
413
|
-
Build and return the configured AppContext.
|
|
414
|
-
|
|
415
|
-
Returns:
|
|
416
|
-
Configured AppContext instance
|
|
417
|
-
"""
|
|
418
|
-
return self._context
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
# Global context instance
|
|
422
|
-
_app_context: Optional[AppContext] = None
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
def get_app_context() -> Optional[AppContext]:
|
|
426
|
-
"""
|
|
427
|
-
Get the global application context.
|
|
428
|
-
|
|
429
|
-
Returns:
|
|
430
|
-
The global AppContext instance, or None if not initialized
|
|
431
|
-
"""
|
|
432
|
-
return _app_context
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
def set_app_context(context: AppContext) -> None:
|
|
436
|
-
"""
|
|
437
|
-
Set the global application context.
|
|
438
|
-
|
|
439
|
-
Args:
|
|
440
|
-
context: The AppContext to set as global
|
|
441
|
-
"""
|
|
442
|
-
global _app_context
|
|
443
|
-
_app_context = context
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
def create_app_context() -> AppContext:
|
|
447
|
-
"""
|
|
448
|
-
Create a new AppContext and set it as the global instance.
|
|
449
|
-
|
|
450
|
-
Returns:
|
|
451
|
-
New AppContext instance
|
|
452
|
-
"""
|
|
453
|
-
global _app_context
|
|
454
|
-
_app_context = AppContext()
|
|
455
|
-
return _app_context
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
def clear_app_context() -> None:
|
|
459
|
-
"""Clear the global application context."""
|
|
460
|
-
global _app_context
|
|
461
|
-
if _app_context is not None:
|
|
462
|
-
_app_context.clear()
|
|
463
|
-
_app_context = None
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
def require_context() -> AppContext:
|
|
467
|
-
"""
|
|
468
|
-
Get the global context, raising if not initialized.
|
|
469
|
-
|
|
470
|
-
Returns:
|
|
471
|
-
The global AppContext
|
|
472
|
-
|
|
473
|
-
Raises:
|
|
474
|
-
RuntimeError: If context is not initialized
|
|
475
|
-
"""
|
|
476
|
-
if _app_context is None:
|
|
477
|
-
raise RuntimeError(
|
|
478
|
-
"Application context not initialized. "
|
|
479
|
-
"Call create_app_context() or set_app_context() first."
|
|
480
|
-
)
|
|
481
|
-
return _app_context
|
|
482
|
-
|
|
1
|
+
"""
|
|
2
|
+
Application Context - Dependency injection container.
|
|
3
|
+
|
|
4
|
+
This module provides a centralized container for application dependencies,
|
|
5
|
+
enabling better testability, modularity, and clear visibility of dependencies.
|
|
6
|
+
|
|
7
|
+
Author: Pomera AI Commander Team
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Optional, Any, Dict, Callable, TypeVar, Generic
|
|
12
|
+
import logging
|
|
13
|
+
import weakref
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
T = TypeVar('T')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ServiceNotFoundError(Exception):
|
|
20
|
+
"""Raised when a requested service is not registered."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ServiceAlreadyRegisteredError(Exception):
|
|
25
|
+
"""Raised when attempting to register a service that already exists."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ServiceDescriptor:
|
|
31
|
+
"""
|
|
32
|
+
Descriptor for a registered service.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
name: Service name/identifier
|
|
36
|
+
instance: The service instance (or None if lazy)
|
|
37
|
+
factory: Factory function for lazy instantiation
|
|
38
|
+
singleton: Whether to cache the instance
|
|
39
|
+
description: Human-readable description
|
|
40
|
+
"""
|
|
41
|
+
name: str
|
|
42
|
+
instance: Any = None
|
|
43
|
+
factory: Optional[Callable[[], Any]] = None
|
|
44
|
+
singleton: bool = True
|
|
45
|
+
description: str = ""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def is_lazy(self) -> bool:
|
|
49
|
+
"""Check if this is a lazy-loaded service."""
|
|
50
|
+
return self.factory is not None and self.instance is None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class AppContext:
|
|
55
|
+
"""
|
|
56
|
+
Container for application dependencies.
|
|
57
|
+
|
|
58
|
+
This allows for:
|
|
59
|
+
- Easy testing with mock dependencies
|
|
60
|
+
- Clear visibility of application dependencies
|
|
61
|
+
- Centralized dependency management
|
|
62
|
+
- Lazy loading of services
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
# Create context
|
|
66
|
+
ctx = AppContext()
|
|
67
|
+
|
|
68
|
+
# Register services
|
|
69
|
+
ctx.register('logger', logger_instance)
|
|
70
|
+
ctx.register_lazy('database', lambda: DatabaseConnection())
|
|
71
|
+
|
|
72
|
+
# Get services
|
|
73
|
+
logger = ctx.get('logger')
|
|
74
|
+
db = ctx.get('database') # Created on first access
|
|
75
|
+
|
|
76
|
+
# Or use typed properties for common services
|
|
77
|
+
ctx.logger # Returns logger if registered
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# Core services (typed for IDE support)
|
|
81
|
+
logger: Optional[logging.Logger] = None
|
|
82
|
+
settings_manager: Any = None
|
|
83
|
+
dialog_manager: Any = None
|
|
84
|
+
error_service: Any = None
|
|
85
|
+
|
|
86
|
+
# Processing services
|
|
87
|
+
async_processor: Any = None
|
|
88
|
+
stats_calculator: Any = None
|
|
89
|
+
event_consolidator: Any = None
|
|
90
|
+
|
|
91
|
+
# Tool management
|
|
92
|
+
tool_loader: Any = None
|
|
93
|
+
widget_cache: Any = None
|
|
94
|
+
|
|
95
|
+
# UI references (set after UI creation)
|
|
96
|
+
root_window: Any = None
|
|
97
|
+
|
|
98
|
+
# Internal service registry
|
|
99
|
+
_services: Dict[str, ServiceDescriptor] = field(default_factory=dict)
|
|
100
|
+
_initialized: bool = False
|
|
101
|
+
|
|
102
|
+
def __post_init__(self):
|
|
103
|
+
"""Initialize the service registry."""
|
|
104
|
+
self._services = {}
|
|
105
|
+
self._initialized = True
|
|
106
|
+
|
|
107
|
+
def register(self,
|
|
108
|
+
name: str,
|
|
109
|
+
instance: Any,
|
|
110
|
+
description: str = "",
|
|
111
|
+
overwrite: bool = False) -> 'AppContext':
|
|
112
|
+
"""
|
|
113
|
+
Register a service instance.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Service name/identifier
|
|
117
|
+
instance: The service instance
|
|
118
|
+
description: Human-readable description
|
|
119
|
+
overwrite: Allow overwriting existing service
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Self for method chaining
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
ServiceAlreadyRegisteredError: If service exists and overwrite=False
|
|
126
|
+
"""
|
|
127
|
+
if name in self._services and not overwrite:
|
|
128
|
+
raise ServiceAlreadyRegisteredError(
|
|
129
|
+
f"Service '{name}' is already registered. Use overwrite=True to replace."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self._services[name] = ServiceDescriptor(
|
|
133
|
+
name=name,
|
|
134
|
+
instance=instance,
|
|
135
|
+
description=description
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Also set typed attribute if it exists
|
|
139
|
+
if hasattr(self, name) and name != '_services':
|
|
140
|
+
setattr(self, name, instance)
|
|
141
|
+
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def register_lazy(self,
|
|
145
|
+
name: str,
|
|
146
|
+
factory: Callable[[], Any],
|
|
147
|
+
singleton: bool = True,
|
|
148
|
+
description: str = "",
|
|
149
|
+
overwrite: bool = False) -> 'AppContext':
|
|
150
|
+
"""
|
|
151
|
+
Register a lazy-loaded service.
|
|
152
|
+
|
|
153
|
+
The factory function is called on first access.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
name: Service name/identifier
|
|
157
|
+
factory: Factory function that creates the service
|
|
158
|
+
singleton: If True, cache the instance after first creation
|
|
159
|
+
description: Human-readable description
|
|
160
|
+
overwrite: Allow overwriting existing service
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Self for method chaining
|
|
164
|
+
"""
|
|
165
|
+
if name in self._services and not overwrite:
|
|
166
|
+
raise ServiceAlreadyRegisteredError(
|
|
167
|
+
f"Service '{name}' is already registered. Use overwrite=True to replace."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self._services[name] = ServiceDescriptor(
|
|
171
|
+
name=name,
|
|
172
|
+
factory=factory,
|
|
173
|
+
singleton=singleton,
|
|
174
|
+
description=description
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
180
|
+
"""
|
|
181
|
+
Get a registered service.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
name: Service name/identifier
|
|
185
|
+
default: Default value if service not found
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The service instance, or default if not found
|
|
189
|
+
"""
|
|
190
|
+
if name not in self._services:
|
|
191
|
+
# Check typed attributes
|
|
192
|
+
if hasattr(self, name):
|
|
193
|
+
value = getattr(self, name)
|
|
194
|
+
if value is not None:
|
|
195
|
+
return value
|
|
196
|
+
return default
|
|
197
|
+
|
|
198
|
+
descriptor = self._services[name]
|
|
199
|
+
|
|
200
|
+
# Return existing instance
|
|
201
|
+
if descriptor.instance is not None:
|
|
202
|
+
return descriptor.instance
|
|
203
|
+
|
|
204
|
+
# Lazy instantiation
|
|
205
|
+
if descriptor.factory is not None:
|
|
206
|
+
instance = descriptor.factory()
|
|
207
|
+
|
|
208
|
+
if descriptor.singleton:
|
|
209
|
+
descriptor.instance = instance
|
|
210
|
+
# Also set typed attribute if it exists
|
|
211
|
+
if hasattr(self, name) and name != '_services':
|
|
212
|
+
setattr(self, name, instance)
|
|
213
|
+
|
|
214
|
+
return instance
|
|
215
|
+
|
|
216
|
+
return default
|
|
217
|
+
|
|
218
|
+
def get_required(self, name: str) -> Any:
|
|
219
|
+
"""
|
|
220
|
+
Get a required service (raises if not found).
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
name: Service name/identifier
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The service instance
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ServiceNotFoundError: If service is not registered
|
|
230
|
+
"""
|
|
231
|
+
result = self.get(name)
|
|
232
|
+
if result is None:
|
|
233
|
+
raise ServiceNotFoundError(f"Required service '{name}' not found")
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
def has(self, name: str) -> bool:
|
|
237
|
+
"""
|
|
238
|
+
Check if a service is registered.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
name: Service name/identifier
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if service is registered
|
|
245
|
+
"""
|
|
246
|
+
if name in self._services:
|
|
247
|
+
return True
|
|
248
|
+
if hasattr(self, name):
|
|
249
|
+
return getattr(self, name) is not None
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
def unregister(self, name: str) -> bool:
|
|
253
|
+
"""
|
|
254
|
+
Unregister a service.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
name: Service name/identifier
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if service was removed, False if not found
|
|
261
|
+
"""
|
|
262
|
+
if name in self._services:
|
|
263
|
+
del self._services[name]
|
|
264
|
+
if hasattr(self, name) and name != '_services':
|
|
265
|
+
setattr(self, name, None)
|
|
266
|
+
return True
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
def list_services(self) -> Dict[str, Dict[str, Any]]:
|
|
270
|
+
"""
|
|
271
|
+
List all registered services.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Dictionary with service info
|
|
275
|
+
"""
|
|
276
|
+
result = {}
|
|
277
|
+
|
|
278
|
+
for name, descriptor in self._services.items():
|
|
279
|
+
result[name] = {
|
|
280
|
+
'registered': True,
|
|
281
|
+
'instantiated': descriptor.instance is not None,
|
|
282
|
+
'lazy': descriptor.is_lazy,
|
|
283
|
+
'singleton': descriptor.singleton,
|
|
284
|
+
'description': descriptor.description
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
def is_initialized(self) -> bool:
|
|
290
|
+
"""
|
|
291
|
+
Check if essential services are initialized.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
True if logger and settings_manager are available
|
|
295
|
+
"""
|
|
296
|
+
return (
|
|
297
|
+
self.logger is not None and
|
|
298
|
+
self.settings_manager is not None
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def clear(self) -> None:
|
|
302
|
+
"""Clear all registered services."""
|
|
303
|
+
self._services.clear()
|
|
304
|
+
|
|
305
|
+
# Reset typed attributes
|
|
306
|
+
self.logger = None
|
|
307
|
+
self.settings_manager = None
|
|
308
|
+
self.dialog_manager = None
|
|
309
|
+
self.error_service = None
|
|
310
|
+
self.async_processor = None
|
|
311
|
+
self.stats_calculator = None
|
|
312
|
+
self.event_consolidator = None
|
|
313
|
+
self.tool_loader = None
|
|
314
|
+
self.widget_cache = None
|
|
315
|
+
self.root_window = None
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class AppContextBuilder:
|
|
319
|
+
"""
|
|
320
|
+
Builder for creating AppContext with proper initialization order.
|
|
321
|
+
|
|
322
|
+
Usage:
|
|
323
|
+
context = (AppContextBuilder()
|
|
324
|
+
.with_logger(logger)
|
|
325
|
+
.with_settings_manager(settings_mgr)
|
|
326
|
+
.with_dialog_manager(dialog_mgr)
|
|
327
|
+
.with_error_service(error_svc)
|
|
328
|
+
.build())
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self):
|
|
332
|
+
self._context = AppContext()
|
|
333
|
+
|
|
334
|
+
def with_logger(self, logger: logging.Logger) -> 'AppContextBuilder':
|
|
335
|
+
"""Add logger to context."""
|
|
336
|
+
self._context.logger = logger
|
|
337
|
+
self._context.register('logger', logger, 'Application logger')
|
|
338
|
+
return self
|
|
339
|
+
|
|
340
|
+
def with_settings_manager(self, manager: Any) -> 'AppContextBuilder':
|
|
341
|
+
"""Add settings manager to context."""
|
|
342
|
+
self._context.settings_manager = manager
|
|
343
|
+
self._context.register('settings_manager', manager, 'Settings manager')
|
|
344
|
+
return self
|
|
345
|
+
|
|
346
|
+
def with_dialog_manager(self, manager: Any) -> 'AppContextBuilder':
|
|
347
|
+
"""Add dialog manager to context."""
|
|
348
|
+
self._context.dialog_manager = manager
|
|
349
|
+
self._context.register('dialog_manager', manager, 'Dialog manager')
|
|
350
|
+
return self
|
|
351
|
+
|
|
352
|
+
def with_error_service(self, service: Any) -> 'AppContextBuilder':
|
|
353
|
+
"""Add error service to context."""
|
|
354
|
+
self._context.error_service = service
|
|
355
|
+
self._context.register('error_service', service, 'Error handling service')
|
|
356
|
+
return self
|
|
357
|
+
|
|
358
|
+
def with_async_processor(self, processor: Any) -> 'AppContextBuilder':
|
|
359
|
+
"""Add async processor to context."""
|
|
360
|
+
self._context.async_processor = processor
|
|
361
|
+
self._context.register('async_processor', processor, 'Async text processor')
|
|
362
|
+
return self
|
|
363
|
+
|
|
364
|
+
def with_stats_calculator(self, calculator: Any) -> 'AppContextBuilder':
|
|
365
|
+
"""Add stats calculator to context."""
|
|
366
|
+
self._context.stats_calculator = calculator
|
|
367
|
+
self._context.register('stats_calculator', calculator, 'Statistics calculator')
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
def with_event_consolidator(self, consolidator: Any) -> 'AppContextBuilder':
|
|
371
|
+
"""Add event consolidator to context."""
|
|
372
|
+
self._context.event_consolidator = consolidator
|
|
373
|
+
self._context.register('event_consolidator', consolidator, 'Event consolidator')
|
|
374
|
+
return self
|
|
375
|
+
|
|
376
|
+
def with_tool_loader(self, loader: Any) -> 'AppContextBuilder':
|
|
377
|
+
"""Add tool loader to context."""
|
|
378
|
+
self._context.tool_loader = loader
|
|
379
|
+
self._context.register('tool_loader', loader, 'Tool loader')
|
|
380
|
+
return self
|
|
381
|
+
|
|
382
|
+
def with_widget_cache(self, cache: Any) -> 'AppContextBuilder':
|
|
383
|
+
"""Add widget cache to context."""
|
|
384
|
+
self._context.widget_cache = cache
|
|
385
|
+
self._context.register('widget_cache', cache, 'Widget cache')
|
|
386
|
+
return self
|
|
387
|
+
|
|
388
|
+
def with_root_window(self, window: Any) -> 'AppContextBuilder':
|
|
389
|
+
"""Add root window reference to context."""
|
|
390
|
+
self._context.root_window = window
|
|
391
|
+
self._context.register('root_window', window, 'Root Tkinter window')
|
|
392
|
+
return self
|
|
393
|
+
|
|
394
|
+
def with_service(self,
|
|
395
|
+
name: str,
|
|
396
|
+
instance: Any,
|
|
397
|
+
description: str = "") -> 'AppContextBuilder':
|
|
398
|
+
"""Add a custom service to context."""
|
|
399
|
+
self._context.register(name, instance, description)
|
|
400
|
+
return self
|
|
401
|
+
|
|
402
|
+
def with_lazy_service(self,
|
|
403
|
+
name: str,
|
|
404
|
+
factory: Callable[[], Any],
|
|
405
|
+
singleton: bool = True,
|
|
406
|
+
description: str = "") -> 'AppContextBuilder':
|
|
407
|
+
"""Add a lazy-loaded service to context."""
|
|
408
|
+
self._context.register_lazy(name, factory, singleton, description)
|
|
409
|
+
return self
|
|
410
|
+
|
|
411
|
+
def build(self) -> AppContext:
|
|
412
|
+
"""
|
|
413
|
+
Build and return the configured AppContext.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Configured AppContext instance
|
|
417
|
+
"""
|
|
418
|
+
return self._context
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# Global context instance
|
|
422
|
+
_app_context: Optional[AppContext] = None
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def get_app_context() -> Optional[AppContext]:
|
|
426
|
+
"""
|
|
427
|
+
Get the global application context.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
The global AppContext instance, or None if not initialized
|
|
431
|
+
"""
|
|
432
|
+
return _app_context
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def set_app_context(context: AppContext) -> None:
|
|
436
|
+
"""
|
|
437
|
+
Set the global application context.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
context: The AppContext to set as global
|
|
441
|
+
"""
|
|
442
|
+
global _app_context
|
|
443
|
+
_app_context = context
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def create_app_context() -> AppContext:
|
|
447
|
+
"""
|
|
448
|
+
Create a new AppContext and set it as the global instance.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
New AppContext instance
|
|
452
|
+
"""
|
|
453
|
+
global _app_context
|
|
454
|
+
_app_context = AppContext()
|
|
455
|
+
return _app_context
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def clear_app_context() -> None:
|
|
459
|
+
"""Clear the global application context."""
|
|
460
|
+
global _app_context
|
|
461
|
+
if _app_context is not None:
|
|
462
|
+
_app_context.clear()
|
|
463
|
+
_app_context = None
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def require_context() -> AppContext:
|
|
467
|
+
"""
|
|
468
|
+
Get the global context, raising if not initialized.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
The global AppContext
|
|
472
|
+
|
|
473
|
+
Raises:
|
|
474
|
+
RuntimeError: If context is not initialized
|
|
475
|
+
"""
|
|
476
|
+
if _app_context is None:
|
|
477
|
+
raise RuntimeError(
|
|
478
|
+
"Application context not initialized. "
|
|
479
|
+
"Call create_app_context() or set_app_context() first."
|
|
480
|
+
)
|
|
481
|
+
return _app_context
|
|
482
|
+
|