Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/client/fdsn.py: 69%

262 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-23 12:34 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Low-level FDSN web service client. 

8 

9This module provides basic functionality to download station metadata, time 

10series data and event information from FDSN web services. Password and token 

11authentication are supported. Query responses are returned as open file-like 

12objects or can be parsed into Pyrocko's native data structures where 

13appropriate. 

14 

15.. _registered-site-names: 

16 

17Registered site names 

18..................... 

19 

20A list of known FDSN site names is maintained within the module for quick 

21selection by the user. This list currently contains the following sites: 

22 

23%s 

24 

25Any other site can be specified by providing its full URL. 

26''' 

27 

28import re 

29import logging 

30import socket 

31 

32 

33from pyrocko import util 

34from pyrocko.util import DownloadError 

35from pyrocko import config 

36 

37from pyrocko.util import \ 

38 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError 

39 

40 

41logger = logging.getLogger('pyrocko.client.fdsn') 

42 

43g_url = '%(site)s/fdsnws/%(service)s/%(majorversion)i/%(method)s' 

44 

45g_site_abbr = { 

46 'geofon': 'https://geofon.gfz-potsdam.de', 

47 'iris': 'http://service.iris.edu', 

48 'orfeus': 'http://www.orfeus-eu.org', 

49 'bgr': 'http://eida.bgr.de', 

50 'emsc': 'http://www.seismicportal.eu', 

51 'geonet': 'http://service.geonet.org.nz', 

52 'knmi': 'http://rdsa.knmi.nl', 

53 'ncedc': 'http://service.ncedc.org', 

54 'scedc': 'http://service.scedc.caltech.edu', 

55 'usgs': 'http://earthquake.usgs.gov', 

56 'koeri': 'http://eida-service.koeri.boun.edu.tr', 

57 'ethz': 'http://eida.ethz.ch', 

58 'icgc': 'http://ws.icgc.cat', 

59 'ipgp': 'http://eida.ipgp.fr', 

60 'ingv': 'http://webservices.ingv.it', 

61 'isc': 'http://www.isc.ac.uk', 

62 'lmu': 'http://erde.geophysik.uni-muenchen.de', 

63 'noa': 'http://eida.gein.noa.gr', 

64 'resif': 'http://ws.resif.fr', 

65 'usp': 'http://seisrequest.iag.usp.br', 

66 'niep': 'http://eida-sc3.infp.ro' 

67} 

68 

69g_default_site = 'geofon' 

70 

71 

72g_default_query_args = { 

73 'station': { 

74 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore', 

75 'endafter', 'network', 'station', 'location', 'channel', 'minlatitude', 

76 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude', 

77 'minradius', 'maxradius', 'level', 'includerestricted', 

78 'includeavailability', 'updatedafter', 'matchtimeseries', 'format', 

79 'nodata'}, 

80 'dataselect': { 

81 'starttime', 'endtime', 'network', 'station', 'location', 'channel', 

82 'quality', 'minimumlength', 'longestonly', 'format', 'nodata'}, 

83 'event': { 

84 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude', 

85 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius', 

86 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype', 

87 'includeallorigins', 'includeallmagnitudes', 'includearrivals', 

88 'eventid', 'limit', 'offset', 'orderby', 'catalog', 'contributor', 

89 'updatedafter', 'format', 'nodata'}, 

90 'availability': { 

91 'starttime', 'endtime', 'network', 'station', 'location', 'channel', 

92 'quality', 'merge', 'orderby', 'limit', 'includerestricted', 'format', 

93 'nodata', 'mergegaps', 'show'}} 

94 

95 

96def doc_escape_slist(li): 

97 return ', '.join("``'%s'``" % s for s in li) 

98 

99 

100def doc_table_dict(d, khead, vhead, indent=''): 

101 keys, vals = zip(*sorted(d.items())) 

102 

103 lk = max(max(len(k) for k in keys), len(khead)) 

104 lv = max(max(len(v) for v in vals), len(vhead)) 

105 

106 hr = '=' * lk + ' ' + '=' * lv 

107 

108 lines = [ 

109 hr, 

110 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)), 

111 hr] 

112 

113 for k, v in zip(keys, vals): 

114 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv))) 

115 

116 lines.append(hr) 

117 return '\n'.join(indent + line for line in lines) 

118 

119 

120def strip_html(s): 

121 s = s.decode('utf-8') 

122 s = re.sub(r'<[^>]+>', '', s) 

123 s = re.sub(r'\r', '', s) 

124 s = re.sub(r'\s*\n', '\n', s) 

125 return s 

126 

127 

128def indent(s, ind=' '): 

129 return '\n'.join(ind + line for line in s.splitlines()) 

130 

131 

132def get_sites(): 

133 ''' 

