requake 0.7.2__tar.gz → 0.8__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 (75) hide show
  1. {requake-0.7.2 → requake-0.8}/CHANGELOG.md +39 -10
  2. {requake-0.7.2 → requake-0.8}/PKG-INFO +11 -2
  3. {requake-0.7.2 → requake-0.8}/README.md +9 -0
  4. {requake-0.7.2 → requake-0.8}/requake/_version.py +3 -3
  5. {requake-0.7.2 → requake-0.8}/requake/catalog/catalog.py +48 -87
  6. {requake-0.7.2 → requake-0.8}/requake/catalog/print_catalog.py +1 -3
  7. {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog.py +27 -11
  8. {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_csv.py +5 -7
  9. {requake-0.7.2 → requake-0.8}/requake/config/__init__.py +5 -1
  10. requake-0.8/requake/config/config.py +125 -0
  11. requake-0.8/requake/config/configobj/_version.py +6 -0
  12. {requake-0.7.2 → requake-0.8}/requake/config/generic_printer.py +1 -1
  13. {requake-0.7.2 → requake-0.8}/requake/config/parse_arguments.py +14 -15
  14. {requake-0.7.2 → requake-0.8}/requake/config/rq_setup.py +9 -23
  15. {requake-0.7.2 → requake-0.8}/requake/config/utils.py +22 -7
  16. requake-0.8/requake/database/__init__.py +12 -0
  17. requake-0.8/requake/database/catalog.py +136 -0
  18. requake-0.8/requake/database/db.py +158 -0
  19. requake-0.8/requake/database/families.py +174 -0
  20. requake-0.8/requake/database/pairs.py +180 -0
  21. requake-0.8/requake/database/templates.py +175 -0
  22. {requake-0.7.2 → requake-0.8}/requake/families/__init__.py +3 -2
  23. {requake-0.7.2 → requake-0.8}/requake/families/build_families.py +19 -26
  24. {requake-0.7.2 → requake-0.8}/requake/families/build_templates.py +1 -3
  25. {requake-0.7.2 → requake-0.8}/requake/families/families.py +21 -71
  26. requake-0.8/requake/families/flag_family.py +31 -0
  27. {requake-0.7.2 → requake-0.8}/requake/families/pairs.py +14 -44
  28. {requake-0.7.2 → requake-0.8}/requake/families/print_families.py +3 -5
  29. {requake-0.7.2 → requake-0.8}/requake/families/print_pairs.py +1 -3
  30. {requake-0.7.2 → requake-0.8}/requake/formulas/conversion.py +2 -4
  31. {requake-0.7.2 → requake-0.8}/requake/formulas/moment.py +2 -2
  32. {requake-0.7.2 → requake-0.8}/requake/formulas/slip.py +6 -6
  33. {requake-0.7.2 → requake-0.8}/requake/main.py +1 -1
  34. {requake-0.7.2 → requake-0.8}/requake/plot/cached_tiler.py +1 -1
  35. {requake-0.7.2 → requake-0.8}/requake/plot/colormaps.py +0 -1
  36. {requake-0.7.2 → requake-0.8}/requake/plot/map_families.py +1 -3
  37. {requake-0.7.2 → requake-0.8}/requake/plot/map_tiles.py +25 -25
  38. {requake-0.7.2 → requake-0.8}/requake/plot/plot_cumulative.py +5 -11
  39. {requake-0.7.2 → requake-0.8}/requake/plot/plot_families.py +23 -20
  40. {requake-0.7.2 → requake-0.8}/requake/plot/plot_pair.py +1 -3
  41. {requake-0.7.2 → requake-0.8}/requake/plot/plot_timespans.py +2 -6
  42. {requake-0.7.2 → requake-0.8}/requake/plot/plot_utils.py +11 -12
  43. {requake-0.7.2 → requake-0.8}/requake/scan/scan_catalog.py +29 -31
  44. {requake-0.7.2 → requake-0.8}/requake/scan/scan_templates.py +36 -47
  45. {requake-0.7.2 → requake-0.8}/requake/waveforms/waveform_pair.py +1 -0
  46. {requake-0.7.2 → requake-0.8}/requake/waveforms/waveforms.py +9 -12
  47. {requake-0.7.2 → requake-0.8}/requake.egg-info/PKG-INFO +11 -2
  48. {requake-0.7.2 → requake-0.8}/requake.egg-info/SOURCES.txt +6 -0
  49. {requake-0.7.2 → requake-0.8}/setup.py +1 -1
  50. requake-0.7.2/requake/config/config.py +0 -31
  51. requake-0.7.2/requake/config/configobj/_version.py +0 -2
  52. requake-0.7.2/requake/families/flag_family.py +0 -46
  53. {requake-0.7.2 → requake-0.8}/CONTRIBUTORS.txt +0 -0
  54. {requake-0.7.2 → requake-0.8}/LICENSE.txt +0 -0
  55. {requake-0.7.2 → requake-0.8}/MANIFEST.in +0 -0
  56. {requake-0.7.2 → requake-0.8}/pyproject.toml +0 -0
  57. {requake-0.7.2 → requake-0.8}/requake/__init__.py +0 -0
  58. {requake-0.7.2 → requake-0.8}/requake/catalog/__init__.py +0 -0
  59. {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_fdsnws.py +0 -0
  60. {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_quakeml.py +0 -0
  61. {requake-0.7.2 → requake-0.8}/requake/config/configobj/LICENSE +0 -0
  62. {requake-0.7.2 → requake-0.8}/requake/config/configobj/__init__.py +0 -0
  63. {requake-0.7.2 → requake-0.8}/requake/config/configobj/validate.py +0 -0
  64. {requake-0.7.2 → requake-0.8}/requake/config/configspec.conf +0 -0
  65. {requake-0.7.2 → requake-0.8}/requake/formulas/__init__.py +0 -0
  66. {requake-0.7.2 → requake-0.8}/requake/plot/__init__.py +0 -0
  67. {requake-0.7.2 → requake-0.8}/requake/scan/__init__.py +0 -0
  68. {requake-0.7.2 → requake-0.8}/requake/waveforms/__init__.py +0 -0
  69. {requake-0.7.2 → requake-0.8}/requake/waveforms/arrivals.py +0 -0
  70. {requake-0.7.2 → requake-0.8}/requake/waveforms/station_metadata.py +0 -0
  71. {requake-0.7.2 → requake-0.8}/requake.egg-info/dependency_links.txt +0 -0
  72. {requake-0.7.2 → requake-0.8}/requake.egg-info/entry_points.txt +0 -0
  73. {requake-0.7.2 → requake-0.8}/requake.egg-info/requires.txt +0 -0
  74. {requake-0.7.2 → requake-0.8}/requake.egg-info/top_level.txt +0 -0
  75. {requake-0.7.2 → requake-0.8}/setup.cfg +0 -0
@@ -2,17 +2,33 @@
2
2
 
3
3
  Copyright (c) 2021-2026 Claudio Satriano <satriano@ipgp.fr>
4
4
 
5
- ## v0.7.2 - 2026-04-13
5
+ ## [0.8] - 2026-05-22
6
+
7
+ This release introduces a major storage change.
8
+
9
+ Requake now stores scan-related data (catalog, event pairs, and families) in a
10
+ SQLite database instead of CSV files. This improves storage efficiency and
11
+ data access performance, especially for large catalogs.
12
+
13
+ This change is not backward compatible at the data format level. The
14
+ high-level behavior of the command-line interface remains unchanged.
15
+
16
+ ## [0.7.3] - 2026-04-14
6
17
 
7
18
  This release is intended to validate the new release workflow.
8
19
  It introduces no new features or bug fixes.
9
20
 
10
- ## v0.7.1 - 2026-04-13
21
+ ## [0.7.2] - 2026-04-13
11
22
 
12
23
  This release is intended to validate the new release workflow.
13
24
  It introduces no new features or bug fixes.
14
25
 
15
- ## v0.7 - 2026-04-10
26
+ ## [0.7.1] - 2026-04-13
27
+
28
+ This release is intended to validate the new release workflow.
29
+ It introduces no new features or bug fixes.
30
+
31
+ ## [0.7] - 2026-04-10
16
32
 
17
33
  This version requires at least Python 3.9.
18
34
 
@@ -52,7 +68,7 @@ to the latest version.
52
68
  - warn if an invalid time format is found
53
69
  - Colored terminal output for warnings and errors
54
70
 
55
- ## v0.6 - 2024-05-04
71
+ ## [0.6] - 2024-05-04
56
72
 
57
73
  Note: you might want to run `requake update_config` to update your config file
58
74
  to the latest version.
@@ -69,7 +85,7 @@ to the latest version.
69
85
  - New verb `update_config`: update an existing config file to the latest
70
86
  version
71
87
 
72
- ## v0.5 - 2024-04-23
88
+ ## [0.5] - 2024-04-23
73
89
 
74
90
  - New config options: `station_metadata_path` and `waveform_data_path` to
75
91
  read station metadata and waveform data from files. Supports any metadata
@@ -81,11 +97,11 @@ to the latest version.
81
97
  - `print_families`: autoset duration units based on the average duration
82
98
  - `plot_timespans`: default sorting changed to `family_number`
83
99
 
84
- ## v0.4.1 - 2024-03-11
100
+ ## [0.4.1] - 2024-03-11
85
101
 
86
102
  - Bugfix: `requake` executable was not installed
87
103
 
88
- ## v0.4 - 2024-03-11
104
+ ## [0.4] - 2024-03-11
89
105
 
90
106
  - Package license changed to GPL-3.0 or later
91
107
  - New verb: `read_catalog`: read a catalog from web services or from a file
@@ -109,7 +125,7 @@ to the latest version.
109
125
  - Support for events with no location
110
126
  - Bugfix: `plot_timespans`: correctly plot x time axis when sort by `time`
111
127
 
112
- ## v0.3 - 2021-11-08
128
+ ## [0.3] - 2021-11-08
113
129
 
114
130
  - New verb: `build_templates`
115
131
  - New option: `cc_allow_negative` to search for anticorrelated events
@@ -123,7 +139,7 @@ to the latest version.
123
139
  - also shift theoretical arrivals when aligning traces
124
140
  - Bugfix: last family was not read from family file
125
141
 
126
- ## v0.2 - 2021-09-24
142
+ ## [0.2] - 2021-09-24
127
143
 
128
144
  Mostly a bugfix release with a slightly improved trace plotting.
129
145
 
@@ -131,6 +147,19 @@ Mostly a bugfix release with a slightly improved trace plotting.
131
147
  - Bugfix: detrend traces before filtering
132
148
  - `plot_families`: print number of events and station-event distance
133
149
 
134
- ## v0.1 - 2021-09-16
150
+ ## [0.1] - 2021-09-16
135
151
 
136
152
  - Initial release, not yet feature complete
153
+
154
+ [0.8]: https://github.com/SeismicSource/requake/compare/v0.7.3...v0.8
155
+ [0.7.3]: https://github.com/SeismicSource/requake/compare/v0.7.2...v0.7.3
156
+ [0.7.2]: https://github.com/SeismicSource/requake/compare/v0.7.1...v0.7.2
157
+ [0.7.1]: https://github.com/SeismicSource/requake/compare/v0.7...v0.7.1
158
+ [0.7]: https://github.com/SeismicSource/requake/compare/v0.6...v0.7
159
+ [0.6]: https://github.com/SeismicSource/requake/compare/v0.5...v0.6
160
+ [0.5]: https://github.com/SeismicSource/requake/compare/v0.4.1...v0.5
161
+ [0.4.1]: https://github.com/SeismicSource/requake/compare/v0.4...v0.4.1
162
+ [0.4]: https://github.com/SeismicSource/requake/compare/v0.3...v0.4
163
+ [0.3]: https://github.com/SeismicSource/requake/compare/v0.2...v0.3
164
+ [0.2]: https://github.com/SeismicSource/requake/compare/v0.1...v0.2
165
+ [0.1]: https://github.com/SeismicSource/requake/releases/tag/v0.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: requake
3
- Version: 0.7.2
3
+ Version: 0.8
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
@@ -37,7 +37,7 @@ Dynamic: description
37
37
  Dynamic: description-content-type
38
38
  Dynamic: license-file
39
39
 
40
- <img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@b6498527a861637124bae1b49943b83f9ca4a7d2/imgs/Requake_logo.svg" width="400">
40
+ <img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@e3ac4cce14f782d62a5f22f432a5391a2ccacfe7/imgs/Requake_logo.svg" width="400">
41
41
 
42
42
  # Requake
43
43
 
@@ -140,6 +140,15 @@ You can keep your local Requake repository updated by running `git pull`
140
140
  from times to times. Thanks to `pip`'s "editable mode", you don't need to
141
141
  reinstall Requake after each update.
142
142
 
143
+ If you want the repository style rules to run locally before each commit,
144
+ enable the included git hook once per clone:
145
+
146
+ scripts/setup_local_hooks.sh
147
+
148
+ You can also run the local style checks manually with:
149
+
150
+ scripts/check_style.sh
151
+
143
152
  ## Running
144
153
 
145
154
  ### Command line arguments
@@ -101,6 +101,15 @@ You can keep your local Requake repository updated by running `git pull`
101
101
  from times to times. Thanks to `pip`'s "editable mode", you don't need to
102
102
  reinstall Requake after each update.
103
103
 
104
+ If you want the repository style rules to run locally before each commit,
105
+ enable the included git hook once per clone:
106
+
107
+ scripts/setup_local_hooks.sh
108
+
109
+ You can also run the local style checks manually with:
110
+
111
+ scripts/check_style.sh
112
+
104
113
  ## Running
105
114
 
106
115
  ### Command line arguments
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2026-04-13T09:24:19+0200",
11
+ "date": "2026-05-22T08:53:09+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "b6498527a861637124bae1b49943b83f9ca4a7d2",
15
- "version": "0.7.2"
14
+ "full-revisionid": "e3ac4cce14f782d62a5f22f432a5391a2ccacfe7",
15
+ "version": "0.8"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -28,6 +28,7 @@ class RequakeEvent():
28
28
  depth=None, mag_type=None, mag=None, author=None,
29
29
  catalog=None, contributor=None, contributor_id=None,
30
30
  mag_author=None, location_name=None, trace_id=None):
31
+ """Initialize an event container."""
31
32
  self.evid = evid
32
33
  self.orig_time = orig_time
33
34
  self.lon = lon
@@ -45,24 +46,31 @@ class RequakeEvent():
45
46
  self.correlations = {}
46
47
 
47
48
  def __eq__(self, other):
49
+ """Return True when two events have the same identity."""
48
50
  return self.evid == other.evid and self.trace_id == other.trace_id
49
51
 
50
52
  def __gt__(self, other):
53
+ """Compare events by origin time."""
51
54
  return self.orig_time > other.orig_time
52
55
 
53
56
  def __ge__(self, other):
57
+ """Compare events by origin time."""
54
58
  return self.orig_time >= other.orig_time
55
59
 
56
60
  def __lt__(self, other):
61
+ """Compare events by origin time."""
57
62
  return self.orig_time < other.orig_time
58
63
 
59
64
  def __le__(self, other):
65
+ """Compare events by origin time."""
60
66
  return self.orig_time <= other.orig_time
61
67
 
62
68
  def __hash__(self):
69
+ """Return the event hash."""
63
70
  return self.evid.__hash__()
64
71
 
65
72
  def __str__(self):
73
+ """Return a compact string representation of the event."""
66
74
  return (
67
75
  f'{self.evid} {self.orig_time} '
68
76
  f'{self.lon} {self.lat} {self.depth} {self.mag_type} {self.mag}'
@@ -148,46 +156,12 @@ class RequakeCatalog(list):
148
156
  """
149
157
  self[:] = sorted(self, key=lambda ev: ev.orig_time)
150
158
 
151
- def read(self, filename):
152
- """
153
- Read catalog from FDSN text file format.
154
-
155
- Skips events already in the catalog.
156
-
157
- :param filename: input filename
158
- :type filename: str
159
-
160
- :raises FileNotFoundError: if filename does not exist
161
- :raises ValueError: if line is not in FDSN text file format
162
- """
163
- with open(filename, 'r', encoding='utf8') as fp:
164
- for line in fp:
165
- if not line:
166
- continue
167
- if line[0] == '#':
168
- continue
169
- ev = RequakeEvent()
170
- ev.from_fdsn_text(line)
171
- self.append(ev)
172
- self.deduplicate()
173
-
174
- def write(self, filename):
175
- """
176
- Write catalog in FDSN text file format.
177
-
178
- :param filename: output filename
179
- :type filename: str
180
- """
181
- with open(filename, 'w', encoding='utf8') as fp:
182
- for ev in self:
183
- fp.write(ev.fdsn_text() + '\n')
184
-
185
159
  def filter(
186
- self, starttime=None, endtime=None,
187
- minlatitude=None, maxlatitude=None,
188
- minlongitude=None, maxlongitude=None,
189
- mindepth=None, maxdepth=None,
190
- minmagnitude=None, maxmagnitude=None
160
+ self, starttime=None, endtime=None,
161
+ minlatitude=None, maxlatitude=None,
162
+ minlongitude=None, maxlongitude=None,
163
+ mindepth=None, maxdepth=None,
164
+ minmagnitude=None, maxmagnitude=None
191
165
  ):
192
166
  """
193
167
  Filter the catalog, based on the specified criteria.
@@ -220,60 +194,47 @@ class RequakeCatalog(list):
220
194
  """
221
195
  outcat = RequakeCatalog()
222
196
  for ev in self:
223
- if ev.orig_time is not None:
224
- if starttime is not None and ev.orig_time < starttime:
225
- continue
226
- if endtime is not None and ev.orig_time > endtime:
227
- continue
228
- if ev.lat is not None:
229
- if minlatitude is not None and ev.lat < minlatitude:
230
- continue
231
- if maxlatitude is not None and ev.lat > maxlatitude:
232
- continue
233
- if ev.lon is not None:
234
- if minlongitude is not None and ev.lon < minlongitude:
235
- continue
236
- if maxlongitude is not None and ev.lon > maxlongitude:
237
- continue
238
- if ev.depth is not None:
239
- if mindepth is not None and ev.depth < mindepth:
240
- continue
241
- if maxdepth is not None and ev.depth > maxdepth:
242
- continue
243
- if ev.mag is not None:
244
- if minmagnitude is not None and ev.mag < minmagnitude:
245
- continue
246
- if maxmagnitude is not None and ev.mag > maxmagnitude:
247
- continue
197
+ if _out_of_range(ev.orig_time, starttime, endtime):
198
+ continue
199
+ if _out_of_range(ev.lat, minlatitude, maxlatitude):
200
+ continue
201
+ if _out_of_range(ev.lon, minlongitude, maxlongitude):
202
+ continue
203
+ if _out_of_range(ev.depth, mindepth, maxdepth):
204
+ continue
205
+ if _out_of_range(ev.mag, minmagnitude, maxmagnitude):
206
+ continue
248
207
  outcat.append(ev)
249
208
  return outcat
250
209
 
251
210
 
211
+ def _out_of_range(value, minimum, maximum):
212
+ """Return True when a value falls outside the requested bounds."""
213
+ if value is None:
214
+ return False
215
+ return (
216
+ (minimum is not None and value < minimum)
217
+ or (maximum is not None and value > maximum)
218
+ )
219
+
220
+
252
221
  def read_stored_catalog():
253
222
  """
254
- Read the catalog stored in the output directory.
223
+ Read the catalog stored in the output database.
255
224
 
256
225
  :return: Catalog object.
257
226
  :rtype: RequakeCatalog
258
227
 
259
- :raises ValueError: if error reading catalog file
260
- :raises FileNotFoundError: if catalog file not found
228
+ :raises ValueError: if the stored catalog is empty
229
+ :raises FileNotFoundError: if the output database is not found
261
230
  """
262
- try:
263
- cat = RequakeCatalog()
264
- cat.read(config.scan_catalog_file)
265
- cat.sort()
266
- if not cat:
267
- raise ValueError('Empty catalog')
268
- return cat
269
- except ValueError as err:
270
- raise ValueError(
271
- f'Error reading catalog file {config.scan_catalog_file}: {err}'
272
- ) from err
273
- except (FileNotFoundError, NotADirectoryError) as err:
274
- raise FileNotFoundError(
275
- f'Catalog file {config.scan_catalog_file} not found'
276
- ) from err
231
+ from ..database.catalog import read_catalog as read_catalog_from_db
232
+
233
+ cat = read_catalog_from_db(config)
234
+ cat.sort()
235
+ if not cat:
236
+ raise ValueError('Empty catalog')
237
+ return cat
277
238
 
278
239
 
279
240
  def fix_non_locatable_events(catalog):
@@ -308,9 +269,9 @@ def _base26(val):
308
269
  :rtype: str
309
270
  """
310
271
  chars = [
311
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
312
- 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
313
- 'u', 'v', 'w', 'x', 'y', 'z'
272
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
273
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
274
+ 'u', 'v', 'w', 'x', 'y', 'z'
314
275
  ]
315
276
  base = len(chars)
316
277
  ret = ''
@@ -337,7 +298,7 @@ def generate_evid(orig_time):
337
298
  orig_year = UTCDateTime(year=year, month=1, day=1)
338
299
  val = int(orig_time - orig_year)
339
300
  # normalize val between 0 (aaaaaa) and 26**6-1 (zzzzzz)
340
- maxval = 366*24*3600 # max number of seconds in leap year
341
- normval = int(val/maxval * (26**6-1))
301
+ maxval = 366 * 24 * 3600 # max number of seconds in leap year
302
+ normval = int(val / maxval * (26 ** 6 - 1))
342
303
  ret = _base26(normval)
343
304
  return f'{prefix}{year}{ret}'
@@ -16,9 +16,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
16
16
 
17
17
 
18
18
  def print_catalog():
19
- """
20
- Print the event catalog to screen.
21
- """
19
+ """Print the event catalog to screen."""
22
20
  try:
23
21
  catalog = read_stored_catalog()
24
22
  except (ValueError, FileNotFoundError) as msg:
@@ -13,7 +13,9 @@ import os
13
13
  import logging
14
14
  import contextlib
15
15
  from ..config import config, rq_exit
16
- from .catalog import RequakeCatalog
16
+ from ..database.db import get_db_path
17
+ from ..database.catalog import write_catalog as write_stored_catalog
18
+ from .catalog import RequakeEvent, RequakeCatalog
17
19
  from .read_catalog_from_fdsnws import read_catalog_from_fdsnws
18
20
  from .read_catalog_from_quakeml import read_catalog_from_quakeml
19
21
  from .read_catalog_from_csv import read_catalog_from_csv
@@ -45,7 +47,16 @@ def _read_catalog_from_file():
45
47
  # try to read the catalog as a FDSN text file
46
48
  with contextlib.suppress(ValueError):
47
49
  cat = RequakeCatalog()
48
- cat.read(catalog_file)
50
+ with open(catalog_file, 'r', encoding='utf8') as fp:
51
+ for line in fp:
52
+ if not line:
53
+ continue
54
+ if line[0] == '#':
55
+ continue
56
+ ev = RequakeEvent()
57
+ ev.from_fdsn_text(line)
58
+ cat.append(ev)
59
+ cat.deduplicate()
49
60
  return cat
50
61
  # try to read the catalog as a CSV file
51
62
  # raises ValueError in case of failure
@@ -54,8 +65,7 @@ def _read_catalog_from_file():
54
65
 
55
66
  def _filter_catalog(catalog):
56
67
  """
57
- Filter an event catalog, based on the criteria specified
58
- in the configuration.
68
+ Filter an event catalog using the configured selection criteria.
59
69
 
60
70
  :param catalog: Event catalog.
61
71
  :type catalog: requake.catalog.RequakeCatalog
@@ -99,14 +109,17 @@ def read_catalog():
99
109
  :rtype: requake.catalog.RequakeCatalog
100
110
  """
101
111
  catalog = RequakeCatalog()
102
- output_cat_file = config.scan_catalog_file
103
112
  nevs_read = 0
104
113
  if config.args.append:
105
114
  with contextlib.suppress(FileNotFoundError):
106
- catalog = RequakeCatalog()
107
- catalog.read(output_cat_file)
115
+ from .catalog import read_stored_catalog
116
+
117
+ catalog = read_stored_catalog()
108
118
  nevs_read = len(catalog)
109
- logger.info(f'{nevs_read} events read from "{output_cat_file}"')
119
+ logger.info(
120
+ f'{nevs_read} events read from db file '
121
+ f'{get_db_path(config)}'
122
+ )
110
123
  logger.info('Reading catalog...')
111
124
  input_cat_file = config.args.catalog_file
112
125
  if input_cat_file is not None:
@@ -136,7 +149,10 @@ def read_catalog():
136
149
  logger.info(f'{nevs_dedup} duplicate events removed')
137
150
  # Sort catalog in increasing time order
138
151
  catalog.sort()
139
- # Write catalog to output file
140
- catalog.write(output_cat_file)
152
+ # Write catalog to the output database
153
+ write_stored_catalog(catalog, config)
141
154
  nevs_written = len(catalog) - nevs_read
142
- logger.info(f'{nevs_written} events written to "{output_cat_file}"')
155
+ logger.info(
156
+ f'{nevs_written} events written to db file '
157
+ f'{get_db_path(config)}'
158
+ )
@@ -19,8 +19,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
19
19
 
20
20
  def _guess_field_names(input_fields):
21
21
  """
22
- Guess the field names corresponding to origin time, latitude, longitude,
23
- depth, magnitude and magnitude type.
22
+ Guess field names for origin time, location, depth, and magnitude.
24
23
 
25
24
  :param input_fields: list of field names
26
25
  :type input_fields: list of str
@@ -80,9 +79,8 @@ def _guess_field_names(input_fields):
80
79
  # make a list of duplicated fields, which have been matched more than once
81
80
  duplicated_fields = [
82
81
  (key, value, output_field_scores[key])
83
- for key, value in output_fields.items() if
84
- value is not None and
85
- list(output_fields.values()).count(value) > 1
82
+ for key, value in output_fields.items()
83
+ if value is not None and list(output_fields.values()).count(value) > 1
86
84
  ]
87
85
  # if there are duplicated fields, keep the one with the highest score
88
86
  for _key, value, score in duplicated_fields:
@@ -167,7 +165,7 @@ def read_catalog_from_csv(filename):
167
165
  nrows -= 1 # first row is the header
168
166
  cat = RequakeCatalog()
169
167
  for n, row in enumerate(reader):
170
- print(f'reading row {n+1}/{nrows}\r', end='')
168
+ print(f'reading row {n + 1}/{nrows}\r', end='')
171
169
  if fields['orig_time'] is None:
172
170
  # try build a date-time field from year, month, day, hour,
173
171
  # minute and seconds fields
@@ -195,7 +193,7 @@ def read_catalog_from_csv(filename):
195
193
  )
196
194
  except ValueError:
197
195
  logger.error(
198
- f'Unable to parse origin time at row {n+2}: '
196
+ f'Unable to parse origin time at row {n + 2}: '
199
197
  f'"{orig_time_str}"')
200
198
  continue
201
199
  row[None] = None
@@ -14,4 +14,8 @@ from .generic_printer import generic_printer # noqa
14
14
  # The config object is created in config.py and needs to be populated
15
15
  # when using requake from command line, this is done by the configure()
16
16
  # function in rq_setup.py
17
- from .config import config # noqa
17
+ from .config import ( # noqa
18
+ config,
19
+ to_picklable_config_dict,
20
+ from_picklable_config_dict,
21
+ )
@@ -0,0 +1,125 @@
1
+ # -*- coding: utf8 -*-
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ """
4
+ Config class 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
+ from argparse import Namespace
13
+
14
+
15
+ # Runtime objects that are useful in-process but should not be sent to
16
+ # worker processes via ProcessPoolExecutor.
17
+ RUNTIME_ONLY_KEYS = frozenset({
18
+ 'station_client',
19
+ 'dataselect_client',
20
+ 'catalog_fdsn_event_clients',
21
+ })
22
+
23
+
24
+ class Config(dict):
25
+ """A class to access config values with dot notation."""
26
+
27
+ def __setitem__(self, key, value):
28
+ """Make Config keys accessible as attributes."""
29
+ super().__setattr__(key, value)
30
+ super().__setitem__(key, value)
31
+
32
+ def __getattr__(self, key):
33
+ """Make Config keys accessible as attributes."""
34
+ try:
35
+ return self.__getitem__(key)
36
+ except KeyError as err:
37
+ raise AttributeError(err) from err
38
+
39
+ __setattr__ = __setitem__
40
+
41
+
42
+ def to_picklable_config_dict(
43
+ cfg,
44
+ drop_inventory=False,
45
+ extra_excluded_keys=None,
46
+ ):
47
+ """
48
+ Build a pickle-safe config snapshot for multiprocessing.
49
+
50
+ This helper strips known runtime-only objects (for example ObsPy clients
51
+ containing SSL contexts) and converts ``args`` from ``argparse.Namespace``
52
+ to a plain ``dict``.
53
+
54
+ Typical usage with ``concurrent.futures.ProcessPoolExecutor``::
55
+
56
+ from concurrent.futures import ProcessPoolExecutor
57
+ from requake.config import (
58
+ config,
59
+ to_picklable_config_dict,
60
+ from_picklable_config_dict,
61
+ )
62
+
63
+ cfg_dict = to_picklable_config_dict(config)
64
+
65
+ def worker(cfg_dict, event_id):
66
+ cfg = from_picklable_config_dict(cfg_dict)
67
+ # Rebuild only what is needed in each process.
68
+ # Re-create network clients here, not in the parent process.
69
+ return cfg['cc_min'], event_id
70
+
71
+ def _worker_call(args):
72
+ cfg, evid = args
73
+ return worker(cfg, evid)
74
+
75
+ with ProcessPoolExecutor() as pool:
76
+ worker_args = ((cfg_dict, evid) for evid in event_ids)
77
+ list(pool.map(_worker_call, worker_args))
78
+
79
+ :param cfg: Requake config-like mapping.
80
+ :type cfg: Mapping
81
+ :param drop_inventory: Exclude ``inventory`` from the snapshot.
82
+ :type drop_inventory: bool
83
+ :param extra_excluded_keys: Additional keys to remove from the snapshot.
84
+ :type extra_excluded_keys: iterable or None
85
+ :return: A pickle-safe shallow copy of the configuration.
86
+ :rtype: dict
87
+ """
88
+ cfg_dict = dict(cfg)
89
+ excluded_keys = set(RUNTIME_ONLY_KEYS)
90
+ if drop_inventory:
91
+ excluded_keys.add('inventory')
92
+ if extra_excluded_keys is not None:
93
+ excluded_keys.update(extra_excluded_keys)
94
+ for key in excluded_keys:
95
+ cfg_dict.pop(key, None)
96
+
97
+ args = cfg_dict.get('args')
98
+ if isinstance(args, Namespace):
99
+ cfg_dict['args'] = vars(args).copy()
100
+
101
+ return cfg_dict
102
+
103
+
104
+ def from_picklable_config_dict(cfg_dict):
105
+ """
106
+ Rebuild a ``Config`` instance from a pickle-safe snapshot.
107
+
108
+ This helper is the counterpart of ``to_picklable_config_dict()`` and is
109
+ intended for worker processes receiving a serialized config payload.
110
+
111
+ If ``args`` is a dict, it is converted back to ``argparse.Namespace``.
112
+
113
+ :param cfg_dict: Pickle-safe config snapshot.
114
+ :type cfg_dict: Mapping
115
+ :return: Reconstructed config object.
116
+ :rtype: Config
117
+ """
118
+ cfg = Config()
119
+ cfg.update(dict(cfg_dict))
120
+ args = cfg.get('args')
121
+ if isinstance(args, dict):
122
+ cfg['args'] = Namespace(**args)
123
+ return cfg
124
+
125
+ config = Config() # noqa
@@ -0,0 +1,6 @@
1
+ """Project version.
2
+
3
+ Copyright (C) 2005-2014 ConfigObj contributors.
4
+ Licensed under BSD-3-Clause.
5
+ """
6
+ __version__ = '5.1.0'
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
21
21
 
22
22
  def generic_printer(rows, headers_fmt, print_headers=True):
23
23
  """
24
- A generic printer function for Requake.
24
+ Print rows using the configured output format.
25
25
 
26
26
  :param rows: Rows to print.
27
27
  :type rows: list of rows