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/__init__.py +2 -2
- pipu_cli/cache.py +316 -0
- pipu_cli/cli.py +863 -813
- pipu_cli/config.py +7 -58
- pipu_cli/config_file.py +80 -0
- pipu_cli/output.py +99 -0
- pipu_cli/package_management.py +1145 -0
- pipu_cli/pretty.py +286 -0
- pipu_cli/requirements.py +100 -0
- pipu_cli/rollback.py +110 -0
- pipu_cli-0.2.0.dist-info/METADATA +422 -0
- pipu_cli-0.2.0.dist-info/RECORD +16 -0
- pipu_cli/common.py +0 -4
- pipu_cli/internals.py +0 -815
- pipu_cli/package_constraints.py +0 -2296
- pipu_cli/thread_safe.py +0 -243
- pipu_cli/ui/__init__.py +0 -51
- pipu_cli/ui/apps.py +0 -1464
- pipu_cli/ui/constants.py +0 -33
- pipu_cli/ui/modal_dialogs.py +0 -1375
- pipu_cli/ui/table_widgets.py +0 -344
- pipu_cli/utils.py +0 -169
- pipu_cli-0.1.dev7.dist-info/METADATA +0 -517
- pipu_cli-0.1.dev7.dist-info/RECORD +0 -19
- {pipu_cli-0.1.dev7.dist-info → pipu_cli-0.2.0.dist-info}/WHEEL +0 -0
- {pipu_cli-0.1.dev7.dist-info → pipu_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {pipu_cli-0.1.dev7.dist-info → pipu_cli-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pipu_cli-0.1.dev7.dist-info → pipu_cli-0.2.0.dist-info}/top_level.txt +0 -0
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
|
-
]
|