134 Get sorted list of registered site names. 

135 ''' 

136 return sorted(g_site_abbr.keys()) 

137 

138 

139if config.config().fdsn_timeout is None: 

140 g_timeout = 20. 

141else: 

142 g_timeout = config.config().fdsn_timeout 

143 

144re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?') 

145 

146 

147class CannotGetRealmFromAuthHeader(DownloadError): 

148 ''' 

149 Raised when failing to parse server response during authentication. 

150 ''' 

151 pass 

152 

153 

154class CannotGetCredentialsFromAuthRequest(DownloadError): 

155 ''' 

156 Raised when failing to parse server response during token authentication. 

157 ''' 

158 pass 

159 

160 

161def get_realm_from_auth_header(headers): 

162 realm = dict(re_realm_from_auth_header.findall( 

163 headers['WWW-Authenticate'])).get('realm', None) 

164 

165 if realm is None: 

166 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers)) 

167 

168 return realm 

169 

170 

171def sdatetime(t): 

172 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S') 

173 

174 

175class EmptyResult(DownloadError): 

176 ''' 

177 Raised when an empty server response is retrieved. 

178 ''' 

179 def __init__(self, url): 

180 DownloadError.__init__(self) 

181 self._url = url 

182 

183 def __str__(self): 

184 return 'No results for request %s' % self._url 

185 

186 

187class RequestEntityTooLarge(DownloadError): 

188 ''' 

189 Raised when the server indicates that too much data was requested. 

190 ''' 

191 def __init__(self, url): 

192 DownloadError.__init__(self) 

193 self._url = url 

194 

195 def __str__(self): 

196 return 'Request entity too large: %s' % self._url 

197 

198 

199class InvalidRequest(DownloadError): 

200 ''' 

201 Raised when an invalid request would be sent / has been sent. 

202 ''' 

203 pass 

204 

205 

206class Timeout(DownloadError): 

207 ''' 

208 Raised when the server does not respond within the allowed timeout period. 

