starbash 0.1.11__py3-none-any.whl → 0.1.15__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.
Files changed (43) hide show
  1. repo/__init__.py +1 -1
  2. repo/manager.py +14 -23
  3. repo/repo.py +52 -10
  4. starbash/__init__.py +10 -3
  5. starbash/aliases.py +49 -4
  6. starbash/analytics.py +3 -2
  7. starbash/app.py +287 -565
  8. starbash/check_version.py +18 -0
  9. starbash/commands/__init__.py +2 -1
  10. starbash/commands/info.py +26 -21
  11. starbash/commands/process.py +76 -24
  12. starbash/commands/repo.py +25 -68
  13. starbash/commands/select.py +140 -148
  14. starbash/commands/user.py +88 -23
  15. starbash/database.py +41 -27
  16. starbash/defaults/starbash.toml +1 -0
  17. starbash/exception.py +21 -0
  18. starbash/main.py +29 -7
  19. starbash/paths.py +23 -9
  20. starbash/processing.py +724 -0
  21. starbash/recipes/README.md +3 -0
  22. starbash/recipes/master_bias/starbash.toml +4 -1
  23. starbash/recipes/master_dark/starbash.toml +0 -1
  24. starbash/recipes/osc.py +190 -0
  25. starbash/recipes/osc_dual_duo/starbash.toml +31 -34
  26. starbash/recipes/osc_simple/starbash.toml +82 -0
  27. starbash/recipes/osc_single_duo/starbash.toml +51 -32
  28. starbash/recipes/seestar/starbash.toml +82 -0
  29. starbash/recipes/starbash.toml +8 -9
  30. starbash/selection.py +29 -38
  31. starbash/templates/repo/master.toml +7 -3
  32. starbash/templates/repo/processed.toml +7 -2
  33. starbash/templates/userconfig.toml +9 -0
  34. starbash/toml.py +13 -13
  35. starbash/tool.py +186 -149
  36. starbash-0.1.15.dist-info/METADATA +216 -0
  37. starbash-0.1.15.dist-info/RECORD +45 -0
  38. starbash/recipes/osc_dual_duo/starbash.py +0 -147
  39. starbash-0.1.11.dist-info/METADATA +0 -147
  40. starbash-0.1.11.dist-info/RECORD +0 -40
  41. {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/WHEEL +0 -0
  42. {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/entry_points.txt +0 -0
  43. {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,18 @@
1
+ import logging
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+ from update_checker import UpdateChecker
5
+
6
+
7
+ def check_version():
8
+ """Check if a newer version of starbash is available on PyPI."""
9
+ try:
10
+ checker = UpdateChecker()
11
+ current_version = version("starbash")
12
+ result = checker.check("starbash", current_version)
13
+ if result:
14
+ logging.warning(result)
15
+
16
+ except PackageNotFoundError:
17
+ # Package not installed (e.g., running from source during development)
18
+ pass
@@ -1,11 +1,12 @@
1
1
  """Shared utilities for starbash commands."""
2
2
 
3
- from datetime import datetime
4
3
  from rich.style import Style
5
4
 
6
5
  # Define reusable table styles
7
6
  TABLE_COLUMN_STYLE = Style(color="cyan")
8
7
  TABLE_VALUE_STYLE = Style(color="green")
8
+ TABLE_HEADER_STYLE = Style(color="magenta", bold=True)
9
+ SPINNER_STYLE = Style(color="magenta", bold=True)
9
10
 
10
11
 
11
12
  def format_duration(seconds: int | float) -> str:
starbash/commands/info.py CHANGED
@@ -1,15 +1,19 @@
1
1
  """Info commands for displaying system and data information."""
2
2
 
3
+ from collections import Counter
4
+ from typing import Annotated
5
+
3
6
  import typer
4
- from typing_extensions import Annotated
5
7
  from rich.table import Table
6
- from collections import Counter
7
8
 
8
9
  from starbash.app import Starbash
9
- from starbash import console
10
+ from starbash.commands import (
11
+ TABLE_COLUMN_STYLE,
12
+ TABLE_HEADER_STYLE,
13
+ TABLE_VALUE_STYLE,
14
+ format_duration,
15
+ )
10
16
  from starbash.database import Database, get_column_name
11
- from starbash.paths import get_user_config_dir, get_user_data_dir
12
- from starbash.commands import format_duration, TABLE_COLUMN_STYLE, TABLE_VALUE_STYLE
13
17
 
14
18
  app = typer.Typer()
15
19
 
@@ -27,7 +31,7 @@ def dump_column(sb: Starbash, human_name: str, column_name: str) -> None:
27
31
  sessions = sb.search_session()
28
32
 
29
33
  # Also do a complete unfiltered search so we can compare for the users
30
- allsessions = sb.db.search_session(("", []))
34
+ allsessions = sb.db.search_session([])
31
35
 
32
36
  column_name = get_column_name(column_name)
33
37
  found = [session[column_name] for session in sessions if session[column_name]]
@@ -42,16 +46,17 @@ def dump_column(sb: Starbash, human_name: str, column_name: str) -> None:
42
46
 
43
47
  # Create and display table
44
48
  table = Table(
45
- title=f"{plural(human_name)} ({len(found_counts)} / {len(all_counts)} selected)"
49
+ header_style=TABLE_HEADER_STYLE,
50
+ title=f"{plural(human_name)} ({len(found_counts)} / {len(all_counts)} selected)",
46
51
  )
47
52
  table.add_column(human_name, style=TABLE_COLUMN_STYLE, no_wrap=False)
48
- table.add_column(
49
- "# of sessions", style=TABLE_COLUMN_STYLE, no_wrap=True, justify="right"
50
- )
53
+ table.add_column("# of sessions", style=TABLE_COLUMN_STYLE, no_wrap=True, justify="right")
51
54
 
52
55
  for i, count in sorted_list:
53
56
  table.add_row(i, str(count))
54
57
 
58
+ from starbash import console
59
+
55
60
  console.print(table)
56
61
 
57
62
 
@@ -90,6 +95,8 @@ def master(
90
95
  ):
91
96
  """List all precalculated master images (darks, biases, flats)."""
92
97
  with Starbash("info.master") as sb:
98
+ from starbash import console
99
+
93
100
  # Get the master repo
94
101
  images = sb.get_master_images(kind)
95
102
 
@@ -102,7 +109,7 @@ def master(
102
109
  title = f"Master Images ({len(images)} total)"
103
110
  if kind:
104
111
  title = f"Master {kind} Images ({len(images)} total)"
105
- table = Table(title=title)
112
+ table = Table(title=title, header_style=TABLE_HEADER_STYLE)
106
113
  table.add_column("Date", style=TABLE_COLUMN_STYLE, no_wrap=True)
107
114
  table.add_column("Type", style=TABLE_COLUMN_STYLE, no_wrap=True)
108
115
  table.add_column("Filename", style=TABLE_VALUE_STYLE, no_wrap=False)
@@ -117,16 +124,14 @@ def master(
117
124
  )
118
125
 
119
126
  for image in sorted_images:
120
- date = (
121
- image.get(Database.DATE_OBS_KEY)
122
- or image.get(Database.DATE_KEY)
123
- or "Unknown"
124
- )
127
+ date = image.get(Database.DATE_OBS_KEY) or image.get(Database.DATE_KEY) or "Unknown"
125
128
  # Extract just the date part (YYYY-MM-DD) if it's a full ISO timestamp
126
129
  if "T" in date:
127
130
  date = date.split("T")[0]
128
131
 
129
- kind = image.get(Database.IMAGETYP_KEY) or "Unknown"
132
+ kind = image.get(Database.IMAGETYP_KEY)
133
+ if kind:
134
+ kind = sb.aliases.normalize(kind)
130
135
  filename = image.get("path") or "Unknown"
131
136
 
132
137
  table.add_row(date, kind, filename)
@@ -151,9 +156,11 @@ def main_callback(ctx: typer.Context):
151
156
 
152
157
  This is the default command when no subcommand is specified.
153
158
  """
159
+ from starbash import console
160
+
154
161
  if ctx.invoked_subcommand is None:
155
162
  with Starbash("info") as sb:
156
- table = Table(title="Starbash Information")
163
+ table = Table(title="Starbash Information", header_style=TABLE_HEADER_STYLE)
157
164
  table.add_column("Setting", style=TABLE_COLUMN_STYLE, no_wrap=True)
158
165
  table.add_column("Value", style=TABLE_VALUE_STYLE)
159
166
 
@@ -175,9 +182,7 @@ def main_callback(ctx: typer.Context):
175
182
  table.add_row("User Repositories", str(len(sb.repo_manager.regular_repos)))
176
183
 
177
184
  # Show database stats
178
- table.add_row(
179
- "Sessions Indexed", str(sb.db.len_table(Database.SESSIONS_TABLE))
180
- )
185
+ table.add_row("Sessions Indexed", str(sb.db.len_table(Database.SESSIONS_TABLE)))
181
186
 
182
187
  table.add_row("Images Indexed", str(sb.db.len_table(Database.IMAGES_TABLE)))
183
188
 
@@ -1,13 +1,19 @@
1
1
  """Processing commands for automated image processing workflows."""
2
2
 
3
- import typer
4
3
  from pathlib import Path
5
- from typing_extensions import Annotated
4
+ from typing import Annotated
5
+
6
+ import rich
7
+ import typer
6
8
 
7
9
  from starbash.app import Starbash, copy_images_to_dir
8
- from starbash import console
10
+ from starbash.commands.__init__ import (
11
+ TABLE_COLUMN_STYLE,
12
+ TABLE_HEADER_STYLE,
13
+ )
9
14
  from starbash.commands.select import selection_by_number
10
15
  from starbash.database import SessionRow
16
+ from starbash.processing import Processing, ProcessingResult
11
17
 
12
18
  app = typer.Typer()
13
19
 
@@ -20,9 +26,7 @@ def siril(
20
26
  ],
21
27
  destdir: Annotated[
22
28
  str,
23
- typer.Argument(
24
- help="Destination directory for Siril directory tree and processing"
25
- ),
29
+ typer.Argument(help="Destination directory for Siril directory tree and processing"),
26
30
  ],
27
31
  run: Annotated[
28
32
  bool,
@@ -42,6 +46,8 @@ def siril(
42
46
  structure loaded and ready for processing.
43
47
  """
44
48
  with Starbash("process.siril") as sb:
49
+ from starbash import console
50
+
45
51
  console.print(
46
52
  f"[yellow]Processing session {session_num} for Siril in {destdir}...[/yellow]"
47
53
  )
@@ -67,9 +73,9 @@ def siril(
67
73
 
68
74
  extras = [
69
75
  # FIXME search for BIAS/DARK/FLAT etc... using multiple canonical names
70
- ("BIAS", "biases"),
71
- ("DARK", "darks"),
72
- ("FLAT", "flats"),
76
+ ("bias", "biases"),
77
+ ("dark", "darks"),
78
+ ("flat", "flats"),
73
79
  ]
74
80
  for typ, subdir in extras:
75
81
  candidates = sb.guess_sessions(session, typ)
@@ -85,6 +91,48 @@ def siril(
85
91
  # Also FIXME, check for the existence of such a file
86
92
 
87
93
 
94
+ def print_results(
95
+ title: str, results: list[ProcessingResult], console: rich.console.Console
96
+ ) -> None:
97
+ """Print processing results in a formatted table.
98
+
99
+ Args:
100
+ title: Title to display above the table
101
+ results: List of ProcessingResult objects to display
102
+ console: Rich console instance for output
103
+ """
104
+ from rich.table import Table
105
+
106
+ if not results:
107
+ console.print(f"[yellow]{title}: No results to display[/yellow]")
108
+ return
109
+
110
+ table = Table(title=title, show_header=True, header_style=TABLE_HEADER_STYLE)
111
+ table.add_column("Target", style=TABLE_COLUMN_STYLE, no_wrap=True)
112
+ table.add_column("Sessions", justify="right", style=TABLE_COLUMN_STYLE)
113
+ table.add_column("Status", justify="center", style=TABLE_COLUMN_STYLE)
114
+ table.add_column("Notes", style=TABLE_COLUMN_STYLE)
115
+
116
+ for result in results:
117
+ # Format status with color
118
+ if result.success is True:
119
+ status = "[green]✓ Success[/green]"
120
+ elif result.success is False:
121
+ status = "[red]✗ Failed[/red]"
122
+ else:
123
+ status = "[yellow]⊘ Skipped[/yellow]"
124
+
125
+ # Format session count
126
+ session_count = str(len(result.sessions))
127
+
128
+ # Format notes (truncate if too long)
129
+ notes = result.notes or ""
130
+
131
+ table.add_row(result.target, session_count, status, notes)
132
+
133
+ console.print(table)
134
+
135
+
88
136
  @app.command()
89
137
  def auto(
90
138
  session_num: Annotated[
@@ -109,15 +157,17 @@ def auto(
109
157
  The output will be saved according to the configured recipes.
110
158
  """
111
159
  with Starbash("process.auto") as sb:
112
- if session_num is not None:
113
- console.print(f"[yellow]Auto-processing session {session_num}...[/yellow]")
114
- else:
115
- console.print("[yellow]Auto-processing all selected sessions...[/yellow]")
160
+ with Processing(sb) as proc:
161
+ from starbash import console
116
162
 
117
- console.print(
118
- "[red]Still in development - see https://github.com/geeksville/starbash[/red]"
119
- )
120
- sb.run_all_stages()
163
+ if session_num is not None:
164
+ console.print(f"[yellow]Auto-processing session {session_num}...[/yellow]")
165
+ else:
166
+ console.print("[yellow]Auto-processing all selected sessions...[/yellow]")
167
+
168
+ results = proc.run_all_stages()
169
+
170
+ print_results("Autoprocessed", results, console)
121
171
 
122
172
 
123
173
  @app.command()
@@ -132,13 +182,13 @@ def masters():
132
182
  and will be automatically used for future processing operations.
133
183
  """
134
184
  with Starbash("process.masters") as sb:
135
- console.print(
136
- "[yellow]Generating master frames from current selection...[/yellow]"
137
- )
138
- console.print(
139
- "[red]Still in development - see https://github.com/geeksville/starbash[/red]"
140
- )
141
- sb.run_master_stages()
185
+ with Processing(sb) as proc:
186
+ from starbash import console
187
+
188
+ console.print("[yellow]Generating master frames from current selection...[/yellow]")
189
+ results = proc.run_master_stages()
190
+
191
+ print_results("Generated masters", results, console)
142
192
 
143
193
 
144
194
  @app.callback(invoke_without_command=True)
@@ -149,6 +199,8 @@ def main_callback(ctx: typer.Context):
149
199
  post-processing of astrophotography sessions.
150
200
  """
151
201
  if ctx.invoked_subcommand is None:
202
+ from starbash import console
203
+
152
204
  # No command provided, show help
153
205
  console.print(ctx.get_help())
154
206
  raise typer.Exit()
starbash/commands/repo.py CHANGED
@@ -1,14 +1,14 @@
1
- import typer
2
- from typing_extensions import Annotated
3
- from pathlib import Path
4
1
  import logging
2
+ from textwrap import dedent
3
+ from typing import Annotated
4
+
5
+ import typer
5
6
 
6
7
  import starbash
7
- from repo import repo_suffix, Repo
8
+ from repo import Repo
9
+ from starbash import console
8
10
  from starbash.app import Starbash
9
- from starbash import console, log_filter_level
10
11
  from starbash.paths import get_user_documents_dir
11
- from starbash.toml import toml_from_template
12
12
 
13
13
  app = typer.Typer(invoke_without_command=True)
14
14
 
@@ -23,9 +23,7 @@ def repo_enumeration(sb: Starbash):
23
23
 
24
24
  def complete_repo_by_num(incomplete: str):
25
25
  # We need to use stderr_logging to prevent confusing the bash completion parser
26
- starbash.log_filter_level = (
27
- logging.ERROR
28
- ) # avoid showing output while doing completion
26
+ starbash.log_filter_level = logging.ERROR # avoid showing output while doing completion
29
27
  with Starbash("repo.complete.num", stderr_logging=True) as sb:
30
28
  for num, repo in repo_enumeration(sb).items():
31
29
  if str(num).startswith(incomplete):
@@ -34,9 +32,7 @@ def complete_repo_by_num(incomplete: str):
34
32
 
35
33
  def complete_repo_by_url(incomplete: str):
36
34
  # We need to use stderr_logging to prevent confusing the bash completion parser
37
- starbash.log_filter_level = (
38
- logging.ERROR
39
- ) # avoid showing output while doing completion
35
+ starbash.log_filter_level = logging.ERROR # avoid showing output while doing completion
40
36
  with Starbash("repo.complete.url", stderr_logging=True) as sb:
41
37
  repos = sb.repo_manager.regular_repos
42
38
 
@@ -46,28 +42,23 @@ def complete_repo_by_url(incomplete: str):
46
42
 
47
43
 
48
44
  @app.command()
49
- def list(
50
- verbose: bool = typer.Option(
51
- False, "--verbose", "-v", help="Show all repos including system repos"
52
- ),
53
- ):
45
+ def list():
54
46
  """
55
47
  lists all repositories.
56
- Use --verbose to show all repos including system/recipe repos.
57
48
  """
58
49
  with Starbash("repo.list") as sb:
59
- repos = sb.repo_manager.repos if verbose else sb.repo_manager.regular_repos
50
+ repos = sb.repo_manager.repos if starbash.verbose_output else sb.repo_manager.regular_repos
60
51
  for i, repo in enumerate(repos):
61
52
  kind = repo.kind("input")
62
53
  # for unknown repos (probably because we haven't written a starbash.toml file to the root yet),
63
54
  # we call them "input" because users will be less confused by that
64
55
 
65
- if verbose:
56
+ if starbash.verbose_output:
66
57
  # No numbers for verbose mode (system repos can't be removed)
67
- console.print(f"{ repo.url } (kind={ kind })")
58
+ console.print(f"{repo.url} (kind={kind})")
68
59
  else:
69
60
  # Show numbers for user repos (can be removed later)
70
- console.print(f"{ i + 1:2}: { repo.url } (kind={ kind })")
61
+ console.print(f"{i + 1:2}: {repo.url} (kind={kind})")
71
62
 
72
63
 
73
64
  @app.callback()
@@ -116,41 +107,14 @@ def add(
116
107
  raise typer.Exit(1)
117
108
 
118
109
  with Starbash("repo.add") as sb:
119
- p = Path(path)
120
-
121
- repo_toml = p / repo_suffix # the starbash.toml file at the root of the repo
122
- if repo_toml.exists():
123
- logging.warning("Using existing repository config file: %s", repo_toml)
124
- else:
125
- if repo_type:
126
- console.print(f"Creating {repo_type} repository: {p}")
127
- p.mkdir(parents=True, exist_ok=True)
128
-
129
- toml_from_template(
130
- f"repo/{repo_type}",
131
- p / repo_suffix,
132
- overrides={
133
- "REPO_TYPE": repo_type,
134
- "REPO_PATH": str(p),
135
- "DEFAULT_RELATIVE": "{instrument}/{date}/{imagetyp}/master_{session_config}.fit",
136
- },
137
- )
138
- else:
139
- # No type specified, therefore (for now) assume we are just using this as an input
140
- # repo (and it must exist)
141
- if not p.exists():
142
- console.print(f"[red]Error: Repo path does not exist: {p}[/red]")
143
- raise typer.Exit(code=1)
144
-
145
- console.print(f"Adding repository: {p}")
146
-
147
- repo = sb.user_repo.add_repo_ref(p)
148
- if repo:
149
- sb.reindex_repo(repo)
110
+ if repo_type and sb.repo_manager.get_repo_by_kind(repo_type):
111
+ console.print(
112
+ dedent(f"""
113
+ [red]Error[/red]: A repository for '{repo_type}' files already exists. If you'd like to replace it, use 'sb repo remove <url|number>' first.""")
114
+ )
115
+ raise typer.Exit(1)
150
116
 
151
- # we don't yet always write default config files at roots of repos, but it would be easy to add here
152
- # r.write_config()
153
- sb.user_repo.write_config()
117
+ sb.add_local_repo(path, repo_type=repo_type)
154
118
 
155
119
 
156
120
  def repo_url_to_repo(sb: Starbash, repo_url: str | None) -> Repo | None:
@@ -189,9 +153,7 @@ def repo_url_to_repo(sb: Starbash, repo_url: str | None) -> Repo | None:
189
153
  def remove(
190
154
  reponum: Annotated[
191
155
  str,
192
- typer.Argument(
193
- help="Repository number or URL", autocompletion=complete_repo_by_url
194
- ),
156
+ typer.Argument(help="Repository number or URL", autocompletion=complete_repo_by_url),
195
157
  ],
196
158
  ):
197
159
  """
@@ -202,7 +164,7 @@ def remove(
202
164
  # Get the repo to remove
203
165
  repo_to_remove = repo_url_to_repo(sb, reponum)
204
166
  if repo_to_remove is None:
205
- console.print(f"[red]Error: You must specify a repository[/red]")
167
+ console.print("[red]Error: You must specify a repository[/red]")
206
168
  raise typer.Exit(code=1)
207
169
  repo_url = repo_to_remove.url
208
170
 
@@ -220,9 +182,6 @@ def reindex(
220
182
  autocompletion=complete_repo_by_url,
221
183
  ),
222
184
  ] = None,
223
- force: bool = typer.Option(
224
- default=False, help="Reread FITS headers, even if they are already indexed."
225
- ),
226
185
  ):
227
186
  """
228
187
  Reindex a repository by number.
@@ -233,14 +192,12 @@ def reindex(
233
192
  repo_to_reindex = repo_url_to_repo(sb, repo_url)
234
193
 
235
194
  if repo_to_reindex is None:
236
- sb.reindex_repos(force=force)
195
+ sb.reindex_repos()
237
196
  else:
238
197
  # Get the repo to reindex
239
198
  console.print(f"Reindexing repository: {repo_to_reindex.url}")
240
- sb.reindex_repo(repo_to_reindex, force=force)
241
- console.print(
242
- f"[green]Successfully reindexed repository {repo_to_reindex}[/green]"
243
- )
199
+ sb.reindex_repo(repo_to_reindex)
200
+ console.print(f"[green]Successfully reindexed repository {repo_to_reindex}[/green]")
244
201
 
245
202
 
246
203
  if __name__ == "__main__":