starbash 0.1.6__py3-none-any.whl → 0.1.9__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.
@@ -1,20 +1,80 @@
1
1
  """Selection commands for filtering sessions and targets."""
2
2
 
3
3
  import os
4
+ from typing import Any
5
+ from collections import Counter
4
6
  import typer
5
7
  from pathlib import Path
6
8
  from typing_extensions import Annotated
7
- from datetime import datetime
8
9
  from rich.table import Table
10
+ import logging
9
11
 
12
+ import starbash
13
+ from starbash import to_shortdate
10
14
  from starbash.app import Starbash, copy_images_to_dir
11
- from starbash.database import Database
15
+ from starbash.database import Database, SessionRow, get_column_name
12
16
  from starbash import console
13
- from starbash.commands import format_duration
17
+ from starbash.commands import (
18
+ format_duration,
19
+ TABLE_COLUMN_STYLE,
20
+ TABLE_VALUE_STYLE,
21
+ )
14
22
 
15
23
  app = typer.Typer()
16
24
 
17
25
 
26
+ def get_column(sb: Starbash, column_name: str) -> Counter:
27
+
28
+ # Also do a complete unfiltered search so we can compare for the users
29
+ allsessions = sb.db.search_session(("", []))
30
+
31
+ column_name = get_column_name(column_name)
32
+ allfound = [session[column_name] for session in allsessions if session[column_name]]
33
+
34
+ # Count occurrences of each telescope
35
+ all_counts = Counter(allfound)
36
+
37
+ return all_counts
38
+
39
+
40
+ def complete_date(incomplete: str, column_name: str):
41
+ """calls get_column() and assumes the returned str->count object has iso datetime strings as the keys
42
+ it merges the counts for all dates that are on the same local timezone day.
43
+ in the returned str->count, just include the date portion (YYYY-MM-DD)."""
44
+
45
+ # We need to use stderr_logging to prevent confusing the bash completion parser
46
+ starbash.log_filter_level = (
47
+ logging.ERROR
48
+ ) # avoid showing output while doing completion
49
+ with Starbash("select.complete.date", stderr_logging=True) as sb:
50
+ c = get_column(sb, column_name)
51
+
52
+ # Merge counts by date (YYYY-MM-DD) in local timezone
53
+ date_counts = Counter()
54
+ for datetime_str, count in c.items():
55
+ # Extract just the date portion (YYYY-MM-DD) from local datetime
56
+ date_only = to_shortdate(datetime_str)
57
+ date_counts[date_only] += count
58
+
59
+ # Yield completions matching the incomplete input
60
+ for date, count in sorted(date_counts.items(), reverse=True):
61
+ if date.startswith(incomplete):
62
+ yield (date, f"{count} sessions")
63
+
64
+
65
+ def complete_column(incomplete: str, column_name: str):
66
+ # We need to use stderr_logging to prevent confusing the bash completion parser
67
+ starbash.log_filter_level = (
68
+ logging.ERROR
69
+ ) # avoid showing output while doing completion
70
+ with Starbash("repo.complete.column", stderr_logging=True) as sb:
71
+ c = get_column(sb, column_name)
72
+
73
+ for item, count in c.items():
74
+ if item.startswith(incomplete):
75
+ yield (item, f"{count} sessions")
76
+
77
+
18
78
  @app.command(name="any")
19
79
  def clear():
20
80
  """Remove any filters on sessions, etc... (select everything)."""
@@ -28,7 +88,10 @@ def target(
28
88
  target_name: Annotated[
29
89
  str,
30
90
  typer.Argument(
31
- help="Target name to add to the selection (e.g., 'M31', 'NGC 7000')"
91
+ help="Target name to add to the selection (e.g., 'M31', 'NGC 7000')",
92
+ autocompletion=lambda incomplete: complete_column(
93
+ incomplete, Database.OBJECT_KEY
94
+ ),
32
95
  ),
33
96
  ],
34
97
  ):
@@ -46,7 +109,10 @@ def telescope(
46
109
  telescope_name: Annotated[
47
110
  str,
48
111
  typer.Argument(
49
- help="Telescope name to add to the selection (e.g., 'Vespera', 'EdgeHD 8')"
112
+ help="Telescope name to add to the selection (e.g., 'Vespera', 'EdgeHD 8')",
113
+ autocompletion=lambda incomplete: complete_column(
114
+ incomplete, Database.TELESCOP_KEY
115
+ ),
50
116
  ),
51
117
  ],
52
118
  ):
@@ -61,21 +127,41 @@ def telescope(
61
127
  )
62
128
 
63
129
 
130
+ def complete_name(incomplete: str, names: list[str]):
131
+ """Return typer style autocompletion from a list of string constants."""
132
+ for name in names:
133
+ if name.startswith(incomplete):
134
+ yield name
135
+
136
+
64
137
  @app.command()