209 ''' 

210 pass 

211 

212 

213def _request( 

214 url, 

215 post=False, 

216 user=None, 

217 passwd=None, 

218 timeout=None, 

219 **kwargs): 

220 

221 if timeout is None: 

222 timeout = g_timeout 

223 

224 url_values = urlencode(kwargs) 

225 if url_values: 

226 url += '?' + url_values 

227 

228 logger.debug('Accessing URL %s' % url) 

229 url_args = { 

230 'timeout': timeout 

231 } 

232 

233 if util.g_ssl_context: 

234 url_args['context'] = util.g_ssl_context 

235 

236 opener = None 

237 

238 req = Request(url) 

239 if post: 

240 if isinstance(post, str): 

241 post = post.encode('utf8') 

242 logger.debug('POST data: \n%s' % post.decode('utf8')) 

243 req.data = post 

244 

245 req.add_header('Accept', '*/*') 

246 

247 itry = 0 

248 while True: 

249 itry += 1 

250 try: 

251 urlopen_ = opener.open if opener else urlopen 

252 try: 

253 resp = urlopen_(req, **url_args) 

254 except TypeError: 

255 # context and cafile not avail before 3.4.3, 2.7.9 

256 url_args.pop('context', None) 

257 url_args.pop('cafile', None) 

258 resp = urlopen_(req, **url_args) 

259 

260 logger.debug('Response: %s' % resp.getcode()) 

261 if resp.getcode() == 204: 

262 raise EmptyResult(url) 

263 return resp 

264 

265 except HTTPError as e: 

266 if e.code == 413: 

267 raise RequestEntityTooLarge(url) 

268 

269 elif e.code == 401: 

270 headers = getattr(e, 'headers', e.hdrs) 

271 

272 realm = get_realm_from_auth_header(headers) 

273 

274 if itry == 1 and user is not None: 

275 auth_handler = HTTPDigestAuthHandler() 

276 auth_handler.add_password( 

277 realm=realm, 

278 uri=url, 

279 user=user, 

280 passwd=passwd or '') 

281 

282 opener = build_opener(auth_handler) 

283 continue 

284 else: 

285 raise DownloadError( 

286 'Authentication failed for realm "%s" when accessing ' 

287 'url "%s". Original error was: %s' % ( 

288 realm, url, str(e))) 

289 

290 else: 

291 raise DownloadError( 

292 'Error content returned by server (HTML stripped):\n%s\n' 

293 ' Original error was: %s' % ( 

294 indent( 

295 strip_html(e.read()), 

296 ' ! '), 

297 str(e))) 

298 

299 except socket.timeout: 

300 raise Timeout( 

301 'Timeout error. No response received within %i s. You ' 

302 'may want to retry with a longer timeout setting.' % timeout) 

303 

304 break 

305 

306 

307def fillurl(service, site, url, majorversion, method): 

308 return url % dict( 

309 site=g_site_abbr.get(site, site), 

310 service=service, 

311 majorversion=majorversion, 

312 method=method) 

313 

314 

315def fix_params(d): 

316 

317 params = dict(d) 

318 for k in ['starttime', 

319 'endtime', 

320 'startbefore', 

321 'startafter', 

322 'endbefore', 

323 'endafter', 

324 'updatedafter']: 

325 

326 if k in params: 

327 params[k] = sdatetime(params[k]) 

328 

329 if params.get('location', None) == '': 

330 params['location'] = '--' 

331 

332 for k in params: 

333 if isinstance(params[k], bool): 

334 params[k] = ['false', 'true'][bool(params[k])] 

335 

336 return params 

337 

338 

339def make_data_selection( 

340 stations, tmin, tmax, 

341 channel_prio=[['BHZ', 'HHZ'], 

342 ['BH1', 'BHN', 'HH1', 'HHN'], 

343 ['BH2', 'BHE', 'HH2', 'HHE']]): 

344 

345 selection = [] 

346 for station in stations: 

347 wanted = [] 

348 for group in channel_prio: 

349 gchannels = [] 

350 for channel in station.get_channels(): 

351 if channel.name in group: 

352 gchannels.append(channel) 

353 if gchannels: 

354 gchannels.sort(key=lambda a: group.index(a.name)) 

355 wanted.append(gchannels[0]) 

356 

357 if wanted: 

358 for channel in wanted: 

359 selection.append((station.network, station.station, 

360 station.location, channel.name, tmin, tmax)) 

361 

362 return selection 

363 

364 

365def station( 

366 site=g_default_site, 

367 url=g_url, 

368 majorversion=1, 

369 timeout=None, 

370 check=True, 

371 selection=None, 

372 parsed=True, 

373 **kwargs): 

374 

375 ''' 

376 Query FDSN web service for station metadata. 

377 

378 :param site: 

379 :ref:`Registered site name <registered-site-names>` or full base URL of 

380 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

381 :type site: str 

382 :param url: 

383 URL template (default should work in 99% of cases). 

384 :type url: str 

385 :param majorversion: 

386 Major version of the service to query (always ``1`` at the time of 

387 writing). 

388 :type majorversion: int 

389 :param timeout: 

390 Network timeout in [s]. Global default timeout can be configured in 

391 Pyrocko's configuration file under ``fdsn_timeout``. 

392 :type timeout: float 

393 :param check: 

394 If ``True`` arguments are checked against self-description (WADL) of 

395 the queried web service if available or FDSN specification. 

396 :type check: bool 

397 :param selection: 

398 If given, selection to be queried as a list of tuples 

399 ``(network, station, location, channel, tmin, tmax)``. Useful for 

400 detailed queries. 

401 :type selection: :py:class:`list` of :py:class:`tuple` 

402 :param parsed: 

403 If ``True`` parse received content into 

404 :py:class:`~pyrocko.io.stationxml.FDSNStationXML` 

405 object, otherwise return open file handle to raw data stream. 

406 :type parsed: bool 

407 :param \\*\\*kwargs: 

