boom-client 0.1.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.
- boom_client-0.1.0/.env.example +16 -0
- boom_client-0.1.0/.github/workflows/format.yml +13 -0
- boom_client-0.1.0/.github/workflows/publish.yml +47 -0
- boom_client-0.1.0/.github/workflows/test.yml +25 -0
- boom_client-0.1.0/.gitignore +19 -0
- boom_client-0.1.0/.pre-commit-config.yaml +23 -0
- boom_client-0.1.0/LICENSE +21 -0
- boom_client-0.1.0/Makefile +22 -0
- boom_client-0.1.0/PKG-INFO +334 -0
- boom_client-0.1.0/README.md +302 -0
- boom_client-0.1.0/examples/quickstart.py +92 -0
- boom_client-0.1.0/pyproject.toml +107 -0
- boom_client-0.1.0/src/boom/__init__.py +71 -0
- boom_client-0.1.0/src/boom/cache.py +218 -0
- boom_client-0.1.0/src/boom/client.py +821 -0
- boom_client-0.1.0/src/boom/config.py +115 -0
- boom_client-0.1.0/src/boom/exceptions.py +36 -0
- boom_client-0.1.0/src/boom/models.py +356 -0
- boom_client-0.1.0/src/boom/objects.py +486 -0
- boom_client-0.1.0/src/boom/photometry.py +227 -0
- boom_client-0.1.0/src/boom/projections.py +90 -0
- boom_client-0.1.0/src/boom/py.typed +0 -0
- boom_client-0.1.0/tests/__init__.py +0 -0
- boom_client-0.1.0/tests/conftest.py +138 -0
- boom_client-0.1.0/tests/fixtures.py +139 -0
- boom_client-0.1.0/tests/test_cache.py +169 -0
- boom_client-0.1.0/tests/test_client.py +472 -0
- boom_client-0.1.0/tests/test_config.py +64 -0
- boom_client-0.1.0/tests/test_models.py +222 -0
- boom_client-0.1.0/tests/test_objects.py +256 -0
- boom_client-0.1.0/tests/test_photometry.py +164 -0
- boom_client-0.1.0/tests/test_projections.py +53 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# BOOM account credentials. Accounts on the main API are created by an
|
|
2
|
+
# admin: there is no self-service signup. If you only have a Babamul
|
|
3
|
+
# account, use the babamul package instead.
|
|
4
|
+
BOOM_API_USER=your-username
|
|
5
|
+
BOOM_API_PASSWORD=your-password
|
|
6
|
+
|
|
7
|
+
# Which deployment to talk to: production (Caltech, default), backup
|
|
8
|
+
# (UMN) or local.
|
|
9
|
+
# BOOM_ENV=production
|
|
10
|
+
|
|
11
|
+
# Full override of the API base URL. Wins over BOOM_ENV.
|
|
12
|
+
# BOOM_API_URL=http://localhost:4000
|
|
13
|
+
|
|
14
|
+
# On-disk cache. Unset means no caching at all.
|
|
15
|
+
# BOOM_CACHE_DIR=~/.cache/boom
|
|
16
|
+
# BOOM_CACHE_MAX_AGE=86400
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build distribution 📦
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0 # Fetch all history and tags for versioning
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.x"
|
|
19
|
+
- name: Setup uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
- name: Install pypa/build
|
|
22
|
+
run: python3 -m pip install build --user
|
|
23
|
+
- name: Build a binary wheel and a source tarball
|
|
24
|
+
run: python3 -m build
|
|
25
|
+
- name: Store the distribution packages
|
|
26
|
+
uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: python-package-distributions
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish-to-pypi:
|
|
32
|
+
name: Publish Python 🐍 distribution 📦 to PyPI
|
|
33
|
+
needs: [build]
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: pypi
|
|
37
|
+
url: https://pypi.org/p/boom-client/
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write # mandatory for trusted publishing
|
|
40
|
+
steps:
|
|
41
|
+
- name: Download all the dists
|
|
42
|
+
uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: python-package-distributions
|
|
45
|
+
path: dist/
|
|
46
|
+
- name: Publish distribution to PyPI
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, windows-latest]
|
|
15
|
+
python-version: ["3.11", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Setup uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Run tests
|
|
23
|
+
run: uv run pytest
|
|
24
|
+
- name: Check types
|
|
25
|
+
run: uv run mypy src/boom
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: "v4.4.0"
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-case-conflict
|
|
6
|
+
- id: check-merge-conflict
|
|
7
|
+
- id: end-of-file-fixer
|
|
8
|
+
- id: trailing-whitespace
|
|
9
|
+
|
|
10
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
+
rev: "v0.6.3"
|
|
12
|
+
hooks:
|
|
13
|
+
- id: ruff
|
|
14
|
+
args: [--fix, --unsafe-fixes]
|
|
15
|
+
types_or: [python, jupyter]
|
|
16
|
+
- id: ruff-format
|
|
17
|
+
types_or: [python, jupyter]
|
|
18
|
+
|
|
19
|
+
- repo: https://github.com/kynan/nbstripout
|
|
20
|
+
rev: "0.7.1"
|
|
21
|
+
hooks:
|
|
22
|
+
- id: nbstripout
|
|
23
|
+
args: [--drop-empty-cells]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BOOM developers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.PHONY: help
|
|
2
|
+
help: ## Show this help.
|
|
3
|
+
@uv run python -c "import re; \
|
|
4
|
+
[[print(f'\033[36m{m[0]:<20}\033[0m {m[1]}') for m in re.findall(r'^([a-zA-Z_-]+):.*?## (.*)$$', open(makefile).read(), re.M)] for makefile in ('$(MAKEFILE_LIST)').strip().split()]"
|
|
5
|
+
|
|
6
|
+
.PHONY: format
|
|
7
|
+
format: ## Automatically format files.
|
|
8
|
+
@echo "🚀 Linting code with pre-commit (prek)"
|
|
9
|
+
@uv run prek run -a
|
|
10
|
+
|
|
11
|
+
.PHONY: test
|
|
12
|
+
test: ## Test the code with pytest.
|
|
13
|
+
@echo "🚀 Testing code with pytest"
|
|
14
|
+
@uv run pytest
|
|
15
|
+
|
|
16
|
+
.PHONY: check-types
|
|
17
|
+
check-types: ## Check types with mypy.
|
|
18
|
+
@echo "🚀 Checking types with mypy"
|
|
19
|
+
@uv run mypy src/boom
|
|
20
|
+
|
|
21
|
+
.PHONY: check
|
|
22
|
+
check: format check-types test ## Run everything CI runs.
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: boom-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the BOOM API: catalog queries, cone searches and aggregation pipelines
|
|
5
|
+
Project-URL: Homepage, https://github.com/boom-astro/boom-client
|
|
6
|
+
Project-URL: Repository, https://github.com/boom-astro/boom-client
|
|
7
|
+
Project-URL: Issues, https://github.com/boom-astro/boom-client/issues
|
|
8
|
+
Author: BOOM Team
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: astronomy,boom,catalogs,lsst,mongodb,transients,ztf
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: astropy>=7.2.0
|
|
23
|
+
Requires-Dist: babamul>=0.1.0
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: numpy>=1.26.0
|
|
26
|
+
Requires-Dist: pandas>=3.0.0
|
|
27
|
+
Requires-Dist: pyarrow>=15.0.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Provides-Extra: jupyter
|
|
30
|
+
Requires-Dist: ipywidgets>=8.0.0; extra == 'jupyter'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# boom-client
|
|
34
|
+
|
|
35
|
+
Python client for the **main BOOM API**: free-form Mongo queries over the
|
|
36
|
+
catalogs, cone searches, aggregation pipelines and cutouts.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from boom import BoomClient, get_source
|
|
40
|
+
|
|
41
|
+
client = BoomClient() # reads BOOM_API_USER / BOOM_API_PASSWORD
|
|
42
|
+
|
|
43
|
+
client.catalog_names()
|
|
44
|
+
# ['LSST_alerts', 'LSST_alerts_aux', 'ZTF_alerts', 'ZTF_alerts_aux', ...]
|
|
45
|
+
|
|
46
|
+
src = get_source(client, "ZTF", "ZTF18abcdefg")
|
|
47
|
+
src.ndethist, src.peak_mag, src.age, src.redshift
|
|
48
|
+
src.plot_lightcurve()
|
|
49
|
+
src.get_detections()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Is this the package you want?
|
|
53
|
+
|
|
54
|
+
BOOM exposes two APIs, with two account systems.
|
|
55
|
+
|
|
56
|
+
| | `babamul` | `boom-client` (this one) |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| API | `/babamul/*`, curated and public | the main API |
|
|
59
|
+
| Account | free self-service signup | provisioned by an admin |
|
|
60
|
+
| Auth | `BABAMUL_API_TOKEN` (`bbml_…`) | username and password |
|
|
61
|
+
| Surface | fixed endpoints: alerts, objects, cutouts, cone search | arbitrary `find`, `count`, `pipeline` on any readable catalog |
|
|
62
|
+
| Analysis | alert models and plotting | the same, plus typed sources, cleaned photometry and a disk cache |
|
|
63
|
+
| Real-time | yes, Kafka streams | no, pull only |
|
|
64
|
+
|
|
65
|
+
If you signed up on the Babamul website, you want
|
|
66
|
+
[`babamul`](https://github.com/boom-astro/babamul). A Babamul token is
|
|
67
|
+
rejected by the main API, and this client says so rather than returning a
|
|
68
|
+
bare 401.
|
|
69
|
+
|
|
70
|
+
This package **depends on** `babamul` and reuses its alert models, light
|
|
71
|
+
curve merging and plotting, so the two behave the same where they overlap.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install boom-client
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
| Variable | Meaning |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `BOOM_API_USER` | BOOM account username |
|
|
84
|
+
| `BOOM_API_PASSWORD` | BOOM account password |
|
|
85
|
+
| `BOOM_ENV` | `production` (Caltech, default), `backup` (UMN) or `local` |
|
|
86
|
+
| `BOOM_API_URL` | Full base URL override, wins over `BOOM_ENV` |
|
|
87
|
+
| `BOOM_CACHE_DIR` | Enables the on-disk cache. Unset means no caching |
|
|
88
|
+
| `BOOM_CACHE_MAX_AGE` | Cache entry lifetime in seconds (default 86400) |
|
|
89
|
+
|
|
90
|
+
Credentials can also be passed directly:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
client = BoomClient(username="alice", password="...", url="http://localhost:4000")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The client logs in lazily on the first request and renews its token when
|
|
97
|
+
the API rejects it, so one instance survives a long notebook session.
|
|
98
|
+
|
|
99
|
+
## Querying
|
|
100
|
+
|
|
101
|
+
### find
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
alerts = client.find(
|
|
105
|
+
"ZTF_alerts",
|
|
106
|
+
{"objectId": "ZTF18abcdefg"},
|
|
107
|
+
projection={"candidate.jd": 1, "candidate.magpsf": 1},
|
|
108
|
+
sort={"candidate.jd": -1},
|
|
109
|
+
limit=10,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
alert = client.find_one("ZTF_alerts", {"_id": 2695378462115015007})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For anything larger than a page, use `iter_find`, which pages with
|
|
116
|
+
skip/limit and sorts on `_id` so pages neither overlap nor skip rows:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
for alert in client.iter_find("ZTF_alerts", {"candidate.drb": {"$gt": 0.9}}, limit=50_000):
|
|
120
|
+
...
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### count
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
client.count("ZTF_alerts", {"objectId": "ZTF18abcdefg"}) # exact, uses the index
|
|
127
|
+
client.estimated_count("ZTF_alerts") # metadata only, instant
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### cone_search and near_search
|
|
131
|
+
|
|
132
|
+
Two different questions. `cone_search` answers "everything inside this
|
|
133
|
+
radius", in no particular order, for many positions at once. `near_search`
|
|
134
|
+
answers "what is closest", one position at a time, and tags each result
|
|
135
|
+
with a `distance_arcsec`. Cross-matching wants the second.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
client.near_search("NED", 189.1, 28.4, radius_arcsec=10)
|
|
139
|
+
# [{'_id': 'NGC1234', 'z': 0.031, ..., 'distance_arcsec': 2.3}, ...]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`cone_search` accepts a `SkyCoord`, an `(ra, dec)` pair, a list of pairs,
|
|
143
|
+
or a `{name: (ra, dec)}` mapping. Results come back keyed the same way.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import astropy.units as u
|
|
147
|
+
from astropy.coordinates import SkyCoord
|
|
148
|
+
|
|
149
|
+
client.cone_search("ZTF_alerts_aux", SkyCoord("12h34m56s +30d00m00s"), 5 * u.arcsec)
|
|
150
|
+
|
|
151
|
+
client.cone_search(
|
|
152
|
+
"LSST_alerts_aux",
|
|
153
|
+
{"sn2024abc": (189.1, 28.4), "sn2024def": (201.7, -5.2)},
|
|
154
|
+
3,
|
|
155
|
+
"arcsec",
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### pipeline
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
client.pipeline("ZTF_alerts", [
|
|
163
|
+
{"$match": {"candidate.jd": {"$gt": 2460000.5}}},
|
|
164
|
+
{"$group": {"_id": "$objectId", "n": {"$sum": 1}}},
|
|
165
|
+
{"$sort": {"n": -1}},
|
|
166
|
+
{"$limit": 20},
|
|
167
|
+
])
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Inspecting catalogs
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
client.catalogs(details=True) # with storage statistics
|
|
174
|
+
client.catalog_indexes("ZTF_alerts") # check before a big find
|
|
175
|
+
client.catalog_sample("ZTF_alerts", 3) # random documents, to see the schema
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Objects, sources and photometry
|
|
179
|
+
|
|
180
|
+
An object's data is split across two collections: `{SURVEY}_alerts` holds
|
|
181
|
+
one document per alert, `{SURVEY}_alerts_aux` holds the full photometric
|
|
182
|
+
history and the cross-matches. There are two levels on top of that join.
|
|
183
|
+
|
|
184
|
+
### get_object: the raw document
|
|
185
|
+
|
|
186
|
+
Merges the two, in the shape the `babamul` tools already read, and finds
|
|
187
|
+
the counterpart in the other survey, by recorded alias where there is one
|
|
188
|
+
and by position otherwise.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from boom import get_object, plot_lightcurve, plot_cutouts
|
|
192
|
+
|
|
193
|
+
obj = get_object(client, "ZTF", "ZTF18abcdefg")
|
|
194
|
+
obj["prv_candidates"], obj["fp_hists"], obj["cross_matches"]
|
|
195
|
+
obj["survey_matches"]["lsst"]
|
|
196
|
+
|
|
197
|
+
plot_lightcurve(obj)
|
|
198
|
+
plot_cutouts(client, "ZTF", object_id="ZTF18abcdefg", which="brightest")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Only the fields this package reads are requested, since an untrimmed aux
|
|
202
|
+
document for an old ZTF object runs to megabytes. Pass `trim=False` for
|
|
203
|
+
everything, or use `boom.projections` to build your own.
|
|
204
|
+
|
|
205
|
+
### get_source: the typed source
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from boom import get_source
|
|
209
|
+
|
|
210
|
+
src = get_source(client, "ZTF", "ZTF18abcdefg")
|
|
211
|
+
|
|
212
|
+
src.objectid, src.ztfid, src.lsstid # identity across surveys
|
|
213
|
+
src.ra, src.dec, src.jd
|
|
214
|
+
src.redshift, src.redshift_origin # host, from the cross-matches
|
|
215
|
+
src.offset, src.host_origin # separation to that host
|
|
216
|
+
|
|
217
|
+
src.ndethist, src.ndetfilters, src.filters
|
|
218
|
+
src.jdstarthist, src.jdendhist, src.age
|
|
219
|
+
src.peak_mag, src.median_ra, src.median_dec
|
|
220
|
+
|
|
221
|
+
src.get_photometry() # everything
|
|
222
|
+
src.get_detections(min_snr=5.0) # real, positive detections only
|
|
223
|
+
src.plot_lightcurve()
|
|
224
|
+
src.to_json(), Source.from_json(blob)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### The photometry table
|
|
228
|
+
|
|
229
|
+
Both `photometry(obj)` and `Source.get_photometry()` return the same
|
|
230
|
+
schema, one row per measurement:
|
|
231
|
+
|
|
232
|
+
| column | meaning |
|
|
233
|
+
|---|---|
|
|
234
|
+
| `jd`, `mjd` | epoch |
|
|
235
|
+
| `band` | filter |
|
|
236
|
+
| `magpsf`, `sigmapsf` | PSF magnitude and its error |
|
|
237
|
+
| `diffmaglim` | limiting magnitude of the visit |
|
|
238
|
+
| `snr` | signal to noise |
|
|
239
|
+
| `ra`, `dec` | measured position |
|
|
240
|
+
| `psf_flux`, `psf_flux_err` | difference flux |
|
|
241
|
+
| `isdiffpos` | the difference is positive, i.e. the source brightened |
|
|
242
|
+
| `det_type` | `alert`, `fp` (forced photometry) or `ul` (upper limit) |
|
|
243
|
+
| `survey` | which survey produced the row |
|
|
244
|
+
|
|
245
|
+
The three histories overlap in time, so epochs already reported as alerts
|
|
246
|
+
are dropped from the forced photometry and the upper limits. Rows the
|
|
247
|
+
survey flags as unusable are dropped too: low real-bogus score, too many
|
|
248
|
+
bad pixels or a shallow limit for ZTF, low reliability and the dipole,
|
|
249
|
+
centroid, pixel and glint flags for LSST. Pass `quality_cuts=False` to see
|
|
250
|
+
everything the pipeline stored, and read `boom.photometry.CUTS` for the
|
|
251
|
+
exact thresholds.
|
|
252
|
+
|
|
253
|
+
A negative difference is not a cut. LSST's `isNegative` and ZTF's
|
|
254
|
+
`isdiffpos` record that the source faded relative to the reference, which
|
|
255
|
+
is a measurement, not a defect: those rows stay in the table, flagged by
|
|
256
|
+
`isdiffpos`, and it is `get_detections()` that leaves them out. Cutting
|
|
257
|
+
them earlier would delete most of a variable source's light curve.
|
|
258
|
+
|
|
259
|
+
## Caching
|
|
260
|
+
|
|
261
|
+
Off unless `BOOM_CACHE_DIR` is set. Entries expire after
|
|
262
|
+
`BOOM_CACHE_MAX_AGE` (a day by default) because alert catalogs keep
|
|
263
|
+
growing and a permanently cached object would hide new photometry.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from boom import Cache, Source
|
|
267
|
+
|
|
268
|
+
cache = Cache("~/.cache/boom", max_age=3600)
|
|
269
|
+
src = get_source(client, "ZTF", "ZTF18abcdefg", cache=cache)
|
|
270
|
+
Source.load(cache, "ZTF18abcdefg")
|
|
271
|
+
cache.clear("sources")
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Layout under the cache directory, all keyed by object id:
|
|
275
|
+
|
|
276
|
+
| namespace | format | holds |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| `objects/` | JSON | merged alert and history documents |
|
|
279
|
+
| `sources/` | Parquet | one row of source metadata and summaries |
|
|
280
|
+
| `photometry/` | Parquet | the measurement table |
|
|
281
|
+
| `crossmatches/` | JSON | the cross-match documents |
|
|
282
|
+
|
|
283
|
+
Writes go through a temporary file, so an interrupted run leaves no half
|
|
284
|
+
entry, and a corrupt or stale file reads as a miss rather than an error.
|
|
285
|
+
|
|
286
|
+
A cache built one object at a time reads back as a sample:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
cache.load_frames("sources") # one row per cached source, with summaries
|
|
290
|
+
cache.load_frames("photometry") # every measurement of every cached source
|
|
291
|
+
cache.keys("sources") # what is on disk
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Raw queries are never cached: caching arbitrary Mongo filters keyed by
|
|
295
|
+
their own text is a good way to serve stale results without noticing.
|
|
296
|
+
|
|
297
|
+
## Things to know before you hammer the API
|
|
298
|
+
|
|
299
|
+
- **Nothing bounds a query server-side.** The API applies no maximum
|
|
300
|
+
`limit` and no default time budget, so an unselective filter on an
|
|
301
|
+
unindexed field runs as a collection scan against production Mongo.
|
|
302
|
+
This client sends `max_time_ms=60000` by default; pass
|
|
303
|
+
`max_time_ms=None` to remove it, deliberately.
|
|
304
|
+
- **`$out` and `$merge` write to the database.** The server passes
|
|
305
|
+
pipeline stages straight to Mongo with no allowlist. This client
|
|
306
|
+
refuses those two stages; everything else is on you.
|
|
307
|
+
- **A malformed query comes back as 500, not 400.** The server hands the
|
|
308
|
+
filter to Mongo and wraps the resulting error in a 500, so this client
|
|
309
|
+
does not retry 500 at all: retrying a syntax error would cost five
|
|
310
|
+
round-trips against production Mongo and delay the message by twenty
|
|
311
|
+
seconds without ever succeeding. Transient 502, 503, 504 and 429 are
|
|
312
|
+
still retried.
|
|
313
|
+
- **404 also means "no access".** Catalogs beginning with `watchlist_`
|
|
314
|
+
are gated per account, and the API answers 404 rather than 403 for
|
|
315
|
+
them, so `APINotFoundError` can mean either.
|
|
316
|
+
- **Catalog schemas are internal.** Field names follow the Rust
|
|
317
|
+
ingestion pipeline and change without a deprecation window. Pin what
|
|
318
|
+
matters in your own code rather than assuming stability.
|
|
319
|
+
|
|
320
|
+
## Development
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
uv sync
|
|
324
|
+
make test # pytest, fully offline against a mock transport
|
|
325
|
+
make check-types # mypy --strict
|
|
326
|
+
make format # ruff via prek
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The test suite never touches the network: it serves a fake API through
|
|
330
|
+
`httpx.MockTransport`, so no credentials are needed to run it.
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT
|