starbash 0.1.0__tar.gz → 0.1.1__tar.gz

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.

Potentially problematic release.


This version of starbash might be problematic. Click here for more details.

Files changed (26) hide show
  1. {starbash-0.1.0 → starbash-0.1.1}/PKG-INFO +25 -11
  2. {starbash-0.1.0 → starbash-0.1.1}/README.md +19 -8
  3. {starbash-0.1.0 → starbash-0.1.1}/pyproject.toml +6 -2
  4. starbash-0.1.1/src/starbash/analytics.py +121 -0
  5. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/app.py +147 -36
  6. starbash-0.1.1/src/starbash/commands/repo.py +68 -0
  7. starbash-0.1.1/src/starbash/commands/selection.py +117 -0
  8. starbash-0.1.1/src/starbash/commands/user.py +63 -0
  9. starbash-0.1.1/src/starbash/database.py +405 -0
  10. starbash-0.1.1/src/starbash/defaults/__init__.py +0 -0
  11. starbash-0.1.0/src/starbash/appdefaults.sb.toml → starbash-0.1.1/src/starbash/defaults/starbash.toml +6 -14
  12. starbash-0.1.1/src/starbash/main.py +150 -0
  13. starbash-0.1.1/src/starbash/paths.py +38 -0
  14. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/repo/manager.py +124 -59
  15. starbash-0.1.1/src/starbash/selection.py +215 -0
  16. starbash-0.1.1/src/starbash/templates/__init__.py +0 -0
  17. starbash-0.1.1/src/starbash/templates/userconfig.toml +21 -0
  18. starbash-0.1.1/src/starbash/url.py +9 -0
  19. starbash-0.1.0/src/starbash/commands/repo.py +0 -51
  20. starbash-0.1.0/src/starbash/database.py +0 -75
  21. starbash-0.1.0/src/starbash/main.py +0 -27
  22. {starbash-0.1.0 → starbash-0.1.1}/LICENSE +0 -0
  23. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/__init__.py +0 -0
  24. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/commands/__init__.py +0 -0
  25. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/repo/__init__.py +0 -0
  26. {starbash-0.1.0 → starbash-0.1.1}/src/starbash/tool.py +0 -0
@@ -1,24 +1,31 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: starbash
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary:
5
+ License-File: LICENSE
5
6
  Author: Kevin Hester
6
7
  Author-email: kevinh@geeksville.com
7
8
  Requires-Python: >=3.12,<3.15
8
9
  Classifier: Programming Language :: Python :: 3
9
10
  Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
10
13
  Requires-Dist: astropy (>=7.1.1,<8.0.0)
11
14
  Requires-Dist: multidict (>=6.7.0,<7.0.0)
12
15
  Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
13
16
  Requires-Dist: restrictedpython (>=8.1,<9.0)
14
17
  Requires-Dist: rich (>=14.2.0,<15.0.0)
15
- Requires-Dist: tinydb (>=4.8.2,<5.0.0)
18
+ Requires-Dist: sentry-sdk (>=2.42.1,<3.0.0)
16
19
  Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
17
20
  Requires-Dist: typer (>=0.20.0,<0.21.0)
18
21
  Description-Content-Type: text/markdown
19
22
 
20
23
  # Starbash