408 Parameters passed to the server (see `FDSN web services specification 

409 <https://www.fdsn.org/webservices>`_). 

410 

411 :returns: 

412 See description of ``parsed`` argument above. 

413 

414 :raises: 

415 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its 

416 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is 

417 raised. 

418 ''' 

419 

420 service = 'station' 

421 

422 if check: 

423 check_params(service, site, url, majorversion, timeout, **kwargs) 

424 

425 params = fix_params(kwargs) 

426 

427 url = fillurl(service, site, url, majorversion, 'query') 

428 if selection: 

429 lst = [] 

430 for k, v in params.items(): 

431 lst.append('%s=%s' % (k, v)) 

432 

433 for (network, station, location, channel, tmin, tmax) in selection: 

434 if location == '': 

435 location = '--' 

436 

437 lst.append(' '.join((network, station, location, channel, 

438 sdatetime(tmin), sdatetime(tmax)))) 

439 

440 post = '\n'.join(lst) 

441 params = dict(post=post.encode()) 

442 

443 if parsed: 

444 from pyrocko.io import stationxml 

445 format = kwargs.get('format', 'xml') 

446 if format == 'text': 

447 if kwargs.get('level', 'station') == 'channel': 

448 return stationxml.load_channel_table( 

449 stream=_request(url, timeout=timeout, **params)) 

450 else: 

451 raise InvalidRequest('if format="text" shall be parsed, ' 

452 'level="channel" is required') 

453 

454 elif format == 'xml': 

455 return stationxml.load_xml( 

456 stream=_request(url, timeout=timeout, **params)) 

457 else: 

458 raise InvalidRequest('format must be "xml" or "text"') 

459 else: 

460 return _request(url, timeout=timeout, **params) 

461 

462 

463def get_auth_credentials(service, site, url, majorversion, token, timeout): 

464 

465 url = fillurl(service, site, url, majorversion, 'auth') 

466 

467 f = _request(url, timeout=timeout, post=token) 

468 s = f.read().decode() 

469 try: 

470 user, passwd = s.strip().split(':') 

471 except ValueError: 

472 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s) 

473 

474 return user, passwd 

475 

476 

477def dataselect( 

478 site=g_default_site, 

479 url=g_url, 

480 majorversion=1, 

481 timeout=None, 

482 check=True, 

483 user=None, 

484 passwd=None, 

485 token=None, 

486 selection=None, 

487 **kwargs): 

488 

489 ''' 

490 Query FDSN web service for time series data in miniSEED format. 

491 

492 :param site: 

493 :ref:`Registered site name <registered-site-names>` or full base URL of 

494 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

495 :type site: str 

496 :param url: 

497 URL template (default should work in 99% of cases). 

498 :type url: str 

499 :param majorversion: 

500 Major version of the service to query (always ``1`` at the time of 

501 writing). 

502 :type majorversion: int 

503 :param timeout: 

504 Network timeout in [s]. Global default timeout can be configured in 

505 Pyrocko's configuration file under ``fdsn_timeout``. 

506 :type timeout: float 

507 :param check: 

508 If ``True`` arguments are checked against self-description (WADL) of 

509 the queried web service if available or FDSN specification. 

510 :type check: bool 

511 :param user: User name for user/password authentication. 

512 :type user: str 

513 :param passwd: Password for user/password authentication. 

514 :type passwd: str 

515 :param token: Token for `token authentication 

516 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

517 :type token: str 

518 :param selection: 

519 If given, selection to be queried as a list of tuples 

520 ``(network, station, location, channel, tmin, tmax)``. 

521 :type selection: :py:class:`list` of :py:class:`tuple` 

522 :param \\*\\*kwargs: 

523 Parameters passed to the server (see `FDSN web services specification 

524 <https://www.fdsn.org/webservices>`_). 

525 

526 :returns: 

527 Open file-like object providing raw miniSEED data. 

