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.
- repo/__init__.py +1 -1
- repo/manager.py +14 -23
- repo/repo.py +52 -10
- starbash/__init__.py +10 -3
- starbash/aliases.py +49 -4
- starbash/analytics.py +3 -2
- starbash/app.py +287 -565
- starbash/check_version.py +18 -0
- starbash/commands/__init__.py +2 -1
- starbash/commands/info.py +26 -21
- starbash/commands/process.py +76 -24
- starbash/commands/repo.py +25 -68
- starbash/commands/select.py +140 -148
- starbash/commands/user.py +88 -23
- starbash/database.py +41 -27
- starbash/defaults/starbash.toml +1 -0
- starbash/exception.py +21 -0
- starbash/main.py +29 -7
- starbash/paths.py +23 -9
- starbash/processing.py +724 -0
- starbash/recipes/README.md +3 -0
- starbash/recipes/master_bias/starbash.toml +4 -1
- starbash/recipes/master_dark/starbash.toml +0 -1
- starbash/recipes/osc.py +190 -0
- starbash/recipes/osc_dual_duo/starbash.toml +31 -34
- starbash/recipes/osc_simple/starbash.toml +82 -0
- starbash/recipes/osc_single_duo/starbash.toml +51 -32
- starbash/recipes/seestar/starbash.toml +82 -0
- starbash/recipes/starbash.toml +8 -9
- starbash/selection.py +29 -38
- starbash/templates/repo/master.toml +7 -3
- starbash/templates/repo/processed.toml +7 -2
- starbash/templates/userconfig.toml +9 -0
- starbash/toml.py +13 -13
- starbash/tool.py +186 -149
- starbash-0.1.15.dist-info/METADATA +216 -0
- starbash-0.1.15.dist-info/RECORD +45 -0
- starbash/recipes/osc_dual_duo/starbash.py +0 -147
- starbash-0.1.11.dist-info/METADATA +0 -147
- starbash-0.1.11.dist-info/RECORD +0 -40
- {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/WHEEL +0 -0
- {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/entry_points.txt +0 -0
- {starbash-0.1.11.dist-info → starbash-0.1.15.dist-info}/licenses/LICENSE +0 -0
starbash/database.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import sqlite3
|
|
4
|
-
from
|
|
5
|
-
from typing import Any, Optional, NamedTuple
|
|
5
|
+
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
|
-
import
|
|
8
|
-
from typing import
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
|
-
from .paths import get_user_data_dir
|
|
11
10
|
from .aliases import normalize_target_name
|
|
11
|
+
from .paths import get_user_data_dir
|
|
12
12
|
|
|
13
|
-
SessionRow
|
|
14
|
-
ImageRow
|
|
13
|
+
type SessionRow = dict[str, Any]
|
|
14
|
+
type ImageRow = dict[str, Any]
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class SearchCondition:
|
|
18
19
|
"""A search condition for database queries.
|
|
19
20
|
|
|
20
21
|
Args:
|
|
@@ -83,7 +84,9 @@ class Database:
|
|
|
83
84
|
IMAGETYP_KEY = "IMAGETYP"
|
|
84
85
|
OBJECT_KEY = "OBJECT"
|
|
85
86
|
TELESCOP_KEY = "TELESCOP"
|
|
86
|
-
EXPTIME_KEY = "EXPTIME"
|
|
87
|
+
EXPTIME_KEY = "EXPTIME" # in all image files
|
|
88
|
+
TOTALEXP_KEY = "TOTALEXP" # in stacked ASI files
|
|
89
|
+
|
|
87
90
|
ID_KEY = "id" # for finding any row by its ID
|
|
88
91
|
REPO_URL_KEY = "repo_url"
|
|
89
92
|
|
|
@@ -93,7 +96,7 @@ class Database:
|
|
|
93
96
|
|
|
94
97
|
def __init__(
|
|
95
98
|
self,
|
|
96
|
-
base_dir:
|
|
99
|
+
base_dir: Path | None = None,
|
|
97
100
|
) -> None:
|
|
98
101
|
# Resolve base data directory (allow override for tests)
|
|
99
102
|
if base_dir is None:
|
|
@@ -187,7 +190,7 @@ class Database:
|
|
|
187
190
|
filter TEXT COLLATE NOCASE,
|
|
188
191
|
imagetyp TEXT COLLATE NOCASE NOT NULL,
|
|
189
192
|
object TEXT,
|
|
190
|
-
telescop TEXT NOT NULL,
|
|
193
|
+
telescop TEXT COLLATENOCASE NOT NULL,
|
|
191
194
|
num_images INTEGER NOT NULL,
|
|
192
195
|
exptime_total REAL NOT NULL,
|
|
193
196
|
exptime REAL NOT NULL,
|
|
@@ -426,26 +429,41 @@ class Database:
|
|
|
426
429
|
|
|
427
430
|
return results
|
|
428
431
|
|
|
429
|
-
def search_session(
|
|
430
|
-
self, where_tuple: tuple[str, list[Any]] = ("", [])
|
|
431
|
-
) -> list[SessionRow]:
|
|
432
|
+
def search_session(self, conditions: list[SearchCondition] = []) -> list[SessionRow]:
|
|
432
433
|
"""Search for sessions matching the given conditions.
|
|
433
434
|
|
|
434
435
|
Args:
|
|
435
|
-
|
|
436
|
+
conditions: List of SearchCondition tuples for filtering sessions.
|
|
437
|
+
Column names should be from the sessions table. If no table prefix
|
|
438
|
+
is given (e.g., "OBJECT"), it will be prefixed with "s." automatically.
|
|
436
439
|
|
|
437
440
|
Returns:
|
|
438
|
-
List of matching session records with metadata from the reference image
|
|
441
|
+
List of matching session records with metadata from the reference image and repo_url
|
|
439
442
|
"""
|
|
440
|
-
# Build WHERE clause
|
|
441
|
-
|
|
443
|
+
# Build WHERE clause from SearchCondition list
|
|
444
|
+
where_clauses = []
|
|
445
|
+
params = []
|
|
446
|
+
|
|
447
|
+
for condition in conditions:
|
|
448
|
+
# Add table prefix 's.' if not already present to avoid ambiguous column names
|
|
449
|
+
column_name = condition.column_name
|
|
450
|
+
if "." not in column_name:
|
|
451
|
+
# Session table columns that might be ambiguous with images table
|
|
452
|
+
column_name = f"s.{column_name.lower()}"
|
|
453
|
+
where_clauses.append(f"{column_name} {condition.comparison_op} ?")
|
|
454
|
+
params.append(condition.value)
|
|
455
|
+
|
|
456
|
+
# Build the query with JOIN to images and repos tables to get reference image metadata and repo_url
|
|
457
|
+
where_clause = ""
|
|
458
|
+
if where_clauses:
|
|
459
|
+
where_clause = " WHERE " + " AND ".join(where_clauses)
|
|
442
460
|
|
|
443
|
-
# Build the query with JOIN to images table to get reference image metadata
|
|
444
461
|
query = f"""
|
|
445
462
|
SELECT s.id, s.start, s.end, s.filter, s.imagetyp, s.object, s.telescop,
|
|
446
|
-
s.num_images, s.exptime_total, s.exptime, s.image_doc_id, i.metadata
|
|
463
|
+
s.num_images, s.exptime_total, s.exptime, s.image_doc_id, i.metadata, r.url as repo_url
|
|
447
464
|
FROM {self.SESSIONS_TABLE} s
|
|
448
465
|
LEFT JOIN {self.IMAGES_TABLE} i ON s.image_doc_id = i.id
|
|
466
|
+
LEFT JOIN {self.REPOS_TABLE} r ON i.repo_id = r.id
|
|
449
467
|
{where_clause}
|
|
450
468
|
"""
|
|
451
469
|
|
|
@@ -684,9 +702,7 @@ class Database:
|
|
|
684
702
|
|
|
685
703
|
return dict(row)
|
|
686
704
|
|
|
687
|
-
def upsert_session(
|
|
688
|
-
self, new: SessionRow, existing: SessionRow | None = None
|
|
689
|
-
) -> None:
|
|
705
|
+
def upsert_session(self, new: SessionRow, existing: SessionRow | None = None) -> None:
|
|
690
706
|
"""Insert or update a session record."""
|
|
691
707
|
cursor = self._db.cursor()
|
|
692
708
|
|
|
@@ -734,9 +750,7 @@ class Database:
|
|
|
734
750
|
new[get_column_name(Database.END_KEY)],
|
|
735
751
|
new.get(get_column_name(Database.FILTER_KEY)),
|
|
736
752
|
new[get_column_name(Database.IMAGETYP_KEY)],
|
|
737
|
-
normalize_target_name(
|
|
738
|
-
new.get(get_column_name(Database.OBJECT_KEY))
|
|
739
|
-
),
|
|
753
|
+
normalize_target_name(new.get(get_column_name(Database.OBJECT_KEY))),
|
|
740
754
|
new.get(get_column_name(Database.TELESCOP_KEY)),
|
|
741
755
|
new[get_column_name(Database.NUM_IMAGES_KEY)],
|
|
742
756
|
new[get_column_name(Database.EXPTIME_TOTAL_KEY)],
|
|
@@ -752,7 +766,7 @@ class Database:
|
|
|
752
766
|
self._db.close()
|
|
753
767
|
|
|
754
768
|
# Context manager support
|
|
755
|
-
def __enter__(self) ->
|
|
769
|
+
def __enter__(self) -> Database:
|
|
756
770
|
return self
|
|
757
771
|
|
|
758
772
|
def __exit__(self, exc_type, exc, tb) -> None:
|
starbash/defaults/starbash.toml
CHANGED
|
@@ -24,6 +24,7 @@ HaOiii = ["HaOiii", "HaO3"]
|
|
|
24
24
|
None = ["None"]
|
|
25
25
|
|
|
26
26
|
camera_osc = ["OSC", "ZWO ASI2600MC Duo"]
|
|
27
|
+
camera_seestar = ["Seestar", "Seestar S50", "Seestar S30", "Seestar S30 Pro"]
|
|
27
28
|
|
|
28
29
|
# Passes SII 672.4nm and H-Beta 486.1nm lines
|
|
29
30
|
# Capturing of the two main emission wavebands in the deep red and blue at the same time
|
starbash/exception.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UserHandledError(ValueError):
|
|
5
|
+
"""An exception that terminates processing of the current file, but we want to help the user fix the problem."""
|
|
6
|
+
|
|
7
|
+
def ask_user_handled(self) -> bool:
|
|
8
|
+
"""Prompt the user with a friendly message about the error.
|
|
9
|
+
Returns:
|
|
10
|
+
True if the error was handled, False otherwise.
|
|
11
|
+
"""
|
|
12
|
+
from starbash import console # Lazy import to avoid circular dependency
|
|
13
|
+
|
|
14
|
+
console.print(f"Error: {self}")
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
def __rich__(self) -> Any:
|
|
18
|
+
return self.__str__() # At least this is something readable...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["UserHandledError"]
|
starbash/main.py
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
2
5
|
import typer
|
|
3
|
-
from typing_extensions import Annotated
|
|
4
6
|
|
|
5
|
-
import starbash.url as url
|
|
6
7
|
import starbash
|
|
8
|
+
import starbash.url as url
|
|
7
9
|
|
|
8
|
-
from .app import Starbash, get_user_config_path, setup_logging
|
|
9
|
-
from .commands import info, process, repo, select, user
|
|
10
10
|
from . import console
|
|
11
|
+
from .analytics import is_development_environment
|
|
12
|
+
from .app import Starbash
|
|
13
|
+
from .commands import info, process, repo, select, user
|
|
14
|
+
from .paths import get_user_config_path
|
|
15
|
+
|
|
16
|
+
# Suppress deprecation warnings in production mode to provide a cleaner user experience.
|
|
17
|
+
# In development mode (VS Code, devcontainer, or SENTRY_ENVIRONMENT=development),
|
|
18
|
+
# all warnings are shown to help developers identify potential issues.
|
|
19
|
+
# See: is_development_environment() in analytics.py for detection logic.
|
|
20
|
+
if not is_development_environment():
|
|
21
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
11
22
|
|
|
12
23
|
app = typer.Typer(
|
|
13
24
|
rich_markup_mode="rich",
|
|
@@ -17,9 +28,7 @@ app.add_typer(user.app, name="user", help="Manage user settings.")
|
|
|
17
28
|
app.add_typer(repo.app, name="repo", help="Manage Starbash repositories.")
|
|
18
29
|
app.add_typer(select.app, name="select", help="Manage session and target selection.")
|
|
19
30
|
app.add_typer(info.app, name="info", help="Display system and data information.")
|
|
20
|
-
app.add_typer(
|
|
21
|
-
process.app, name="process", help="Process images using automated workflows."
|
|
22
|
-
)
|
|
31
|
+
app.add_typer(process.app, name="process", help="Process images using automated workflows.")
|
|
23
32
|
|
|
24
33
|
|
|
25
34
|
@app.callback(invoke_without_command=True)
|
|
@@ -32,11 +41,24 @@ def main_callback(
|
|
|
32
41
|
help="Enable debug logging output.",
|
|
33
42
|
),
|
|
34
43
|
] = False,
|
|
44
|
+
force: bool = typer.Option(
|
|
45
|
+
default=False,
|
|
46
|
+
help="Force reindexing/output file regeneration - even if unchanged.",
|
|
47
|
+
),
|
|
48
|
+
verbose: bool = typer.Option(
|
|
49
|
+
False,
|
|
50
|
+
"--verbose",
|
|
51
|
+
help="When providing responses, include all entries. Normally long responses are truncated.",
|
|
52
|
+
),
|
|
35
53
|
):
|
|
36
54
|
"""Main callback for the Starbash application."""
|
|
37
55
|
# Set the log level based on --debug flag
|
|
38
56
|
if debug:
|
|
39
57
|
starbash.log_filter_level = logging.DEBUG
|
|
58
|
+
if force:
|
|
59
|
+
starbash.force_regen = True
|
|
60
|
+
if verbose:
|
|
61
|
+
starbash.verbose_output = True
|
|
40
62
|
|
|
41
63
|
if ctx.invoked_subcommand is None:
|
|
42
64
|
if not get_user_config_path().exists():
|
starbash/paths.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
|
|
3
4
|
from platformdirs import PlatformDirs
|
|
4
5
|
|
|
5
6
|
app_name = "starbash"
|
|
@@ -7,35 +8,45 @@ app_author = "geeksville"
|
|
|
7
8
|
dirs = PlatformDirs(app_name, app_author)
|
|
8
9
|
config_dir = Path(dirs.user_config_dir)
|
|
9
10
|
data_dir = Path(dirs.user_data_dir)
|
|
11
|
+
cache_dir = Path(dirs.user_cache_dir)
|
|
10
12
|
documents_dir = Path(dirs.user_documents_dir) / "starbash"
|
|
11
13
|
|
|
12
14
|
# These can be overridden for testing
|
|
13
15
|
_override_config_dir: Path | None = None
|
|
14
16
|
_override_data_dir: Path | None = None
|
|
17
|
+
_override_cache_dir: Path | None = None
|
|
15
18
|
_override_documents_dir: Path | None = None
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
def set_test_directories(
|
|
19
22
|
config_dir_override: Path | None = None,
|
|
20
23
|
data_dir_override: Path | None = None,
|
|
24
|
+
cache_dir_override: Path | None = None,
|
|
21
25
|
documents_dir_override: Path | None = None,
|
|
22
26
|
) -> None:
|
|
23
27
|
"""Set override directories for testing. Used by test fixtures to isolate test data."""
|
|
24
|
-
global _override_config_dir, _override_data_dir, _override_documents_dir
|
|
28
|
+
global _override_config_dir, _override_data_dir, _override_cache_dir, _override_documents_dir
|
|
25
29
|
_override_config_dir = config_dir_override
|
|
26
30
|
_override_data_dir = data_dir_override
|
|
31
|
+
_override_cache_dir = cache_dir_override
|
|
27
32
|
_override_documents_dir = documents_dir_override
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
def get_user_config_dir() -> Path:
|
|
31
36
|
"""Get the user config directory. Returns test override if set, otherwise the real user directory."""
|
|
32
|
-
dir_to_use =
|
|
33
|
-
_override_config_dir if _override_config_dir is not None else config_dir
|
|
34
|
-
)
|
|
37
|
+
dir_to_use = _override_config_dir if _override_config_dir is not None else config_dir
|
|
35
38
|
os.makedirs(dir_to_use, exist_ok=True)
|
|
36
39
|
return dir_to_use
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
def get_user_config_path() -> Path:
|
|
43
|
+
"""Returns the path to the user config file (starbash.toml)."""
|
|
44
|
+
from repo import repo_suffix # Lazy import to avoid circular dependency
|
|
45
|
+
|
|
46
|
+
config_dir = get_user_config_dir()
|
|
47
|
+
return config_dir / repo_suffix
|
|
48
|
+
|
|
49
|
+
|
|
39
50
|
def get_user_data_dir() -> Path:
|
|
40
51
|
"""Get the user data directory. Returns test override if set, otherwise the real user directory."""
|
|
41
52
|
dir_to_use = _override_data_dir if _override_data_dir is not None else data_dir
|
|
@@ -43,12 +54,15 @@ def get_user_data_dir() -> Path:
|
|
|
43
54
|
return dir_to_use
|
|
44
55
|
|
|
45
56
|
|
|
57
|
+
def get_user_cache_dir() -> Path:
|
|
58
|
+
"""Get the user cache directory. Returns test override if set, otherwise the real user directory."""
|
|
59
|
+
dir_to_use = _override_cache_dir if _override_cache_dir is not None else cache_dir
|
|
60
|
+
os.makedirs(dir_to_use, exist_ok=True)
|
|
61
|
+
return dir_to_use
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
def get_user_documents_dir() -> Path:
|
|
47
65
|
"""Get the user documents directory. Returns test override if set, otherwise the real user directory."""
|
|
48
|
-
dir_to_use =
|
|
49
|
-
_override_documents_dir
|
|
50
|
-
if _override_documents_dir is not None
|
|
51
|
-
else documents_dir
|
|
52
|
-
)
|
|
66
|
+
dir_to_use = _override_documents_dir if _override_documents_dir is not None else documents_dir
|
|
53
67
|
os.makedirs(dir_to_use, exist_ok=True)
|
|
54
68
|
return dir_to_use
|