gitflow-analytics 3.6.2__py3-none-any.whl → 3.7.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. gitflow_analytics/__init__.py +8 -12
  2. gitflow_analytics/_version.py +1 -1
  3. gitflow_analytics/cli.py +323 -203
  4. gitflow_analytics/cli_wizards/install_wizard.py +5 -5
  5. gitflow_analytics/config/repository.py +9 -1
  6. gitflow_analytics/config/schema.py +39 -0
  7. gitflow_analytics/identity_llm/analysis_pass.py +7 -2
  8. gitflow_analytics/models/database.py +229 -8
  9. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/METADATA +2 -4
  10. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/RECORD +14 -27
  11. gitflow_analytics/tui/__init__.py +0 -5
  12. gitflow_analytics/tui/app.py +0 -726
  13. gitflow_analytics/tui/progress_adapter.py +0 -313
  14. gitflow_analytics/tui/screens/__init__.py +0 -8
  15. gitflow_analytics/tui/screens/analysis_progress_screen.py +0 -857
  16. gitflow_analytics/tui/screens/configuration_screen.py +0 -523
  17. gitflow_analytics/tui/screens/loading_screen.py +0 -348
  18. gitflow_analytics/tui/screens/main_screen.py +0 -321
  19. gitflow_analytics/tui/screens/results_screen.py +0 -735
  20. gitflow_analytics/tui/widgets/__init__.py +0 -7
  21. gitflow_analytics/tui/widgets/data_table.py +0 -255
  22. gitflow_analytics/tui/widgets/export_modal.py +0 -301
  23. gitflow_analytics/tui/widgets/progress_widget.py +0 -187
  24. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/WHEEL +0 -0
  25. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/entry_points.txt +0 -0
  26. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/licenses/LICENSE +0 -0
  27. {gitflow_analytics-3.6.2.dist-info → gitflow_analytics-3.7.4.dist-info}/top_level.txt +0 -0