528 ''' 

529 

530 service = 'dataselect' 

531 

532 if user or token: 

533 method = 'queryauth' 

534 else: 

535 method = 'query' 

536 

537 if token is not None: 

538 user, passwd = get_auth_credentials( 

539 service, site, url, majorversion, token, timeout) 

540 

541 if check: 

542 check_params(service, site, url, majorversion, timeout, **kwargs) 

543 

544 params = fix_params(kwargs) 

545 

546 url = fillurl(service, site, url, majorversion, method) 

547 if selection: 

548 lst = [] 

549 

550 for k, v in params.items(): 

551 lst.append('%s=%s' % (k, v)) 

552 

553 for (network, station, location, channel, tmin, tmax) in selection: 

554 if location == '': 

555 location = '--' 

556 

557 lst.append(' '.join((network, station, location, channel, 

558 sdatetime(tmin), sdatetime(tmax)))) 

559 

560 post = '\n'.join(lst) 

561 return _request( 

562 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout) 

563 else: 

564 return _request( 

565 url, user=user, passwd=passwd, timeout=timeout, **params) 

566 

567 

568def event( 

569 site=g_default_site, 

570 url=g_url, 

571 majorversion=1, 

572 timeout=None, 

573 check=True, 

574 user=None, 

575 passwd=None, 

576 token=None, 

577 parsed=False, 

578 **kwargs): 

579 

580 ''' 

581 Query FDSN web service for events. 

582 

583 :param site: 

584 :ref:`Registered site name <registered-site-names>` or full base URL of 

585 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

586 :type site: str 

587 :param url: 

588 URL template (default should work in 99% of cases). 

589 :type url: str 

590 :param majorversion: 

591 Major version of the service to query (always ``1`` at the time of 

592 writing). 

593 :type majorversion: int 

594 :param timeout: 

595 Network timeout in [s]. Global default timeout can be configured in 

596 Pyrocko's configuration file under ``fdsn_timeout``. 

597 :type timeout: float 

598 :param check: 

599 If ``True`` arguments are checked against self-description (WADL) of 

600 the queried web service if available or FDSN specification. 

601 :type check: bool 

602 :param user: User name for user/password authentication. 

603 :type user: str 

604 :param passwd: Password for user/password authentication. 

605 :type passwd: str 

606 :param token: Token for `token authentication 

607 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

608 :type token: str 

609 :param parsed: 

610 If ``True`` parse received content into 

611 :py:class:`~pyrocko.io.quakeml.QuakeML` 

612 object, otherwise return open file handle to raw data stream. Note: 

613 by default unparsed data is retrieved, differently from the default 

614 behaviour of :py:func:`station` (for backward compatibility). 

615 :type parsed: bool 

616 :param \\*\\*kwargs: 

617 Parameters passed to the server (see `FDSN web services specification 

618 <https://www.fdsn.org/webservices>`_). 

619 

620 :returns: 

621 See description of ``parsed`` argument above. 

622 ''' 

623 

624 service = 'event' 

625 

626 if user or token: 

627 method = 'queryauth' 

628 else: 

629 method = 'query' 

630 

631 if token is not None: 

632 user, passwd = get_auth_credentials( 

633 service, site, url, majorversion, token, timeout) 

634 

635 if check: 

636 check_params(service, site, url, majorversion, timeout, **kwargs) 

637 

638 params = fix_params(kwargs) 

639 

640 url = fillurl(service, site, url, majorversion, method) 

641 

642 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params) 

643 if parsed: 

644 from pyrocko.io import quakeml 

645 format = kwargs.get('format', 'xml') 

646 if format != 'xml': 

647 raise InvalidRequest( 

648 'If parsed=True is selected, format="xml" must be selected.') 

649 

650 return quakeml.QuakeML.load_xml(stream=fh) 

651 

652 else: 

653 return fh 

654 

655 

656def availability( 

657 method='query', 

658 site=g_default_site, 

659 url=g_url, 

660 majorversion=1, 

661 timeout=None, 

662 check=True, 

663 user=None, 

664 passwd=None, 

665 token=None, 

666 selection=None, 

667 **kwargs): 

668 

669 ''' 

670 Query FDSN web service for time series data availablity. 

671 

672 :param method: Availablility method to call: ``'query'``, or ``'extent'``. 

673 :param site: 

674 :ref:`Registered site name <registered-site-names>` or full base URL of 

675 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

676 :type site: str 

677 :param url: 

678 URL template (default should work in 99% of cases). 

679 :type url: str 

680 :param majorversion: 

681 Major version of the service to query (always ``1`` at the time of 

682 writing). 

