pomera-ai-commander 0.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -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
+