65
138
  def date(
66
139
  operation: Annotated[
67
140
  str,
68
- typer.Argument(help="Date operation: 'after', 'before', or 'between'"),
141
+ typer.Argument(
142
+ help="Date operation: 'after', 'before', or 'between'",
143
+ autocompletion=lambda incomplete: complete_name(
144
+ incomplete, ["after", "before", "between"]
145
+ ),
146
+ ),
69
147
  ],
70
148
  date_value: Annotated[
71
149
  str,
72
150
  typer.Argument(
73
- help="Date in ISO format (YYYY-MM-DD) or two dates separated by space for 'between'"
151
+ help="Date in ISO format (YYYY-MM-DD) or two dates separated by space for 'between'",
152
+ autocompletion=lambda incomplete: complete_date(
153
+ incomplete, Database.START_KEY
154
+ ),
74
155
  ),
75
156
  ],
76
157
  end_date: Annotated[
77
158
  str | None,
78
- typer.Argument(help="End date for 'between' operation (YYYY-MM-DD)"),
159
+ typer.Argument(
160
+ help="End date for 'between' operation (YYYY-MM-DD)",
161
+ autocompletion=lambda incomplete: complete_date(
162
+ incomplete, Database.START_KEY
163
+ ),
164
+ ),
79
165
  ] = None,
80
166
  ):
81
167
  """Limit to sessions in the specified date range.
@@ -116,7 +202,14 @@ def date(
116
202
 
117
203
 
118
204
  @app.command(name="list")
119
- def list_sessions():
205
+ def list_sessions(
206
+ verbose: bool = typer.Option(
207
+ False,
208
+ "--verbose",
209
+ "-v",
210
+ help="Show all sessions (normally Dark/Bias/Flat are hidden)",
211
+ )
212
+ ):
120
213
  """List sessions (filtered based on the current selection)"""
121
214
 
122
215
  with Starbash("selection.list") as sb:
@@ -127,14 +220,14 @@ def list_sessions():
127
220
  sb.analytics.set_data("session.num_selected", len(sessions))
128
221
  sb.analytics.set_data("session.num_total", len_all)
129
222
 
130
- table.add_column("#", style="cyan", no_wrap=True)
131
- table.add_column("Date", style="cyan", no_wrap=True)
132
- table.add_column("# images", style="cyan", no_wrap=True)
133
- table.add_column("Time", style="cyan", no_wrap=True)
134
- table.add_column("Type/Filter", style="cyan", no_wrap=True)
135
- table.add_column("Telescope", style="cyan", no_wrap=True)
223
+ table.add_column("#", style=TABLE_COLUMN_STYLE, no_wrap=True)
224
+ table.add_column("Date", style=TABLE_COLUMN_STYLE, no_wrap=True)
225
+ table.add_column("# images", style=TABLE_COLUMN_STYLE, no_wrap=True)
226
+ table.add_column("Time", style=TABLE_COLUMN_STYLE, no_wrap=True)
227
+ table.add_column("Type/Filter", style=TABLE_COLUMN_STYLE, no_wrap=True)
228
+ table.add_column("Telescope", style=TABLE_COLUMN_STYLE, no_wrap=True)
136
229
  table.add_column(
137
- "About", style="cyan", no_wrap=True
230
+ "About", style=TABLE_COLUMN_STYLE, no_wrap=True
138
231
  ) # type of frames, filter, target
139
232
 
140
233
  total_images = 0
@@ -143,26 +236,25 @@ def list_sessions():
143
236
  image_types = set()
144
237
  telescopes = set()
145
238
 
239
+ def get_key(k: str, default: Any = "N/A") -> Any:
240
+ """Convert keynames to SQL legal column names"""
241
+ k = get_column_name(k)
242
+ return sess.get(k, default)
243
+
146
244
  for session_index, sess in enumerate(sessions):
147
- date_iso = sess.get(Database.START_KEY, "N/A")
148
- # Try to convert ISO UTC datetime to local short date string
149
- try:
150
- dt_utc = datetime.fromisoformat(date_iso)
151
- dt_local = dt_utc.astimezone()
152
- date = dt_local.strftime("%Y-%m-%d")
153
- except (ValueError, TypeError):
154
- date = date_iso
245
+ date_iso = get_key(Database.START_KEY)
246
+ date = to_shortdate(date_iso)
155
247
 
156
- object = str(sess.get(Database.OBJECT_KEY, "N/A"))
157
- filter = sess.get(Database.FILTER_KEY, "N/A")
248
+ object = get_key(Database.OBJECT_KEY)
249
+ filter = get_key(Database.FILTER_KEY)
158
250
  filters.add(filter)
159
- image_type = str(sess.get(Database.IMAGETYP_KEY, "N/A"))
251
+ image_type = get_key(Database.IMAGETYP_KEY)
160
252
  image_types.add(image_type)
161
- telescope = str(sess.get(Database.TELESCOP_KEY, "N/A"))
253
+ telescope = get_key(Database.TELESCOP_KEY)
162
254
  telescopes.add(telescope)
163
255
 
164
256
  # Format total exposure time as integer seconds
165
- exptime_raw = str(sess.get(Database.EXPTIME_TOTAL_KEY, "N/A"))
257
+ exptime_raw = get_key(Database.EXPTIME_TOTAL_KEY)
166
258
  try:
167
259
  exptime_float = float(exptime_raw)
168
260
  total_seconds += exptime_float
@@ -172,10 +264,10 @@ def list_sessions():
172
264
 
173
265
  # Count images
174
266
  try:
175
- num_images = int(sess.get(Database.NUM_IMAGES_KEY, 0))
267
+ num_images = int(get_key(Database.NUM_IMAGES_KEY, 0))
176
268
  total_images += num_images
177
269
  except (ValueError, TypeError):
178
- num_images = sess.get(Database.NUM_IMAGES_KEY, "N/A")
270
+ num_images = get_key(Database.NUM_IMAGES_KEY)
179
271
 
180
272
  type_str = image_type
181
273
  if image_type.upper() == "LIGHT":
@@ -218,6 +310,32 @@ def list_sessions():
218
310
  sb.analytics.set_data("session.image_types", image_types)
219
311
 
220
312
 
313
+ def selection_by_number(
314
+ sb: Starbash,
315
+ session_num: int,
316
+ ) -> SessionRow:
317
+ """Get the session corresponding to the given session number in the current selection."""
318
+ # Get the filtered sessions
319
+ sessions = sb.search_session()
320
+
321
+ if not sessions or not isinstance(sessions, list):
322
+ console.print("[red]No sessions found. Check your selection criteria.[/red]")
323
+ raise typer.Exit(1)
324
+
325
+ # Validate session number
326
+ if session_num < 1 or session_num > len(sessions):
327
+ console.print(
328
+ f"[red]Error: Session number {session_num} is out of range. "
329
+ f"Valid range is 1-{len(sessions)}.[/red]"
330
+ )
331
+ console.print("[yellow]Use 'select list' to see available sessions.[/yellow]")
332
+ raise typer.Exit(1)
333
+
334
+ # Get the selected session (convert from 1-based to 0-based index)
335
+ session = sessions[session_num - 1]
336
+ return session
337
+
338
+
221
339
  @app.command()
222
340
  def export(
223
341
  session_num: Annotated[
@@ -237,36 +355,16 @@ def export(
237
355
  The session number corresponds to the '#' column in 'select list' output.
238
356
  """