683 :type majorversion: int 

684 :param timeout: 

685 Network timeout in [s]. Global default timeout can be configured in 

686 Pyrocko's configuration file under ``fdsn_timeout``. 

687 :type timeout: float 

688 :param check: 

689 If ``True`` arguments are checked against self-description (WADL) of 

690 the queried web service if available or FDSN specification. 

691 :type check: bool 

692 :param user: User name for user/password authentication. 

693 :type user: str 

694 :param passwd: Password for user/password authentication. 

695 :type passwd: str 

696 :param token: Token for `token authentication 

697 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

698 :type token: str 

699 :param selection: 

700 If given, selection to be queried as a list of tuples 

701 ``(network, station, location, channel, tmin, tmax)``. 

702 :type selection: :py:class:`list` of :py:class:`tuple` 

703 :param \\*\\*kwargs: 

704 Parameters passed to the server (see `FDSN web services specification 

705 <https://www.fdsn.org/webservices>`_). 

706 

707 :returns: 

708 Open file-like object providing raw response. 

709 ''' 

710 

711 service = 'availability' 

712 

713 assert method in ('query', 'extent') 

714 

715 if user or token: 

716 method += 'auth' 

717 

718 if token is not None: 

719 user, passwd = get_auth_credentials( 

720 service, site, url, majorversion, token, timeout) 

721 

722 if check: 

723 check_params(service, site, url, majorversion, timeout, **kwargs) 

724 

725 params = fix_params(kwargs) 

726 

727 url = fillurl(service, site, url, majorversion, method) 

728 if selection: 

729 lst = [] 

730 

731 for k, v in params.items(): 

732 lst.append('%s=%s' % (k, v)) 

733 

734 for (network, station, location, channel, tmin, tmax) in selection: 

735 if location == '': 

736 location = '--' 

737 

738 lst.append(' '.join((network, station, location, channel, 

739 sdatetime(tmin), sdatetime(tmax)))) 

740 

741 post = '\n'.join(lst) 

742 return _request( 

743 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout) 

744 else: 

745 return _request( 

746 url, user=user, passwd=passwd, timeout=timeout, **params) 

747 

748 

749def check_params( 

750 service, 

751 site=g_default_site, 

752 url=g_url, 

753 majorversion=1, 

754 timeout=None, 

755 method='query', 

756 **kwargs): 

757 

758 ''' 

759 Check query parameters against self-description of web service. 

760 

761 Downloads WADL description of the given service and site and checks 

762 parameters if they are available. Queried WADLs are cached in memory. 

763 

764 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

765 ``'availability'``. 

766 :param site: 

767 :ref:`Registered site name <registered-site-names>` or full base URL of 

768 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

769 :type site: str 

770 :param url: 

771 URL template (default should work in 99% of cases). 

772 :type url: str 

773 :param majorversion: 

774 Major version of the service to query (always ``1`` at the time of 

775 writing). 

776 :type majorversion: int 

777 :param timeout: 

778 Network timeout in [s]. Global default timeout can be configured in 

779 Pyrocko's configuration file under ``fdsn_timeout``. 

780 :type timeout: float 

781 :param \\*\\*kwargs: 

782 Parameters that would be passed to the server (see `FDSN web services 

783 specification <https://www.fdsn.org/webservices>`_). 

784 

785 :raises: :py:exc:`ValueError` is raised if unsupported parameters are 

786 encountered. 

787 ''' 

788 

789 avail = supported_params_wadl( 

790 service, site, url, majorversion, timeout, method) 

791 

792 unavail = sorted(set(kwargs.keys()) - avail) 

793 if unavail: 

794 raise ValueError( 

795 'Unsupported parameter%s for service "%s" at site "%s": %s' % ( 

796 '' if len(unavail) == 1 else 's', 

797 service, 

798 site, 

799 ', '.join(unavail))) 

800 

801 

802def supported_params_wadl( 

803 service, 

804 site=g_default_site, 

805 url=g_url, 

806 majorversion=1, 

807 timeout=None, 

808 method='query'): 

809 

810 ''' 

811 Get query parameter names supported by a given FDSN site and service. 

812 

813 If no WADL is provided by the queried service, default parameter sets from 

814 the FDSN standard are returned. Queried WADLs are cached in memory. 