21
- ![app icon](img/icon.png "Starbash: Astrophotography workflows simplified")
24
+
25
+ ![PyPI - Version](https://img.shields.io/pypi/v/starbash)
26
+ ![GitHub branch check runs](https://img.shields.io/github/check-runs/geeksville/starbash/main)
27
+
28
+ ![app icon](https://github.com/geeksville/starbash/blob/main/img/icon.png "Starbash: Astrophotography workflows simplified")
22
29
 
23
30
  A tool for automating/standardizing/sharing astrophotography workflows.
24
31
 
@@ -48,20 +55,27 @@ See my personal [TODO](TODO.md) file. I'll be looking for pre-alpha testers/fee
48
55
  * repo list
49
56
  * repo reindex REPONAME|REPONUM|all
50
57
 
51
- * target list
52
- * target select TARGETNAME
58
+ * user analytics on|off - turn analytics collection on/off
59
+ * user name "Your Name" - used for attribution in generated images
60
+ * user email "foo@blah.com" - used for attribution in generated images
61
+
62
+ * selection any - remove any filters on sessions, etc...
63
+ * selection target TARGETNAME - limit the current selection to only the named targets
64
+ * selection date op DATE - limit to sessions in the specified date range
65
+ * selection - list information about the current selection
66
+
67
+ * target - list targets (filtered based on the current selection)
53
68
 
54
- * reset - remove any filters on targets, sessions, etc...
69
+ * session- list sessions (filtered based on the current selection)
55
70
 
56
- * session list
57
- * session date after DATE
58
- * session date before DATE
71
+ * instrument - list instruments (filtered based on the current selection)
59
72
 
60
- * instrument list
73
+ * filter - list all filters found in current selection
61
74
 
62
75
  * export dirs|BIAS|LIGHT|DARK|FLAT [DIRLOC]
63
76
 
64
77
  * process auto
78
+ * process masters - generate master flats, darks, biases from any raws that are available
65
79
 
66
80
  ## Supported tools
67
81
 
@@ -1,5 +1,9 @@
1
1
  # Starbash
2
- ![app icon](img/icon.png "Starbash: Astrophotography workflows simplified")
2
+
3
+ ![PyPI - Version](https://img.shields.io/pypi/v/starbash)
4
+ ![GitHub branch check runs](https://img.shields.io/github/check-runs/geeksville/starbash/main)
5
+
6
+ ![app icon](https://github.com/geeksville/starbash/blob/main/img/icon.png "Starbash: Astrophotography workflows simplified")
3
7
 
4
8
  A tool for automating/standardizing/sharing astrophotography workflows.
5
9
 
@@ -29,20 +33,27 @@ See my personal [TODO](TODO.md) file. I'll be looking for pre-alpha testers/fee
29
33
  * repo list
30
34
  * repo reindex REPONAME|REPONUM|all
31
35
 
32
- * target list
33
- * target select TARGETNAME
36
+ * user analytics on|off - turn analytics collection on/off
37
+ * user name "Your Name" - used for attribution in generated images
38
+ * user email "foo@blah.com" - used for attribution in generated images
39
+
40
+ * selection any - remove any filters on sessions, etc...
41
+ * selection target TARGETNAME - limit the current selection to only the named targets
42
+ * selection date op DATE - limit to sessions in the specified date range
43
+ * selection - list information about the current selection
44
+
45
+ * target - list targets (filtered based on the current selection)
34
46
 
35
- * reset - remove any filters on targets, sessions, etc...
47
+ * session- list sessions (filtered based on the current selection)
36
48
 
37
- * session list
38
- * session date after DATE
39
- * session date before DATE
49
+ * instrument - list instruments (filtered based on the current selection)
40
50
 
41
- * instrument list
51
+ * filter - list all filters found in current selection
42
52
 
43
53
  * export dirs|BIAS|LIGHT|DARK|FLAT [DIRLOC]
44
54
 
45
55
  * process auto
56
+ * process masters - generate master flats, darks, biases from any raws that are available
46
57
 
47
58
  ## Supported tools
48
59
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "starbash"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = ""
5
5
  authors = ["Kevin Hester <kevinh@geeksville.com>"]
6
6
  readme = "README.md"
@@ -13,9 +13,9 @@ multidict = ">=6.7.0,<7.0.0"
13
13
  rich = ">=14.2.0,<15.0.0"
14
14
  restrictedpython = ">=8.1,<9.0"
15
15
  astropy = ">=7.1.1,<8.0.0"
16
- tinydb = ">=4.8.2,<5.0.0"
17
16
  platformdirs = ">=4.5.0,<5.0.0"
18
17
  typer = "^0.20.0"
18
+ sentry-sdk = "^2.42.1"
19
19
 
20
20
  [tool.poetry.group.dev.dependencies]
21
21
  pytest = ">=8.4.2,<9.0.0"
@@ -24,6 +24,10 @@ pytest = ">=8.4.2,<9.0.0"
24
24
  starbash = "starbash.main:app"
25
25
  sb = "starbash.main:app" # std alias for convenience
26
26
 
27
+ [tool.pytest.ini_options]
28
+ markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"]
29
+ addopts = "-m 'not slow'"
30
+
27
31
  [build-system]
28
32
  requires = ["poetry-core>=2.0.0,<3.0.0"]
29
33
  build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,121 @@
1
+ import logging
2
+
3
+ from starbash import console
4
+ import starbash.url as url
5
+
6
+ # Default to no analytics/auto crash reports
7
+ analytics_allowed = False
8
+
9
+
10
+ def analytics_setup(allowed: bool = False, user_email: str | None = None) -> None:
11
+ import sentry_sdk
12
+
13
+ global analytics_allowed
14
+ analytics_allowed = allowed
15
+ if analytics_allowed:
16
+ logging.info(
17
+ f"Analytics/crash-reports enabled. To change [link={url.analytics_docs}]click here[/link]",
18
+ extra={"markup": True},
19
+ )
20
+ sentry_sdk.init(
21
+ dsn="https://e9496a4ea8b37a053203a2cbc10d64e6@o209837.ingest.us.sentry.io/4510264204132352",
22
+ send_default_pii=True,
23
+ enable_logs=True,
24
+ traces_sample_rate=1.0,
25
+ )
26
+
27
+ if user_email:
28
+ sentry_sdk.set_user({"email": user_email})
29
+ else:
30
+ logging.info(
31
+ f"Analytics/crash-reports disabled. To learn more [link={url.analytics_docs}]click here[/link]",
32
+ extra={"markup": True},
33
+ )
34
+
35
+
36
+ def analytics_shutdown() -> None:
37
+ """Shut down the analytics service, if enabled."""
38
+ if analytics_allowed:
39
+ import sentry_sdk
40
+
41
+ sentry_sdk.flush()
42
+
43
+
44
+ def is_development_environment() -> bool:
45
+ """Detect if running in a development environment."""
46
+ import os
47
+ import sys
48
+ from pathlib import Path
49
+
50
+ # Check for explicit environment variable
51
+ if os.getenv("STARBASH_ENV") == "development":
52
+ return True
53
+
54
+ # Check if running under VS Code
55
+ if any(k.startswith("VSCODE_") for k in os.environ):
56
+ return True
57
+
58
+ return False
59
+
60
+
61
+ def analytics_exception(exc: Exception) -> bool:
62
+ """Report an exception to the analytics service, if enabled.
63
+ return True to suppress exception propagation/log messages"""
64
+
65
+ if is_development_environment():
66
+ return False # We want to let devs see full exception traces
67
+
68
+ if analytics_allowed:
69
+ import sentry_sdk
70
+
71
+ report_id = sentry_sdk.capture_exception(exc)
72
+
73
+ logging.info(
74
+ f"""An unexpected error has occurred and been reported. Thank you for your help.
75
+ If you'd like to chat with the devs about it, please click
76
+ [link={url.new_issue(str(report_id))}]here[/link] to open an issue.""",
77
+ extra={"markup": True},
78
+ )
79
+ else:
80
+ logging.error(
81
+ f"""An unexpected error has occurred. Automated crash reporting is disabled,
82
+ but we encourage you to contact the developers
83
+ at [link={url.new_issue()}]here[/link] and we will try to help.
84
+
85
+ The full exception is: {exc}""",
86
+ extra={"markup": True},
87
+ )
88
+ return True
89
+
90
+
91
+ class NopAnalytics:
92
+ """Used when users have disabled analytics/crash reporting."""
93
+
94
+ def __enter__(self):
95
+ return self
96
+
97
+ def __exit__(self, exc_type, exc_value, traceback):
98
+ return False
99
+
100
+ def set_data(self, key, value):
101
+ pass
102
+
103
+
104
+ def analytics_start_span(**kwargs):
105
+ """Start an analytics/tracing span if analytics is enabled, otherwise return a no-op context manager."""
106
+ if analytics_allowed:
107
+ import sentry_sdk
108
+
109
+ return sentry_sdk.start_span(**kwargs)
110
+ else:
111
+ return NopAnalytics()
112
+
113
+
114
+ def analytics_start_transaction(**kwargs):
115
+ """Start an analytics/tracing transaction if analytics is enabled, otherwise return a no-op context manager."""
116
+ if analytics_allowed:
117
+ import sentry_sdk
118
+
119
+ return sentry_sdk.start_transaction(**kwargs)
120
+ else:
121
+ return NopAnalytics()
@@ -1,6 +1,9 @@
1
1
  import logging
2
2
  from importlib import resources
3
+ from pathlib import Path
3
4
 
5
+ import tomlkit
6
+ from tomlkit.toml_file import TOMLFile
4
7
  import glob
5
8
  from typing import Any
6
9
  from astropy.io import fits
@@ -8,9 +11,19 @@ import itertools
8
11
  from rich.progress import track
9
12
  from rich.logging import RichHandler
10
13
  from starbash.database import Database
14
+ from starbash.repo.manager import Repo
11
15
  from starbash.tool import Tool
12
16
  from starbash.repo import RepoManager
13
17
  from starbash.tool import tools
18
+ from starbash.paths import get_user_config_dir, get_user_data_dir
19
+ from starbash.selection import Selection
20
+ from starbash.analytics import (
21
+ NopAnalytics,
22
+ analytics_exception,
23
+ analytics_setup,
24
+ analytics_shutdown,
25
+ analytics_start_transaction,
26
+ )
14
27
 
15
28
 
16
29
  def setup_logging():
@@ -28,78 +41,176 @@ def setup_logging():
28
41
  setup_logging()
29
42
 
30
43
 
31
- class AstroGlue:
32
- """The main AstroGlue application class."""
44
+ def create_user() -> Path:
45
+ """Create user directories if they don't exist yet."""
46
+ config_dir = get_user_config_dir()
47
+ userconfig_path = config_dir / "starbash.toml"
48
+ if not (userconfig_path).exists():
49
+ tomlstr = (
50
+ resources.files("starbash")
51
+ .joinpath("templates/userconfig.toml")
52
+ .read_text()
53
+ )
54
+ toml = tomlkit.parse(tomlstr)
55
+ TOMLFile(userconfig_path).write(toml)
56
+ logging.info(f"Created user config file: {userconfig_path}")
57
+ return config_dir
58
+
59
+
60
+ class Starbash:
61
+ """The main Starbash application class."""
33
62
 
34
- def __init__(self):
63
+ def __init__(self, cmd: str = "unspecified"):
35
64
  """
36
- Initializes the AstroGlue application by loading configurations
65
+ Initializes the Starbash application by loading configurations
37
66
  and setting up the repository manager.
38
67
  """
39
68
  setup_logging()
40
- logging.info("AstroGlue application initializing...")
69
+ logging.info("Starbash starting...")
41
70
 
42
71
  # Load app defaults and initialize the repository manager
43
- app_defaults_text = (
44
- resources.files("starbash").joinpath("appdefaults.sb.toml").read_text()
45
- )
46
- self.repo_manager = RepoManager(app_defaults_text)
72
+ self.repo_manager = RepoManager()
73
+ self.repo_manager.add_repo("pkg://defaults")
74
+
75
+ # Add user prefs as a repo
76
+ self.user_repo = self.repo_manager.add_repo("file://" + str(create_user()))
77
+
78
+ self.analytics = NopAnalytics()
79
+ if self.user_repo.get("analytics.enabled", True):
80
+ include_user = self.user_repo.get("analytics.include_user", False)
81
+ user_email = (
82
+ self.user_repo.get("user.email", None) if include_user else None
83
+ )
84
+ if user_email is not None:
85
+ user_email = str(user_email)
86
+ analytics_setup(allowed=True, user_email=user_email)
87
+ # this is intended for use with "with" so we manually do enter/exit
88
+ self.analytics = analytics_start_transaction(name="App session", op=cmd)
89
+ self.analytics.__enter__()
90
+
47
91
  logging.info(
48
92
  f"Repo manager initialized with {len(self.repo_manager.repos)} default repo references."
49
93
  )
50
94
  # self.repo_manager.dump()
51
95
 
52
96
  self.db = Database()
97
+ self.session_query = None # None means search all sessions
98
+
99
+ # Initialize selection state
100
+ data_dir = get_user_data_dir()
101
+ selection_file = data_dir / "selection.json"
102
+ self.selection = Selection(selection_file)
103
+
53
104
  # FIXME, call reindex somewhere and also index whenever new repos are added
54
105
  # self.reindex_repos()
55
106
 
56
107
  # --- Lifecycle ---
57
108
  def close(self) -> None:
109
+ self.analytics.__exit__(None, None, None)
110
+
111
+ analytics_shutdown()
58
112
  self.db.close()
59
113
 
60
114
  # Context manager support
61
- def __enter__(self) -> "AstroGlue":
115
+ def __enter__(self) -> "Starbash":
62
116
  return self
63
117
 
64
- def __exit__(self, exc_type, exc, tb) -> None:
118
+ def __exit__(self, exc_type, exc, tb) -> bool:
119
+ handled = False
120
+ if exc:
121
+ handled = analytics_exception(exc)
65
122
  self.close()
123
+ return handled
124
+
125
+ def _add_session(self, f: str, image_doc_id: int, header: dict) -> None:
126
+ filter = header.get(Database.FILTER_KEY, "unspecified")
127
+ image_type = header.get(Database.IMAGETYP_KEY)
128
+ date = header.get(Database.DATE_OBS_KEY)
129
+ if not date or not image_type:
130
+ logging.warning(
131
+ "Image %s missing critical FITS header, please submit image at https://github.com/geeksville/starbash/issues/new",
132
+ f,
133
+ )
134
+ else:
135
+ exptime = header.get(Database.EXPTIME_KEY, 0)
136
+ new = {
137
+ Database.FILTER_KEY: filter,
138
+ Database.START_KEY: date,
139
+ Database.END_KEY: date, # FIXME not quite correct, should be longer by exptime
140
+ Database.IMAGE_DOC_KEY: image_doc_id,
141
+ Database.IMAGETYP_KEY: image_type,
142
+ Database.NUM_IMAGES_KEY: 1,
143
+ Database.EXPTIME_TOTAL_KEY: exptime,
144
+ Database.OBJECT_KEY: header.get(Database.OBJECT_KEY, "unspecified"),
145
+ }
146
+ session = self.db.get_session(new)
147
+ self.db.upsert_session(new, existing=session)
148
+
149
+ def search_session(self) -> list[dict[str, Any]] | None:
150
+ """Search for sessions, optionally filtered by the current selection."""
151
+ # If selection has filters, use them; otherwise return all sessions
152
+ if self.selection.is_empty():
153
+ return self.db.search_session(None)
154
+ else:
155
+ # Get query conditions from selection
156
+ conditions = self.selection.get_query_conditions()
157
+ return self.db.search_session(conditions)
66
158
 
67
- def reindex_repos(self):
159
+ def reindex_repo(self, repo: Repo, force: bool = False):
68
160
  """Reindex all repositories managed by the RepoManager."""
69
- logging.info("Reindexing all repositories...")
70
- config = self.repo_manager.merged.get("config")
71
- if not config:
72
- raise ValueError(f"App config not found.")
73
- whitelist = config["fits-whitelist"]
74
-
75
- for repo in track(self.repo_manager.repos, description="Reindexing repos..."):
76
- # FIXME, add a method to get just the repos that contain images
77
- if repo.is_local and repo.kind != "recipe":
78
- logging.debug("Reindexing %s...", repo.url)
79
- path = repo.get_path()
80
-
81
- # Find all FITS files under this repo path
82
- for f in track(
83
- list(path.rglob("*.fit*")),
84
- description=f"Indexing {repo.url}...",
85
- ):
86
- # progress.console.print(f"Indexing {f}...")
87
- try:
161
+ # FIXME, add a method to get just the repos that contain images
162
+ if repo.is_scheme("file") and repo.kind != "recipe":
163
+ logging.debug("Reindexing %s...", repo.url)
164
+
165
+ config = self.repo_manager.merged.get("config")
166
+ if not config:
167
+ raise ValueError(f"App config not found.")
168
+ whitelist = config["fits-whitelist"]
169
+
170
+ path = repo.get_path()
171
+ if not path:
172
+ raise ValueError(f"Repo path not found for {repo}")
173
+
174
+ # Find all FITS files under this repo path
175
+ for f in track(
176
+ list(path.rglob("*.fit*")),
177
+ description=f"Indexing {repo.url}...",
178
+ ):
179
+ # progress.console.print(f"Indexing {f}...")
180
+ try:
181
+ found = self.db.get_image(str(f))
182
+ if not found or force:
88
183
  # Read and log the primary header (HDU 0)
89
184
  with fits.open(str(f), memmap=False) as hdul:
90
185
  # convert headers to dict
91
186
  hdu0: Any = hdul[0]
92
- items = hdu0.header.items()
187
+ header = hdu0.header
188
+ if type(header).__name__ == "Unknown":
189
+ raise ValueError("FITS header has Unknown type: %s", f)
190
+
191
+ items = header.items()
93
192
  headers = {}
94
193
  for key, value in items:
95
194
  if key in whitelist:
96
195
  headers[key] = value
97
196
  logging.debug("Headers for %s: %s", f, headers)
98
- self.db.add_from_fits(f, headers)
99
- except Exception as e:
100
- logging.warning("Failed to read FITS header for %s: %s", f, e)
197
+ headers["path"] = str(f)
198
+ image_doc_id = self.db.upsert_image(headers)
101
199
 
102
- logging.info("Reindexing complete.")
200
+ if not found:
201
+ # Update the session infos, but ONLY on first file scan
202
+ # (otherwise invariants will get messed up)
203
+ self._add_session(str(f), image_doc_id, header)
204
+
205
+ except Exception as e:
206
+ logging.warning("Failed to read FITS header for %s: %s", f, e)
207
+
208
+ def reindex_repos(self, force: bool = False):
209
+ """Reindex all repositories managed by the RepoManager."""
210
+ logging.info("Reindexing all repositories...")
211
+
212
+ for repo in track(self.repo_manager.repos, description="Reindexing repos..."):
213
+ self.reindex_repo(repo, force=force)
103
214
 
104
215
  def test_processing(self):
105
216
  """A crude test of image processing pipeline - FIXME move into testing"""
@@ -0,0 +1,68 @@
1
+ import typer
2
+ from typing_extensions import Annotated
3
+
4
+ from starbash.app import Starbash
5
+ from starbash import console
6
+
7
+ app = typer.Typer()
8
+
9
+
10
+ @app.command()
11
+ def add(path: str):
12
+ """
13
+ Add a repository. path is either a local path or a remote URL.
14
+ """
15
+ with Starbash("repo-add") as sb:
16
+ sb.user_repo.add_repo_ref(path)
17
+ # we don't yet write default config files at roots of repos, but it would be easy to add here
18
+ # r.write_config()
19
+ sb.user_repo.write_config()
20
+ # FIXME, we also need to index the newly added repo!!!
21
+ console.print(f"Added repository: {path}")
22
+
23
+
24
+ @app.command()
25
+ def remove(reponame: str):
26
+ """
27
+ Remove a repository by name or number.
28
+ """
29
+ with Starbash("repo-remove") as sb:
30
+ raise NotImplementedError("Removing repositories not yet implemented.")
31
+
32
+
33
+ @app.command()
34
+ def list():
35
+ """
36
+ List all repositories. The listed names/numbers can be used with other commands.
37
+ """
38
+ with Starbash("repo-list") as sb:
39
+ for i, repo in enumerate(sb.repo_manager.repos):
40
+ console.print(f"{ i + 1:2}: { repo.url } (kind={ repo.kind})")
41
+
42
+
43
+ @app.command()
44
+ def reindex(
45
+ repo: Annotated[
46
+ str | None,
47
+ typer.Argument(
48
+ help="The repository name or number, if not specified reindex all."
49
+ ),
50
+ ] = None,
51
+ force: bool = typer.Option(
52
+ default=False, help="Reread FITS headers, even if they are already indexed."
53
+ ),
54
+ ):
55
+ """
56
+ Reindex the named repository.
57
+ If no name is given, reindex all repositories.
58
+ """
59
+ with Starbash("repo-reindex") as sb:
60
+ if repo is None:
61
+ console.print("Reindexing all repositories...")
62
+ sb.reindex_repos(force=force)
63
+ else:
64
+ raise NotImplementedError("Reindexing a single repo not yet implemented.")
65
+
66
+
67
+ if __name__ == "__main__":
68
+ app()