requake 0.8.2__tar.gz → 0.8.4__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 (92) hide show
  1. {requake-0.8.2 → requake-0.8.4}/CHANGELOG.md +82 -0
  2. {requake-0.8.2 → requake-0.8.4}/PKG-INFO +24 -4
  3. {requake-0.8.2 → requake-0.8.4}/README.md +20 -2
  4. {requake-0.8.2 → requake-0.8.4}/pyproject.toml +5 -0
  5. {requake-0.8.2 → requake-0.8.4}/requake/_version.py +3 -3
  6. {requake-0.8.2 → requake-0.8.4}/requake/catalog/catalog.py +3 -1
  7. {requake-0.8.2 → requake-0.8.4}/requake/catalog/print_catalog.py +12 -4
  8. {requake-0.8.2 → requake-0.8.4}/requake/catalog/read_catalog.py +6 -2
  9. {requake-0.8.2 → requake-0.8.4}/requake/catalog/read_catalog_from_fdsnws.py +15 -4
  10. {requake-0.8.2 → requake-0.8.4}/requake/config/configspec.conf +11 -1
  11. requake-0.8.4/requake/config/generic_printer.py +208 -0
  12. {requake-0.8.2 → requake-0.8.4}/requake/config/parse_arguments.py +483 -12
  13. {requake-0.8.2 → requake-0.8.4}/requake/config/rq_setup.py +43 -32
  14. {requake-0.8.2 → requake-0.8.4}/requake/config/utils.py +181 -10
  15. {requake-0.8.2 → requake-0.8.4}/requake/database/catalog.py +104 -3
  16. {requake-0.8.2 → requake-0.8.4}/requake/database/db.py +32 -0
  17. {requake-0.8.2 → requake-0.8.4}/requake/database/families.py +26 -8
  18. {requake-0.8.2 → requake-0.8.4}/requake/database/pairs.py +299 -24
  19. {requake-0.8.2 → requake-0.8.4}/requake/families/__init__.py +1 -0
  20. {requake-0.8.2 → requake-0.8.4}/requake/families/build_families.py +22 -9
  21. requake-0.8.4/requake/families/dedup_events.py +431 -0
  22. {requake-0.8.2 → requake-0.8.4}/requake/families/families.py +13 -6
  23. {requake-0.8.2 → requake-0.8.4}/requake/families/pairs.py +6 -2
  24. {requake-0.8.2 → requake-0.8.4}/requake/families/print_families.py +57 -3
  25. requake-0.8.4/requake/families/print_pairs.py +230 -0
  26. requake-0.8.4/requake/logo.py +15 -0
  27. {requake-0.8.2 → requake-0.8.4}/requake/main.py +24 -0
  28. requake-0.8.4/requake/pager.py +1392 -0
  29. {requake-0.8.2 → requake-0.8.4}/requake/plot/colormaps.py +13 -13
  30. requake-0.8.4/requake/plot/map_families.py +38 -0
  31. requake-0.8.2/requake/plot/map_families.py → requake-0.8.4/requake/plot/map_families_cartopy.py +47 -11
  32. requake-0.8.4/requake/plot/map_families_folium.py +582 -0
  33. requake-0.8.4/requake/plot/map_families_utils.py +291 -0
  34. requake-0.8.4/requake/plot/plot_families.py +996 -0
  35. requake-0.8.4/requake/scan/parallel_diagnostics.py +273 -0
  36. {requake-0.8.2 → requake-0.8.4}/requake/scan/scan_catalog.py +51 -38
  37. requake-0.8.4/requake/scan/scan_catalog_helpers.py +359 -0
  38. {requake-0.8.2 → requake-0.8.4}/requake/scan/scan_catalog_pairs.py +44 -44
  39. {requake-0.8.2 → requake-0.8.4}/requake/scan/scan_catalog_workers.py +332 -77
  40. requake-0.8.4/requake/scan/slurm_diagnostics.py +130 -0
  41. requake-0.8.4/requake/terminal.py +328 -0
  42. {requake-0.8.2 → requake-0.8.4}/requake/waveforms/arrivals.py +14 -2
  43. {requake-0.8.2 → requake-0.8.4}/requake/waveforms/station_metadata.py +110 -13
  44. {requake-0.8.2 → requake-0.8.4}/requake/waveforms/waveform_pair.py +72 -4
  45. {requake-0.8.2 → requake-0.8.4}/requake/waveforms/waveforms.py +131 -68
  46. requake-0.8.4/requake/wfcache/__init__.py +35 -0
  47. requake-0.8.4/requake/wfcache/commands.py +931 -0
  48. requake-0.8.4/requake/wfcache/storage.py +765 -0
  49. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/PKG-INFO +24 -4
  50. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/SOURCES.txt +13 -1
  51. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/requires.txt +3 -0
  52. requake-0.8.2/requake/config/generic_printer.py +0 -65
  53. requake-0.8.2/requake/families/print_pairs.py +0 -83
  54. requake-0.8.2/requake/plot/plot_families.py +0 -249
  55. requake-0.8.2/requake/scan/scan_catalog_helpers.py +0 -317
  56. {requake-0.8.2 → requake-0.8.4}/CONTRIBUTORS.txt +0 -0
  57. {requake-0.8.2 → requake-0.8.4}/LICENSE.txt +0 -0
  58. {requake-0.8.2 → requake-0.8.4}/MANIFEST.in +0 -0
  59. {requake-0.8.2 → requake-0.8.4}/requake/__init__.py +0 -0
  60. {requake-0.8.2 → requake-0.8.4}/requake/catalog/__init__.py +0 -0
  61. {requake-0.8.2 → requake-0.8.4}/requake/catalog/read_catalog_from_csv.py +0 -0
  62. {requake-0.8.2 → requake-0.8.4}/requake/catalog/read_catalog_from_quakeml.py +0 -0
  63. {requake-0.8.2 → requake-0.8.4}/requake/config/__init__.py +0 -0
  64. {requake-0.8.2 → requake-0.8.4}/requake/config/config.py +0 -0
  65. {requake-0.8.2 → requake-0.8.4}/requake/config/configobj/LICENSE +0 -0
  66. {requake-0.8.2 → requake-0.8.4}/requake/config/configobj/__init__.py +0 -0
  67. {requake-0.8.2 → requake-0.8.4}/requake/config/configobj/_version.py +0 -0
  68. {requake-0.8.2 → requake-0.8.4}/requake/config/configobj/validate.py +0 -0
  69. {requake-0.8.2 → requake-0.8.4}/requake/database/__init__.py +0 -0
  70. {requake-0.8.2 → requake-0.8.4}/requake/database/templates.py +0 -0
  71. {requake-0.8.2 → requake-0.8.4}/requake/database/trace_metadata.py +0 -0
  72. {requake-0.8.2 → requake-0.8.4}/requake/families/build_templates.py +0 -0
  73. {requake-0.8.2 → requake-0.8.4}/requake/families/flag_family.py +0 -0
  74. {requake-0.8.2 → requake-0.8.4}/requake/formulas/__init__.py +0 -0
  75. {requake-0.8.2 → requake-0.8.4}/requake/formulas/conversion.py +0 -0
  76. {requake-0.8.2 → requake-0.8.4}/requake/formulas/moment.py +0 -0
  77. {requake-0.8.2 → requake-0.8.4}/requake/formulas/slip.py +0 -0
  78. {requake-0.8.2 → requake-0.8.4}/requake/plot/__init__.py +0 -0
  79. {requake-0.8.2 → requake-0.8.4}/requake/plot/cached_tiler.py +0 -0
  80. {requake-0.8.2 → requake-0.8.4}/requake/plot/map_tiles.py +0 -0
  81. {requake-0.8.2 → requake-0.8.4}/requake/plot/plot_cumulative.py +0 -0
  82. {requake-0.8.2 → requake-0.8.4}/requake/plot/plot_pair.py +0 -0
  83. {requake-0.8.2 → requake-0.8.4}/requake/plot/plot_timespans.py +0 -0
  84. {requake-0.8.2 → requake-0.8.4}/requake/plot/plot_utils.py +0 -0
  85. {requake-0.8.2 → requake-0.8.4}/requake/scan/__init__.py +0 -0
  86. {requake-0.8.2 → requake-0.8.4}/requake/scan/scan_templates.py +0 -0
  87. {requake-0.8.2 → requake-0.8.4}/requake/waveforms/__init__.py +0 -0
  88. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/dependency_links.txt +0 -0
  89. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/entry_points.txt +0 -0
  90. {requake-0.8.2 → requake-0.8.4}/requake.egg-info/top_level.txt +0 -0
  91. {requake-0.8.2 → requake-0.8.4}/setup.cfg +0 -0
  92. {requake-0.8.2 → requake-0.8.4}/setup.py +0 -0