815 

816 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

817 ``'availability'``. 

818 :param site: 

819 :ref:`Registered site name <registered-site-names>` or full base URL of 

820 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

821 :type site: str 

822 :param url: 

823 URL template (default should work in 99% of cases). 

824 :type url: str 

825 :param majorversion: 

826 Major version of the service to query (always ``1`` at the time of 

827 writing). 

828 :type majorversion: int 

829 :param timeout: 

830 Network timeout in [s]. Global default timeout can be configured in 

831 Pyrocko's configuration file under ``fdsn_timeout``. 

832 :type timeout: float 

833 

834 :returns: Supported parameter names. 

835 :rtype: :py:class:`set` of :py:class:`str` 

836 ''' 

837 

838 wadl = cached_wadl(service, site, url, majorversion, timeout) 

839 

840 if wadl: 

841 url = fillurl(service, site, url, majorversion, method) 

842 return set(wadl.supported_param_names(url)) 

843 else: 

844 return g_default_query_args[service] 

845 

846 

847def patch_geonet_wadl(wadl): 

848 for r in wadl.resources_list: 

849 r.base = r.base.replace('1/station', 'station/1') 

850 r.base = r.base.replace('1/dataselect', 'dataselect/1') 

851 r.base = r.base.replace('1/event', 'event/1') 

852 

853 

854def wadl( 

855 service, 

856 site=g_default_site, 

857 url=g_url, 

858 majorversion=1, 

859 timeout=None): 

860 

861 ''' 

862 Retrieve self-description of a specific FDSN service. 

863 

864 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

865 ``'availability'``. 

866 :param site: 

867 :ref:`Registered site name <registered-site-names>` or full base URL of 

868 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

869 :type site: str 

870 :param url: 

871 URL template (default should work in 99% of cases). 

872 :type url: str 

873 :param majorversion: 

874 Major version of the service to query (always ``1`` at the time of 

875 writing). 

876 :type majorversion: int 

877 :param timeout: 

878 Network timeout in [s]. Global default timeout can be configured in 

879 Pyrocko's configuration file under ``fdsn_timeout``. 

880 :type timeout: float 

881 ''' 

882 

883 from pyrocko.client.wadl import load_xml 

884 

885 url = fillurl(service, site, url, majorversion, 'application.wadl') 

886 

887 wadl = load_xml(stream=_request(url, timeout=timeout)) 

888 

889 if site == 'geonet' or site.find('geonet.org.nz') != -1: 

890 patch_geonet_wadl(wadl) 

891 

892 return wadl 

893 

894 

895g_wadls = {} 

896 

897 

898def cached_wadl( 

899 service, 

900 site=g_default_site, 

901 url=g_url, 

902 majorversion=1, 

903 timeout=None): 

904 

905 ''' 

906 Get self-description of a specific FDSN service. 

907 

908 Same as :py:func:`wadl`, but results are cached in memory. 

909 ''' 

910 

911 k = (service, site, url, majorversion) 

912 if k not in g_wadls: 

913 try: 

914 g_wadls[k] = wadl(service, site, url, majorversion, timeout) 

915 

916 except Timeout: 

917 raise 

918 

919 except DownloadError: 

920 logger.info( 

921 'No service description (WADL) found for "%s" at site "%s".' 

922 % (service, site)) 

923 

924 g_wadls[k] = None 

925 

926 return g_wadls[k] 

927 

928 

929__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ') 

930 

931 

932if __name__ == '__main__': 

933 import sys 

934 

935 util.setup_logging('pyrocko.client.fdsn', 'info') 

936 

937 if len(sys.argv) == 1: 

938 sites = get_sites() 

939 else: 

940 sites = sys.argv[1:] 

941 

942 for site in sites: 

943 print('=== %s (%s) ===' % (site, g_site_abbr[site])) 

944 

945 for service in ['station', 'dataselect', 'event']: 

946 try: 

947 app = wadl(service, site=site, timeout=2.0) 

948 print(indent(str(app))) 

949 

950 except Timeout as e: 

951 logger.error(str(e)) 

952 print('%s: timeout' % (service,)) 

953 

954 except util.DownloadError as e: 

955 logger.error(str(e)) 

956 print('%s: no wadl' % (service,))