timewise 0.5.4__tar.gz → 1.0.0a1__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.
Files changed (53) hide show
  1. timewise-1.0.0a1/PKG-INFO +205 -0
  2. timewise-1.0.0a1/README.md +154 -0
  3. timewise-1.0.0a1/pyproject.toml +96 -0
  4. timewise-1.0.0a1/timewise/__init__.py +1 -0
  5. timewise-1.0.0a1/timewise/backend/__init__.py +6 -0
  6. timewise-1.0.0a1/timewise/backend/base.py +36 -0
  7. timewise-1.0.0a1/timewise/backend/filesystem.py +80 -0
  8. timewise-1.0.0a1/timewise/chunking.py +50 -0
  9. timewise-1.0.0a1/timewise/cli.py +124 -0
  10. timewise-1.0.0a1/timewise/config.py +34 -0
  11. timewise-1.0.0a1/timewise/io/__init__.py +1 -0
  12. timewise-1.0.0a1/timewise/io/config.py +64 -0
  13. timewise-1.0.0a1/timewise/io/download.py +302 -0
  14. timewise-1.0.0a1/timewise/io/stable_tap.py +121 -0
  15. timewise-1.0.0a1/timewise/plot/__init__.py +3 -0
  16. timewise-1.0.0a1/timewise/plot/diagnostic.py +242 -0
  17. timewise-1.0.0a1/timewise/plot/lightcurve.py +112 -0
  18. timewise-1.0.0a1/timewise/plot/panstarrs.py +260 -0
  19. timewise-1.0.0a1/timewise/plot/sdss.py +109 -0
  20. timewise-1.0.0a1/timewise/process/__init__.py +2 -0
  21. timewise-1.0.0a1/timewise/process/config.py +30 -0
  22. timewise-1.0.0a1/timewise/process/interface.py +143 -0
  23. timewise-1.0.0a1/timewise/process/keys.py +10 -0
  24. timewise-1.0.0a1/timewise/process/stacking.py +310 -0
  25. timewise-1.0.0a1/timewise/process/template.yml +49 -0
  26. timewise-1.0.0a1/timewise/query/__init__.py +6 -0
  27. timewise-1.0.0a1/timewise/query/base.py +45 -0
  28. timewise-1.0.0a1/timewise/query/positional.py +40 -0
  29. timewise-1.0.0a1/timewise/tables/__init__.py +10 -0
  30. timewise-1.0.0a1/timewise/tables/allwise_p3as_mep.py +22 -0
  31. timewise-1.0.0a1/timewise/tables/base.py +9 -0
  32. timewise-1.0.0a1/timewise/tables/neowiser_p1bs_psd.py +22 -0
  33. timewise-1.0.0a1/timewise/types.py +30 -0
  34. timewise-1.0.0a1/timewise/util/backoff.py +12 -0
  35. timewise-1.0.0a1/timewise/util/csv_utils.py +12 -0
  36. timewise-1.0.0a1/timewise/util/error_threading.py +70 -0
  37. timewise-1.0.0a1/timewise/util/visits.py +33 -0
  38. timewise-0.5.4/PKG-INFO +0 -56
  39. timewise-0.5.4/README.md +0 -14
  40. timewise-0.5.4/pyproject.toml +0 -50
  41. timewise-0.5.4/timewise/__init__.py +0 -5
  42. timewise-0.5.4/timewise/big_parent_sample.py +0 -106
  43. timewise-0.5.4/timewise/cli.py +0 -18
  44. timewise-0.5.4/timewise/config_loader.py +0 -157
  45. timewise-0.5.4/timewise/general.py +0 -52
  46. timewise-0.5.4/timewise/parent_sample_base.py +0 -89
  47. timewise-0.5.4/timewise/point_source_utils.py +0 -68
  48. timewise-0.5.4/timewise/utils.py +0 -558
  49. timewise-0.5.4/timewise/wise_bigdata_desy_cluster.py +0 -1407
  50. timewise-0.5.4/timewise/wise_data_base.py +0 -2027
  51. timewise-0.5.4/timewise/wise_data_by_visit.py +0 -672
  52. timewise-0.5.4/timewise/wise_flux_conversion_correction.dat +0 -19
  53. {timewise-0.5.4 → timewise-1.0.0a1}/LICENSE +0 -0
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: timewise
3
+ Version: 1.0.0a1
4
+ Summary: Download WISE infrared data for many objects and process them with AMPEL
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Jannis Necker
8
+ Author-email: jannis.necker@gmail.com
9
+ Requires-Python: >=3.11,<3.12
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Provides-Extra: dev
14
+ Provides-Extra: docs
15
+ Requires-Dist: ampel-alerts (==0.10.3a5)
16
+ Requires-Dist: ampel-core (>=0.10.4.post0,<0.11.0)
17
+ Requires-Dist: ampel-photometry (>=0.10.1,<0.11.0)
18
+ Requires-Dist: ampel-plot (>=0.9.1,<0.10.0)
19
+ Requires-Dist: astropy (>=5.1,<6.0.0)
20
+ Requires-Dist: autodoc_pydantic[erdantic] (>=2.2.0,<3.0.0) ; extra == "docs"
21
+ Requires-Dist: backoff (>=2.1.2,<3.0.0)
22
+ Requires-Dist: coveralls (>=3.3.1,<4.0.0) ; extra == "dev"
23
+ Requires-Dist: jupyter[jupyter] (>=1.0.0,<2.0.0)
24
+ Requires-Dist: jupyterlab[jupyter] (>=4.0.6,<5.0.0)
25
+ Requires-Dist: matplotlib (>=3.5.3,<4.0.0)
26
+ Requires-Dist: mypy (>=1.18.2,<2.0.0) ; extra == "dev"
27
+ Requires-Dist: myst-parser (>=1,<3) ; extra == "docs"
28
+ Requires-Dist: numpy (>=1.23.2,<2.0.0)
29
+ Requires-Dist: pandas (>=1.4.3,<3.0.0)
30
+ Requires-Dist: pandas-stubs (>=2.3.2.250926,<3.0.0.0) ; extra == "dev"
31
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
32
+ Requires-Dist: pytest (>=7.2.2,<8.0.0) ; extra == "dev"
33
+ Requires-Dist: pyvo (>=1.7.0,<2.0.0)
34
+ Requires-Dist: requests (>=2.28.1,<3.0.0)
35
+ Requires-Dist: ruff (>=0.13.0,<0.14.0) ; extra == "dev"
36
+ Requires-Dist: scikit-image (>=0.19.3,<0.22.0)
37
+ Requires-Dist: scikit-learn (>=1.3.0,<2.0.0)
38
+ Requires-Dist: scipy-stubs (>=1.16.2.0,<2.0.0.0) ; extra == "dev"
39
+ Requires-Dist: seaborn (>=0.11.2,<0.14.0)
40
+ Requires-Dist: sphinx-rtd-theme (>=1.3.0,<2.0.0) ; extra == "docs"
41
+ Requires-Dist: tqdm (>=4.64.0,<5.0.0)
42
+ Requires-Dist: typer (>=0.19.2,<0.20.0)
43
+ Requires-Dist: types-pyyaml (>=6.0.12.20250915,<7.0.0.0) ; extra == "dev"
44
+ Requires-Dist: types-requests (>=2.32.4.20250913,<3.0.0.0) ; extra == "dev"
45
+ Requires-Dist: urllib3 (>=2.5.0,<3.0.0)
46
+ Requires-Dist: virtualenv (>=20.16.3,<21.0.0)
47
+ Project-URL: Bug Tracker, https://github.com/JannisNe/timewise/issues
48
+ Project-URL: Homepage, https://github.com/JannisNe/timewise
49
+ Description-Content-Type: text/markdown
50
+
51
+ [![CI](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml/badge.svg)](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml)
52
+ [![Coverage Status](https://coveralls.io/repos/github/JannisNe/timewise/badge.svg?branch=main)](https://coveralls.io/github/JannisNe/timewise?branch=main)
53
+ [![PyPI version](https://badge.fury.io/py/timewise.svg)](https://badge.fury.io/py/timewise)
54
+ [![DOI](https://zenodo.org/badge/449677569.svg)](https://zenodo.org/badge/latestdoi/449677569)
55
+
56
+
57
+ ![](timewise.png)
58
+ # Infrared light curves from WISE data
59
+
60
+ This package downloads WISE data for positions on the sky and stacks single-exposure photometry per visit
61
+
62
+ ## Prerequisites
63
+
64
+ `timewise` makes use of [AMPEL](https://ampelproject.github.io/ampelastro/) and needs a running [MongoDB](https://www.mongodb.com/).
65
+
66
+ ## Installation
67
+ The package can be installed via `pip`:
68
+ ```bash
69
+ pip install timewise
70
+ ```
71
+
72
+ To tell AMPEL which modules, aka units, to use, build the corresponding configuration file:
73
+ ```bash
74
+ ampel config build -distributions ampel timewise -stop-on-errors 0 -out <path-to-ampel-config-file>
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ ### Command line interface
80
+
81
+ ```
82
+ Usage: timewise [OPTIONS] COMMAND [ARGS]...
83
+
84
+ Timewsie CLI
85
+
86
+ ╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────╮
87
+ │ --log-level -l TEXT Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) │
88
+ │ [default: INFO] │
89
+ │ --install-completion Install completion for the current shell. │
90
+ │ --show-completion Show completion for the current shell, to copy it or customize the │
91
+ │ installation. │
92
+ │ --help Show this message and exit. │
93
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
94
+ ╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────╮
95
+ │ download Download WISE photometry from IRSA │
96
+ │ prepare-ampel Prepares the AMPEL job file so AMPEL can be run manually │
97
+ │ process Processes the lightcurves using AMPEL │
98
+ │ export Write stacked lightcurves to disk │
99
+ │ run-chain Run download, process and export │
100
+ │ plot Make diagnostic plots │
101
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
102
+
103
+ ```
104
+
105
+ The input is a CSV file with at least three columns:
106
+ - `orig_id`: an original identifier that **must** be an integer (for now)
107
+ - `ra`, `dec`: Right Ascension and Declination
108
+
109
+
110
+
111
+ `timewise` is configured with a YAML file. This is a sensible default which will use all single exposure photometry from AllWISE and NEOWISE:
112
+ ```yaml
113
+ download:
114
+ input_csv: <path-to-input>
115
+
116
+ backend:
117
+ type: filesystem
118
+ base_path: <path-to-working-directory>
119
+
120
+ queries:
121
+ - type: positional
122
+ radius_arcsec: 6
123
+ table:
124
+ name: allwise_p3as_mep
125
+ columns:
126
+ - ra
127
+ - dec
128
+ - mjd
129
+ - cntr_mf
130
+ - w1mpro_ep
131
+ - w1sigmpro_ep
132
+ - w2mpro_ep
133
+ - w2sigmpro_ep
134
+ - w1flux_ep
135
+ - w1sigflux_ep
136
+ - w2flux_ep
137
+ - w2sigflux_ep
138
+
139
+ - type: positional
140
+ radius_arcsec: 6
141
+ table:
142
+ name: neowiser_p1bs_psd
143
+ columns:
144
+ - ra
145
+ - dec
146
+ - mjd
147
+ - allwise_cntr
148
+ - w1mpro
149
+ - w1sigmpro
150
+ - w2mpro
151
+ - w2sigmpro
152
+ - w1flux
153
+ - w1sigflux
154
+ - w2flux
155
+ - w2sigflux
156
+
157
+ ampel:
158
+ mongo_db_name: <mongodb-name>
159
+ ```
160
+
161
+ This configuration file will be the input to all subcommands. Downloading and stacking can be run together or separate.
162
+
163
+
164
+ #### All-in-one:
165
+ Run download, stacking, and export:
166
+ ```bash
167
+ timewise run-chain <path-to-config-file> <path-to-ampel-config-file> <output-directory>
168
+ ```
169
+
170
+ #### Separate download and processing:
171
+ To only download the data:
172
+ ```bash
173
+ timewise download <path-to-config-file>
174
+ ```
175
+
176
+ To execute the stacking:
177
+ ```bash
178
+ timewise process <path-to-config-file> <path-to-ampel-config-file>
179
+ ```
180
+
181
+ #### Run AMPEL manually
182
+ Prepare an AMPEL job file for stacking the single-exposure data:
183
+ ```bash
184
+ timewise prepare-ampel <path-to-config-file>
185
+ ```
186
+ The result will contain the path to the prepared AMPEL job file that can be run with
187
+ ```bash
188
+ ampel job -config <path-to-ampel-config-file> -schema <path-to-ampel-job-file>
189
+ ```
190
+
191
+ #### Make some diagnostic plots
192
+ To check the datapoint selection and binning, take a quick look at the data:
193
+ ```bash
194
+ timewise plot <path-to-config-file> <indices-to-plot> <output-directory>
195
+ ```
196
+
197
+
198
+ ## Citation
199
+ If you use `timewise` please make sure to cite [Necker et al. A&A 695, A228 (2025)](https://www.aanda.org/articles/aa/abs/2025/03/aa51340-24/aa51340-24.html).
200
+ Additionally, you might want to include a reference to the specific version you are using: [![DOI](https://zenodo.org/badge/449677569.svg)](https://zenodo.org/badge/latestdoi/449677569)
201
+
202
+ ## Difference lightcurves
203
+ Make sure to check out `timewise-sup`, the Timewise Subtraction Pipeline:
204
+ [link](https://gitlab.desy.de/jannisnecker/timewise_sup).
205
+
@@ -0,0 +1,154 @@
1
+ [![CI](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml/badge.svg)](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml)
2
+ [![Coverage Status](https://coveralls.io/repos/github/JannisNe/timewise/badge.svg?branch=main)](https://coveralls.io/github/JannisNe/timewise?branch=main)
3
+ [![PyPI version](https://badge.fury.io/py/timewise.svg)](https://badge.fury.io/py/timewise)
4
+ [![DOI](https://zenodo.org/badge/449677569.svg)](https://zenodo.org/badge/latestdoi/449677569)
5
+
6
+
7
+ ![](timewise.png)
8
+ # Infrared light curves from WISE data
9
+
10
+ This package downloads WISE data for positions on the sky and stacks single-exposure photometry per visit
11
+
12
+ ## Prerequisites
13
+
14
+ `timewise` makes use of [AMPEL](https://ampelproject.github.io/ampelastro/) and needs a running [MongoDB](https://www.mongodb.com/).
15
+
16
+ ## Installation
17
+ The package can be installed via `pip`:
18
+ ```bash
19
+ pip install timewise
20
+ ```
21
+
22
+ To tell AMPEL which modules, aka units, to use, build the corresponding configuration file:
23
+ ```bash
24
+ ampel config build -distributions ampel timewise -stop-on-errors 0 -out <path-to-ampel-config-file>
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Command line interface
30
+
31
+ ```
32
+ Usage: timewise [OPTIONS] COMMAND [ARGS]...
33
+
34
+ Timewsie CLI
35
+
36
+ ╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────╮
37
+ │ --log-level -l TEXT Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) │
38
+ │ [default: INFO] │
39
+ │ --install-completion Install completion for the current shell. │
40
+ │ --show-completion Show completion for the current shell, to copy it or customize the │
41
+ │ installation. │
42
+ │ --help Show this message and exit. │
43
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
44
+ ╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────╮
45
+ │ download Download WISE photometry from IRSA │
46
+ │ prepare-ampel Prepares the AMPEL job file so AMPEL can be run manually │
47
+ │ process Processes the lightcurves using AMPEL │
48
+ │ export Write stacked lightcurves to disk │
49
+ │ run-chain Run download, process and export │
50
+ │ plot Make diagnostic plots │
51
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
52
+
53
+ ```
54
+
55
+ The input is a CSV file with at least three columns:
56
+ - `orig_id`: an original identifier that **must** be an integer (for now)
57
+ - `ra`, `dec`: Right Ascension and Declination
58
+
59
+
60
+
61
+ `timewise` is configured with a YAML file. This is a sensible default which will use all single exposure photometry from AllWISE and NEOWISE:
62
+ ```yaml
63
+ download:
64
+ input_csv: <path-to-input>
65
+
66
+ backend:
67
+ type: filesystem
68
+ base_path: <path-to-working-directory>
69
+
70
+ queries:
71
+ - type: positional
72
+ radius_arcsec: 6
73
+ table:
74
+ name: allwise_p3as_mep
75
+ columns:
76
+ - ra
77
+ - dec
78
+ - mjd
79
+ - cntr_mf
80
+ - w1mpro_ep
81
+ - w1sigmpro_ep
82
+ - w2mpro_ep
83
+ - w2sigmpro_ep
84
+ - w1flux_ep
85
+ - w1sigflux_ep
86
+ - w2flux_ep
87
+ - w2sigflux_ep
88
+
89
+ - type: positional
90
+ radius_arcsec: 6
91
+ table:
92
+ name: neowiser_p1bs_psd
93
+ columns:
94
+ - ra
95
+ - dec
96
+ - mjd
97
+ - allwise_cntr
98
+ - w1mpro
99
+ - w1sigmpro
100
+ - w2mpro
101
+ - w2sigmpro
102
+ - w1flux
103
+ - w1sigflux
104
+ - w2flux
105
+ - w2sigflux
106
+
107
+ ampel:
108
+ mongo_db_name: <mongodb-name>
109
+ ```
110
+
111
+ This configuration file will be the input to all subcommands. Downloading and stacking can be run together or separate.
112
+
113
+
114
+ #### All-in-one:
115
+ Run download, stacking, and export:
116
+ ```bash
117
+ timewise run-chain <path-to-config-file> <path-to-ampel-config-file> <output-directory>
118
+ ```
119
+
120
+ #### Separate download and processing:
121
+ To only download the data:
122
+ ```bash
123
+ timewise download <path-to-config-file>
124
+ ```
125
+
126
+ To execute the stacking:
127
+ ```bash
128
+ timewise process <path-to-config-file> <path-to-ampel-config-file>
129
+ ```
130
+
131
+ #### Run AMPEL manually
132
+ Prepare an AMPEL job file for stacking the single-exposure data:
133
+ ```bash
134
+ timewise prepare-ampel <path-to-config-file>
135
+ ```
136
+ The result will contain the path to the prepared AMPEL job file that can be run with
137
+ ```bash
138
+ ampel job -config <path-to-ampel-config-file> -schema <path-to-ampel-job-file>
139
+ ```
140
+
141
+ #### Make some diagnostic plots
142
+ To check the datapoint selection and binning, take a quick look at the data:
143
+ ```bash
144
+ timewise plot <path-to-config-file> <indices-to-plot> <output-directory>
145
+ ```
146
+
147
+
148
+ ## Citation
149
+ If you use `timewise` please make sure to cite [Necker et al. A&A 695, A228 (2025)](https://www.aanda.org/articles/aa/abs/2025/03/aa51340-24/aa51340-24.html).
150
+ Additionally, you might want to include a reference to the specific version you are using: [![DOI](https://zenodo.org/badge/449677569.svg)](https://zenodo.org/badge/latestdoi/449677569)
151
+
152
+ ## Difference lightcurves
153
+ Make sure to check out `timewise-sup`, the Timewise Subtraction Pipeline:
154
+ [link](https://gitlab.desy.de/jannisnecker/timewise_sup).
@@ -0,0 +1,96 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=2.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [project]
6
+ name = "timewise"
7
+ version = "1.0.0a1"
8
+ description = "Download WISE infrared data for many objects and process them with AMPEL"
9
+ authors = [
10
+ { name = "Jannis Necker", email = "jannis.necker@gmail.com" },
11
+ ]
12
+ license = { text = "MIT" }
13
+ readme = "README.md"
14
+ requires-python = ">=3.11,<3.12"
15
+ dependencies = [
16
+ "tqdm>=4.64.0,<5.0.0",
17
+ "requests>=2.28.1,<3.0.0",
18
+ "pandas>=1.4.3,<3.0.0",
19
+ "numpy>=1.23.2,<2.0.0",
20
+ "pyvo>=1.7.0,<2.0.0",
21
+ "astropy>=5.1,<6.0.0",
22
+ "matplotlib>=3.5.3,<4.0.0",
23
+ "scikit-image>=0.19.3,<0.22.0",
24
+ "backoff>=2.1.2,<3.0.0",
25
+ "virtualenv>=20.16.3,<21.0.0",
26
+ "seaborn>=0.11.2,<0.14.0",
27
+ "pydantic>=2.0.0,<3.0.0",
28
+ "scikit-learn>=1.3.0,<2.0.0",
29
+ "jupyterlab[jupyter]>=4.0.6,<5.0.0",
30
+ "jupyter[jupyter]>=1.0.0,<2.0.0",
31
+ "ampel-alerts (==0.10.3a5)",
32
+ "typer (>=0.19.2,<0.20.0)",
33
+ "ampel-photometry (>=0.10.1,<0.11.0)",
34
+ "ampel-plot (>=0.9.1,<0.10.0)",
35
+ "ampel-core (>=0.10.4.post0,<0.11.0)",
36
+ "urllib3 (>=2.5.0,<3.0.0)",
37
+ ]
38
+
39
+ [project.scripts]
40
+ timewise = "timewise.cli:app"
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/JannisNe/timewise"
44
+ "Bug Tracker" = "https://github.com/JannisNe/timewise/issues"
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "coveralls>=3.3.1,<4.0.0",
49
+ "pytest>=7.2.2,<8.0.0",
50
+ "ruff>=0.13.0,<0.14.0",
51
+ "mypy (>=1.18.2,<2.0.0)",
52
+ "pandas-stubs (>=2.3.2.250926,<3.0.0.0)",
53
+ "scipy-stubs (>=1.16.2.0,<2.0.0.0)",
54
+ "types-pyyaml (>=6.0.12.20250915,<7.0.0.0)",
55
+ "types-requests (>=2.32.4.20250913,<3.0.0.0)",
56
+ ]
57
+ docs = [
58
+ "myst-parser>=1,<3",
59
+ "sphinx-rtd-theme>=1.3.0,<2.0.0",
60
+ "autodoc_pydantic[erdantic]>=2.2.0,<3.0.0"
61
+ ]
62
+
63
+ [tool.coverage.run]
64
+ source = ["timewise", "ampel"]
65
+ omit = ["timewise/v0"]
66
+ parallel = true
67
+ concurrency = ["thread", "multiprocessing"]
68
+
69
+ [tool.pytest]
70
+ testpaths = "tests"
71
+ pythonpath = "."
72
+
73
+ # Per-module ignores
74
+ [tool.mypy]
75
+ python_version = "3.11"
76
+ explicit_package_bases = true
77
+ install_types = false
78
+ non_interactive = false
79
+ mypy_path = ["."]
80
+ packages = ["timewise", "ampel"]
81
+
82
+ [[tool.mypy.overrides]]
83
+ module = [
84
+ "astropy.*",
85
+ "pyvo.*",
86
+ "SciServer.*",
87
+ "sklearn.*"
88
+ ]
89
+ ignore_missing_imports = true
90
+
91
+ [tool.ruff]
92
+ src = ["timewise", "ampel"]
93
+ exclude = ["tests", "SciScript-Python"]
94
+
95
+ [tool.ruff.lint]
96
+ ignore = ["F401"]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0a1"
@@ -0,0 +1,6 @@
1
+ from typing import Union
2
+
3
+ from .base import Backend
4
+ from .filesystem import FileSystemBackend
5
+
6
+ BackendType = Union[FileSystemBackend]
@@ -0,0 +1,36 @@
1
+ import abc
2
+ from typing import Any
3
+ from pydantic import BaseModel
4
+ from astropy.table import Table
5
+ from ..types import TaskID
6
+
7
+
8
+ class Backend(abc.ABC, BaseModel):
9
+ type: str
10
+ base_path: Any
11
+ """
12
+ Abstract persistence backend for jobs, results, and markers.
13
+ Works with generic TaskIDs so it can be reused across Downloader/Processor.
14
+ """
15
+
16
+ # --- metadata ---
17
+ @abc.abstractmethod
18
+ def meta_exists(self, task: TaskID) -> bool: ...
19
+ @abc.abstractmethod
20
+ def save_meta(self, task: TaskID, meta: dict[str, Any]) -> None: ...
21
+ @abc.abstractmethod
22
+ def load_meta(self, task: TaskID) -> dict[str, Any] | None: ...
23
+
24
+ # --- Markers ---
25
+ @abc.abstractmethod
26
+ def mark_done(self, task: TaskID) -> None: ...
27
+ @abc.abstractmethod
28
+ def is_done(self, task: TaskID) -> bool: ...
29
+
30
+ # --- Data ---
31
+ @abc.abstractmethod
32
+ def save_data(self, task: TaskID, content: Table) -> None: ...
33
+ @abc.abstractmethod
34
+ def load_data(self, task: TaskID) -> Table: ...
35
+ @abc.abstractmethod
36
+ def data_exists(self, task: TaskID) -> bool: ...
@@ -0,0 +1,80 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+ from typing import Any, Literal
5
+ from astropy.table import Table
6
+
7
+ from .base import Backend
8
+ from ..types import TaskID
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class FileSystemBackend(Backend):
15
+ type: Literal["filesystem"] = "filesystem"
16
+ base_path: Path
17
+
18
+ # ----------------------------
19
+ # Helpers for paths
20
+ # ----------------------------
21
+ def _meta_path(self, task: TaskID) -> Path:
22
+ return self.base_path / f"{task}.meta.json"
23
+
24
+ def _marker_path(self, task: TaskID) -> Path:
25
+ return self.base_path / f"{task}.ok"
26
+
27
+ def _data_path(self, task: TaskID) -> Path:
28
+ return self.base_path / f"{task}.fits"
29
+
30
+ # ----------------------------
31
+ # Metadata
32
+ # ----------------------------
33
+ def save_meta(self, task: TaskID, meta: dict[str, Any]) -> None:
34
+ path = self._meta_path(task)
35
+ tmp = path.with_suffix(".tmp")
36
+ tmp.parent.mkdir(parents=True, exist_ok=True)
37
+ logger.debug(f"writing {path}")
38
+ tmp.write_text(json.dumps(meta, indent=2))
39
+ tmp.replace(path)
40
+
41
+ def load_meta(self, task: TaskID) -> dict[str, Any] | None:
42
+ path = self._meta_path(task)
43
+ if not path.exists():
44
+ return None
45
+ return json.loads(path.read_text())
46
+
47
+ def meta_exists(self, task: TaskID) -> bool:
48
+ return self._meta_path(task).exists()
49
+
50
+ # ----------------------------
51
+ # Markers
52
+ # ----------------------------
53
+ def mark_done(self, task: TaskID) -> None:
54
+ mp = self._marker_path(task)
55
+ mp.parent.mkdir(parents=True, exist_ok=True)
56
+ logger.debug(f"writing {mp}")
57
+ mp.write_text("done")
58
+
59
+ def is_done(self, task: TaskID) -> bool:
60
+ return self._marker_path(task).exists()
61
+
62
+ # ----------------------------
63
+ # Data
64
+ # ----------------------------
65
+ def save_data(self, task: TaskID, content: Table) -> None:
66
+ path = self._data_path(task)
67
+ tmp = path.with_suffix(".tmp")
68
+ tmp.parent.mkdir(parents=True, exist_ok=True)
69
+ logger.debug(f"writing {path}")
70
+ content.write(tmp, format="fits")
71
+ tmp.replace(path)
72
+
73
+ def load_data(self, task: TaskID) -> Table:
74
+ path = self._data_path(task)
75
+ if not path.exists():
76
+ raise FileNotFoundError(path)
77
+ return Table.read(path, format="fits")
78
+
79
+ def data_exists(self, task: TaskID) -> bool:
80
+ return self._data_path(task).exists()
@@ -0,0 +1,50 @@
1
+ from typing import Iterator
2
+ from pathlib import Path
3
+ import numpy as np
4
+ from numpy import typing as npt
5
+ import pandas as pd
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class Chunk:
12
+ def __init__(
13
+ self, chunk_id: int, indices: npt.ArrayLike, row_indices: npt.ArrayLike
14
+ ):
15
+ self.chunk_id = chunk_id
16
+ self.indices = indices
17
+ self.row_numbers = row_indices
18
+
19
+
20
+ class Chunker:
21
+ def __init__(self, input_csv: Path, chunk_size: int):
22
+ self.input_csv = input_csv
23
+ self.chunk_size = chunk_size
24
+ self._n_rows = self._count_rows()
25
+ logger.debug(f"found {self._n_rows} rows in {self.input_csv}")
26
+
27
+ def _count_rows(self) -> int:
28
+ chunk = 1024 * 1024 # Process 1 MB at a time.
29
+ f = np.memmap(self.input_csv)
30
+ num_newlines = sum(
31
+ np.sum(f[i : i + chunk] == ord("\n")) for i in range(0, len(f), chunk)
32
+ )
33
+ del f
34
+ return num_newlines - 1 # one header row
35
+
36
+ def __len__(self) -> int:
37
+ return int(np.ceil(self._n_rows / self.chunk_size))
38
+
39
+ def __iter__(self) -> Iterator[Chunk]:
40
+ for chunk_id in range(len(self)):
41
+ yield self.get_chunk(chunk_id)
42
+
43
+ def get_chunk(self, chunk_id: int) -> Chunk:
44
+ if chunk_id >= len(self):
45
+ raise IndexError(f"Invalid chunk_id {chunk_id}")
46
+ start = chunk_id * self.chunk_size
47
+ stop = min(start + self.chunk_size, self._n_rows)
48
+ indices = pd.read_csv(self.input_csv, skiprows=start, nrows=stop - start).index
49
+ logger.debug(f"chunk {chunk_id}: from {start} to {stop}")
50
+ return Chunk(chunk_id, indices, np.arange(start=start, stop=stop))