pipu-cli 0.1.dev0__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 +49 -0
- pipu_cli/cli.py +861 -0
- pipu_cli/common.py +4 -0
- pipu_cli/config.py +95 -0
- pipu_cli/internals.py +750 -0
- pipu_cli/package_constraints.py +2273 -0
- pipu_cli/thread_safe.py +243 -0
- pipu_cli/ui/__init__.py +51 -0
- pipu_cli/ui/apps.py +1460 -0
- pipu_cli/ui/constants.py +19 -0
- pipu_cli/ui/modal_dialogs.py +1340 -0
- pipu_cli/ui/table_widgets.py +345 -0
- pipu_cli/utils.py +169 -0
- pipu_cli-0.1.dev0.dist-info/METADATA +517 -0
- pipu_cli-0.1.dev0.dist-info/RECORD +19 -0
- pipu_cli-0.1.dev0.dist-info/WHEEL +5 -0
- pipu_cli-0.1.dev0.dist-info/entry_points.txt +2 -0
- pipu_cli-0.1.dev0.dist-info/licenses/LICENSE +21 -0
- pipu_cli-0.1.dev0.dist-info/top_level.txt +1 -0
pipu_cli/cli.py
ADDED
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import List
|
|
3
|
+
import rich_click as click
|
|
4
|
+
from .internals import list_outdated
|
|
5
|
+
from .package_constraints import read_constraints, read_ignores, read_invalidation_triggers
|
|
6
|
+
from .common import console
|
|
7
|
+
from pip._internal.commands.install import InstallCommand
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _install_packages(package_specs: List[str]) -> int:
|
|
11
|
+
"""
|
|
12
|
+
Install packages using pip API.
|
|
13
|
+
|
|
14
|
+
:param package_specs: List of package specifications to install
|
|
15
|
+
:returns: Exit code (0 for success, non-zero for failure)
|
|
16
|
+
"""
|
|
17
|
+
install_cmd = InstallCommand("install", "Install packages")
|
|
18
|
+
install_args = ["--upgrade"] + package_specs
|
|
19
|
+
return install_cmd.main(install_args)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def launch_tui() -> None:
|
|
23
|
+
"""
|
|
24
|
+
Launch the main TUI interface.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
# Check for invalid constraints and triggers from removed/renamed packages
|
|
28
|
+
from .package_constraints import cleanup_invalid_constraints_and_triggers
|
|
29
|
+
_, _, cleanup_summary = cleanup_invalid_constraints_and_triggers()
|
|
30
|
+
if cleanup_summary:
|
|
31
|
+
console.print(f"[yellow]🧹 {cleanup_summary}[/yellow]")
|
|
32
|
+
console.print("[dim]Press any key to continue...[/dim]")
|
|
33
|
+
input() # Wait for user acknowledgment before launching TUI
|
|
34
|
+
|
|
35
|
+
from .ui import main_tui_app
|
|
36
|
+
main_tui_app()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
console.print(f"[red]Error launching TUI: {e}[/red]")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.group(invoke_without_command=True)
|
|
45
|
+
@click.pass_context
|
|
46
|
+
def cli(ctx):
|
|
47
|
+
"""
|
|
48
|
+
pipu - Python package updater with constraint management
|
|
49
|
+
|
|
50
|
+
If no command is specified, launches the interactive TUI.
|
|
51
|
+
"""
|
|
52
|
+
if ctx.invoked_subcommand is None:
|
|
53
|
+
# No subcommand was invoked, launch the TUI
|
|
54
|
+
launch_tui()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@cli.command(name='list')
|
|
58
|
+
@click.option('--pre', is_flag=True, help='Include pre-release versions (alpha, beta, rc, dev)')
|
|
59
|
+
@click.option('--debug', is_flag=True, help='Print debug information as packages are checked')
|
|
60
|
+
def list_packages(pre, debug):
|
|
61
|
+
"""
|
|
62
|
+
List outdated packages
|
|
63
|
+
|
|
64
|
+
Displays a formatted table of installed packages that have newer versions
|
|
65
|
+
available on the configured package indexes. By default, pre-release versions
|
|
66
|
+
are excluded unless --pre is specified.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# Check for invalid constraints and triggers from removed/renamed packages
|
|
70
|
+
from .package_constraints import cleanup_invalid_constraints_and_triggers
|
|
71
|
+
_, _, cleanup_summary = cleanup_invalid_constraints_and_triggers()
|
|
72
|
+
if cleanup_summary:
|
|
73
|
+
console.print(f"[yellow]🧹 {cleanup_summary}[/yellow]")
|
|
74
|
+
|
|
75
|
+
# Read constraints, ignores, and invalidation triggers from configuration
|
|
76
|
+
constraints = read_constraints()
|
|
77
|
+
ignores = read_ignores()
|
|
78
|
+
invalidation_triggers = read_invalidation_triggers()
|
|
79
|
+
|
|
80
|
+
# Set up debug callbacks if debug mode is enabled
|
|
81
|
+
progress_callback = None
|
|
82
|
+
result_callback = None
|
|
83
|
+
if debug:
|
|
84
|
+
def debug_progress(package_name):
|
|
85
|
+
console.print(f"[dim]DEBUG: Checking {package_name}...[/dim]")
|
|
86
|
+
|
|
87
|
+
def debug_callback(package_result):
|
|
88
|
+
pkg_name = package_result.get('name', 'unknown')
|
|
89
|
+
current_ver = package_result.get('version', 'unknown')
|
|
90
|
+
latest_ver = package_result.get('latest_version', 'unknown')
|
|
91
|
+
if current_ver != latest_ver:
|
|
92
|
+
console.print(f"[dim]DEBUG: {pkg_name}: {current_ver} -> {latest_ver}[/dim]")
|
|
93
|
+
else:
|
|
94
|
+
console.print(f"[dim]DEBUG: {pkg_name}: {current_ver} (up-to-date)[/dim]")
|
|
95
|
+
|
|
96
|
+
progress_callback = debug_progress
|
|
97
|
+
result_callback = debug_callback
|
|
98
|
+
|
|
99
|
+
# Use the internals function to get outdated packages and print the table
|
|
100
|
+
outdated_packages = list_outdated(
|
|
101
|
+
console=console,
|
|
102
|
+
print_table=True,
|
|
103
|
+
constraints=constraints,
|
|
104
|
+
ignores=ignores,
|
|
105
|
+
pre=pre,
|
|
106
|
+
progress_callback=progress_callback,
|
|
107
|
+
result_callback=result_callback,
|
|
108
|
+
invalidation_triggers=invalidation_triggers
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# The function already prints the table, so we just return the data
|
|
112
|
+
return outdated_packages
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@cli.command()
|
|
120
|
+
@click.option('--pre', is_flag=True, help='Include pre-release versions (alpha, beta, rc, dev)')
|
|
121
|
+
@click.option('-y', '--yes', is_flag=True, help='Skip confirmation prompt and install all updates')
|
|
122
|
+
def update(pre, yes):
|
|
123
|
+
"""
|
|
124
|
+
Update outdated packages
|
|
125
|
+
|
|
126
|
+
Lists all outdated packages and prompts for confirmation before installing
|
|
127
|
+
updates. Respects version constraints from configuration files.
|
|
128
|
+
|
|
129
|
+
For interactive package selection, run 'pipu' with no arguments to launch the TUI.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
# Check for invalid constraints and triggers from removed/renamed packages
|
|
133
|
+
from .package_constraints import cleanup_invalid_constraints_and_triggers
|
|
134
|
+
_, _, cleanup_summary = cleanup_invalid_constraints_and_triggers()
|
|
135
|
+
if cleanup_summary:
|
|
136
|
+
console.print(f"[yellow]🧹 {cleanup_summary}[/yellow]")
|
|
137
|
+
|
|
138
|
+
# Read constraints, ignores, and invalidation triggers from configuration
|
|
139
|
+
constraints = read_constraints()
|
|
140
|
+
ignores = read_ignores()
|
|
141
|
+
invalidation_triggers = read_invalidation_triggers()
|
|
142
|
+
|
|
143
|
+
# Get outdated packages
|
|
144
|
+
outdated_packages = list_outdated(
|
|
145
|
+
console=console, print_table=True, constraints=constraints, ignores=ignores, pre=pre, invalidation_triggers=invalidation_triggers
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if not outdated_packages:
|
|
149
|
+
console.print("[green]All packages are already up to date![/green]")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Determine which packages to update
|
|
153
|
+
packages_to_update = outdated_packages
|
|
154
|
+
|
|
155
|
+
if not yes:
|
|
156
|
+
# Standard confirmation mode
|
|
157
|
+
console.print()
|
|
158
|
+
response = click.confirm("Do you want to update these packages?", default=False)
|
|
159
|
+
if not response:
|
|
160
|
+
console.print("[yellow]Update cancelled.[/yellow]")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Validate constraint compatibility before installation
|
|
164
|
+
console.print()
|
|
165
|
+
console.print("[bold blue]Validating constraint compatibility...[/bold blue]")
|
|
166
|
+
|
|
167
|
+
from .package_constraints import validate_package_installation, get_constraint_violation_summary
|
|
168
|
+
|
|
169
|
+
# Extract package names for validation
|
|
170
|
+
package_names_to_install = [pkg['name'] for pkg in packages_to_update]
|
|
171
|
+
|
|
172
|
+
# Check for constraint violations
|
|
173
|
+
safe_packages, invalidated_constraints = validate_package_installation(package_names_to_install)
|
|
174
|
+
|
|
175
|
+
if invalidated_constraints:
|
|
176
|
+
# Show constraint violations
|
|
177
|
+
console.print("[bold red]âš Constraint Violations Detected![/bold red]")
|
|
178
|
+
console.print(get_constraint_violation_summary(invalidated_constraints))
|
|
179
|
+
|
|
180
|
+
# Filter out packages that would violate constraints
|
|
181
|
+
violating_package_names = set()
|
|
182
|
+
for violators in invalidated_constraints.values():
|
|
183
|
+
violating_package_names.update(pkg.lower() for pkg in violators)
|
|
184
|
+
|
|
185
|
+
# Keep only safe packages
|
|
186
|
+
original_count = len(packages_to_update)
|
|
187
|
+
packages_to_update = [
|
|
188
|
+
pkg for pkg in packages_to_update
|
|
189
|
+
if pkg['name'].lower() not in violating_package_names
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
blocked_count = original_count - len(packages_to_update)
|
|
193
|
+
|
|
194
|
+
if packages_to_update:
|
|
195
|
+
console.print(f"\n[yellow]Proceeding with {len(packages_to_update)} packages that don't violate constraints.")
|
|
196
|
+
console.print(f"Blocked {blocked_count} packages due to constraint violations.[/yellow]")
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"\n[red]All {blocked_count} packages would violate constraints. No packages will be installed.[/red]")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
# Install updates
|
|
202
|
+
console.print()
|
|
203
|
+
console.print("[bold green]Installing updates...[/bold green]")
|
|
204
|
+
|
|
205
|
+
# Create list of package specs to install
|
|
206
|
+
package_specs = []
|
|
207
|
+
for package in packages_to_update:
|
|
208
|
+
# Check if package has a constraint that should be applied instead of latest version
|
|
209
|
+
constraint = package.get('constraint')
|
|
210
|
+
if constraint:
|
|
211
|
+
# Apply the constraint instead of pinning to latest version
|
|
212
|
+
spec = f"{package['name']}{constraint}"
|
|
213
|
+
else:
|
|
214
|
+
# No constraint, use latest version
|
|
215
|
+
spec = f"{package['name']}=={package['latest_version']}"
|
|
216
|
+
package_specs.append(spec)
|
|
217
|
+
|
|
218
|
+
# Install packages using pip API
|
|
219
|
+
exit_code = _install_packages(package_specs)
|
|
220
|
+
|
|
221
|
+
if exit_code == 0:
|
|
222
|
+
console.print("[bold green]✓ All packages updated successfully![/bold green]")
|
|
223
|
+
|
|
224
|
+
# Clean up constraints whose invalidation triggers have been satisfied
|
|
225
|
+
from .package_constraints import post_install_cleanup
|
|
226
|
+
post_install_cleanup(console)
|
|
227
|
+
|
|
228
|
+
else:
|
|
229
|
+
console.print("[bold red]✗ Some packages failed to update.[/bold red]")
|
|
230
|
+
sys.exit(exit_code)
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@cli.command()
|
|
238
|
+
@click.argument('constraint_specs', nargs=-1, required=False)
|
|
239
|
+
@click.option('--env', help='Target environment section (defaults to current environment or global)')
|
|
240
|
+
@click.option('--list', 'list_constraints', is_flag=True, help='List existing constraints for specified environment (or all environments if no --env specified)')
|
|
241
|
+
@click.option('--remove', 'remove_constraints', is_flag=True, help='Remove constraints for specified packages')
|
|
242
|
+
@click.option('--remove-all', 'remove_all_constraints', is_flag=True, help='Remove all constraints from specified environment (or all environments if no --env specified)')
|
|
243
|
+
@click.option('--yes', '-y', 'skip_confirmation', is_flag=True, help='Skip confirmation prompt')
|
|
244
|
+
@click.option('--invalidates-when', 'invalidation_triggers', multiple=True, help='Specify trigger conditions that invalidate this constraint (format: "package>=version" or "package>version"). Only ">=" and ">" operators allowed.')
|
|
245
|
+
def constrain(constraint_specs, env, list_constraints, remove_constraints, remove_all_constraints, skip_confirmation, invalidation_triggers):
|
|
246
|
+
"""
|
|
247
|
+
Add or update package constraints in pip configuration
|
|
248
|
+
|
|
249
|
+
Sets version constraints for packages in the pip configuration file.
|
|
250
|
+
Constraints prevent packages from being updated beyond specified versions.
|
|
251
|
+
|
|
252
|
+
Note: Automatic constraints from installed packages are now discovered
|
|
253
|
+
on every pipu execution and do not need to be manually added. The constraints
|
|
254
|
+
you add here will override any automatic constraints.
|
|
255
|
+
|
|
256
|
+
\b
|
|
257
|
+
Examples:
|
|
258
|
+
pipu constrain "requests==2.31.0"
|
|
259
|
+
pipu constrain "numpy>=1.20.0" "pandas<2.0.0"
|
|
260
|
+
pipu constrain "django~=4.1.0" --env production
|
|
261
|
+
pipu constrain "flask<2" --invalidates-when "other_package>=1" --invalidates-when "another_package>1.5"
|
|
262
|
+
pipu constrain --list
|
|
263
|
+
pipu constrain --list --env production
|
|
264
|
+
pipu constrain --remove requests numpy
|
|
265
|
+
pipu constrain --remove django --env production
|
|
266
|
+
pipu constrain --remove-all --env production
|
|
267
|
+
pipu constrain --remove-all --yes
|
|
268
|
+
|
|
269
|
+
\f
|
|
270
|
+
:param constraint_specs: One or more constraint specifications or package names (for --remove)
|
|
271
|
+
:param env: Target environment section name
|
|
272
|
+
:param list_constraints: List existing constraints instead of adding new ones
|
|
273
|
+
:param remove_constraints: Remove constraints for specified packages
|
|
274
|
+
:param remove_all_constraints: Remove all constraints from environment(s)
|
|
275
|
+
:param skip_confirmation: Skip confirmation prompt for --remove-all
|
|
276
|
+
:raises SystemExit: Exits with code 1 if an error occurs
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
# Validate mutually exclusive options
|
|
280
|
+
# Note: constraint_specs with --remove are package names, not constraint specs
|
|
281
|
+
has_constraint_specs_for_adding = bool(constraint_specs) and not (remove_constraints or remove_all_constraints)
|
|
282
|
+
active_options = [list_constraints, remove_constraints, remove_all_constraints, has_constraint_specs_for_adding]
|
|
283
|
+
if sum(active_options) > 1:
|
|
284
|
+
console.print("[red]Error: Cannot use --list, --remove, --remove-all, and constraint specs together. Use only one at a time.[/red]")
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
|
|
287
|
+
# Validate --invalidates-when can only be used when adding constraints
|
|
288
|
+
if invalidation_triggers and (list_constraints or remove_constraints or remove_all_constraints):
|
|
289
|
+
console.print("[red]Error: --invalidates-when cannot be used with --list, --remove, or --remove-all.[/red]")
|
|
290
|
+
sys.exit(1)
|
|
291
|
+
|
|
292
|
+
if invalidation_triggers and not has_constraint_specs_for_adding:
|
|
293
|
+
console.print("[red]Error: --invalidates-when can only be used when adding constraint specifications.[/red]")
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
# Handle --list option
|
|
297
|
+
if list_constraints:
|
|
298
|
+
from .package_constraints import list_all_constraints
|
|
299
|
+
|
|
300
|
+
console.print("[bold blue]Listing constraints from pip configuration...[/bold blue]")
|
|
301
|
+
|
|
302
|
+
all_constraints = list_all_constraints(env)
|
|
303
|
+
|
|
304
|
+
if not all_constraints:
|
|
305
|
+
if env:
|
|
306
|
+
console.print(f"[yellow]No constraints found for environment '[bold]{env}[/bold]'.[/yellow]")
|
|
307
|
+
else:
|
|
308
|
+
console.print("[yellow]No constraints found in any environment.[/yellow]")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# Display constraints
|
|
312
|
+
for env_name, constraints in all_constraints.items():
|
|
313
|
+
console.print(f"\n[bold cyan]Environment: {env_name}[/bold cyan]")
|
|
314
|
+
if constraints:
|
|
315
|
+
for package, constraint_spec in sorted(constraints.items()):
|
|
316
|
+
console.print(f" {package}{constraint_spec}")
|
|
317
|
+
else:
|
|
318
|
+
console.print(" [dim]No constraints[/dim]")
|
|
319
|
+
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
# Handle --remove option
|
|
323
|
+
if remove_constraints:
|
|
324
|
+
if not constraint_specs:
|
|
325
|
+
console.print("[red]Error: At least one package name must be specified for removal.[/red]")
|
|
326
|
+
sys.exit(1)
|
|
327
|
+
|
|
328
|
+
from .package_constraints import remove_constraints_from_config, parse_invalidation_triggers_storage, get_current_environment_name, parse_inline_constraints
|
|
329
|
+
|
|
330
|
+
package_names = list(constraint_specs)
|
|
331
|
+
console.print(f"[bold blue]Removing constraints for {len(package_names)} package(s)...[/bold blue]")
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
config_path, removed_constraints, removed_triggers = remove_constraints_from_config(package_names, env)
|
|
335
|
+
|
|
336
|
+
if not removed_constraints:
|
|
337
|
+
console.print("[yellow]No constraints were removed (packages not found in constraints).[/yellow]")
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
# Triggers are already cleaned up by remove_constraints_from_config
|
|
341
|
+
|
|
342
|
+
# Display summary
|
|
343
|
+
console.print("\n[bold green]✓ Constraints removed successfully![/bold green]")
|
|
344
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
345
|
+
|
|
346
|
+
# Show removed constraints
|
|
347
|
+
console.print("\n[bold]Constraints removed:[/bold]")
|
|
348
|
+
for package, constraint_spec in removed_constraints.items():
|
|
349
|
+
console.print(f" [red]Removed[/red]: {package}{constraint_spec}")
|
|
350
|
+
|
|
351
|
+
# Show removed invalidation triggers if any
|
|
352
|
+
if removed_triggers:
|
|
353
|
+
console.print("\n[bold]Invalidation triggers removed:[/bold]")
|
|
354
|
+
for package, triggers in removed_triggers.items():
|
|
355
|
+
for trigger in triggers:
|
|
356
|
+
console.print(f" [red]Removed trigger[/red]: {trigger}")
|
|
357
|
+
|
|
358
|
+
# Show which environment was updated
|
|
359
|
+
if env:
|
|
360
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
361
|
+
else:
|
|
362
|
+
from .package_constraints import get_current_environment_name
|
|
363
|
+
current_env = get_current_environment_name()
|
|
364
|
+
if current_env and current_env != "global":
|
|
365
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {current_env}")
|
|
366
|
+
else:
|
|
367
|
+
console.print("\n[bold cyan]Environment updated:[/bold cyan] global")
|
|
368
|
+
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
except ValueError as e:
|
|
372
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
373
|
+
sys.exit(1)
|
|
374
|
+
except IOError as e:
|
|
375
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
376
|
+
sys.exit(1)
|
|
377
|
+
|
|
378
|
+
# Handle --remove-all option
|
|
379
|
+
if remove_all_constraints:
|
|
380
|
+
from .package_constraints import remove_all_constraints_from_config, list_all_constraints, parse_invalidation_triggers_storage
|
|
381
|
+
|
|
382
|
+
# Get confirmation if not using --yes and removing from all environments
|
|
383
|
+
if not env and not skip_confirmation:
|
|
384
|
+
# Show what will be removed
|
|
385
|
+
try:
|
|
386
|
+
all_constraints = list_all_constraints()
|
|
387
|
+
if not all_constraints:
|
|
388
|
+
console.print("[yellow]No constraints found in any environment.[/yellow]")
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
console.print("[bold red]WARNING: This will remove ALL constraints from ALL environments![/bold red]")
|
|
392
|
+
console.print("\n[bold]Constraints that will be removed:[/bold]")
|
|
393
|
+
|
|
394
|
+
total_constraints = 0
|
|
395
|
+
for env_name, constraints in all_constraints.items():
|
|
396
|
+
console.print(f"\n[bold cyan]{env_name}:[/bold cyan]")
|
|
397
|
+
for package, constraint_spec in sorted(constraints.items()):
|
|
398
|
+
console.print(f" {package}{constraint_spec}")
|
|
399
|
+
total_constraints += 1
|
|
400
|
+
|
|
401
|
+
console.print(f"\n[bold]Total: {total_constraints} constraint(s) in {len(all_constraints)} environment(s)[/bold]")
|
|
402
|
+
|
|
403
|
+
if not click.confirm("\nAre you sure you want to remove all constraints?"):
|
|
404
|
+
console.print("[yellow]Operation cancelled.[/yellow]")
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
except Exception:
|
|
408
|
+
# If we can't list constraints, ask for generic confirmation
|
|
409
|
+
if not click.confirm("Are you sure you want to remove all constraints from all environments?"):
|
|
410
|
+
console.print("[yellow]Operation cancelled.[/yellow]")
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
console.print(f"[bold blue]Removing all constraints from {'all environments' if not env else env}...[/bold blue]")
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
config_path, removed_constraints, removed_triggers_by_env = remove_all_constraints_from_config(env)
|
|
417
|
+
|
|
418
|
+
if not removed_constraints:
|
|
419
|
+
if env:
|
|
420
|
+
console.print(f"[yellow]No constraints found in environment '{env}'.[/yellow]")
|
|
421
|
+
else:
|
|
422
|
+
console.print("[yellow]No constraints found in any environment.[/yellow]")
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# Triggers are already cleaned up by remove_all_constraints_from_config
|
|
426
|
+
|
|
427
|
+
# Display summary
|
|
428
|
+
console.print("\n[bold green]✓ All constraints removed successfully![/bold green]")
|
|
429
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
430
|
+
|
|
431
|
+
# Show removed constraints
|
|
432
|
+
console.print("\n[bold]Constraints removed:[/bold]")
|
|
433
|
+
total_removed = 0
|
|
434
|
+
for env_name, constraints in removed_constraints.items():
|
|
435
|
+
console.print(f"\n[bold cyan]{env_name}:[/bold cyan]")
|
|
436
|
+
for package, constraint_spec in sorted(constraints.items()):
|
|
437
|
+
console.print(f" [red]Removed[/red]: {package}{constraint_spec}")
|
|
438
|
+
total_removed += 1
|
|
439
|
+
|
|
440
|
+
console.print(f"\n[bold]Total removed: {total_removed} constraint(s) from {len(removed_constraints)} environment(s)[/bold]")
|
|
441
|
+
|
|
442
|
+
# Show removed invalidation triggers if any
|
|
443
|
+
if removed_triggers_by_env:
|
|
444
|
+
console.print("\n[bold]Invalidation triggers removed:[/bold]")
|
|
445
|
+
total_triggers_removed = 0
|
|
446
|
+
for env_name, env_triggers in removed_triggers_by_env.items():
|
|
447
|
+
console.print(f"\n[bold cyan]{env_name}:[/bold cyan]")
|
|
448
|
+
for package, triggers in env_triggers.items():
|
|
449
|
+
for trigger in triggers:
|
|
450
|
+
console.print(f" [red]Removed trigger[/red]: {trigger}")
|
|
451
|
+
total_triggers_removed += 1
|
|
452
|
+
console.print(f"\n[bold]Total triggers removed: {total_triggers_removed}[/bold]")
|
|
453
|
+
|
|
454
|
+
# Show which environment(s) were updated
|
|
455
|
+
if env:
|
|
456
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
457
|
+
else:
|
|
458
|
+
environments_updated = list(removed_constraints.keys())
|
|
459
|
+
console.print(f"\n[bold cyan]Environments updated:[/bold cyan] {', '.join(sorted(environments_updated))}")
|
|
460
|
+
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
except ValueError as e:
|
|
464
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
465
|
+
sys.exit(1)
|
|
466
|
+
except IOError as e:
|
|
467
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
468
|
+
sys.exit(1)
|
|
469
|
+
|
|
470
|
+
constraint_list = list(constraint_specs)
|
|
471
|
+
|
|
472
|
+
if not constraint_list:
|
|
473
|
+
console.print("[red]Error: At least one constraint must be specified.[/red]")
|
|
474
|
+
sys.exit(1)
|
|
475
|
+
|
|
476
|
+
console.print("[bold blue]Adding constraints to pip configuration...[/bold blue]")
|
|
477
|
+
|
|
478
|
+
# Use backend function for constraint addition
|
|
479
|
+
from .package_constraints import (
|
|
480
|
+
add_constraints_to_config,
|
|
481
|
+
validate_invalidation_triggers,
|
|
482
|
+
get_recommended_pip_config_path,
|
|
483
|
+
get_current_environment_name,
|
|
484
|
+
parse_invalidation_triggers_storage,
|
|
485
|
+
merge_invalidation_triggers,
|
|
486
|
+
format_invalidation_triggers,
|
|
487
|
+
parse_inline_constraints,
|
|
488
|
+
parse_requirement_line
|
|
489
|
+
)
|
|
490
|
+
import configparser
|
|
491
|
+
|
|
492
|
+
# Get the recommended config file path early for error handling
|
|
493
|
+
config_path = get_recommended_pip_config_path()
|
|
494
|
+
|
|
495
|
+
try:
|
|
496
|
+
# Validate invalidation triggers if provided
|
|
497
|
+
validated_triggers = []
|
|
498
|
+
if invalidation_triggers:
|
|
499
|
+
validated_triggers = validate_invalidation_triggers(list(invalidation_triggers))
|
|
500
|
+
|
|
501
|
+
# Add constraints using the backend function
|
|
502
|
+
config_path, changes = add_constraints_to_config(constraint_list, env)
|
|
503
|
+
|
|
504
|
+
# Handle invalidation triggers if provided
|
|
505
|
+
if validated_triggers:
|
|
506
|
+
# Load the config and add triggers for the constrained packages
|
|
507
|
+
config = configparser.ConfigParser()
|
|
508
|
+
config.read(config_path)
|
|
509
|
+
|
|
510
|
+
# Determine target environment section
|
|
511
|
+
if env is None:
|
|
512
|
+
env = get_current_environment_name()
|
|
513
|
+
section_name = env if env else 'global'
|
|
514
|
+
|
|
515
|
+
# Get existing invalidation triggers
|
|
516
|
+
existing_triggers_storage = {}
|
|
517
|
+
if config.has_option(section_name, 'constraint_invalid_when'):
|
|
518
|
+
existing_value = config.get(section_name, 'constraint_invalid_when')
|
|
519
|
+
existing_triggers_storage = parse_invalidation_triggers_storage(existing_value)
|
|
520
|
+
|
|
521
|
+
# Get current constraints to find the packages that were added/updated
|
|
522
|
+
current_constraints = {}
|
|
523
|
+
if config.has_option(section_name, 'constraints'):
|
|
524
|
+
constraints_value = config.get(section_name, 'constraints')
|
|
525
|
+
if any(op in constraints_value for op in ['>=', '<=', '==', '!=', '~=', '>', '<']):
|
|
526
|
+
current_constraints = parse_inline_constraints(constraints_value)
|
|
527
|
+
|
|
528
|
+
# Process triggers for each package that was specified (whether changed or not)
|
|
529
|
+
updated_triggers_storage = existing_triggers_storage.copy()
|
|
530
|
+
|
|
531
|
+
# Get all package names from the constraint list
|
|
532
|
+
all_package_names = set()
|
|
533
|
+
for spec in constraint_list:
|
|
534
|
+
parsed = parse_requirement_line(spec)
|
|
535
|
+
if parsed:
|
|
536
|
+
all_package_names.add(parsed['name'].lower())
|
|
537
|
+
|
|
538
|
+
for package_name in all_package_names:
|
|
539
|
+
# Get existing triggers for this package
|
|
540
|
+
existing_package_triggers = existing_triggers_storage.get(package_name, [])
|
|
541
|
+
|
|
542
|
+
# Merge with new triggers
|
|
543
|
+
merged_triggers = merge_invalidation_triggers(existing_package_triggers, validated_triggers)
|
|
544
|
+
|
|
545
|
+
if merged_triggers:
|
|
546
|
+
updated_triggers_storage[package_name] = merged_triggers
|
|
547
|
+
|
|
548
|
+
# Format and store the triggers
|
|
549
|
+
if updated_triggers_storage:
|
|
550
|
+
trigger_entries = []
|
|
551
|
+
for package_name, triggers in updated_triggers_storage.items():
|
|
552
|
+
# Get the constraint for this package to format properly
|
|
553
|
+
if package_name in current_constraints:
|
|
554
|
+
package_constraint = current_constraints[package_name]
|
|
555
|
+
formatted_entry = format_invalidation_triggers(f"{package_name}{package_constraint}", triggers)
|
|
556
|
+
if formatted_entry:
|
|
557
|
+
trigger_entries.append(formatted_entry)
|
|
558
|
+
|
|
559
|
+
if trigger_entries:
|
|
560
|
+
triggers_value = ','.join(trigger_entries)
|
|
561
|
+
config.set(section_name, 'constraint_invalid_when', triggers_value)
|
|
562
|
+
|
|
563
|
+
# Write the updated config file
|
|
564
|
+
with open(config_path, 'w', encoding='utf-8') as f:
|
|
565
|
+
config.write(f)
|
|
566
|
+
|
|
567
|
+
except Exception as e:
|
|
568
|
+
if isinstance(e, ValueError):
|
|
569
|
+
raise e
|
|
570
|
+
else:
|
|
571
|
+
raise IOError(f"Failed to write pip config file '{config_path}': {e}")
|
|
572
|
+
|
|
573
|
+
if not changes and not validated_triggers:
|
|
574
|
+
console.print("[yellow]No changes made - all constraints already exist with the same values.[/yellow]")
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
# Display summary
|
|
578
|
+
console.print("\n[bold green]✓ Configuration updated successfully![/bold green]")
|
|
579
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
580
|
+
|
|
581
|
+
# Show changes
|
|
582
|
+
if changes:
|
|
583
|
+
console.print("\n[bold]Constraints modified:[/bold]")
|
|
584
|
+
for package, (action, constraint) in changes.items():
|
|
585
|
+
action_color = "green" if action == "added" else "yellow"
|
|
586
|
+
console.print(f" [{action_color}]{action.title()}[/{action_color}]: {package}{constraint}")
|
|
587
|
+
|
|
588
|
+
# Show invalidation triggers if added
|
|
589
|
+
if validated_triggers:
|
|
590
|
+
console.print("\n[bold]Invalidation triggers added:[/bold]")
|
|
591
|
+
for trigger in validated_triggers:
|
|
592
|
+
console.print(f" [cyan]Trigger[/cyan]: {trigger}")
|
|
593
|
+
|
|
594
|
+
# Show which environment was updated
|
|
595
|
+
if env:
|
|
596
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
597
|
+
else:
|
|
598
|
+
current_env = get_current_environment_name()
|
|
599
|
+
if current_env and current_env != "global":
|
|
600
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {current_env}")
|
|
601
|
+
else:
|
|
602
|
+
console.print("\n[bold cyan]Environment updated:[/bold cyan] global")
|
|
603
|
+
|
|
604
|
+
except ValueError as e:
|
|
605
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
606
|
+
sys.exit(1)
|
|
607
|
+
except IOError as e:
|
|
608
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
609
|
+
sys.exit(1)
|
|
610
|
+
except Exception as e:
|
|
611
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
612
|
+
sys.exit(1)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@cli.command()
|
|
616
|
+
@click.argument('package_names', nargs=-1, required=False)
|
|
617
|
+
@click.option('--env', help='Target environment section (defaults to current environment or global)')
|
|
618
|
+
@click.option('--list', 'list_ignores', is_flag=True, help='List existing ignores for specified environment (or all environments if no --env specified)')
|
|
619
|
+
@click.option('--remove', 'remove_ignores', is_flag=True, help='Remove ignores for specified packages')
|
|
620
|
+
@click.option('--remove-all', 'remove_all_ignores', is_flag=True, help='Remove all ignores from specified environment (or all environments if no --env specified)')
|
|
621
|
+
@click.option('--yes', '-y', 'skip_confirmation', is_flag=True, help='Skip confirmation prompt')
|
|
622
|
+
def ignore(package_names, env, list_ignores, remove_ignores, remove_all_ignores, skip_confirmation):
|
|
623
|
+
"""
|
|
624
|
+
Add or remove package ignores in pip configuration
|
|
625
|
+
|
|
626
|
+
Manages packages that should be ignored during update operations.
|
|
627
|
+
Ignored packages will be skipped when checking for outdated packages.
|
|
628
|
+
|
|
629
|
+
\b
|
|
630
|
+
Examples:
|
|
631
|
+
pipu ignore requests numpy
|
|
632
|
+
pipu ignore flask --env production
|
|
633
|
+
pipu ignore --list
|
|
634
|
+
pipu ignore --list --env production
|
|
635
|
+
pipu ignore --remove requests numpy
|
|
636
|
+
pipu ignore --remove flask --env production
|
|
637
|
+
pipu ignore --remove-all --env production
|
|
638
|
+
pipu ignore --remove-all --yes
|
|
639
|
+
|
|
640
|
+
\f
|
|
641
|
+
:param package_names: One or more package names to ignore or remove
|
|
642
|
+
:param env: Target environment section name
|
|
643
|
+
:param list_ignores: List existing ignores instead of adding new ones
|
|
644
|
+
:param remove_ignores: Remove ignores for specified packages
|
|
645
|
+
:param remove_all_ignores: Remove all ignores from environment(s)
|
|
646
|
+
:param skip_confirmation: Skip confirmation prompt for --remove-all
|
|
647
|
+
:raises SystemExit: Exits with code 1 if an error occurs
|
|
648
|
+
"""
|
|
649
|
+
try:
|
|
650
|
+
# Validate mutually exclusive options
|
|
651
|
+
active_options = [list_ignores, remove_ignores, remove_all_ignores, bool(package_names and not (list_ignores or remove_ignores or remove_all_ignores))]
|
|
652
|
+
if sum(active_options) > 1:
|
|
653
|
+
console.print("[red]Error: Cannot use --list, --remove, --remove-all, and package names together. Use only one at a time.[/red]")
|
|
654
|
+
sys.exit(1)
|
|
655
|
+
|
|
656
|
+
# Handle --list option
|
|
657
|
+
if list_ignores:
|
|
658
|
+
from .package_constraints import list_all_ignores
|
|
659
|
+
|
|
660
|
+
console.print("[bold blue]Listing ignores from pip configuration...[/bold blue]")
|
|
661
|
+
|
|
662
|
+
all_ignores = list_all_ignores(env)
|
|
663
|
+
|
|
664
|
+
if not all_ignores:
|
|
665
|
+
if env:
|
|
666
|
+
console.print(f"[yellow]No ignores found for environment '[bold]{env}[/bold]'.[/yellow]")
|
|
667
|
+
else:
|
|
668
|
+
console.print("[yellow]No ignores found in any environment.[/yellow]")
|
|
669
|
+
return
|
|
670
|
+
|
|
671
|
+
# Display ignores
|
|
672
|
+
for env_name, ignores in all_ignores.items():
|
|
673
|
+
console.print(f"\n[bold cyan]Environment: {env_name}[/bold cyan]")
|
|
674
|
+
if ignores:
|
|
675
|
+
for package in sorted(ignores):
|
|
676
|
+
console.print(f" {package}")
|
|
677
|
+
else:
|
|
678
|
+
console.print(" [dim]No ignores[/dim]")
|
|
679
|
+
|
|
680
|
+
return
|
|
681
|
+
|
|
682
|
+
# Handle --remove option
|
|
683
|
+
if remove_ignores:
|
|
684
|
+
if not package_names:
|
|
685
|
+
console.print("[red]Error: At least one package name must be specified for removal.[/red]")
|
|
686
|
+
sys.exit(1)
|
|
687
|
+
|
|
688
|
+
from .package_constraints import remove_ignores_from_config
|
|
689
|
+
|
|
690
|
+
packages_list = list(package_names)
|
|
691
|
+
console.print(f"[bold blue]Removing ignores for {len(packages_list)} package(s)...[/bold blue]")
|
|
692
|
+
|
|
693
|
+
try:
|
|
694
|
+
config_path, removed_packages = remove_ignores_from_config(packages_list, env)
|
|
695
|
+
|
|
696
|
+
if not removed_packages:
|
|
697
|
+
console.print("[yellow]No ignores were removed (packages not found in ignores).[/yellow]")
|
|
698
|
+
return
|
|
699
|
+
|
|
700
|
+
# Display summary
|
|
701
|
+
console.print("\n[bold green]✓ Ignores removed successfully![/bold green]")
|
|
702
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
703
|
+
|
|
704
|
+
# Show removed ignores
|
|
705
|
+
console.print("\n[bold]Ignores removed:[/bold]")
|
|
706
|
+
for package in removed_packages:
|
|
707
|
+
console.print(f" [red]Removed[/red]: {package}")
|
|
708
|
+
|
|
709
|
+
# Show which environment was updated
|
|
710
|
+
if env:
|
|
711
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
712
|
+
else:
|
|
713
|
+
from .package_constraints import get_current_environment_name
|
|
714
|
+
current_env = get_current_environment_name()
|
|
715
|
+
if current_env and current_env != "global":
|
|
716
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {current_env}")
|
|
717
|
+
else:
|
|
718
|
+
console.print("\n[bold cyan]Environment updated:[/bold cyan] global")
|
|
719
|
+
|
|
720
|
+
return
|
|
721
|
+
|
|
722
|
+
except ValueError as e:
|
|
723
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
724
|
+
sys.exit(1)
|
|
725
|
+
except IOError as e:
|
|
726
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
727
|
+
sys.exit(1)
|
|
728
|
+
|
|
729
|
+
# Handle --remove-all option
|
|
730
|
+
if remove_all_ignores:
|
|
731
|
+
from .package_constraints import remove_all_ignores_from_config, list_all_ignores
|
|
732
|
+
|
|
733
|
+
# Get confirmation if not using --yes and removing from all environments
|
|
734
|
+
if not env and not skip_confirmation:
|
|
735
|
+
# Show what will be removed
|
|
736
|
+
try:
|
|
737
|
+
all_ignores = list_all_ignores()
|
|
738
|
+
if not all_ignores:
|
|
739
|
+
console.print("[yellow]No ignores found in any environment.[/yellow]")
|
|
740
|
+
return
|
|
741
|
+
|
|
742
|
+
console.print("[bold red]WARNING: This will remove ALL ignores from ALL environments![/bold red]")
|
|
743
|
+
console.print("\n[bold]Ignores that will be removed:[/bold]")
|
|
744
|
+
|
|
745
|
+
total_ignores = 0
|
|
746
|
+
for env_name, ignores in all_ignores.items():
|
|
747
|
+
console.print(f"\n[bold cyan]{env_name}:[/bold cyan]")
|
|
748
|
+
for package in sorted(ignores):
|
|
749
|
+
console.print(f" {package}")
|
|
750
|
+
total_ignores += 1
|
|
751
|
+
|
|
752
|
+
console.print(f"\n[bold]Total: {total_ignores} ignore(s) in {len(all_ignores)} environment(s)[/bold]")
|
|
753
|
+
|
|
754
|
+
if not click.confirm("\nAre you sure you want to remove all ignores?"):
|
|
755
|
+
console.print("[yellow]Operation cancelled.[/yellow]")
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
except Exception:
|
|
759
|
+
# If we can't list ignores, ask for generic confirmation
|
|
760
|
+
if not click.confirm("Are you sure you want to remove all ignores from all environments?"):
|
|
761
|
+
console.print("[yellow]Operation cancelled.[/yellow]")
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
console.print(f"[bold blue]Removing all ignores from {'all environments' if not env else env}...[/bold blue]")
|
|
765
|
+
|
|
766
|
+
try:
|
|
767
|
+
config_path, removed_ignores = remove_all_ignores_from_config(env)
|
|
768
|
+
|
|
769
|
+
if not removed_ignores:
|
|
770
|
+
if env:
|
|
771
|
+
console.print(f"[yellow]No ignores found in environment '{env}'.[/yellow]")
|
|
772
|
+
else:
|
|
773
|
+
console.print("[yellow]No ignores found in any environment.[/yellow]")
|
|
774
|
+
return
|
|
775
|
+
|
|
776
|
+
# Display summary
|
|
777
|
+
console.print("\n[bold green]✓ All ignores removed successfully![/bold green]")
|
|
778
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
779
|
+
|
|
780
|
+
# Show removed ignores
|
|
781
|
+
console.print("\n[bold]Ignores removed:[/bold]")
|
|
782
|
+
total_removed = 0
|
|
783
|
+
for env_name, ignores in removed_ignores.items():
|
|
784
|
+
console.print(f"\n[bold cyan]{env_name}:[/bold cyan]")
|
|
785
|
+
for package in sorted(ignores):
|
|
786
|
+
console.print(f" [red]Removed[/red]: {package}")
|
|
787
|
+
total_removed += 1
|
|
788
|
+
|
|
789
|
+
console.print(f"\n[bold]Total removed: {total_removed} ignore(s) from {len(removed_ignores)} environment(s)[/bold]")
|
|
790
|
+
|
|
791
|
+
# Show which environment(s) were updated
|
|
792
|
+
if env:
|
|
793
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
794
|
+
else:
|
|
795
|
+
environments_updated = list(removed_ignores.keys())
|
|
796
|
+
console.print(f"\n[bold cyan]Environments updated:[/bold cyan] {', '.join(sorted(environments_updated))}")
|
|
797
|
+
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
except ValueError as e:
|
|
801
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
802
|
+
sys.exit(1)
|
|
803
|
+
except IOError as e:
|
|
804
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
805
|
+
sys.exit(1)
|
|
806
|
+
|
|
807
|
+
# Handle adding ignores (default behavior)
|
|
808
|
+
if not package_names:
|
|
809
|
+
console.print("[red]Error: At least one package name must be specified.[/red]")
|
|
810
|
+
sys.exit(1)
|
|
811
|
+
|
|
812
|
+
from .package_constraints import add_ignores_to_config, get_current_environment_name
|
|
813
|
+
|
|
814
|
+
packages_list = list(package_names)
|
|
815
|
+
console.print("[bold blue]Adding ignores to pip configuration...[/bold blue]")
|
|
816
|
+
|
|
817
|
+
# Get the recommended config file path early for error handling
|
|
818
|
+
from .package_constraints import get_recommended_pip_config_path
|
|
819
|
+
config_path = get_recommended_pip_config_path()
|
|
820
|
+
|
|
821
|
+
try:
|
|
822
|
+
config_path, changes = add_ignores_to_config(packages_list, env)
|
|
823
|
+
|
|
824
|
+
if not any(action == 'added' for action in changes.values()):
|
|
825
|
+
console.print("[yellow]No changes made - all packages are already ignored.[/yellow]")
|
|
826
|
+
return
|
|
827
|
+
|
|
828
|
+
# Display summary
|
|
829
|
+
console.print("\n[bold green]✓ Configuration updated successfully![/bold green]")
|
|
830
|
+
console.print(f"[bold]File:[/bold] {config_path}")
|
|
831
|
+
|
|
832
|
+
# Show changes
|
|
833
|
+
console.print("\n[bold]Ignores modified:[/bold]")
|
|
834
|
+
for package, action in changes.items():
|
|
835
|
+
if action == 'added':
|
|
836
|
+
console.print(f" [green]Added[/green]: {package}")
|
|
837
|
+
elif action == 'already_exists':
|
|
838
|
+
console.print(f" [yellow]Already ignored[/yellow]: {package}")
|
|
839
|
+
|
|
840
|
+
# Show which environment was updated
|
|
841
|
+
if env:
|
|
842
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {env}")
|
|
843
|
+
else:
|
|
844
|
+
current_env = get_current_environment_name()
|
|
845
|
+
if current_env and current_env != "global":
|
|
846
|
+
console.print(f"\n[bold cyan]Environment updated:[/bold cyan] {current_env}")
|
|
847
|
+
else:
|
|
848
|
+
console.print("\n[bold cyan]Environment updated:[/bold cyan] global")
|
|
849
|
+
|
|
850
|
+
except IOError as e:
|
|
851
|
+
console.print(f"[red]Error writing configuration: {e}[/red]")
|
|
852
|
+
sys.exit(1)
|
|
853
|
+
|
|
854
|
+
except Exception as e:
|
|
855
|
+
console.print(f"[red]Unexpected error: {e}[/red]")
|
|
856
|
+
sys.exit(1)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
# This allows the module to be run with: python -m pipu_cli.cli
|
|
860
|
+
if __name__ == '__main__':
|
|
861
|
+
cli()
|