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 2024-07-03 12:22 +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 'auspass': 'http://auspass.edu.au:8080', 

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

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

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

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

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

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

53 'iesdmc': 'http://batsws.earth.sinica.edu.tw:8080', 

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

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

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

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

58 'kagsr': 'http://sdis.emsd.ru', 

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

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

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

62 'ncedc': 'https://service.ncedc.org', 

63 'niep': 'http://eida-sc3.infp.ro', 

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

65 'norsar': 'http://eida.geo.uib.no', 

66 'nrcan': 'https://earthquakescanada.nrcan.gc.ca', 

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

68 'raspishake': 'https://data.raspberryshake.org', 

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

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

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

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

73} 

74 

75g_default_site = 'geofon' 

76 

77 

78g_default_query_args = { 

79 'station': { 

80 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore', 

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

82 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude', 

83 'minradius', 'maxradius', 'level', 'includerestricted', 

84 'includeavailability', 'updatedafter', 'matchtimeseries', 'format', 

85 'nodata'}, 

86 'dataselect': { 

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

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

89 'event': { 

90 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude', 

91 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius', 

92 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype', 

93 'includeallorigins', 'includeallmagnitudes', 'includearrivals', 

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

95 'updatedafter', 'format', 'nodata'}, 

96 'availability': { 

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

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

99 'nodata', 'mergegaps', 'show'}} 

100 

101 

102def doc_escape_slist(li): 

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

104 

105 

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

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

108 

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

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

111 

112 hr = '=' * lk + ' ' + '=' * lv 

113 

114 lines = [ 

115 hr, 

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

117 hr] 

118 

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

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

121 

122 lines.append(hr) 

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

124 

125 

126def strip_html(s): 

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

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

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

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

131 return s 

132 

133 

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

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

136 

137 

138def get_sites(): 

139 ''' 

140 Get sorted list of registered site names. 

141 ''' 

142 return sorted(g_site_abbr.keys()) 

143 

144 

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

146 g_timeout = 20. 

147else: 

148 g_timeout = config.config().fdsn_timeout 

149 

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

151 

152 

153class CannotGetRealmFromAuthHeader(DownloadError): 

154 ''' 

155 Raised when failing to parse server response during authentication. 

156 ''' 

157 pass 

158 

159 

160class CannotGetCredentialsFromAuthRequest(DownloadError): 

161 ''' 

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

163 ''' 

164 pass 

165 

166 

167def get_realm_from_auth_header(headers): 

168 realm = dict(re_realm_from_auth_header.findall( 

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

170 

171 if realm is None: 

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

173 

174 return realm 

175 

176 

177def sdatetime(t): 

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

179 

180 

181class EmptyResult(DownloadError): 

182 ''' 

183 Raised when an empty server response is retrieved. 

184 ''' 

185 def __init__(self, url): 

186 DownloadError.__init__(self) 

187 self._url = url 

188 

189 def __str__(self): 

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

191 

192 

193class RequestEntityTooLarge(DownloadError): 

194 ''' 

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

196 ''' 

197 def __init__(self, url): 

198 DownloadError.__init__(self) 

199 self._url = url 

200 

201 def __str__(self): 

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

203 

204 

205class InvalidRequest(DownloadError): 

206 ''' 

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

208 ''' 

209 pass 

210 

211 

212class Timeout(DownloadError): 

213 ''' 

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

215 ''' 

216 pass 

217 

218 

219def _request( 

220 url, 

221 post=False, 

222 user=None, 

223 passwd=None, 

224 timeout=None, 

225 **kwargs): 

226 

227 if timeout is None: 

228 timeout = g_timeout 

229 

230 url_values = urlencode(kwargs) 

231 if url_values: 

232 url += '?' + url_values 

233 

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

235 url_args = { 

236 'timeout': timeout 

237 } 

238 

239 if util.g_ssl_context: 

240 url_args['context'] = util.g_ssl_context 

241 

242 opener = None 

243 

244 req = Request(url) 

245 if post: 

246 if isinstance(post, str): 

247 post = post.encode('utf8') 

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

249 req.data = post 

250 

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

252 

253 itry = 0 

254 while True: 

255 itry += 1 

256 try: 

257 urlopen_ = opener.open if opener else urlopen 

258 try: 

259 resp = urlopen_(req, **url_args) 

260 except TypeError: 

261 # context and cafile not avail before 3.4.3, 2.7.9 

262 url_args.pop('context', None) 

263 url_args.pop('cafile', None) 

264 resp = urlopen_(req, **url_args) 

265 

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

267 if resp.getcode() == 204: 

268 raise EmptyResult(url) 

269 return resp 

270 

271 except HTTPError as e: 

272 if e.code == 413: 

273 raise RequestEntityTooLarge(url) 

274 

275 elif e.code == 401: 

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

277 

278 realm = get_realm_from_auth_header(headers) 

279 

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

281 auth_handler = HTTPDigestAuthHandler() 

282 auth_handler.add_password( 

283 realm=realm, 

284 uri=url, 

285 user=user, 

286 passwd=passwd or '') 

287 

288 opener = build_opener(auth_handler) 

289 continue 

290 else: 

291 raise DownloadError( 

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

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

294 realm, url, str(e))) 

295 

296 else: 

297 raise DownloadError( 

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

299 ' Original error was: %s' % ( 

300 indent( 

301 strip_html(e.read()), 

302 ' ! '), 

303 str(e))) 

304 

305 except socket.timeout: 

306 raise Timeout( 

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

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

309 

310 break 

311 

312 

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

314 return url % dict( 

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

316 service=service, 

317 majorversion=majorversion, 

318 method=method) 

319 

320 

321def fix_params(d): 

322 

323 params = dict(d) 

324 for k in ['starttime', 

325 'endtime', 

326 'startbefore', 

327 'startafter', 

328 'endbefore', 

329 'endafter', 

330 'updatedafter']: 

331 

332 if k in params: 

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

334 

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

336 params['location'] = '--' 

337 

338 for k in params: 

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

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

341 

342 return params 

343 

344 

345def make_data_selection( 

346 stations, tmin, tmax, 

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

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

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

350 

351 selection = [] 

352 for station in stations: 

353 wanted = [] 

354 for group in channel_prio: 

355 gchannels = [] 

356 for channel in station.get_channels(): 

357 if channel.name in group: 

358 gchannels.append(channel) 

359 if gchannels: 

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

361 wanted.append(gchannels[0]) 

362 

363 if wanted: 

364 for channel in wanted: 

365 selection.append((station.network, station.station, 

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

367 

368 return selection 

369 

370 

371def station( 

372 site=g_default_site, 

373 url=g_url, 

374 majorversion=1, 

375 timeout=None, 

376 check=True, 

377 selection=None, 

378 parsed=True, 

379 **kwargs): 

380 

381 ''' 

382 Query FDSN web service for station metadata. 

383 

384 :param site: 

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

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

387 :type site: str 

388 :param url: 

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

390 :type url: str 

391 :param majorversion: 

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

393 writing). 

394 :type majorversion: int 

395 :param timeout: 

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

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

398 :type timeout: float 

399 :param check: 

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

401 the queried web service if available or FDSN specification. 

402 :type check: bool 

403 :param selection: 

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

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

406 detailed queries. 

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

408 :param parsed: 

409 If ``True`` parse received content into 

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

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

412 :type parsed: bool 

413 :param \\*\\*kwargs: 

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

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

416 

417 :returns: 

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

419 

420 :raises: 

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

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

423 raised. 

424 ''' 

425 

426 service = 'station' 

427 

428 if check: 

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

430 

431 params = fix_params(kwargs) 

432 

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

434 if selection: 

435 lst = [] 

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

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

438 

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

440 if location == '': 

441 location = '--' 

442 

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

444 sdatetime(tmin), sdatetime(tmax)))) 

445 

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

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

448 

449 if parsed: 

450 from pyrocko.io import stationxml 

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

452 if format == 'text': 

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

454 return stationxml.load_channel_table( 

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

456 else: 

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

458 'level="channel" is required') 

459 

460 elif format == 'xml': 

461 return stationxml.load_xml( 

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

463 else: 

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

465 else: 

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

467 

468 

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

470 

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

472 

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

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

475 try: 

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

477 except ValueError: 

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

479 

480 return user, passwd 

481 

482 

483def dataselect( 

484 site=g_default_site, 

485 url=g_url, 

486 majorversion=1, 

487 timeout=None, 

488 check=True, 

489 user=None, 

490 passwd=None, 

491 token=None, 

492 selection=None, 

493 **kwargs): 

494 

495 ''' 

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

497 

498 :param site: 

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

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

501 :type site: str 

502 :param url: 

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

504 :type url: str 

505 :param majorversion: 

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

507 writing). 

508 :type majorversion: int 

509 :param timeout: 

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

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

512 :type timeout: float 

513 :param check: 

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

515 the queried web service if available or FDSN specification. 

516 :type check: bool 

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

518 :type user: str 

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

520 :type passwd: str 

521 :param token: Token for `token authentication 

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

523 :type token: str 

524 :param selection: 

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

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

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

528 :param \\*\\*kwargs: 

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

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

531 

532 :returns: 

533 Open file-like object providing raw miniSEED data. 

534 ''' 

535 

536 service = 'dataselect' 

537 

538 if user or token: 

539 method = 'queryauth' 

540 else: 

541 method = 'query' 

542 

543 if token is not None: 

544 user, passwd = get_auth_credentials( 

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

546 

547 if check: 

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

549 

550 params = fix_params(kwargs) 

551 

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

553 if selection: 

554 lst = [] 

555 

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

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

558 

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

560 if location == '': 

561 location = '--' 

562 

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

564 sdatetime(tmin), sdatetime(tmax)))) 

565 

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

567 return _request( 

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

569 else: 

570 return _request( 

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

572 

573 

574def event( 

575 site=g_default_site, 

576 url=g_url, 

577 majorversion=1, 

578 timeout=None, 

579 check=True, 

580 user=None, 

581 passwd=None, 

582 token=None, 

583 parsed=False, 

584 **kwargs): 

585 

586 ''' 

587 Query FDSN web service for events. 

588 

589 :param site: 

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

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

592 :type site: str 

593 :param url: 

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

595 :type url: str 

596 :param majorversion: 

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

598 writing). 

599 :type majorversion: int 

600 :param timeout: 

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

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

603 :type timeout: float 

604 :param check: 

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

606 the queried web service if available or FDSN specification. 

607 :type check: bool 

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

609 :type user: str 

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

611 :type passwd: str 

612 :param token: Token for `token authentication 

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

614 :type token: str 

615 :param parsed: 

616 If ``True`` parse received content into 

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

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

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

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

621 :type parsed: bool 

622 :param \\*\\*kwargs: 

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

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

625 

626 :returns: 

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

628 ''' 

629 

630 service = 'event' 

631 

632 if user or token: 

633 method = 'queryauth' 

634 else: 

635 method = 'query' 

636 

637 if token is not None: 

638 user, passwd = get_auth_credentials( 

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

640 

641 if check: 

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

643 

644 params = fix_params(kwargs) 

645 

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

647 

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

649 if parsed: 

650 from pyrocko.io import quakeml 

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

652 if format != 'xml': 

653 raise InvalidRequest( 

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

655 

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

657 

658 else: 

659 return fh 

660 

661 

662def availability( 

663 method='query', 

664 site=g_default_site, 

665 url=g_url, 

666 majorversion=1, 

667 timeout=None, 

668 check=True, 

669 user=None, 

670 passwd=None, 

671 token=None, 

672 selection=None, 

673 **kwargs): 

674 

675 ''' 

676 Query FDSN web service for time series data availablity. 

677 

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

679 :param site: 

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

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

682 :type site: str 

683 :param url: 

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

685 :type url: str 

686 :param majorversion: 

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

688 writing). 

689 :type majorversion: int 

690 :param timeout: 

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

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

693 :type timeout: float 

694 :param check: 

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

696 the queried web service if available or FDSN specification. 

697 :type check: bool 

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

699 :type user: str 

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

701 :type passwd: str 

702 :param token: Token for `token authentication 

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

704 :type token: str 

705 :param selection: 

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

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

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

709 :param \\*\\*kwargs: 

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

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

712 

713 :returns: 

714 Open file-like object providing raw response. 

715 ''' 

716 

717 service = 'availability' 

718 

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

720 

721 if user or token: 

722 method += 'auth' 

723 

724 if token is not None: 

725 user, passwd = get_auth_credentials( 

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

727 

728 if check: 

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

730 

731 params = fix_params(kwargs) 

732 

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

734 if selection: 

735 lst = [] 

736 

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

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

739 

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

741 if location == '': 

742 location = '--' 

743 

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

745 sdatetime(tmin), sdatetime(tmax)))) 

746 

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

748 return _request( 

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

750 else: 

751 return _request( 

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

753 

754 

755def check_params( 

756 service, 

757 site=g_default_site, 

758 url=g_url, 

759 majorversion=1, 

760 timeout=None, 

761 method='query', 

762 **kwargs): 

763 

764 ''' 

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

766 

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

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

769 

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

771 ``'availability'``. 

772 :param site: 

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

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

775 :type site: str 

776 :param url: 

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

778 :type url: str 

779 :param majorversion: 

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

781 writing). 

782 :type majorversion: int 

783 :param timeout: 

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

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

786 :type timeout: float 

787 :param \\*\\*kwargs: 

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

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

790 

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

792 encountered. 

793 ''' 

794 

795 avail = supported_params_wadl( 

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

797 

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

799 if unavail: 

800 raise ValueError( 

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

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

803 service, 

804 site, 

805 ', '.join(unavail))) 

806 

807 

808def supported_params_wadl( 

809 service, 

810 site=g_default_site, 

811 url=g_url, 

812 majorversion=1, 

813 timeout=None, 

814 method='query'): 

815 

816 ''' 

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

818 

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

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

821 

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

823 ``'availability'``. 

824 :param site: 

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

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

827 :type site: str 

828 :param url: 

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

830 :type url: str 

831 :param majorversion: 

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

833 writing). 

834 :type majorversion: int 

835 :param timeout: 

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

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

838 :type timeout: float 

839 

840 :returns: Supported parameter names. 

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

842 ''' 

843 

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

845 

846 if wadl: 

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

848 return set(wadl.supported_param_names(url)) 

849 else: 

850 return g_default_query_args[service] 

851 

852 

853def patch_geonet_wadl(wadl): 

854 for r in wadl.resources_list: 

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

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

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

858 

859 

860def wadl( 

861 service, 

862 site=g_default_site, 

863 url=g_url, 

864 majorversion=1, 

865 timeout=None): 

866 

867 ''' 

868 Retrieve self-description of a specific FDSN service. 

869 

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

871 ``'availability'``. 

872 :param site: 

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

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

875 :type site: str 

876 :param url: 

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

878 :type url: str 

879 :param majorversion: 

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

881 writing). 

882 :type majorversion: int 

883 :param timeout: 

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

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

886 :type timeout: float 

887 ''' 

888 

889 from pyrocko.client.wadl import load_xml 

890 

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

892 

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

894 

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

896 patch_geonet_wadl(wadl) 

897 

898 return wadl 

899 

900 

901g_wadls = {} 

902 

903 

904def cached_wadl( 

905 service, 

906 site=g_default_site, 

907 url=g_url, 

908 majorversion=1, 

909 timeout=None): 

910 

911 ''' 

912 Get self-description of a specific FDSN service. 

913 

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

915 ''' 

916 

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

918 if k not in g_wadls: 

919 try: 

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

921 

922 except Timeout: 

923 raise 

924 

925 except DownloadError: 

926 logger.info( 

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

928 % (service, site)) 

929 

930 g_wadls[k] = None 

931 

932 return g_wadls[k] 

933 

934 

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

936 

937 

938if __name__ == '__main__': 

939 import sys 

940 

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

942 

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

944 sites = get_sites() 

945 else: 

946 sites = sys.argv[1:] 

947 

948 for site in sites: 

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

950 

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

952 try: 

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

954 print(indent(str(app))) 

955 

956 except Timeout as e: 

957 logger.error(str(e)) 

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

959 

960 except util.DownloadError as e: 

961 logger.error(str(e)) 

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