yanex 0.1.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.
@@ -0,0 +1,203 @@
1
+ """
2
+ Delete experiments permanently.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ import click
8
+
9
+ from ...core.constants import EXPERIMENT_STATUSES
10
+ from ..filters import ExperimentFilter, parse_time_spec
11
+ from .confirm import (
12
+ confirm_experiment_operation,
13
+ find_experiments_by_filters,
14
+ find_experiments_by_identifiers,
15
+ )
16
+
17
+
18
+ @click.command("delete")
19
+ @click.argument("experiment_identifiers", nargs=-1)
20
+ @click.option(
21
+ "--status",
22
+ type=click.Choice(EXPERIMENT_STATUSES),
23
+ help="Delete experiments with specific status",
24
+ )
25
+ @click.option(
26
+ "--name",
27
+ "name_pattern",
28
+ help="Delete experiments matching name pattern (glob syntax)",
29
+ )
30
+ @click.option(
31
+ "--tag", "tags", multiple=True, help="Delete experiments with ALL specified tags"
32
+ )
33
+ @click.option(
34
+ "--started-after",
35
+ help="Delete experiments started after date/time (e.g., '2025-01-01', 'yesterday', '1 week ago')",
36
+ )
37
+ @click.option("--started-before", help="Delete experiments started before date/time")
38
+ @click.option("--ended-after", help="Delete experiments ended after date/time")
39
+ @click.option("--ended-before", help="Delete experiments ended before date/time")
40
+ @click.option(
41
+ "--archived",
42
+ is_flag=True,
43
+ help="Delete from archived experiments (default: delete from regular experiments)",
44
+ )
45
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt")
46
+ @click.pass_context
47
+ def delete_experiments(
48
+ ctx,
49
+ experiment_identifiers: tuple,
50
+ status: Optional[str],
51
+ name_pattern: Optional[str],
52
+ tags: tuple,
53
+ started_after: Optional[str],
54
+ started_before: Optional[str],
55
+ ended_after: Optional[str],
56
+ ended_before: Optional[str],
57
+ archived: bool,
58
+ force: bool,
59
+ ):
60
+ """
61
+ Permanently delete experiments.
62
+
63
+ ⚠️ WARNING: This operation cannot be undone!
64
+
65
+ EXPERIMENT_IDENTIFIERS can be experiment IDs or names.
66
+ If no identifiers provided, experiments are filtered by options.
67
+
68
+ Examples:
69
+ \\b
70
+ yanex delete exp1 exp2 # Delete specific experiments
71
+ yanex delete --status failed # Delete all failed experiments
72
+ yanex delete --archived --ended-before "6 months ago"
73
+ yanex delete --name "*test*" # Delete experiments with "test" in name
74
+ yanex delete --tag temp # Delete experiments with "temp" tag
75
+ """
76
+ try:
77
+ filter_obj = ExperimentFilter()
78
+
79
+ # Validate mutually exclusive targeting
80
+ has_identifiers = len(experiment_identifiers) > 0
81
+ has_filters = any(
82
+ [
83
+ status,
84
+ name_pattern,
85
+ tags,
86
+ started_after,
87
+ started_before,
88
+ ended_after,
89
+ ended_before,
90
+ ]
91
+ )
92
+
93
+ if has_identifiers and has_filters:
94
+ click.echo(
95
+ "Error: Cannot use both experiment identifiers and filter options. Choose one approach.",
96
+ err=True,
97
+ )
98
+ ctx.exit(1)
99
+
100
+ if not has_identifiers and not has_filters:
101
+ click.echo(
102
+ "Error: Must specify either experiment identifiers or filter options",
103
+ err=True,
104
+ )
105
+ ctx.exit(1)
106
+
107
+ # Parse time specifications
108
+ started_after_dt = parse_time_spec(started_after) if started_after else None
109
+ started_before_dt = parse_time_spec(started_before) if started_before else None
110
+ ended_after_dt = parse_time_spec(ended_after) if ended_after else None
111
+ ended_before_dt = parse_time_spec(ended_before) if ended_before else None
112
+
113
+ # Find experiments to delete
114
+ if experiment_identifiers:
115
+ # Delete specific experiments by ID/name
116
+ experiments = find_experiments_by_identifiers(
117
+ filter_obj, list(experiment_identifiers), include_archived=archived
118
+ )
119
+ else:
120
+ # Delete experiments by filter criteria
121
+
122
+ experiments = find_experiments_by_filters(
123
+ filter_obj,
124
+ status=status,
125
+ name_pattern=name_pattern,
126
+ tags=list(tags) if tags else None,
127
+ started_after=started_after_dt,
128
+ started_before=started_before_dt,
129
+ ended_after=ended_after_dt,
130
+ ended_before=ended_before_dt,
131
+ include_archived=archived,
132
+ )
133
+
134
+ # Filter experiments based on archived flag
135
+ if archived:
136
+ experiments = [exp for exp in experiments if exp.get("archived", False)]
137
+ else:
138
+ experiments = [exp for exp in experiments if not exp.get("archived", False)]
139
+
140
+ if not experiments:
141
+ location = "archived" if archived else "regular"
142
+ click.echo(f"No {location} experiments found to delete.")
143
+ return
144
+
145
+ # Show experiments and get confirmation (always required for deletion)
146
+ operation_verb = "permanently deleted"
147
+ if not confirm_experiment_operation(
148
+ experiments, "delete", force, operation_verb
149
+ ):
150
+ click.echo("Delete operation cancelled.")
151
+ return
152
+
153
+ # Additional warning for bulk deletions
154
+ if len(experiments) > 1 and not force:
155
+ click.echo()
156
+ click.echo("⚠️ You are about to permanently delete multiple experiments.")
157
+ click.echo(" This action cannot be undone!")
158
+ if not click.confirm("Are you absolutely sure?", default=False):
159
+ click.echo("Delete operation cancelled.")
160
+ return
161
+
162
+ # Delete experiments
163
+ click.echo(f"Deleting {len(experiments)} experiment(s)...")
164
+
165
+ success_count = 0
166
+ for exp in experiments:
167
+ try:
168
+ experiment_id = exp["id"]
169
+
170
+ if exp.get("archived", False):
171
+ # Delete from archived directory
172
+ filter_obj.manager.storage.delete_archived_experiment(experiment_id)
173
+ else:
174
+ # Delete from regular directory
175
+ filter_obj.manager.storage.delete_experiment(experiment_id)
176
+
177
+ # Show progress
178
+ exp_name = exp.get("name", "[unnamed]")
179
+ click.echo(f" ✓ Deleted {experiment_id} ({exp_name})")
180
+ success_count += 1
181
+
182
+ except Exception as e:
183
+ exp_name = exp.get("name", "[unnamed]")
184
+ click.echo(
185
+ f" ✗ Failed to delete {experiment_id} ({exp_name}): {e}", err=True
186
+ )
187
+
188
+ # Summary
189
+ if success_count == len(experiments):
190
+ click.echo(f"Successfully deleted {success_count} experiment(s).")
191
+ else:
192
+ failed_count = len(experiments) - success_count
193
+ click.echo(
194
+ f"Deleted {success_count} experiment(s), {failed_count} failed.",
195
+ err=True,
196
+ )
197
+ ctx.exit(1)
198
+
199
+ except click.ClickException:
200
+ raise # Re-raise ClickException to show proper error message
201
+ except Exception as e:
202
+ click.echo(f"Error: {e}", err=True)
203
+ ctx.exit(1)
@@ -0,0 +1,243 @@
1
+ """
2
+ List command implementation for yanex CLI.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ import click
8
+
9
+ from ...core.constants import EXPERIMENT_STATUSES
10
+ from ..filters import ExperimentFilter, parse_time_spec
11
+ from ..formatters import ExperimentTableFormatter
12
+
13
+
14
+ @click.command()
15
+ @click.option(
16
+ "--all",
17
+ "show_all",
18
+ is_flag=True,
19
+ help="Show all experiments (overrides default limit of 10)",
20
+ )
21
+ @click.option("-n", "--limit", type=int, help="Maximum number of experiments to show")
22
+ @click.option(
23
+ "--status",
24
+ type=click.Choice(EXPERIMENT_STATUSES, case_sensitive=False),
25
+ help="Filter by experiment status",
26
+ )
27
+ @click.option(
28
+ "--name",
29
+ "name_pattern",
30
+ help="Filter by name using glob patterns (e.g., '*tuning*')",
31
+ )
32
+ @click.option(
33
+ "--tag",
34
+ "tags",
35
+ multiple=True,
36
+ help="Filter by tag (repeatable, experiments must have ALL specified tags)",
37
+ )
38
+ @click.option(
39
+ "--started",
40
+ "started_spec",
41
+ help="Show experiments started on or after this time (e.g., 'today', 'last week', '2023-01-01')",
42
+ )
43
+ @click.option(
44
+ "--ended",
45
+ "ended_spec",
46
+ help="Show experiments ended on or after this time (e.g., 'today', 'last week', '2023-01-01')",
47
+ )
48
+ @click.option(
49
+ "--archived",
50
+ is_flag=True,
51
+ help="Show archived experiments instead of regular experiments",
52
+ )
53
+ @click.pass_context
54
+ def list_experiments(
55
+ ctx: click.Context,
56
+ show_all: bool,
57
+ limit: Optional[int],
58
+ status: Optional[str],
59
+ name_pattern: Optional[str],
60
+ tags: List[str],
61
+ started_spec: Optional[str],
62
+ ended_spec: Optional[str],
63
+ archived: bool,
64
+ ) -> None:
65
+ """
66
+ List experiments with filtering options.
67
+
68
+ Shows the last 10 experiments by default. Use --all to show all experiments
69
+ or -n to specify a custom limit.
70
+
71
+ Examples:
72
+
73
+ # Show last 10 experiments
74
+ yanex list
75
+
76
+ # Show all experiments
77
+ yanex list --all
78
+
79
+ # Show last 5 experiments
80
+ yanex list -n 5
81
+
82
+ # Filter by status
83
+ yanex list --status completed
84
+
85
+ # Filter by name pattern
86
+ yanex list --name "*tuning*"
87
+
88
+ # Filter by multiple tags (AND logic)
89
+ yanex list --tag hyperopt --tag production
90
+
91
+ # Filter by time (started since last week)
92
+ yanex list --started "last week"
93
+
94
+ # Complex filtering
95
+ yanex list --status completed --tag production --started "last month" -n 20
96
+ """
97
+ verbose = ctx.obj.get("verbose", False)
98
+
99
+ try:
100
+ # Parse time specifications
101
+ started_after = None
102
+ started_before = None
103
+ ended_after = None
104
+ ended_before = None
105
+
106
+ if started_spec:
107
+ started_after = parse_time_spec(started_spec)
108
+ if started_after is None:
109
+ click.echo(
110
+ f"Error: Could not parse start time '{started_spec}'", err=True
111
+ )
112
+ click.echo(
113
+ "Examples: 'today', 'last week', '2 hours ago', '2023-01-01'",
114
+ err=True,
115
+ )
116
+ raise click.Abort()
117
+
118
+ if ended_spec:
119
+ ended_after = parse_time_spec(ended_spec)
120
+ if ended_after is None:
121
+ click.echo(f"Error: Could not parse end time '{ended_spec}'", err=True)
122
+ click.echo(
123
+ "Examples: 'today', 'last week', '2 hours ago', '2023-01-01'",
124
+ err=True,
125
+ )
126
+ raise click.Abort()
127
+
128
+ if verbose:
129
+ click.echo("Filtering experiments...")
130
+ if status:
131
+ click.echo(f" Status: {status}")
132
+ if name_pattern:
133
+ click.echo(f" Name pattern: {name_pattern}")
134
+ if tags:
135
+ click.echo(f" Tags: {', '.join(tags)}")
136
+ if started_spec:
137
+ click.echo(f" Started: {started_spec}")
138
+ if ended_spec:
139
+ click.echo(f" Ended: {ended_spec}")
140
+
141
+ # Create filter and apply criteria
142
+ experiment_filter = ExperimentFilter()
143
+
144
+ # When showing archived experiments, we need to get all experiments first
145
+ # to avoid the default limit cutting off archived experiments
146
+ force_all = show_all or archived
147
+
148
+ experiments = experiment_filter.filter_experiments(
149
+ status=status,
150
+ name_pattern=name_pattern,
151
+ tags=list(tags) if tags else None,
152
+ started_after=started_after,
153
+ started_before=started_before,
154
+ ended_after=ended_after,
155
+ ended_before=ended_before,
156
+ limit=None if force_all else limit,
157
+ include_all=force_all,
158
+ include_archived=archived,
159
+ )
160
+
161
+ # Filter experiments based on archived flag
162
+ if archived:
163
+ experiments = [exp for exp in experiments if exp.get("archived", False)]
164
+ else:
165
+ experiments = [exp for exp in experiments if not exp.get("archived", False)]
166
+
167
+ # Apply limit after filtering by archived status if needed
168
+ if not show_all and limit is not None:
169
+ experiments = experiments[:limit]
170
+ elif not show_all and limit is None and not archived:
171
+ # Only apply default limit to regular experiments, not archived
172
+ experiments = experiments[:10]
173
+
174
+ if verbose:
175
+ click.echo(f"Found {len(experiments)} matching experiments")
176
+
177
+ # Format and display results
178
+ formatter = ExperimentTableFormatter()
179
+
180
+ if not experiments:
181
+ click.echo("No experiments found.")
182
+ _show_filter_suggestions(
183
+ status, name_pattern, tags, started_spec, ended_spec
184
+ )
185
+ return
186
+
187
+ # Display table
188
+ table_title = "Yanex Archived Experiments" if archived else "Yanex Experiments"
189
+ formatter.print_experiments_table(experiments, title=table_title)
190
+
191
+ # Show summary if filtering was applied or not showing all
192
+ if any([status, name_pattern, tags, started_spec, ended_spec]) or (
193
+ not show_all and limit != len(experiments)
194
+ ):
195
+ # Get total count for summary
196
+ total_experiments = experiment_filter.filter_experiments(
197
+ include_all=True, include_archived=archived
198
+ )
199
+
200
+ # Filter total based on archived flag too
201
+ if archived:
202
+ total_experiments = [
203
+ exp for exp in total_experiments if exp.get("archived", False)
204
+ ]
205
+ else:
206
+ total_experiments = [
207
+ exp for exp in total_experiments if not exp.get("archived", False)
208
+ ]
209
+
210
+ formatter.print_summary(experiments, len(total_experiments))
211
+
212
+ except ValueError as e:
213
+ click.echo(f"Error: {e}", err=True)
214
+ raise click.Abort()
215
+ except Exception as e:
216
+ click.echo(f"Error listing experiments: {e}", err=True)
217
+ if verbose:
218
+ import traceback
219
+
220
+ click.echo(traceback.format_exc(), err=True)
221
+ raise click.Abort()
222
+
223
+
224
+ def _show_filter_suggestions(
225
+ status: Optional[str],
226
+ name_pattern: Optional[str],
227
+ tags: List[str],
228
+ started_spec: Optional[str],
229
+ ended_spec: Optional[str],
230
+ ) -> None:
231
+ """Show helpful suggestions when no experiments are found."""
232
+
233
+ # Check if any filters were applied
234
+ has_filters = any([status, name_pattern, tags, started_spec, ended_spec])
235
+
236
+ if has_filters:
237
+ click.echo("\nTry adjusting your filters:")
238
+ click.echo(" • Remove some filters to see more results")
239
+ click.echo(" • Use 'yanex list --all' to see all experiments")
240
+ click.echo(" • Check status with: yanex list --status completed")
241
+ else:
242
+ click.echo("\nNo experiments found. To create your first experiment:")
243
+ click.echo(" yanex run your_script.py")