pipu-cli 0.1.dev7__py3-none-any.whl → 0.2.0__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.
pipu_cli/thread_safe.py DELETED
@@ -1,243 +0,0 @@
1
- """
2
- Thread-safe data structures and utilities for pipu.
3
-
4
- Provides thread-safe implementations of caches and shared state management
5
- to prevent race conditions in concurrent operations.
6
- """
7
-
8
- import threading
9
- import time
10
- import logging
11
- from typing import Dict, List, Any, Optional, TypeVar, Generic, Callable
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
- T = TypeVar('T')
16
-
17
-
18
- class ThreadSafeCache(Generic[T]):
19
- """
20
- Thread-safe cache with TTL support.
21
-
22
- This cache is safe for concurrent access from multiple threads.
23
- """
24
-
25
- def __init__(self, ttl: float = 60.0):
26
- """
27
- Initialize a thread-safe cache.
28
-
29
- :param ttl: Time-to-live for cache entries in seconds
30
- """
31
- self._lock = threading.RLock() # Reentrant lock
32
- self._cache: Optional[T] = None
33
- self._cache_time: float = 0.0
34
- self._ttl = ttl
35
-
36
- def get(self, factory: Callable[[], T]) -> T:
37
- """
38
- Get cached value or create it using the factory function.
39
-
40
- :param factory: Function to create value if cache is expired
41
- :returns: Cached or freshly created value
42
- """
43
- with self._lock:
44
- current_time = time.time()
45
-
46
- # Check if cache is valid
47
- if self._cache is not None and (current_time - self._cache_time) < self._ttl:
48
- logger.debug("Cache hit")
49
- # Type checker: we've verified self._cache is not None above
50
- return self._cache # type: ignore[return-value]
51
-
52
- # Cache miss or expired
53
- logger.debug("Cache miss - creating new value")
54
- try:
55
- new_value: T = factory()
56
- self._cache = new_value
57
- self._cache_time = time.time()
58
- return new_value
59
- except Exception as e:
60
- logger.error(f"Factory function failed: {e}")
61
- # Return stale cache if available, otherwise raise
62
- if self._cache is not None:
63
- logger.warning("Returning stale cache due to factory failure")
64
- # Type checker: we've verified self._cache is not None above
65
- return self._cache # type: ignore[return-value]
66
- raise
67
-
68
- def invalidate(self):
69
- """Invalidate the cache."""
70
- with self._lock:
71
- self._cache = None
72
- self._cache_time = 0.0
73
- logger.debug("Cache invalidated")
74
-
75
- def is_valid(self) -> bool:
76
- """Check if cache has valid data."""
77
- with self._lock:
78
- if self._cache is None:
79
- return False
80
- current_time = time.time()
81
- return (current_time - self._cache_time) < self._ttl
82
-
83
-
84
- class ThreadSafeList(Generic[T]):
85
- """
86
- Thread-safe list wrapper.
87
-
88
- Provides synchronized access to a list for concurrent operations.
89
- """
90
-
91
- def __init__(self, initial: Optional[List[T]] = None):
92
- """
93
- Initialize a thread-safe list.
94
-
95
- :param initial: Initial list contents
96
- """
97
- self._lock = threading.RLock()
98
- self._items: List[T] = initial.copy() if initial else []
99
-
100
- def get_all(self) -> List[T]:
101
- """Get a copy of all items."""
102
- with self._lock:
103
- return self._items.copy()
104
-
105
- def set_all(self, items: List[T]):
106
- """Replace all items."""
107
- with self._lock:
108
- self._items = items.copy()
109
-
110
- def append(self, item: T):
111
- """Append an item."""
112
- with self._lock:
113
- self._items.append(item)
114
-
115
- def extend(self, items: List[T]):
116
- """Extend with multiple items."""
117
- with self._lock:
118
- self._items.extend(items)
119
-
120
- def clear(self):
121
- """Clear all items."""
122
- with self._lock:
123
- self._items.clear()
124
-
125
- def __len__(self) -> int:
126
- """Get length."""
127
- with self._lock:
128
- return len(self._items)
129
-
130
- def filter(self, predicate: Callable[[T], bool]) -> List[T]:
131
- """Filter items and return a copy."""
132
- with self._lock:
133
- return [item for item in self._items if predicate(item)]
134
-
135
- def update_item(self, predicate: Callable[[T], bool], updater: Callable[[T], T]):
136
- """
137
- Update items matching a predicate.
138
-
139
- :param predicate: Function to identify items to update
140
- :param updater: Function to modify the item
141
- """
142
- with self._lock:
143
- for i, item in enumerate(self._items):
144
- if predicate(item):
145
- self._items[i] = updater(item)
146
-
147
-
148
- class PackageStateManager:
149
- """
150
- Thread-safe manager for package state in the TUI.
151
-
152
- Handles concurrent updates to package information from multiple threads.
153
- """
154
-
155
- def __init__(self):
156
- """Initialize the package state manager."""
157
- self._lock = threading.RLock()
158
- self._packages: List[Dict[str, Any]] = []
159
- self._row_mapping: Dict[str, int] = {}
160
-
161
- def set_packages(self, packages: List[Dict[str, Any]]):
162
- """
163
- Set all packages and rebuild row mapping.
164
-
165
- :param packages: List of package dictionaries
166
- """
167
- with self._lock:
168
- self._packages = [pkg.copy() for pkg in packages]
169
- self._rebuild_row_mapping()
170
-
171
- def get_packages(self) -> List[Dict[str, Any]]:
172
- """Get a copy of all packages."""
173
- with self._lock:
174
- return [pkg.copy() for pkg in self._packages]
175
-
176
- def get_package(self, name: str) -> Optional[Dict[str, Any]]:
177
- """
178
- Get a specific package by name.
179
-
180
- :param name: Package name
181
- :returns: Package dict or None
182
- """
183
- with self._lock:
184
- for pkg in self._packages:
185
- if pkg.get('name') == name:
186
- return pkg.copy()
187
- return None
188
-
189
- def update_package(self, name: str, updates: Dict[str, Any]):
190
- """
191
- Update a specific package's fields.
192
-
193
- :param name: Package name
194
- :param updates: Dictionary of fields to update
195
- """
196
- with self._lock:
197
- for pkg in self._packages:
198
- if pkg.get('name') == name:
199
- pkg.update(updates)
200
- break
201
-
202
- def get_row_index(self, name: str) -> Optional[int]:
203
- """
204
- Get row index for a package.
205
-
206
- :param name: Package name
207
- :returns: Row index or None
208
- """
209
- with self._lock:
210
- return self._row_mapping.get(name)
211
-
212
- def _rebuild_row_mapping(self):
213
- """Rebuild the row mapping (call with lock held)."""
214
- self._row_mapping = {
215
- pkg['name']: i
216
- for i, pkg in enumerate(self._packages)
217
- }
218
-
219
- def get_filtered(self, predicate: Callable[[Dict[str, Any]], bool]) -> List[Dict[str, Any]]:
220
- """
221
- Get packages matching a predicate.
222
-
223
- :param predicate: Function to filter packages
224
- :returns: List of matching packages (copies)
225
- """
226
- with self._lock:
227
- return [
228
- pkg.copy()
229
- for pkg in self._packages
230
- if predicate(pkg)
231
- ]
232
-
233
- def count(self, predicate: Optional[Callable[[Dict[str, Any]], bool]] = None) -> int:
234
- """
235
- Count packages, optionally matching a predicate.
236
-
237
- :param predicate: Optional filter function
238
- :returns: Count of packages
239
- """
240
- with self._lock:
241
- if predicate is None:
242
- return len(self._packages)
243
- return sum(1 for pkg in self._packages if predicate(pkg))
pipu_cli/ui/__init__.py DELETED
@@ -1,51 +0,0 @@
1
- # UI Module for pipu package management
2
- """
3
- Textual-based TUI interfaces for pipu package management.
4
-
5
- This module provides the main TUI applications and supporting components
6
- for interactive package management, selection, and constraint configuration.
7
- """
8
-
9
- from .apps import MainTUIApp, PackageSelectionApp, main_tui_app, interactive_package_selection
10
- from .modal_dialogs import (
11
- ConstraintInputScreen, HelpScreen,
12
- DeleteConstraintConfirmScreen, RemoveAllConstraintsConfirmScreen,
13
- UninstallConfirmScreen, NetworkErrorScreen
14
- )
15
- from .table_widgets import PackageSelectionTable
16
- from .constants import (
17
- COLUMN_SELECTION, COLUMN_PACKAGE, COLUMN_CURRENT, COLUMN_LATEST,
18
- COLUMN_TYPE, COLUMN_CONSTRAINT, COLUMN_INVALID_WHEN,
19
- FORCE_EXIT_TIMEOUT, UNINSTALL_TIMEOUT
20
- )
21
-
22
- # Main entry point functions
23
- __all__ = [
24
- # Main applications
25
- 'MainTUIApp',
26
- 'PackageSelectionApp',
27
- 'main_tui_app',
28
- 'interactive_package_selection',
29
-
30
- # Modal dialogs
31
- 'ConstraintInputScreen',
32
- 'HelpScreen',
33
- 'DeleteConstraintConfirmScreen',
34
- 'RemoveAllConstraintsConfirmScreen',
35
- 'UninstallConfirmScreen',
36
- 'NetworkErrorScreen',
37
-
38
- # Table widgets
39
- 'PackageSelectionTable',
40
-
41
- # Constants
42
- 'COLUMN_SELECTION',
43
- 'COLUMN_PACKAGE',
44
- 'COLUMN_CURRENT',
45
- 'COLUMN_LATEST',
46
- 'COLUMN_TYPE',
47
- 'COLUMN_CONSTRAINT',
48
- 'COLUMN_INVALID_WHEN',
49
- 'FORCE_EXIT_TIMEOUT',
50
- 'UNINSTALL_TIMEOUT'
51
- ]