@@ -2,6 +2,86 @@
2
2
 
3
3
  Copyright (c) 2021-2026 Claudio Satriano <satriano@ipgp.fr>
4
4
 
5
+ ## [0.8.4] - 2026-10-23
6
+
7
+ - ``plot_families``: new ``--mode`` option, to draw the families as a
8
+ heatmap (``--mode heatmap``): one row per event, with the trace
9
+ amplitudes encoded in color and the family average drawn as a wiggle
10
+ below. The default remains the wiggle plot (``--mode wiggle``).
11
+ - ``plot_families``: the family average is now drawn in its own panel
12
+ also in ``--mode wiggle``, and both modes show an extra panel below
13
+ it, where all the traces of the family are drawn stacked on top of
14
+ each other, with the average highlighted on top, so that the waveform
15
+ similarity can be judged at a glance.
16
+ - ``plot_families``: new ``--max-labels`` option (default 15), to set
17
+ the maximum number of traces carrying a label. When a family has more
18
+ traces than that, only a subset of them is labeled (in wiggle mode,
19
+ the labeled traces are also highlighted and the traces are scaled up),
20
+ so that large families remain readable. Use 0 to label every trace.
21
+ - New ``-E``/``--events`` option for ``print_families``, to print one
22
+ row per event (ordered by family number and origin time), instead of
23
+ one row per family; combine with ``-f csv`` to export the list.
24
+ - New ``dedup_events`` command: flags likely duplicate events (event
25
+ pairs with high cross-correlation, close in time and space) with the
26
+ new ``duplicate`` column in the catalog, and marks all the pairs
27
+ involving them as invalid (new ``invalid`` column in ``event_pairs``);
28
+ ``build_families`` ignores the invalid pairs. A compact report is
29
+ printed at the end (``-n``/``--dry-run`` only prints it) and
30
+ ``--reset`` clears the flags.
31
+ - ``print_pairs`` hides the invalid pairs by default; the new
32
+ ``--include-invalid`` option lists them as well.
33
+ - Long operations now show a progress indicator, and long database
34
+ queries can be interrupted with ``Ctrl+C`` (pressed twice): the
35
+ database is closed cleanly (no stray ``-wal``/``-shm`` files) and the
36
+ abort is no longer reported as a bug.
37
+ - Fixed the non-paged table output (``--no-pager``), which dropped all
38
+ the rows but the last batch.
39
+ - Stylized ``◀◀ RƎQuake`` logo, shown in the command line help.
40
+ - Colored terminal output for help and usage messages, log messages,
41
+ errors, table headers and waveform-cache diagnostics. Colors are used
42
+ only on interactive terminals, and are therefore automatically removed
43
+ when the output is piped or redirected. The ``NO_COLOR`` and
44
+ ``FORCE_COLOR`` environment variables can be used to force colors off
45
+ or on.
46
+ - New interactive curses pager for all ``print_`` commands
47
+ (``print_catalog``, ``print_pairs``, ``print_families``).
48
+ Automatically activated when output is a terminal; use ``--no-pager``
49
+ to fall back to plain-text output. Requesting a specific output format
50
+ with ``-f``/``--format`` implies ``--no-pager``.
51
+ - New ``-b/--backend`` option for ``map_families``, to plot families on
52
+ an interactive HTML map using
53
+ [folium](https://python-visualization.github.io/folium/), instead of
54
+ the default static Cartopy map. Folium is an optional dependency (not
55
+ installed by default): the folium backend is only used if folium is
56
+ installed.
57
+ - Family marker size is now scaled with a quantity selected with the new
58
+ ``-s/--sizeby`` option (default: number of events), and a legend on the
59
+ map explains the marker size. This applies to both the Cartopy and the
60
+ Folium map backends.
61
+ - Clickable URLs in terminal output for compatible terminals, e.g. in the
62
+ help messages and in the bug report.
63
+ - New documentation page on event pairs, covering `print_pairs`,
64
+ `plot_pair` and `dedup_events`.
65
+
66
+ ## [0.8.3] - 2026-06-19
67
+
68
+ Note: you should run `requake update_config` to update your config file
69
+ to the latest version.
70
+
71
+ The waveform disk cache is now a single SQLite database
72
+ (`OUTDIR/waveform_cache.sqlite`) with a persistent negative cache that
73
+ remembers failed downloads across runs, eliminating redundant HTTP requests.
74
+
75
+ - New `wfcache` CLI: `prefetch`, `print`/`inspect`, `extract`,
76
+ `reset-failures`.
77
+ - New config: `catalog_waveform_cache_failure_max_retries`,
78
+ `catalog_waveform_cache_failure_backoff_s`,
79
+ `catalog_waveform_require_prefetch`.
80
+ - Several optimization for parallel runs in `scan_catalog` (especially on
81
+ Slurm clusters) and for startup time of `scan_catalog -c` (continue an
82
+ interrupted scan).
83
+ - Extended documentation for `read_catalog`, `scan_catalog` and `wfcache`.
84
+
5
85
  ## [0.8.2] - 2026-06-03
6
86
 
7
87
  Note: you might want to run `requake update_config` to update your config file
@@ -187,6 +267,8 @@ Mostly a bugfix release with a slightly improved trace plotting.
187
267
 
188
268
  - Initial release, not yet feature complete
189
269
 
270
+ [0.8.4]: https://github.com/SeismicSource/requake/compare/v0.8.3...v0.8.4
271
+ [0.8.3]: https://github.com/SeismicSource/requake/compare/v0.8.2...v0.8.3
190
272
  [0.8.2]: https://github.com/SeismicSource/requake/compare/v0.8.1...v0.8.2
191
273
  [0.8.1]: https://github.com/SeismicSource/requake/compare/v0.8...v0.8.1
192
274
  [0.8]: https://github.com/SeismicSource/requake/compare/v0.7.3...v0.8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: requake
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Repeating earthquakes search and analysis
5
5
  Author-email: Claudio Satriano <satriano@ipgp.fr>
6
6
  License-Expression: GPL-3.0-or-later
@@ -33,11 +33,13 @@ Requires-Dist: argcomplete
33
33
  Requires-Dist: tqdm
34
34
  Requires-Dist: tabulate
35
35
  Requires-Dist: pygments
36
+ Provides-Extra: folium
37
+ Requires-Dist: folium; extra == "folium"
36
38
  Dynamic: description
37
39
  Dynamic: description-content-type
38
40
  Dynamic: license-file
39
41
 
40
- <img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@9071fd7343802f3ef642a16f6bb02ff5ce1d88f5/imgs/Requake_logo.svg" width="400">
42
+ <img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@5cd818d88427140b2705841c2046f72b10612d72/imgs/Requake_logo.svg" width="400">
41
43
 
42
44
  # Requake
43
45
 
@@ -167,6 +169,8 @@ Different commands are available:
167
169
  print_catalog print the event catalog to screen
168
170
  scan_catalog scan an existing catalog for earthquake pairs
169
171
  print_pairs print pairs to screen
172
+ dedup_events detect and flag duplicate events in the catalog from
173
+ close event pairs
170
174
  plot_pair plot traces for a given event pair
171
175
  build_families build families of repeating earthquakes from a catalog
172
176
  of pairs
@@ -181,6 +185,8 @@ Different commands are available:
181
185
  families
182
186
  scan_templates scan a continuous waveform stream using one or more
183
187
  templates
188
+ wfcache manage persistent waveform cache (prefetch, print,
189
+ inspect, extract, reset_failures)
184
190
 
185
191
  Certain commands (e.g., `plot_pair`) require further arguments (use, e.g.,
186
192
  `requake plot_pair -h` to get help).
@@ -216,6 +222,14 @@ Now, build the catalog of event pairs with:
216
222
 
217
223
  requake scan_catalog
218
224
 
225
+ When relying on FDSN web services for waveform data, it is strongly
226
+ recommended to prefetch all waveform windows before running the scan.
227
+ This downloads every required waveform once and stores it in a local
228
+ SQLite cache, avoiding repeated downloads and dramatically reducing
229
+ overall runtime for large catalogs:
230
+
231
+ requake wfcache prefetch
232
+
219
233
  Once done ([it will take time!](#performances)), you are ready to build
220
234
  repeating earthquake families:
221
235
 
@@ -232,6 +246,12 @@ repeating earthquake families:
232
246
  performance is typically dominated by waveform download latency rather than
233
247
  computation.
234
248
 
249
+ - For large FDSN-based runs, use `requake wfcache prefetch` before
250
+ `requake scan_catalog` to download all waveforms upfront into a local
251
+ SQLite cache. This eliminates repeated downloads, lets the scan read
252
+ exclusively from disk, and is the single most effective way to speed up
253
+ catalog scanning.
254
+
235
255
  - `requake build_families` is fast™.
236
256
 
237
257
  ## How to Cite
@@ -244,7 +264,7 @@ If you used Requake for a scientific paper, please cite it as:
244
264
  Please replace `X.Y` with the Requake version number you used.
245
265
 
246
266
  You can also cite the following abstract presented at the
247
- 2016 AGU Fall Meeting:
267
+ 2021 AGU Fall Meeting:
248
268
 
249
269
  > Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
250
270
  > Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
@@ -260,7 +280,7 @@ You can also cite the following abstract presented at the
260
280
  [docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
261
281
  [changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
262
282
  [changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
263
- [DOI-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.10832203.svg
283
+ [DOI-badge]: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.10832203-blue.svg
264
284
  [DOI-link]: https://doi.org/10.5281/zenodo.10832203
265
285
  [Requake GitHub repository]: https://github.com/SeismicSource/requake
266
286
 
@@ -128,6 +128,8 @@ Different commands are available:
128
128
  print_catalog print the event catalog to screen
129
129
  scan_catalog scan an existing catalog for earthquake pairs
130
130
  print_pairs print pairs to screen
131
+ dedup_events detect and flag duplicate events in the catalog from
132
+ close event pairs
131
133
  plot_pair plot traces for a given event pair
132
134
  build_families build families of repeating earthquakes from a catalog
133
135
  of pairs
@@ -142,6 +144,8 @@ Different commands are available:
142
144
  families
143
145
  scan_templates scan a continuous waveform stream using one or more
144
146
  templates
147
+ wfcache manage persistent waveform cache (prefetch, print,
148
+ inspect, extract, reset_failures)
145
149
 
146
150
  Certain commands (e.g., `plot_pair`) require further arguments (use, e.g.,
147
151
  `requake plot_pair -h` to get help).
@@ -177,6 +181,14 @@ Now, build the catalog of event pairs with:
177
181
 
178
182
  requake scan_catalog
179
183
 
184
+ When relying on FDSN web services for waveform data, it is strongly
185
+ recommended to prefetch all waveform windows before running the scan.
186
+ This downloads every required waveform once and stores it in a local
187
+ SQLite cache, avoiding repeated downloads and dramatically reducing
188
+ overall runtime for large catalogs:
189
+
190
+ requake wfcache prefetch
191
+
180
192
  Once done ([it will take time!](#performances)), you are ready to build
181
193
  repeating earthquake families:
182
194
 
@@ -193,6 +205,12 @@ repeating earthquake families:
193
205
  performance is typically dominated by waveform download latency rather than
194
206
  computation.
195
207
 
208
+ - For large FDSN-based runs, use `requake wfcache prefetch` before
209
+ `requake scan_catalog` to download all waveforms upfront into a local
210
+ SQLite cache. This eliminates repeated downloads, lets the scan read
211
+ exclusively from disk, and is the single most effective way to speed up
212
+ catalog scanning.
213
+
196
214
  - `requake build_families` is fast™.
197
215
 
198
216
  ## How to Cite
@@ -205,7 +223,7 @@ If you used Requake for a scientific paper, please cite it as:
205
223
  Please replace `X.Y` with the Requake version number you used.
206
224
 
207
225
  You can also cite the following abstract presented at the
208
- 2016 AGU Fall Meeting:
226
+ 2021 AGU Fall Meeting:
209
227
 
210
228
  > Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
211
229
  > Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
@@ -221,7 +239,7 @@ You can also cite the following abstract presented at the
221
239
  [docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
222
240
  [changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
223
241
  [changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
224
- [DOI-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.10832203.svg
242
+ [DOI-badge]: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.10832203-blue.svg
225
243
  [DOI-link]: https://doi.org/10.5281/zenodo.10832203
226
244
  [Requake GitHub repository]: https://github.com/SeismicSource/requake
227
245
 
@@ -38,6 +38,11 @@ dependencies = [
38
38
  "pygments",
39
39
  ]
40
40
 
41
+ [project.optional-dependencies]
42
+ folium = [
43
+ "folium",
44
+ ]
45
+
41
46
  [project.urls]
42
47
  Homepage = "https://requake.seismicsource.org"
43
48
  Source = "https://github.com/SeismicSource/requake"
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2026-06-03T14:39:27+0200",
11
+ "date": "2026-10-23T18:15:54+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "9071fd7343802f3ef642a16f6bb02ff5ce1d88f5",
15
- "version": "0.8.2"
14
+ "full-revisionid": "5cd818d88427140b2705841c2046f72b10612d72",
15
+ "version": "0.8.4"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -26,7 +26,8 @@ class RequakeEvent():
26
26
  def __init__(self, evid=None, orig_time=None, lon=None, lat=None,
27
27
  depth=None, mag_type=None, mag=None, author=None,
28
28
  catalog=None, contributor=None, contributor_id=None,
29
- mag_author=None, location_name=None, trace_id=None):
29
+ mag_author=None, location_name=None, trace_id=None,
30
+ duplicate=False):
30
31
  """Initialize an event container."""
31
32
  self.evid = evid
32
33
  self.orig_time = orig_time
@@ -42,6 +43,7 @@ class RequakeEvent():
42
43
  self.mag_author = mag_author
43
44
  self.location_name = location_name
44
45
  self.trace_id = trace_id
46
+ self.duplicate = duplicate
45
47
  self.correlations = {}
46
48
 
47
49
  def __eq__(self, other):
@@ -10,7 +10,8 @@ Print the event catalog to screen.
10
10
  (https://www.gnu.org/licenses/gpl-3.0-standalone.html)
11
11
  """
12
12
  import logging
13
- from ..config import rq_exit, generic_printer
13
+ from ..config import rq_exit
14
+ from ..config.generic_printer import _display_table
14
15
  from .catalog import read_stored_catalog
15
16
  logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
16
17
 
@@ -30,7 +31,8 @@ def print_catalog():
30
31
  ('latitude', '.4f'),
31
32
  ('depth\n(km)', '.3f'),
32
33
  ('mag\ntype', None),
33
- ('mag', '.1f')
34
+ ('mag', '.1f'),
35
+ ('dup', None)
34
36
  ]
35
37
  rows = [
36
38
  [
@@ -40,8 +42,14 @@ def print_catalog():
40
42
  ev.lat,
41
43
  ev.depth,
42
44
  ev.mag_type,
43
- ev.mag
45
+ ev.mag,
46
+ ev.duplicate
44
47
  ]
45
48
  for ev in catalog
46
49
  ]
47
- generic_printer(rows, headers_fmt)
50
+ _display_table(
51
+ headers_fmt, rows,
52
+ row_label='Events',
53
+ copy_label='evid',
54
+ detail_title='Event Details',
55
+ )
@@ -13,6 +13,7 @@ import os
13
13
  import logging
14
14
  import contextlib
15
15
  from ..config import config, rq_exit
16
+ from ..config.utils import run_with_spinner
16
17
  from ..database.db import get_db_path
17
18
  from ..database.catalog import write_catalog as write_stored_catalog
18
19
  from .catalog import RequakeEvent, RequakeCatalog
@@ -120,11 +121,14 @@ def read_catalog():
120
121
  f'{nevs_read:n} events read from db file '
121
122
  f'{get_db_path()}'
122
123
  )
123
- logger.info('Reading catalog...')
124
+ logger.debug('Reading catalog...')
124
125
  input_cat_file = config.args.catalog_file
125
126
  if input_cat_file is not None:
126
127
  try:
127
- catalog += _read_catalog_from_file()
128
+ catalog += run_with_spinner(
129
+ 'Reading catalog...',
130
+ _read_catalog_from_file,
131
+ )
128
132
  # Filter catalog based on configuration
129
133
  catalog = _filter_catalog(catalog)
130
134
  except FileNotFoundError:
@@ -15,6 +15,7 @@ import urllib.request
15
15
  from obspy import UTCDateTime
16
16
  from obspy.clients.fdsn.header import URL_MAPPINGS
17
17
  from ..config import config
18
+ from ..config.utils import run_with_spinner
18
19
  from .catalog import RequakeCatalog, RequakeEvent
19
20
  logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
20
21
 
@@ -110,7 +111,19 @@ def read_catalog_from_fdsnws():
110
111
  :return: Event catalog.
111
112
  :rtype: requake.catalog.RequakeCatalog
112
113
  """
113
- logger.info('Downloading events from FDSN web services...')
114
+ download_message = 'Downloading events from FDSN web services...'
115
+ logger.debug(download_message)
116
+ event_list = run_with_spinner(
117
+ download_message,
118
+ _download_catalog_events,
119
+ )
120
+ catalog = RequakeCatalog(event_list)
121
+ logger.info(f'{len(catalog)} events downloaded')
122
+ return catalog
123
+
124
+
125
+ def _download_catalog_events():
126
+ """Download events for all the configured FDSN web services."""
114
127
  cat_info = zip(
115
128
  config.catalog_fdsn_event_urls,
116
129
  config.catalog_start_times,
@@ -135,6 +148,4 @@ def read_catalog_from_fdsnws():
135
148
  f'Unable to download events from {url} for period '
136
149
  f'{start_time} - {end_time}. {msg}'
137
150
  )
138
- catalog = RequakeCatalog(event_list)
139
- logger.info(f'{len(catalog)} events downloaded')
140
- return catalog
151
+ return event_list
@@ -76,9 +76,19 @@ catalog_waveform_cache_size = integer(min=2, default=5000)
76
76
  ## Set to 0 to derive it automatically from catalog_waveform_cache_size.
77
77
  catalog_waveform_cache_size_parallel = integer(min=0, default=0)
78
78
  ## Keep a persistent copy of fetched event waveforms on disk to speed up
79
- ## reruns. This on-disk cache lives in OUTDIR/waveform_cache and is not
79
+ ## reruns. This on-disk cache lives in OUTDIR/waveform_cache.sqlite and is
80
80
  ## limited by catalog_waveform_cache_size.
81
81
  catalog_waveform_disk_cache_enabled = boolean(default=true)
82
+ ## Maximum number of retries for a failed waveform download before
83
+ ## suppressing new attempts for the same cache key.
84
+ catalog_waveform_cache_failure_max_retries = integer(min=0, default=3)
85
+ ## Base backoff in seconds for persistent waveform download failures.
86
+ ## The retry delay grows exponentially with each failure.
87
+ catalog_waveform_cache_failure_backoff_s = float(min=0, default=600)
88
+ ## When True, skip live downloads for events absent from both positive
89
+ ## and negative cache, trusting a prior prefetch run to have covered
90
+ ## all fetchable events. Requires disk cache to be enabled.
91
+ catalog_waveform_require_prefetch = boolean(default=false)
82
92
  ## Number of worker processes for catalog scan.
83
93
  ## 0 means automatic selection; 1 disables parallelism.
84
94
  catalog_scan_nprocs = integer(min=0, default=0)
@@ -0,0 +1,208 @@
1
+ # -*- coding: utf8 -*-
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ """
4
+ Generic printer functions for Requake.
5
+
6
+ :copyright:
7
+ 2021-2026 Claudio Satriano <satriano@ipgp.fr>
8
+ :license:
9
+ GNU General Public License v3.0 or later
10
+ (https://www.gnu.org/licenses/gpl-3.0-standalone.html)
11
+ """
12
+ import sys
13
+ import os
14
+ import csv
15
+ import contextlib
16
+ import logging
17
+ from tabulate import tabulate
18
+ from ..terminal import color_enabled, style
19
+ from .config import config
20
+ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
21
+
22
+ # Characters appearing in a ``tabulate`` separator (or divider) line.
23
+ _TABLE_SEPARATOR_CHARS = frozenset('-|=+: ')
24
+
25
+
26
+ def _is_separator_line(line):
27
+ """Return True if ``line`` is a table separator (or divider) line."""
28
+ stripped = line.strip()
29
+ return bool(stripped) and set(stripped) <= _TABLE_SEPARATOR_CHARS
30
+
31
+
32
+ def _style_table(text, has_header=True):
33
+ """
34
+ Colorize a plain-text table produced by :func:`tabulate`.
35
+
36
+ Header lines are highlighted in bold cyan, separator lines are
37
+ dimmed, and data rows are left untouched.
38
+
39
+ :param text: table text
40
+ :type text: str
41
+ :param has_header: whether the table starts with a header
42
+ :type has_header: bool
43
+
44
+ :return: colorized table text
45
+ :rtype: str
46
+ """
47
+ if not color_enabled():
48
+ return text
49
+ lines = text.split('\n')
50
+ separator_index = next(
51
+ (i for i, line in enumerate(lines) if _is_separator_line(line)),
52
+ None,
53
+ )
54
+ if separator_index is None:
55
+ separator_index = 1 if has_header else 0
56
+ styled_lines = []
57
+ for index, line in enumerate(lines):
58
+ if _is_separator_line(line):
59
+ styled_lines.append(style(line, 'dim'))
60
+ elif has_header and index < separator_index:
61
+ styled_lines.append(style(line, 'bold', 'cyan'))
62
+ else:
63
+ styled_lines.append(line)
64
+ return '\n'.join(styled_lines)
65
+
66
+
67
+ def _drop_header_block(text):
68
+ """
69
+ Return ``text`` without the table header and its separator line.
70
+
71
+ Used for continuation batches, which must not repeat the header
72
+ but must keep all their data rows.
73
+
74
+ :param text: table text
75
+ :type text: str
76
+ :return: table text without the header block
77
+ :rtype: str
78
+ """
79
+ lines = text.split('\n')
80
+ separator_index = next(
81
+ (i for i, line in enumerate(lines) if _is_separator_line(line)),
82
+ None,
83
+ )
84
+ if separator_index is None:
85
+ return '\n'.join(lines[1:])
86
+ return '\n'.join(lines[separator_index + 1:])
87
+
88
+
89
+ def generic_printer(rows, headers_fmt, print_headers=True):
90
+ """
91
+ Print rows using the configured output format.
92
+
93
+ :param rows: Rows to print.
94
+ :type rows: list of rows
95
+ :param headers_fmt: Headers and format strings.
96
+ :type headers_fmt: list of tuples of str
97
+ """
98
+ headers = [h[0] for h in headers_fmt]
99
+ # Replace None with a safe default so tabulate never receives
100
+ # None as a format specifier (crashes on float columns).
101
+ floatfmt = [
102
+ h[1] if h[1] is not None else '.6g' for h in headers_fmt
103
+ ]
104
+ tablefmt = config.args.format
105
+ if tablefmt == 'csv':
106
+ writer = csv.writer(sys.stdout)
107
+ if print_headers:
108
+ # replace newlines with spaces in headers
109
+ headers = [h.replace('\n', ' ') for h in headers]
110
+ writer.writerow(headers)
111
+ elif tablefmt == 'markdown':
112
+ headers = [h.replace('\n', '<br>') for h in headers]
113
+ if tablefmt == 'csv':
114
+ try:
115
+ writer.writerows(rows)
116
+ except BrokenPipeError:
117
+ # Redirect remaining output to devnull to avoid another
118
+ # BrokenPipeError at shutdown
119
+ devnull = os.open(os.devnull, os.O_WRONLY)
120
+ os.dup2(devnull, sys.stdout.fileno())
121
+ else:
122
+ format_dict = {
123
+ 'simple': 'simple',
124
+ 'markdown': 'github'
125
+ }
126
+ tablefmt = format_dict[config.args.format]
127
+ with contextlib.suppress(BrokenPipeError):
128
+ kwargs = {
129
+ 'headers': headers,
130
+ 'floatfmt': floatfmt,
131
+ 'tablefmt': tablefmt
132
+ }
133
+ line = tabulate(rows, **kwargs)
134
+ if not print_headers:
135
+ line = _drop_header_block(line)
136
+ if not line:
137
+ return
138
+ print(_style_table(line, has_header=print_headers))
139
+
140
+
141
+ def _display_table(headers_fmt, rows=None, data_source=None,
142
+ row_label='Rows', copy_label=None,
143
+ copy_fn=None, detail_title='Row Details',
144
+ print_headers=True):
145
+ """
146
+ Display a table using the interactive curses pager.
147
+
148
+ When stdout is a TTY (and ``--no-pager`` is not set), the
149
+ interactive pager is used. Otherwise the table is printed
150
+ to stdout via :func:`generic_printer`.
151
+
152
+ :param headers_fmt: Headers and format strings
153
+ (list of (name, fmt) tuples).
154
+ :param rows: In-memory rows (list of lists). Ignored when
155
+ *data_source* is given.
156
+ :param data_source: Optional :class:`~requake.pager.DataSource`
157
+ for database-backed pagination.
158
+ :param row_label: Label for rows in the pager status bar.
159
+ :param copy_label: Label for the copy-to-clipboard message.
160
+ :param copy_fn: Optional callable ``copy_fn(row) -> str`` for
161
+ custom copy behaviour.
162
+ :param detail_title: Title for the detail popup.
163
+ :param print_headers: Whether to include the header row
164
+ (used for iterative printing fallback).
165
+ """
166
+ # Determine whether to use the pager
167
+ use_pager = sys.stdout.isatty()
168
+ if use_pager:
169
+ no_pager = getattr(config.args, 'no_pager', False)
170
+ if no_pager:
171
+ use_pager = False
172
+
173
+ if use_pager:
174
+ from ..pager import (
175
+ display_table_pager,
176
+ PagerException,
177
+ ListDataSource,
178
+ )
179
+ with contextlib.suppress(PagerException):
180
+ if data_source is not None:
181
+ ds = data_source
182
+ else:
183
+ fields = [h[0].replace('\n', ' ') for h in headers_fmt]
184
+ ds = ListDataSource(fields, rows or [])
185
+ display_table_pager(
186
+ ds,
187
+ row_label=row_label,
188
+ copy_label=copy_label,
189
+ copy_fn=copy_fn,
190
+ detail_title=detail_title,
191
+ )
192
+ return
193
+ # Fallback: plain-text output via generic_printer
194
+ if data_source is not None:
195
+ # For DB-backed sources, stream rows via generic_printer
196
+ fields = data_source.fields
197
+ total = data_source.total_count
198
+ batch_size = 1000
199
+ for offset in range(0, total, batch_size):
200
+ batch = data_source.get_rows(offset, batch_size, None, True)
201
+ # Rebuild headers_fmt for generic_printer
202
+ batch_headers_fmt = [(f, None) for f in fields]
203
+ generic_printer(
204
+ batch, batch_headers_fmt,
205
+ print_headers=(offset == 0 and print_headers)
206
+ )
207
+ else:
208
+ generic_printer(rows, headers_fmt, print_headers)