requake 0.8.3__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.
- {requake-0.8.3 → requake-0.8.4}/CHANGELOG.md +62 -0
- {requake-0.8.3 → requake-0.8.4}/PKG-INFO +7 -3
- {requake-0.8.3 → requake-0.8.4}/README.md +3 -1
- {requake-0.8.3 → requake-0.8.4}/pyproject.toml +5 -0
- {requake-0.8.3 → requake-0.8.4}/requake/_version.py +3 -3
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/catalog.py +3 -1
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/print_catalog.py +12 -4
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/read_catalog.py +6 -2
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/read_catalog_from_fdsnws.py +15 -4
- requake-0.8.4/requake/config/generic_printer.py +208 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/parse_arguments.py +310 -12
- {requake-0.8.3 → requake-0.8.4}/requake/config/rq_setup.py +30 -17
- {requake-0.8.3 → requake-0.8.4}/requake/config/utils.py +150 -10
- {requake-0.8.3 → requake-0.8.4}/requake/database/catalog.py +104 -3
- {requake-0.8.3 → requake-0.8.4}/requake/database/db.py +32 -0
- {requake-0.8.3 → requake-0.8.4}/requake/database/pairs.py +262 -9
- {requake-0.8.3 → requake-0.8.4}/requake/families/__init__.py +1 -0
- {requake-0.8.3 → requake-0.8.4}/requake/families/build_families.py +22 -9
- requake-0.8.4/requake/families/dedup_events.py +431 -0
- {requake-0.8.3 → requake-0.8.4}/requake/families/families.py +4 -1
- {requake-0.8.3 → requake-0.8.4}/requake/families/pairs.py +6 -2
- {requake-0.8.3 → requake-0.8.4}/requake/families/print_families.py +57 -3
- requake-0.8.4/requake/families/print_pairs.py +230 -0
- requake-0.8.4/requake/logo.py +15 -0
- {requake-0.8.3 → requake-0.8.4}/requake/main.py +9 -0
- requake-0.8.4/requake/pager.py +1392 -0
- requake-0.8.4/requake/plot/map_families.py +38 -0
- requake-0.8.3/requake/plot/map_families.py → requake-0.8.4/requake/plot/map_families_cartopy.py +47 -11
- requake-0.8.4/requake/plot/map_families_folium.py +582 -0
- requake-0.8.4/requake/plot/map_families_utils.py +291 -0
- requake-0.8.4/requake/plot/plot_families.py +996 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/scan_catalog_pairs.py +16 -8
- requake-0.8.4/requake/terminal.py +328 -0
- {requake-0.8.3 → requake-0.8.4}/requake/waveforms/arrivals.py +14 -2
- {requake-0.8.3 → requake-0.8.4}/requake/waveforms/station_metadata.py +25 -12
- {requake-0.8.3 → requake-0.8.4}/requake/waveforms/waveform_pair.py +0 -2
- {requake-0.8.3 → requake-0.8.4}/requake/waveforms/waveforms.py +5 -1
- {requake-0.8.3 → requake-0.8.4}/requake/wfcache/commands.py +26 -14
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/PKG-INFO +7 -3
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/SOURCES.txt +7 -0
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/requires.txt +3 -0
- requake-0.8.3/requake/config/generic_printer.py +0 -65
- requake-0.8.3/requake/families/print_pairs.py +0 -83
- requake-0.8.3/requake/plot/plot_families.py +0 -252
- {requake-0.8.3 → requake-0.8.4}/CONTRIBUTORS.txt +0 -0
- {requake-0.8.3 → requake-0.8.4}/LICENSE.txt +0 -0
- {requake-0.8.3 → requake-0.8.4}/MANIFEST.in +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/read_catalog_from_csv.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/catalog/read_catalog_from_quakeml.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/config.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/configobj/LICENSE +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/configobj/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/configobj/_version.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/configobj/validate.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/config/configspec.conf +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/database/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/database/families.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/database/templates.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/database/trace_metadata.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/families/build_templates.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/families/flag_family.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/formulas/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/formulas/conversion.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/formulas/moment.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/formulas/slip.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/cached_tiler.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/colormaps.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/map_tiles.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/plot_cumulative.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/plot_pair.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/plot_timespans.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/plot/plot_utils.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/parallel_diagnostics.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/scan_catalog.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/scan_catalog_helpers.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/scan_catalog_workers.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/scan_templates.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/scan/slurm_diagnostics.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/waveforms/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/wfcache/__init__.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake/wfcache/storage.py +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/dependency_links.txt +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/entry_points.txt +0 -0
- {requake-0.8.3 → requake-0.8.4}/requake.egg-info/top_level.txt +0 -0
- {requake-0.8.3 → requake-0.8.4}/setup.cfg +0 -0
- {requake-0.8.3 → requake-0.8.4}/setup.py +0 -0
|
@@ -2,6 +2,67 @@
|
|
|
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
|
+
|
|
5
66
|
## [0.8.3] - 2026-06-19
|
|
6
67
|
|
|
7
68
|
Note: you should run `requake update_config` to update your config file
|
|
@@ -206,6 +267,7 @@ Mostly a bugfix release with a slightly improved trace plotting.
|
|
|
206
267
|
|
|
207
268
|
- Initial release, not yet feature complete
|
|
208
269
|
|
|
270
|
+
[0.8.4]: https://github.com/SeismicSource/requake/compare/v0.8.3...v0.8.4
|
|
209
271
|
[0.8.3]: https://github.com/SeismicSource/requake/compare/v0.8.2...v0.8.3
|
|
210
272
|
[0.8.2]: https://github.com/SeismicSource/requake/compare/v0.8.1...v0.8.2
|
|
211
273
|
[0.8.1]: https://github.com/SeismicSource/requake/compare/v0.8...v0.8.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: requake
|
|
3
|
-
Version: 0.8.
|
|
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@
|
|
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
|
|
@@ -276,7 +280,7 @@ You can also cite the following abstract presented at the
|
|
|
276
280
|
[docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
|
|
277
281
|
[changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
|
|
278
282
|
[changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
|
|
279
|
-
[DOI-badge]: https://
|
|
283
|
+
[DOI-badge]: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.10832203-blue.svg
|
|
280
284
|
[DOI-link]: https://doi.org/10.5281/zenodo.10832203
|
|
281
285
|
[Requake GitHub repository]: https://github.com/SeismicSource/requake
|
|
282
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
|
|
@@ -237,7 +239,7 @@ You can also cite the following abstract presented at the
|
|
|
237
239
|
[docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
|
|
238
240
|
[changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
|
|
239
241
|
[changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
|
|
240
|
-
[DOI-badge]: https://
|
|
242
|
+
[DOI-badge]: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.10832203-blue.svg
|
|
241
243
|
[DOI-link]: https://doi.org/10.5281/zenodo.10832203
|
|
242
244
|
[Requake GitHub repository]: https://github.com/SeismicSource/requake
|
|
243
245
|
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-
|
|
11
|
+
"date": "2026-10-23T18:15:54+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.8.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 +=
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
logger.info(f'{len(catalog)} events downloaded')
|
|
140
|
-
return catalog
|
|
151
|
+
return event_list
|
|
@@ -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)
|