239
357
  with Starbash("selection.export") as sb:
240
- # Get the filtered sessions
241
- sessions = sb.search_session()
242
-
243
- if not sessions or not isinstance(sessions, list):
244
- console.print(
245
- "[red]No sessions found. Check your selection criteria.[/red]"
246
- )
247
- raise typer.Exit(1)
248
-
249
- # Validate session number
250
- if session_num < 1 or session_num > len(sessions):
251
- console.print(
252
- f"[red]Error: Session number {session_num} is out of range. "
253
- f"Valid range is 1-{len(sessions)}.[/red]"
254
- )
255
- console.print(
256
- "[yellow]Use 'select list' to see available sessions.[/yellow]"
257
- )
258
- raise typer.Exit(1)
259
-
260
358
  # Get the selected session (convert from 1-based to 0-based index)
261
- session = sessions[session_num - 1]
359
+ session = selection_by_number(sb, session_num)
262
360
 
263
- # Get the session's database row ID
264
- session_id = session.get("id")
265
- if session_id is None:
361
+ # Get images for this session
362
+ images = sb.get_session_images(session)
363
+ if not images:
266
364
  console.print(
267
- f"[red]Error: Could not find session ID for session {session_num}.[/red]"
365
+ f"[red]Error: No images found for session {session_num}.[/red]"
268
366
  )
269
- raise typer.Exit(1)
367
+ raise typer.Exit(0)
270
368
 
271
369
  # Determine output directory
272
370
  output_dir = Path(destdir)
@@ -274,15 +372,6 @@ def export(
274
372
  # Create output directory if it doesn't exist
275
373
  output_dir.mkdir(parents=True, exist_ok=True)
276
374
 
277
- # Get images for this session
278
- images = sb.get_session_images(session_id)
279
-
280
- if not images:
281
- console.print(
282
- f"[yellow]Warning: No images found for session {session_num}.[/yellow]"
283
- )
284
- raise typer.Exit(0)
285
-
286
375
  copy_images_to_dir(images, output_dir)
287
376
 
288
377
 
@@ -300,8 +389,8 @@ def show_selection(ctx: typer.Context):
300
389
  console.print(f"[yellow]{summary['message']}[/yellow]")
301
390
  else:
302
391
  table = Table(title="Current Selection")
303
- table.add_column("Criteria", style="cyan")
304
- table.add_column("Value", style="green")
392
+ table.add_column("Criteria", style=TABLE_COLUMN_STYLE)
393
+ table.add_column("Value", style=TABLE_VALUE_STYLE)
305
394
 
306
395
  for criterion in summary["criteria"]:
307
396
  parts = criterion.split(": ", 1)