@@ -1,313 +0,0 @@
1
- """Progress adapter for TUI to connect with core analysis progress."""
2
-
3
- import asyncio
4
- import time
5
- from typing import Optional
6
-
7
- from gitflow_analytics.core.progress import ProgressContext, ProgressService
8
- from gitflow_analytics.tui.widgets.progress_widget import AnalysisProgressWidget
9
-
10
-
11
- class TUIProgressAdapter(ProgressService):
12
- """
13
- Adapter that bridges the core ProgressService with TUI progress widgets.
14
-
15
- This allows the GitAnalyzer's progress updates to be reflected in the TUI
16
- interface in real-time.
17
- """
18
-
19
- def __init__(self, widget: Optional[AnalysisProgressWidget] = None):
20
- """Initialize the TUI progress adapter.
21
-
22
- Args:
23
- widget: The progress widget to update
24
- """
25
- super().__init__()
26
- self.widget = widget
27
- self.current_progress = 0.0
28
- self.total_items = 100.0
29
- self._loop = None
30
- self.processing_stats = {
31
- "total": 0,
32
- "processed": 0,
33
- "success": 0,
34
- "failed": 0,
35
- "timeout": 0,
36
- }
37
- self.repositories_in_progress = {}
38
-
39
- def set_widget(self, widget: AnalysisProgressWidget) -> None:
40
- """Set or update the progress widget."""
41
- self.widget = widget
42
-
43
- def set_event_loop(self, loop: asyncio.AbstractEventLoop) -> None:
44
- """Set the event loop for async updates."""
45
- self._loop = loop
46
-
47
- def create_progress(
48
- self,
49
- total: int,
50
- description: str = "",
51
- unit: str = "items",
52
- nested: bool = False,
53
- leave: bool = True,
54
- position: Optional[int] = None,
55
- ) -> ProgressContext:
56
- """Create a progress context for tracking.
57
-
58
- Args:
59
- total: Total number of items to process
60
- description: Description of the task
61
- unit: Unit of work (e.g., "commits", "files")
62
- nested: Whether this is a nested progress bar
63
- leave: Whether to leave the progress bar on screen after completion
64
- position: Explicit position for the progress bar (for nested contexts)
65
-
66
- Returns:
67
- Progress context for this task
68
- """
69
- # Pass all parameters to parent class with correct signature
70
- context = super().create_progress(total, description, unit, nested, leave, position)
71
- self.total_items = float(total)
72
- self.current_progress = 0.0
73
-
74
- # Update widget if available
75
- if self.widget and self._loop:
76
- self._loop.call_soon_threadsafe(self._update_widget_sync, 0.0, description)
77
-
78
- return context
79
-
80
- def update(self, context: ProgressContext, advance: int = 1) -> None:
81
- """Update progress by advancing the counter.
82
-
83
- Args:
84
- context: Progress context to update
85
- advance: Number of items completed
86
- """
87
- super().update(context, advance)
88
-
89
- # Calculate percentage
90
- if self.total_items > 0:
91
- self.current_progress += advance
92
- percentage = (self.current_progress / self.total_items) * 100.0
93
-
94
- # Update widget if available
95
- if self.widget and self._loop:
96
- description = getattr(context, "description", "")
97
- self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
98
-
99
- def set_description(self, context: ProgressContext, description: str) -> None:
100
- """Update the description of a progress context.
101
-
102
- Args:
103
- context: Progress context to update
104
- description: New description
105
- """
106
- super().set_description(context, description)
107
-
108
- # Update widget description if available
109
- if self.widget and self._loop:
110
- percentage = (
111
- (self.current_progress / self.total_items) * 100.0 if self.total_items > 0 else 0
112
- )
113
- self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
114
-
115
- def complete(self, context: ProgressContext) -> None:
116
- """Mark a progress context as complete.
117
-
118
- Args:
119
- context: Progress context to complete
120
- """
121
- super().complete(context)
122
-
123
- # Update widget to 100% if available
124
- if self.widget and self._loop:
125
- self._loop.call_soon_threadsafe(self._update_widget_sync, 100.0, "Complete")
126
-
127
- def _update_widget_sync(self, percentage: float, description: str) -> None:
128
- """Synchronously update the widget (called from event loop).
129
-
130
- Args:
131
- percentage: Progress percentage (0-100)
132
- description: Status description
133
- """
134
- if self.widget:
135
- try:
136
- # Format description with processing statistics if available
137
- if self.processing_stats["total"] > 0:
138
- stats_str = (
139
- f"Processed: {self.processing_stats['processed']}/{self.processing_stats['total']}, "
140
- f"Success: {self.processing_stats['success']}, "
141
- f"Failed: {self.processing_stats['failed']}"
142
- )
143
- if self.processing_stats["timeout"] > 0:
144
- stats_str += f", Timeout: {self.processing_stats['timeout']}"
145
-
146
- full_description = f"{description}\n{stats_str}"
147
- else:
148
- full_description = description
149
-
150
- self.widget.update_progress(percentage, full_description)
151
- # Force a refresh of the TUI to ensure updates are visible
152
- if hasattr(self.widget, "refresh"):
153
- self.widget.refresh()
154
- except Exception as e:
155
- # Log error but don't crash the progress tracking
156
- import logging
157
-
158
- logging.getLogger(__name__).error(f"Failed to update TUI widget: {e}")
159
-
160
- def start_repository(self, repo_name: str, total_commits: int) -> None:
161
- """Track the start of repository processing.
162
-
163
- Args:
164
- repo_name: Name of the repository
165
- total_commits: Expected number of commits to process
166
- """
167
- self.repositories_in_progress[repo_name] = {
168
- "started_at": time.time(),
169
- "total_commits": total_commits,
170
- "status": "processing",
171
- }
172
-
173
- # Update the widget with repository info
174
- if self.widget and self._loop:
175
- # Calculate correct percentage based on actual processed count
176
- if self.processing_stats["total"] > 0:
177
- percentage = (
178
- self.processing_stats["processed"] / self.processing_stats["total"]
179
- ) * 100.0
180
- else:
181
- percentage = 0
182
-
183
- description = f"Processing: {repo_name} ({len(self.repositories_in_progress)}/{self.processing_stats['total']})"
184
- self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
185
-
186
- def finish_repository(
187
- self, repo_name: str, success: bool = True, error_message: str = None, stats: dict = None
188
- ) -> None:
189
- """Mark a repository as finished processing.
190
-
191
- Args:
192
- repo_name: Name of the repository
193
- success: Whether processing was successful
194
- error_message: Error message if failed
195
- stats: Updated processing statistics
196
- """
197
- if repo_name in self.repositories_in_progress:
198
- self.repositories_in_progress[repo_name]["status"] = "success" if success else "failed"
199
- if error_message:
200
- self.repositories_in_progress[repo_name]["error"] = error_message
201
-
202
- # Update stats if provided
203
- if stats:
204
- self.processing_stats.update(stats)
205
-
206
- # Update the widget with completion info
207
- if self.widget and self._loop:
208
- # Calculate correct percentage based on processed count
209
- if self.processing_stats["total"] > 0:
210
- percentage = (
211
- self.processing_stats["processed"] / self.processing_stats["total"]
212
- ) * 100.0
213
- else:
214
- percentage = 100.0 if self.processing_stats["processed"] > 0 else 0
215
-
216
- status = "✅" if success else "❌"
217
- description = f"{status} {repo_name} completed"
218
- if error_message:
219
- description += f" - {error_message[:30]}..."
220
-
221
- self._loop.call_soon_threadsafe(
222
- self._update_widget_sync, min(percentage, 100.0), description # Cap at 100%
223
- )
224
-
225
- def update_stats(
226
- self,
227
- processed: int = 0,
228
- success: int = 0,
229
- failed: int = 0,
230
- timeout: int = 0,
231
- total: int = 0,
232
- ) -> None:
233
- """Update processing statistics.
234
-
235
- Args:
236
- processed: Number of repositories processed
237
- success: Number of successful repositories
238
- failed: Number of failed repositories
239
- timeout: Number of timed out repositories
240
- total: Total number of repositories
241
- """
242
- if processed > 0:
243
- self.processing_stats["processed"] = processed
244
- if success > 0:
245
- self.processing_stats["success"] = success
246
- if failed > 0:
247
- self.processing_stats["failed"] = failed
248
- if timeout > 0:
249
- self.processing_stats["timeout"] = timeout
250
- if total > 0:
251
- self.processing_stats["total"] = total
252
-
253
- # Update widget with latest stats
254
- if self.widget and self._loop:
255
- percentage = (
256
- (self.processing_stats["processed"] / self.processing_stats["total"]) * 100.0
257
- if self.processing_stats["total"] > 0
258
- else 0
259
- )
260
-
261
- self._loop.call_soon_threadsafe(
262
- self._update_widget_sync,
263
- min(percentage, 100.0), # Cap at 100%
264
- "Processing repositories...",
265
- )
266
-
267
-
268
- class TUIProgressService:
269
- """Service to manage TUI progress adapters for different analysis phases."""
270
-
271
- def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None):
272
- """Initialize the TUI progress service.
273
-
274
- Args:
275
- loop: Event loop for async updates
276
- """
277
- self.loop = loop or asyncio.get_event_loop()
278
- self.adapters = {}
279
-
280
- def create_adapter(self, name: str, widget: AnalysisProgressWidget) -> TUIProgressAdapter:
281
- """Create a progress adapter for a specific widget.
282
-
283
- Args:
284
- name: Name/ID of the adapter
285
- widget: The progress widget to connect
286
-
287
- Returns:
288
- The created adapter
289
- """
290
- adapter = TUIProgressAdapter(widget)
291
- adapter.set_event_loop(self.loop)
292
- self.adapters[name] = adapter
293
- return adapter
294
-
295
- def get_adapter(self, name: str) -> Optional[TUIProgressAdapter]:
296
- """Get an existing adapter by name.
297
-
298
- Args:
299
- name: Name/ID of the adapter
300
-
301
- Returns:
302
- The adapter if found, None otherwise
303
- """
304
- return self.adapters.get(name)
305
-
306
- def remove_adapter(self, name: str) -> None:
307
- """Remove an adapter.
308
-
309
- Args:
310
- name: Name/ID of the adapter to remove
311
- """
312
- if name in self.adapters:
313
- del self.adapters[name]
@@ -1,8 +0,0 @@
1
- """TUI screens for GitFlow Analytics."""
2
-
3
- from .analysis_progress_screen import AnalysisProgressScreen
4
- from .configuration_screen import ConfigurationScreen
5
- from .main_screen import MainScreen
6
- from .results_screen import ResultsScreen
7
-
8
- __all__ = ["MainScreen", "ConfigurationScreen", "AnalysisProgressScreen", "ResultsScreen"]