isgri 0.5.1__tar.gz → 0.6.0__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.
- {isgri-0.5.1 → isgri-0.6.0}/PKG-INFO +5 -25
- {isgri-0.5.1 → isgri-0.6.0}/README.md +4 -24
- {isgri-0.5.1 → isgri-0.6.0}/pyproject.toml +2 -2
- isgri-0.6.0/src/isgri/__version__.py +1 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/catalog/scwquery.py +42 -0
- isgri-0.6.0/src/isgri/cli/__init__.py +1 -0
- isgri-0.5.1/src/isgri/cli.py → isgri-0.6.0/src/isgri/cli/main.py +168 -224
- isgri-0.6.0/src/isgri/cli/query.py +172 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/catalog/test_scwquery.py +119 -19
- isgri-0.5.1/tests/test_cli.py → isgri-0.6.0/tests/cli/test_main.py +71 -90
- isgri-0.5.1/src/isgri/__version__.py +0 -1
- {isgri-0.5.1 → isgri-0.6.0}/.gitignore +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/.python-version +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/LICENSE +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/demo/data/255900280010_isgri_model.fits.gz +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/demo/data/isgri_events.fits.gz +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/demo/lightcurve_walkthrough.ipynb +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/demo/scwquery_walkthrough.ipynb +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/__init__.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/catalog/__init__.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/catalog/builder.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/catalog/wcs.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/config.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/__init__.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/file_loaders.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/lightcurve.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/pif.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/quality.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/src/isgri/utils/time_conversion.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/__init__.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/catalog/test_wcs.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/test_config.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/__init__.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/test_file_loaders.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/test_lightcurve.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/test_pif.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/test_quality.py +0 -0
- {isgri-0.5.1 → isgri-0.6.0}/tests/utils/test_time_conversion.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: isgri
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Python package for INTEGRAL IBIS/ISGRI lightcurve analysis
|
|
5
5
|
Author: Dominik Patryk Pacholski
|
|
6
6
|
License: MIT
|
|
@@ -55,6 +55,9 @@ pip install isgri
|
|
|
55
55
|
# Configure default paths (once)
|
|
56
56
|
isgri config-set --catalog ~/data/scw_catalog.fits
|
|
57
57
|
|
|
58
|
+
# Interactive method
|
|
59
|
+
isgri query
|
|
60
|
+
|
|
58
61
|
# Query by time range
|
|
59
62
|
isgri query --tstart 2010-01-01 --tstop 2010-12-31
|
|
60
63
|
|
|
@@ -138,27 +141,4 @@ Local config file `isgri_config.toml` in current directory overrides global conf
|
|
|
138
141
|
- **CLI Reference**: Run `isgri --help` or `isgri <command> --help`
|
|
139
142
|
- **Catalog Tutorial**: [scwquery_walkthrough.ipynb](https://github.com/dominp/isgri/blob/main/demo/scwquery_walkthrough.ipynb)
|
|
140
143
|
- **Light Curve Tutorial**: [lightcurve_walkthrough.ipynb](https://github.com/dominp/isgri/blob/main/demo/lightcurve_walkthrough.ipynb)
|
|
141
|
-
- **API Reference**: Use `help()` in Python or see docstrings
|
|
142
|
-
|
|
143
|
-
## Project Structure
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
isgri/
|
|
147
|
-
├── catalog/ # SCW catalog query tools
|
|
148
|
-
│ ├── scwquery.py # Main query interface
|
|
149
|
-
│ └── wcs.py # Coordinate transformations
|
|
150
|
-
├── utils/ # Light curve analysis utilities
|
|
151
|
-
│ ├── lightcurve.py # Light curve class
|
|
152
|
-
│ ├── quality.py # Quality metrics
|
|
153
|
-
│ ├── pif.py # PIF tools
|
|
154
|
-
│ ├── file_loaders.py
|
|
155
|
-
│ └── time_conversion.py
|
|
156
|
-
├── config.py # Configuration management
|
|
157
|
-
└── cli.py # Command line interface
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Requirements
|
|
161
|
-
|
|
162
|
-
- Python ≥ 3.10
|
|
163
|
-
- astropy
|
|
164
|
-
- numpy
|
|
144
|
+
- **API Reference**: Use `help()` in Python or see docstrings
|
|
@@ -39,6 +39,9 @@ pip install isgri
|
|
|
39
39
|
# Configure default paths (once)
|
|
40
40
|
isgri config-set --catalog ~/data/scw_catalog.fits
|
|
41
41
|
|
|
42
|
+
# Interactive method
|
|
43
|
+
isgri query
|
|
44
|
+
|
|
42
45
|
# Query by time range
|
|
43
46
|
isgri query --tstart 2010-01-01 --tstop 2010-12-31
|
|
44
47
|
|
|
@@ -122,27 +125,4 @@ Local config file `isgri_config.toml` in current directory overrides global conf
|
|
|
122
125
|
- **CLI Reference**: Run `isgri --help` or `isgri <command> --help`
|
|
123
126
|
- **Catalog Tutorial**: [scwquery_walkthrough.ipynb](https://github.com/dominp/isgri/blob/main/demo/scwquery_walkthrough.ipynb)
|
|
124
127
|
- **Light Curve Tutorial**: [lightcurve_walkthrough.ipynb](https://github.com/dominp/isgri/blob/main/demo/lightcurve_walkthrough.ipynb)
|
|
125
|
-
- **API Reference**: Use `help()` in Python or see docstrings
|
|
126
|
-
|
|
127
|
-
## Project Structure
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
isgri/
|
|
131
|
-
├── catalog/ # SCW catalog query tools
|
|
132
|
-
│ ├── scwquery.py # Main query interface
|
|
133
|
-
│ └── wcs.py # Coordinate transformations
|
|
134
|
-
├── utils/ # Light curve analysis utilities
|
|
135
|
-
│ ├── lightcurve.py # Light curve class
|
|
136
|
-
│ ├── quality.py # Quality metrics
|
|
137
|
-
│ ├── pif.py # PIF tools
|
|
138
|
-
│ ├── file_loaders.py
|
|
139
|
-
│ └── time_conversion.py
|
|
140
|
-
├── config.py # Configuration management
|
|
141
|
-
└── cli.py # Command line interface
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Requirements
|
|
145
|
-
|
|
146
|
-
- Python ≥ 3.10
|
|
147
|
-
- astropy
|
|
148
|
-
- numpy
|
|
128
|
+
- **API Reference**: Use `help()` in Python or see docstrings
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "isgri"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
authors = [{name = "Dominik Patryk Pacholski"}]
|
|
9
9
|
license = {text = "MIT"}
|
|
10
10
|
description = "Python package for INTEGRAL IBIS/ISGRI lightcurve analysis"
|
|
@@ -23,4 +23,4 @@ dependencies = [
|
|
|
23
23
|
where = ["src"]
|
|
24
24
|
|
|
25
25
|
[project.scripts]
|
|
26
|
-
isgri = "isgri.cli:main"
|
|
26
|
+
isgri = "isgri.cli.main:main"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -344,6 +344,48 @@ class ScwQuery:
|
|
|
344
344
|
combined_mask &= filt.mask
|
|
345
345
|
return self.catalog[combined_mask]
|
|
346
346
|
|
|
347
|
+
def write(
|
|
348
|
+
self, output_path: Union[str, Path], swid_only: Optional[str] = False, overwrite: Optional[bool] = False
|
|
349
|
+
):
|
|
350
|
+
"""Write filtered catalog to the file.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
output_path : str or Path
|
|
355
|
+
Path to output file. Format auto-detected from extension:
|
|
356
|
+
- .txt: SWID list (one per line)
|
|
357
|
+
- .fits: FITS table
|
|
358
|
+
- .csv: CSV table
|
|
359
|
+
swid_only : bool, optional
|
|
360
|
+
If True, write only SWID list regardless of extension
|
|
361
|
+
overwrite : bool, optional
|
|
362
|
+
Whether to overwrite existing file, by default False
|
|
363
|
+
|
|
364
|
+
Raises
|
|
365
|
+
------
|
|
366
|
+
FileExistsError
|
|
367
|
+
If file exists and overwrite=False
|
|
368
|
+
|
|
369
|
+
Examples
|
|
370
|
+
--------
|
|
371
|
+
>>> query.time(tstart=3000).write("filtered_scws.fits", overwrite=True)
|
|
372
|
+
>>> query.quality(max_chi=2.0).write("good_scws.txt", swid_only=True)
|
|
373
|
+
>>> query.write("scws.csv")
|
|
374
|
+
>>> query.write("output", swid_only=True) # Force SWID list regardless of extension
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
results = self.get()
|
|
378
|
+
if isinstance(output_path, str):
|
|
379
|
+
output_path = Path(output_path)
|
|
380
|
+
if output_path.exists() and not overwrite:
|
|
381
|
+
raise FileExistsError(f"Output file already exists: {output_path}")
|
|
382
|
+
if swid_only or output_path.suffix == ".txt":
|
|
383
|
+
with open(output_path, "w") as f:
|
|
384
|
+
for swid in results["SWID"]:
|
|
385
|
+
f.write(f"{swid}\n")
|
|
386
|
+
else:
|
|
387
|
+
results.write(output_path, overwrite=overwrite)
|
|
388
|
+
|
|
347
389
|
def count(self) -> int:
|
|
348
390
|
"""
|
|
349
391
|
Count SCWs matching current filters.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import main
|
|
@@ -1,224 +1,168 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@click.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@main.command()
|
|
171
|
-
@click.option("--archive", type=click.Path(), help="INTEGRAL archive directory path")
|
|
172
|
-
@click.option("--catalog", type=click.Path(), help="Catalog FITS file path")
|
|
173
|
-
def config_set(archive, catalog):
|
|
174
|
-
"""
|
|
175
|
-
Set configuration values.
|
|
176
|
-
|
|
177
|
-
Set default paths for archive directory and/or catalog file.
|
|
178
|
-
Paths are expanded (~ becomes home directory) and resolved to absolute paths.
|
|
179
|
-
Warns if path doesn't exist but allows setting anyway.
|
|
180
|
-
|
|
181
|
-
Examples:
|
|
182
|
-
|
|
183
|
-
Set archive path:
|
|
184
|
-
|
|
185
|
-
isgri config-set --archive /anita/archivio/
|
|
186
|
-
|
|
187
|
-
Set catalog path:
|
|
188
|
-
|
|
189
|
-
isgri config-set --catalog ~/data/scw_catalog.fits
|
|
190
|
-
|
|
191
|
-
Set both at once:
|
|
192
|
-
|
|
193
|
-
isgri config-set --archive /anita/archivio/ --catalog ~/data/scw_catalog.fits
|
|
194
|
-
"""
|
|
195
|
-
if not archive and not catalog:
|
|
196
|
-
click.echo("Error: Specify at least one option (--archive or --catalog)", err=True)
|
|
197
|
-
raise click.Abort()
|
|
198
|
-
|
|
199
|
-
cfg = Config()
|
|
200
|
-
|
|
201
|
-
if archive:
|
|
202
|
-
archive_path = Path(archive).expanduser().resolve()
|
|
203
|
-
if not archive_path.exists():
|
|
204
|
-
click.echo(f"Warning: Archive path does not exist: {archive_path}", err=True)
|
|
205
|
-
if not click.confirm("Set anyway?"):
|
|
206
|
-
raise click.Abort()
|
|
207
|
-
cfg.set(archive_path=archive_path)
|
|
208
|
-
click.echo(f"✓ Archive path set to: {archive_path}")
|
|
209
|
-
|
|
210
|
-
if catalog:
|
|
211
|
-
catalog_path = Path(catalog).expanduser().resolve()
|
|
212
|
-
if not catalog_path.exists():
|
|
213
|
-
click.echo(f"Warning: Catalog file does not exist: {catalog_path}", err=True)
|
|
214
|
-
if not click.confirm("Set anyway?"):
|
|
215
|
-
raise click.Abort()
|
|
216
|
-
cfg.set(catalog_path=catalog_path)
|
|
217
|
-
click.echo(f"✓ Catalog path set to: {catalog_path}")
|
|
218
|
-
|
|
219
|
-
click.echo()
|
|
220
|
-
click.echo(f"Configuration saved to: {cfg.path}")
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if __name__ == "__main__":
|
|
224
|
-
main()
|
|
1
|
+
import click
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from ..catalog import ScwQuery
|
|
4
|
+
from ..__version__ import __version__
|
|
5
|
+
from ..config import Config
|
|
6
|
+
from .query import query_direct, query_interactive
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
@click.version_option(version=__version__)
|
|
11
|
+
def main():
|
|
12
|
+
"""ISGRI - INTEGRAL/ISGRI data analysis toolkit."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@main.command()
|
|
17
|
+
@click.option("--catalog", type=click.Path(), help="Path to catalog FITS file. If not provided, uses config value.")
|
|
18
|
+
@click.option("--tstart", help="Start time (YYYY-MM-DD or IJD)")
|
|
19
|
+
@click.option("--tstop", help="Stop time (YYYY-MM-DD or IJD)")
|
|
20
|
+
@click.option("--ra", help="Right ascension (degrees or HH:MM:SS)")
|
|
21
|
+
@click.option("--dec", help="Declination (degrees or DD:MM:SS)")
|
|
22
|
+
@click.option("--radius", type=float, help="Angular separation (degrees)")
|
|
23
|
+
@click.option("--fov", type=click.Choice(["full", "any"]), default="any", help="Field of view mode")
|
|
24
|
+
@click.option("--max-chi", type=float, help="Maximum chi-squared value")
|
|
25
|
+
@click.option("--chi-type", type=click.Choice(["RAW", "CUT", "GTI"]), default="CUT", help="Type of chi-squared value")
|
|
26
|
+
@click.option("--revolution", help="Revolution number")
|
|
27
|
+
@click.option(
|
|
28
|
+
"--output", "-o", type=click.Path(), help="Output file (.fits or .csv or any if --list-swids or --count)"
|
|
29
|
+
)
|
|
30
|
+
@click.option("--list-swids", is_flag=True, help="Only output SWID list")
|
|
31
|
+
@click.option("--count", is_flag=True, help="Only show count")
|
|
32
|
+
def query(catalog, tstart, tstop, ra, dec, radius, fov, max_chi, chi_type, revolution, output, list_swids, count):
|
|
33
|
+
"""
|
|
34
|
+
Query INTEGRAL science window catalog.
|
|
35
|
+
|
|
36
|
+
If no catalog path is provided, uses the default from configuration.
|
|
37
|
+
Multiple filters can be combined.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
Query by time range (IJD):
|
|
41
|
+
|
|
42
|
+
isgri query --tstart 3000 --tstop 3100
|
|
43
|
+
|
|
44
|
+
Query by time range (ISO date):
|
|
45
|
+
|
|
46
|
+
isgri query --tstart 2010-01-01 --tstop 2010-12-31
|
|
47
|
+
|
|
48
|
+
Query by sky position:
|
|
49
|
+
|
|
50
|
+
isgri query --ra 83.63 --dec 22.01 --fov full
|
|
51
|
+
isgri query --ra 83.63 --dec 22.01 --radius 5.0
|
|
52
|
+
|
|
53
|
+
Query with quality cut:
|
|
54
|
+
|
|
55
|
+
isgri query --max-chi 2.0 --chi-type CUT
|
|
56
|
+
|
|
57
|
+
Save results to file:
|
|
58
|
+
|
|
59
|
+
isgri query --tstart 3000 --tstop 3100 --output results.fits
|
|
60
|
+
|
|
61
|
+
Get only SWID list:
|
|
62
|
+
|
|
63
|
+
isgri query --tstart 3000 --tstop 3100 --list-swids
|
|
64
|
+
|
|
65
|
+
Count matching science windows:
|
|
66
|
+
|
|
67
|
+
isgri query --ra 83.63 --dec 22.01 --count
|
|
68
|
+
"""
|
|
69
|
+
if catalog is None:
|
|
70
|
+
cfg = Config()
|
|
71
|
+
catalog = cfg.catalog_path
|
|
72
|
+
|
|
73
|
+
if not catalog:
|
|
74
|
+
click.echo("Error: No catalog configured", err=True)
|
|
75
|
+
raise click.Abort()
|
|
76
|
+
|
|
77
|
+
if any(param is not None for param in [tstart, tstop, ra, dec, radius, max_chi, revolution]):
|
|
78
|
+
query_direct(
|
|
79
|
+
catalog, tstart, tstop, ra, dec, radius, fov, max_chi, chi_type, revolution, output, list_swids, count
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
query_interactive(catalog)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@main.command()
|
|
86
|
+
def config():
|
|
87
|
+
"""
|
|
88
|
+
Show current configuration.
|
|
89
|
+
|
|
90
|
+
Displays paths to config file, archive directory, and catalog file,
|
|
91
|
+
along with their existence status.
|
|
92
|
+
"""
|
|
93
|
+
cfg = Config()
|
|
94
|
+
|
|
95
|
+
click.echo(f"Config file: {cfg.path}")
|
|
96
|
+
click.echo(f" Exists: {cfg.path.exists()}")
|
|
97
|
+
click.echo()
|
|
98
|
+
|
|
99
|
+
archive = cfg.archive_path
|
|
100
|
+
click.echo(f"Archive path: {archive if archive else '(not set)'}")
|
|
101
|
+
if archive:
|
|
102
|
+
click.echo(f" Exists: {archive.exists()}")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
catalog = cfg.catalog_path
|
|
106
|
+
click.echo(f"Catalog path: {catalog if catalog else '(not set)'}")
|
|
107
|
+
if catalog:
|
|
108
|
+
click.echo(f" Exists: {catalog.exists()}")
|
|
109
|
+
except FileNotFoundError as e:
|
|
110
|
+
click.echo(f"Catalog path: (configured but file not found)")
|
|
111
|
+
click.echo(f" Error: {e}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@main.command()
|
|
115
|
+
@click.option("--archive", type=click.Path(), help="INTEGRAL archive directory path")
|
|
116
|
+
@click.option("--catalog", type=click.Path(), help="Catalog FITS file path")
|
|
117
|
+
def config_set(archive, catalog):
|
|
118
|
+
"""
|
|
119
|
+
Set configuration values.
|
|
120
|
+
|
|
121
|
+
Set default paths for archive directory and/or catalog file.
|
|
122
|
+
Paths are expanded (~ becomes home directory) and resolved to absolute paths.
|
|
123
|
+
Warns if path doesn't exist but allows setting anyway.
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
|
|
127
|
+
Set archive path:
|
|
128
|
+
|
|
129
|
+
isgri config-set --archive /anita/archivio/
|
|
130
|
+
|
|
131
|
+
Set catalog path:
|
|
132
|
+
|
|
133
|
+
isgri config-set --catalog ~/data/scw_catalog.fits
|
|
134
|
+
|
|
135
|
+
Set both at once:
|
|
136
|
+
|
|
137
|
+
isgri config-set --archive /anita/archivio/ --catalog ~/data/scw_catalog.fits
|
|
138
|
+
"""
|
|
139
|
+
if not archive and not catalog:
|
|
140
|
+
click.echo("Error: Specify at least one option (--archive or --catalog)", err=True)
|
|
141
|
+
raise click.Abort()
|
|
142
|
+
|
|
143
|
+
cfg = Config()
|
|
144
|
+
|
|
145
|
+
if archive:
|
|
146
|
+
archive_path = Path(archive).expanduser().resolve()
|
|
147
|
+
if not archive_path.exists():
|
|
148
|
+
click.echo(f"Warning: Archive path does not exist: {archive_path}", err=True)
|
|
149
|
+
if not click.confirm("Set anyway?"):
|
|
150
|
+
raise click.Abort()
|
|
151
|
+
cfg.set(archive_path=archive_path)
|
|
152
|
+
click.echo(f"✓ Archive path set to: {archive_path}")
|
|
153
|
+
|
|
154
|
+
if catalog:
|
|
155
|
+
catalog_path = Path(catalog).expanduser().resolve()
|
|
156
|
+
if not catalog_path.exists():
|
|
157
|
+
click.echo(f"Warning: Catalog file does not exist: {catalog_path}", err=True)
|
|
158
|
+
if not click.confirm("Set anyway?"):
|
|
159
|
+
raise click.Abort()
|
|
160
|
+
cfg.set(catalog_path=catalog_path)
|
|
161
|
+
click.echo(f"✓ Catalog path set to: {catalog_path}")
|
|
162
|
+
|
|
163
|
+
click.echo()
|
|
164
|
+
click.echo(f"Configuration saved to: {cfg.path}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from ..catalog import ScwQuery
|
|
4
|
+
from ..config import Config
|
|
5
|
+
from ..__version__ import __version__
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_time(time_str):
|
|
9
|
+
"""
|
|
10
|
+
Parse time string as IJD float or ISO date string.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
time_str : str or None
|
|
15
|
+
Time as "YYYY-MM-DD" or IJD number
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
float or str or None
|
|
20
|
+
Parsed time value
|
|
21
|
+
"""
|
|
22
|
+
if time_str is None:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
return float(time_str)
|
|
27
|
+
except ValueError:
|
|
28
|
+
return time_str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_coord(coord):
|
|
32
|
+
"""
|
|
33
|
+
Parse RA and Dec strings as float degrees or sexagesimal strings.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
coord : str or None
|
|
38
|
+
Coordinate as float degrees or sexagesimal string
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
float or str or None
|
|
42
|
+
Parsed coordinate value
|
|
43
|
+
"""
|
|
44
|
+
if coord is None:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
return float(coord)
|
|
49
|
+
except ValueError:
|
|
50
|
+
return coord
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def query_direct(
|
|
54
|
+
catalog_path, tstart, tstop, ra, dec, radius, fov, max_chi, chi_type, revolution, output, list_swids, count
|
|
55
|
+
):
|
|
56
|
+
try:
|
|
57
|
+
q = ScwQuery(catalog_path)
|
|
58
|
+
initial_count = len(q.catalog)
|
|
59
|
+
# Parse times (handle both IJD and ISO)
|
|
60
|
+
tstart = parse_time(tstart)
|
|
61
|
+
tstop = parse_time(tstop)
|
|
62
|
+
|
|
63
|
+
# Apply filters
|
|
64
|
+
if tstart or tstop:
|
|
65
|
+
q = q.time(tstart=tstart, tstop=tstop)
|
|
66
|
+
|
|
67
|
+
if ra is not None and dec is not None:
|
|
68
|
+
ra = parse_coord(ra)
|
|
69
|
+
dec = parse_coord(dec)
|
|
70
|
+
if radius is not None:
|
|
71
|
+
q = q.position(ra=ra, dec=dec, radius=radius)
|
|
72
|
+
else:
|
|
73
|
+
q = q.position(ra=ra, dec=dec, fov_mode=fov)
|
|
74
|
+
|
|
75
|
+
if max_chi is not None:
|
|
76
|
+
q = q.quality(max_chi=max_chi, chi_type=chi_type)
|
|
77
|
+
|
|
78
|
+
if revolution:
|
|
79
|
+
q = q.revolution(revolution)
|
|
80
|
+
|
|
81
|
+
results = q.get()
|
|
82
|
+
|
|
83
|
+
if count:
|
|
84
|
+
click.echo(len(results))
|
|
85
|
+
|
|
86
|
+
elif list_swids:
|
|
87
|
+
for swid in results["SWID"]:
|
|
88
|
+
click.echo(swid)
|
|
89
|
+
|
|
90
|
+
elif output:
|
|
91
|
+
if output.endswith(".csv"):
|
|
92
|
+
results.write(output, format="ascii.csv", overwrite=True)
|
|
93
|
+
else:
|
|
94
|
+
results.write(output, format="fits", overwrite=True)
|
|
95
|
+
click.echo(f"Saved {len(results)} SCWs to {output}")
|
|
96
|
+
|
|
97
|
+
else:
|
|
98
|
+
click.echo(f"Found {len(results)}/{initial_count} SCWs")
|
|
99
|
+
if len(results) > 0:
|
|
100
|
+
display_cols = ["SWID", "TSTART", "TSTOP", "RA_SCX", "DEC_SCX"]
|
|
101
|
+
chi_col = f"{chi_type}_CHI" if chi_type != "RAW" else "CHI"
|
|
102
|
+
if chi_col in results.colnames:
|
|
103
|
+
display_cols.append(chi_col)
|
|
104
|
+
click.echo(results[display_cols][:10])
|
|
105
|
+
if len(results) > 10:
|
|
106
|
+
click.echo(f"... and {len(results) - 10} more")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
click.echo(f"Error: {e}", err=True)
|
|
110
|
+
raise click.Abort()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def query_interactive(catalog_path):
|
|
114
|
+
"""Run interactive query session."""
|
|
115
|
+
click.echo("=== Interactive Query Mode ===\n")
|
|
116
|
+
|
|
117
|
+
q = ScwQuery(catalog_path)
|
|
118
|
+
click.echo(f"Loaded {len(q.catalog)} SCWs")
|
|
119
|
+
click.echo("Type 'help' for commands\n")
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
try:
|
|
123
|
+
cmd = click.prompt("query>", default="").strip().lower()
|
|
124
|
+
|
|
125
|
+
if cmd in ("exit", "quit", "q"):
|
|
126
|
+
break
|
|
127
|
+
elif cmd == "help":
|
|
128
|
+
click.echo("Commands: time, pos, quality, show, reset, save, exit")
|
|
129
|
+
elif cmd == "time":
|
|
130
|
+
tstart = click.prompt("Start", default="", show_default=False)
|
|
131
|
+
tstop = click.prompt("Stop", default="", show_default=False)
|
|
132
|
+
tstart = parse_time(tstart) if tstart else None
|
|
133
|
+
tstop = parse_time(tstop) if tstop else None
|
|
134
|
+
q = q.time(tstart=tstart or None, tstop=tstop or None)
|
|
135
|
+
click.echo(f"→ {q.count()} SCWs")
|
|
136
|
+
elif cmd == "pos":
|
|
137
|
+
ra = click.prompt("RA")
|
|
138
|
+
dec = click.prompt("Dec")
|
|
139
|
+
mode = click.prompt("Mode", type=click.Choice(["fov", "radius"]), default="fov")
|
|
140
|
+
if mode == "radius":
|
|
141
|
+
radius = click.prompt("Radius (deg)", type=float, default=10.0)
|
|
142
|
+
q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), radius=radius)
|
|
143
|
+
else:
|
|
144
|
+
fov_mode = click.prompt("FOV mode", type=click.Choice(["full", "any"]), default="any")
|
|
145
|
+
q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), fov_mode=fov_mode)
|
|
146
|
+
click.echo(f"→ {q.count()} SCWs")
|
|
147
|
+
|
|
148
|
+
elif cmd == "quality":
|
|
149
|
+
max_chi = click.prompt("Max chi-squared", type=float)
|
|
150
|
+
chi_type = click.prompt("Chi type", type=click.Choice(["RAW", "CUT", "GTI"]), default="CUT")
|
|
151
|
+
q = q.quality(max_chi=max_chi, chi_type=chi_type)
|
|
152
|
+
click.echo(f"→ {q.count()} SCWs")
|
|
153
|
+
|
|
154
|
+
elif cmd == "show":
|
|
155
|
+
results = q.get()
|
|
156
|
+
click.echo(f"\n{len(results)} SCWs:")
|
|
157
|
+
click.echo(results[["SWID", "TSTART", "TSTOP"]])
|
|
158
|
+
elif cmd == "reset":
|
|
159
|
+
q = q.reset()
|
|
160
|
+
click.echo(f"→ {len(q.catalog)} SCWs")
|
|
161
|
+
elif cmd == "save":
|
|
162
|
+
only_scws = click.confirm("Save only SWID list?", default=False)
|
|
163
|
+
path = click.prompt("File")
|
|
164
|
+
q.write(path, overwrite=True, swid_only=only_scws)
|
|
165
|
+
click.echo(f"✓ Saved")
|
|
166
|
+
else:
|
|
167
|
+
click.echo(f"Unknown: {cmd}")
|
|
168
|
+
|
|
169
|
+
except KeyboardInterrupt:
|
|
170
|
+
click.echo("\nUse 'exit' to quit")
|
|
171
|
+
except Exception as e:
|
|
172
|
+
click.echo(f"Error: {e}", err=True)
|
|
@@ -377,31 +377,34 @@ def test_scwquery_uses_config(tmp_path, mock_catalog, monkeypatch):
|
|
|
377
377
|
"""Test ScwQuery uses config when no path provided."""
|
|
378
378
|
config_path = tmp_path / "config.toml"
|
|
379
379
|
cfg = Config(config_path)
|
|
380
|
-
cfg.set(catalog_path=mock_catalog)
|
|
380
|
+
cfg.set(catalog_path=mock_catalog)
|
|
381
381
|
|
|
382
382
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
383
383
|
|
|
384
384
|
query = ScwQuery()
|
|
385
385
|
assert str(query.catalog_path) == mock_catalog
|
|
386
|
-
assert len(query.catalog) == 1000
|
|
386
|
+
assert len(query.catalog) == 1000
|
|
387
|
+
|
|
387
388
|
|
|
388
389
|
def test_scwquery_explicit_path_overrides_config(tmp_path, mock_catalog, monkeypatch):
|
|
389
390
|
"""Test explicit path overrides config."""
|
|
390
391
|
# Create a second catalog
|
|
391
392
|
n_scw = 50
|
|
392
|
-
other_catalog = Table(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
393
|
+
other_catalog = Table(
|
|
394
|
+
{
|
|
395
|
+
"SWID": [f"{i:012d}" for i in range(n_scw)],
|
|
396
|
+
"REVOL": np.random.randint(100, 500, n_scw),
|
|
397
|
+
"TSTART": np.linspace(5000, 6000, n_scw),
|
|
398
|
+
"TSTOP": np.linspace(5001, 6001, n_scw),
|
|
399
|
+
"RA_SCX": np.random.uniform(0, 360, n_scw),
|
|
400
|
+
"DEC_SCX": np.random.uniform(-80, 80, n_scw),
|
|
401
|
+
"RA_SCZ": np.random.uniform(0, 360, n_scw),
|
|
402
|
+
"DEC_SCZ": np.random.uniform(-80, 80, n_scw),
|
|
403
|
+
"CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
404
|
+
"CUT_CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
405
|
+
"GTI_CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
406
|
+
}
|
|
407
|
+
)
|
|
405
408
|
other_path = tmp_path / "other_catalog.fits"
|
|
406
409
|
other_catalog.write(other_path, overwrite=True)
|
|
407
410
|
|
|
@@ -412,13 +415,12 @@ def test_scwquery_explicit_path_overrides_config(tmp_path, mock_catalog, monkeyp
|
|
|
412
415
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
413
416
|
query = ScwQuery()
|
|
414
417
|
assert query.catalog_path == Path(other_path)
|
|
415
|
-
assert len(query.catalog) == 50
|
|
416
|
-
|
|
418
|
+
assert len(query.catalog) == 50
|
|
417
419
|
|
|
418
420
|
# path should override config
|
|
419
421
|
query = ScwQuery(mock_catalog)
|
|
420
422
|
assert query.catalog_path == Path(mock_catalog)
|
|
421
|
-
assert len(query.catalog) == 1000
|
|
423
|
+
assert len(query.catalog) == 1000
|
|
422
424
|
|
|
423
425
|
|
|
424
426
|
def test_scwquery_no_config_raises(tmp_path, monkeypatch):
|
|
@@ -441,6 +443,104 @@ def test_scwquery_config_file_not_exists(tmp_path, monkeypatch):
|
|
|
441
443
|
|
|
442
444
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
443
445
|
|
|
444
|
-
# Should raise when file doesn't exist (in config.catalog_path property)
|
|
445
446
|
with pytest.raises(FileNotFoundError, match="Catalog path does not exist"):
|
|
446
447
|
ScwQuery()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_write_fits(tmp_path, mock_catalog):
|
|
451
|
+
"""Test writing to FITS file"""
|
|
452
|
+
query = ScwQuery(mock_catalog)
|
|
453
|
+
output = tmp_path / "results.fits"
|
|
454
|
+
|
|
455
|
+
query.time(tstart=3400, tstop=3600).write(output)
|
|
456
|
+
|
|
457
|
+
assert output.exists()
|
|
458
|
+
written = Table.read(output)
|
|
459
|
+
assert len(written) > 0
|
|
460
|
+
assert all(written["TSTOP"] >= 3400)
|
|
461
|
+
assert all(written["TSTART"] <= 3600)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_write_csv(tmp_path, mock_catalog):
|
|
465
|
+
"""Test writing to CSV file"""
|
|
466
|
+
query = ScwQuery(mock_catalog)
|
|
467
|
+
output = tmp_path / "results.csv"
|
|
468
|
+
|
|
469
|
+
query.quality(max_chi=2.0).write(output)
|
|
470
|
+
|
|
471
|
+
assert output.exists()
|
|
472
|
+
written = Table.read(output, format="ascii.csv")
|
|
473
|
+
assert len(written) > 0
|
|
474
|
+
assert all(written["CHI"] <= 2.0)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_write_swid_list_txt(tmp_path, mock_catalog):
|
|
478
|
+
"""Test writing SWID list to .txt file"""
|
|
479
|
+
query = ScwQuery(mock_catalog)
|
|
480
|
+
output = tmp_path / "swids.txt"
|
|
481
|
+
|
|
482
|
+
query.time(tstart=3500).write(output)
|
|
483
|
+
|
|
484
|
+
assert output.exists()
|
|
485
|
+
swids = output.read_text().strip().split("\n")
|
|
486
|
+
assert len(swids) > 0
|
|
487
|
+
assert all(len(swid) == 12 for swid in swids)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_write_swid_only_flag(tmp_path, mock_catalog):
|
|
491
|
+
"""Test swid_only flag forces SWID list"""
|
|
492
|
+
query = ScwQuery(mock_catalog)
|
|
493
|
+
output = tmp_path / "swids.fits" # .fits extension but force SWID
|
|
494
|
+
|
|
495
|
+
query.time(tstart=3500).write(output, swid_only=True)
|
|
496
|
+
|
|
497
|
+
assert output.exists()
|
|
498
|
+
# Should be text file despite .fits extension
|
|
499
|
+
swids = output.read_text().strip().split("\n")
|
|
500
|
+
assert len(swids) > 0
|
|
501
|
+
assert all(len(swid) == 12 for swid in swids)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_write_overwrite_false(tmp_path, mock_catalog):
|
|
505
|
+
"""Test write raises when file exists and overwrite=False"""
|
|
506
|
+
query = ScwQuery(mock_catalog)
|
|
507
|
+
output = tmp_path / "results.fits"
|
|
508
|
+
|
|
509
|
+
# Write once
|
|
510
|
+
query.write(output)
|
|
511
|
+
|
|
512
|
+
# Try to write again without overwrite
|
|
513
|
+
with pytest.raises(FileExistsError, match="already exists"):
|
|
514
|
+
query.write(output, overwrite=False)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def test_write_overwrite_true(tmp_path, mock_catalog):
|
|
518
|
+
"""Test write overwrites when overwrite=True"""
|
|
519
|
+
query = ScwQuery(mock_catalog)
|
|
520
|
+
output = tmp_path / "results.fits"
|
|
521
|
+
|
|
522
|
+
# First write
|
|
523
|
+
query.time(tstart=3400, tstop=3600).write(output)
|
|
524
|
+
first_result = Table.read(output)
|
|
525
|
+
|
|
526
|
+
# Overwrite with different data
|
|
527
|
+
query.time(tstart=3700, tstop=3800).write(output, overwrite=True)
|
|
528
|
+
second_result = Table.read(output)
|
|
529
|
+
|
|
530
|
+
# Check different time ranges (data should be different)
|
|
531
|
+
assert all(first_result["TSTART"] <= 3600)
|
|
532
|
+
assert all(second_result["TSTOP"] >= 3700)
|
|
533
|
+
assert first_result != second_result
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_write_empty_result(tmp_path, mock_catalog):
|
|
537
|
+
"""Test writing empty result set"""
|
|
538
|
+
query = ScwQuery(mock_catalog)
|
|
539
|
+
output = tmp_path / "empty.fits"
|
|
540
|
+
|
|
541
|
+
# Query that returns no results
|
|
542
|
+
query.time(tstart=10000).write(output)
|
|
543
|
+
|
|
544
|
+
assert output.exists()
|
|
545
|
+
result = Table.read(output)
|
|
546
|
+
assert len(result) == 0
|
|
@@ -17,98 +17,85 @@ def runner():
|
|
|
17
17
|
def mock_catalog(tmp_path):
|
|
18
18
|
"""Create mock SCW catalog."""
|
|
19
19
|
catalog_path = tmp_path / "test_catalog.fits"
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
n_scw = 100
|
|
22
22
|
data = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
"SWID": [f"{i:012d}" for i in range(n_scw)],
|
|
24
|
+
"REVOL": np.random.randint(100, 500, n_scw),
|
|
25
|
+
"TSTART": np.linspace(3000, 4000, n_scw),
|
|
26
|
+
"TSTOP": np.linspace(3000.5, 4000.5, n_scw),
|
|
27
|
+
"RA_SCX": np.random.uniform(0, 360, n_scw),
|
|
28
|
+
"DEC_SCX": np.random.uniform(-80, 80, n_scw),
|
|
29
|
+
"RA_SCZ": np.random.uniform(0, 360, n_scw),
|
|
30
|
+
"DEC_SCZ": np.random.uniform(-80, 80, n_scw),
|
|
31
|
+
"CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
32
|
+
"CUT_CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
33
|
+
"GTI_CHI": np.random.uniform(0.5, 5.0, n_scw),
|
|
34
34
|
}
|
|
35
35
|
table = Table(data)
|
|
36
|
-
table.write(catalog_path, format=
|
|
37
|
-
|
|
36
|
+
table.write(catalog_path, format="fits", overwrite=True)
|
|
37
|
+
|
|
38
38
|
return catalog_path
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def test_cli_version(runner):
|
|
42
42
|
"""Test --version flag."""
|
|
43
|
-
result = runner.invoke(main, [
|
|
43
|
+
result = runner.invoke(main, ["--version"])
|
|
44
44
|
assert result.exit_code == 0
|
|
45
|
-
assert
|
|
45
|
+
assert "version" in result.output.lower()
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def test_cli_help(runner):
|
|
49
49
|
"""Test --help flag."""
|
|
50
|
-
result = runner.invoke(main, [
|
|
50
|
+
result = runner.invoke(main, ["--help"])
|
|
51
51
|
assert result.exit_code == 0
|
|
52
|
-
assert
|
|
52
|
+
assert "ISGRI" in result.output
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def test_query_with_catalog(runner, mock_catalog):
|
|
56
56
|
"""Test query command with explicit catalog."""
|
|
57
|
-
result = runner.invoke(main, [
|
|
57
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog), "--tstart", "3000", "--tstop", "4000"])
|
|
58
58
|
assert result.exit_code == 0
|
|
59
|
-
assert
|
|
60
|
-
assert
|
|
59
|
+
assert "Found" in result.output
|
|
60
|
+
assert "100" in result.output
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def test_query_count(runner, mock_catalog):
|
|
64
64
|
"""Test query --count flag."""
|
|
65
|
-
result = runner.invoke(main, [
|
|
65
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog),"--tstart", "3000", "--count"])
|
|
66
66
|
assert result.exit_code == 0
|
|
67
|
-
assert result.output.strip() ==
|
|
67
|
+
assert result.output.strip() == "100"
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def test_query_list_swids(runner, mock_catalog):
|
|
71
71
|
"""Test query --list-swids flag."""
|
|
72
|
-
result = runner.invoke(main, [
|
|
72
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog), "--tstart", "3000","--list-swids"])
|
|
73
73
|
assert result.exit_code == 0
|
|
74
|
-
lines = result.output.strip().split(
|
|
74
|
+
lines = result.output.strip().split("\n")
|
|
75
75
|
assert len(lines) == 100
|
|
76
76
|
assert all(len(line) == 12 for line in lines)
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def test_query_with_filters(runner, mock_catalog):
|
|
80
80
|
"""Test query with time filters."""
|
|
81
|
-
result = runner.invoke(main, [
|
|
82
|
-
'query',
|
|
83
|
-
'--catalog', str(mock_catalog),
|
|
84
|
-
'--tstart', '3000',
|
|
85
|
-
'--tstop', '3100'
|
|
86
|
-
])
|
|
81
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog), "--tstart", "3000", "--tstop", "3100"])
|
|
87
82
|
assert result.exit_code == 0
|
|
88
|
-
assert
|
|
83
|
+
assert "Found" in result.output
|
|
89
84
|
|
|
90
85
|
|
|
91
86
|
def test_query_output_fits(runner, mock_catalog, tmp_path):
|
|
92
87
|
"""Test query with FITS output."""
|
|
93
88
|
output_file = tmp_path / "results.fits"
|
|
94
|
-
result = runner.invoke(main, [
|
|
95
|
-
'query',
|
|
96
|
-
'--catalog', str(mock_catalog),
|
|
97
|
-
'--output', str(output_file)
|
|
98
|
-
])
|
|
89
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog),"--tstart", "3000", "--output", str(output_file)])
|
|
99
90
|
assert result.exit_code == 0
|
|
100
91
|
assert output_file.exists()
|
|
101
|
-
assert
|
|
92
|
+
assert "Saved" in result.output
|
|
102
93
|
|
|
103
94
|
|
|
104
95
|
def test_query_output_csv(runner, mock_catalog, tmp_path):
|
|
105
96
|
"""Test query with CSV output."""
|
|
106
97
|
output_file = tmp_path / "results.csv"
|
|
107
|
-
result = runner.invoke(main, [
|
|
108
|
-
'query',
|
|
109
|
-
'--catalog', str(mock_catalog),
|
|
110
|
-
'--output', str(output_file)
|
|
111
|
-
])
|
|
98
|
+
result = runner.invoke(main, ["query", "--catalog", str(mock_catalog),"--tstart", "3000", "--output", str(output_file)])
|
|
112
99
|
assert result.exit_code == 0
|
|
113
100
|
assert output_file.exists()
|
|
114
101
|
|
|
@@ -117,10 +104,10 @@ def test_query_no_catalog_no_config(runner, tmp_path, monkeypatch):
|
|
|
117
104
|
"""Test query fails when no catalog and no config."""
|
|
118
105
|
config_path = tmp_path / "config.toml"
|
|
119
106
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
120
|
-
|
|
121
|
-
result = runner.invoke(main, [
|
|
107
|
+
|
|
108
|
+
result = runner.invoke(main, ["query"])
|
|
122
109
|
assert result.exit_code != 0
|
|
123
|
-
assert
|
|
110
|
+
assert "No catalog configured" in result.output
|
|
124
111
|
|
|
125
112
|
|
|
126
113
|
def test_query_uses_config(runner, mock_catalog, tmp_path, monkeypatch):
|
|
@@ -128,23 +115,23 @@ def test_query_uses_config(runner, mock_catalog, tmp_path, monkeypatch):
|
|
|
128
115
|
config_path = tmp_path / "config.toml"
|
|
129
116
|
cfg = Config(config_path)
|
|
130
117
|
cfg.set(catalog_path=mock_catalog)
|
|
131
|
-
|
|
118
|
+
|
|
132
119
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
133
|
-
|
|
134
|
-
result = runner.invoke(main, [
|
|
120
|
+
|
|
121
|
+
result = runner.invoke(main, ["query","--tstart", "3000", ])
|
|
135
122
|
assert result.exit_code == 0
|
|
136
|
-
assert
|
|
123
|
+
assert "Found 100" in result.output
|
|
137
124
|
|
|
138
125
|
|
|
139
126
|
def test_config_show_empty(runner, tmp_path, monkeypatch):
|
|
140
127
|
"""Test config command with empty config."""
|
|
141
128
|
config_path = tmp_path / "config.toml"
|
|
142
129
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
143
|
-
|
|
144
|
-
result = runner.invoke(main, [
|
|
130
|
+
|
|
131
|
+
result = runner.invoke(main, ["config"])
|
|
145
132
|
assert result.exit_code == 0
|
|
146
133
|
assert str(config_path) in result.output
|
|
147
|
-
assert
|
|
134
|
+
assert "(not set)" in result.output
|
|
148
135
|
|
|
149
136
|
|
|
150
137
|
def test_config_show_with_values(runner, tmp_path, mock_catalog, monkeypatch):
|
|
@@ -152,10 +139,10 @@ def test_config_show_with_values(runner, tmp_path, mock_catalog, monkeypatch):
|
|
|
152
139
|
config_path = tmp_path / "config.toml"
|
|
153
140
|
cfg = Config(config_path)
|
|
154
141
|
cfg.set(archive_path=tmp_path, catalog_path=mock_catalog)
|
|
155
|
-
|
|
142
|
+
|
|
156
143
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
157
|
-
|
|
158
|
-
result = runner.invoke(main, [
|
|
144
|
+
|
|
145
|
+
result = runner.invoke(main, ["config"])
|
|
159
146
|
assert result.exit_code == 0
|
|
160
147
|
assert str(tmp_path) in result.output
|
|
161
148
|
assert str(mock_catalog) in result.output
|
|
@@ -165,14 +152,14 @@ def test_config_set_archive(runner, tmp_path, monkeypatch):
|
|
|
165
152
|
"""Test config-set --archive."""
|
|
166
153
|
config_path = tmp_path / "test_config.toml"
|
|
167
154
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
168
|
-
|
|
155
|
+
|
|
169
156
|
archive_path = tmp_path / "archive"
|
|
170
157
|
archive_path.mkdir()
|
|
171
|
-
|
|
172
|
-
result = runner.invoke(main, [
|
|
158
|
+
|
|
159
|
+
result = runner.invoke(main, ["config-set", "--archive", str(archive_path)])
|
|
173
160
|
assert result.exit_code == 0
|
|
174
|
-
assert
|
|
175
|
-
|
|
161
|
+
assert "Archive path set" in result.output
|
|
162
|
+
|
|
176
163
|
cfg = Config(config_path)
|
|
177
164
|
assert cfg.archive_path == archive_path
|
|
178
165
|
|
|
@@ -181,11 +168,11 @@ def test_config_set_catalog(runner, tmp_path, mock_catalog, monkeypatch):
|
|
|
181
168
|
"""Test config-set --catalog."""
|
|
182
169
|
config_path = tmp_path / "test_config.toml"
|
|
183
170
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
184
|
-
|
|
185
|
-
result = runner.invoke(main, [
|
|
171
|
+
|
|
172
|
+
result = runner.invoke(main, ["config-set", "--catalog", str(mock_catalog)])
|
|
186
173
|
assert result.exit_code == 0
|
|
187
|
-
assert
|
|
188
|
-
|
|
174
|
+
assert "Catalog path set" in result.output
|
|
175
|
+
|
|
189
176
|
cfg = Config(config_path)
|
|
190
177
|
assert str(cfg.catalog_path) == str(mock_catalog)
|
|
191
178
|
|
|
@@ -194,50 +181,44 @@ def test_config_set_both(runner, tmp_path, mock_catalog, monkeypatch):
|
|
|
194
181
|
"""Test config-set with both options."""
|
|
195
182
|
config_path = tmp_path / "test_config.toml"
|
|
196
183
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
197
|
-
|
|
184
|
+
|
|
198
185
|
archive_path = tmp_path / "archive"
|
|
199
186
|
archive_path.mkdir()
|
|
200
|
-
|
|
201
|
-
result = runner.invoke(main, [
|
|
202
|
-
'config-set',
|
|
203
|
-
'--archive', str(archive_path),
|
|
204
|
-
'--catalog', str(mock_catalog)
|
|
205
|
-
])
|
|
187
|
+
|
|
188
|
+
result = runner.invoke(main, ["config-set", "--archive", str(archive_path), "--catalog", str(mock_catalog)])
|
|
206
189
|
assert result.exit_code == 0
|
|
207
|
-
assert
|
|
208
|
-
assert
|
|
190
|
+
assert "Archive path set" in result.output
|
|
191
|
+
assert "Catalog path set" in result.output
|
|
209
192
|
|
|
210
193
|
|
|
211
194
|
def test_config_set_no_options(runner):
|
|
212
195
|
"""Test config-set with no options fails."""
|
|
213
|
-
result = runner.invoke(main, [
|
|
196
|
+
result = runner.invoke(main, ["config-set"])
|
|
214
197
|
assert result.exit_code != 0
|
|
215
|
-
assert
|
|
198
|
+
assert "Specify at least one option" in result.output
|
|
216
199
|
|
|
217
200
|
|
|
218
201
|
def test_config_set_nonexistent_path_abort(runner, tmp_path, monkeypatch):
|
|
219
202
|
"""Test config-set with non-existent path (user aborts)."""
|
|
220
203
|
config_path = tmp_path / "test_config.toml"
|
|
221
204
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
222
|
-
|
|
223
|
-
result = runner.invoke(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
205
|
+
|
|
206
|
+
result = runner.invoke(
|
|
207
|
+
main, ["config-set", "--archive", "/nonexistent/path"], input="n\n"
|
|
208
|
+
) # User says 'no' to confirmation
|
|
209
|
+
|
|
228
210
|
assert result.exit_code != 0
|
|
229
|
-
assert
|
|
211
|
+
assert "Warning" in result.output
|
|
230
212
|
|
|
231
213
|
|
|
232
214
|
def test_config_set_nonexistent_path_confirm(runner, tmp_path, monkeypatch):
|
|
233
215
|
"""Test config-set with non-existent path (user confirms)."""
|
|
234
216
|
config_path = tmp_path / "test_config.toml"
|
|
235
217
|
monkeypatch.setattr(Config, "DEFAULT_PATH", config_path)
|
|
236
|
-
|
|
237
|
-
result = runner.invoke(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
218
|
+
|
|
219
|
+
result = runner.invoke(
|
|
220
|
+
main, ["config-set", "--archive", "/nonexistent/path"], input="y\n"
|
|
221
|
+
) # User says 'yes' to confirmation
|
|
222
|
+
|
|
242
223
|
assert result.exit_code == 0
|
|
243
|
-
assert
|
|
224
|
+
assert "Archive path set" in result.output
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|