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 

28from __future__ import absolute_import 

29 

30import re 

31import logging 

32import ssl 

33import socket 

34 

35try: 

36 import certifi 

37except ImportError: 

38 certifi = None 

39 

40 

41from pyrocko import util 

42from pyrocko.util import DownloadError 

43from pyrocko import config 

44 

45from pyrocko.util import \ 

46 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError 

47 

48try: 

49 newstr = unicode 

50except NameError: 

51 newstr = str 

52 

53 

54if certifi: 

55 g_cafile = certifi.where() 

56else: 

57 g_cafile = None 

58 

59 

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

61 

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

63 

64g_site_abbr = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

85} 

86 

87g_default_site = 'geofon' 

88 

89 

90g_default_query_args = { 

91 'station': { 

92 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore', 

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

94 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude', 

95 'minradius', 'maxradius', 'level', 'includerestricted', 

96 'includeavailability', 'updatedafter', 'matchtimeseries', 'format', 

97 'nodata'}, 

98 'dataselect': { 

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

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

101 'event': { 

102 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude', 

103 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius', 

104 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype', 

105 'includeallorigins', 'includeallmagnitudes', 'includearrivals', 

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

107 'updatedafter', 'format', 'nodata'}, 

108 'availability': { 

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

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

111 'nodata', 'mergegaps', 'show'}} 

112 

113 

114def doc_escape_slist(li): 

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

116 

117 

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

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

120 

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

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

123 

124 hr = '=' * lk + ' ' + '=' * lv 

125 

126 lines = [ 

127 hr, 

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

129 hr] 

130 

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

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

133 

134 lines.append(hr) 

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

136 

137 

138def strip_html(s): 

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

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

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

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

143 return s 

144 

145 

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

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

148 

149 

150def get_sites(): 

151 ''' 

152 Get sorted list of registered site names. 

153 ''' 

154 return sorted(g_site_abbr.keys()) 

155 

156 

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

158 g_timeout = 20. 

159else: 

160 g_timeout = config.config().fdsn_timeout 

161 

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

163 

164 

165class CannotGetRealmFromAuthHeader(DownloadError): 

166 ''' 

167 Raised when failing to parse server response during authentication. 

168 ''' 

169 pass 

170 

171 

172class CannotGetCredentialsFromAuthRequest(DownloadError): 

173 ''' 

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

175 ''' 

176 pass 

177 

178 

179def get_realm_from_auth_header(headers): 

180 realm = dict(re_realm_from_auth_header.findall( 

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

182 

183 if realm is None: 

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

185 

186 return realm 

187 

188 

189def sdatetime(t): 

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

191 

192 

193class EmptyResult(DownloadError): 

194 ''' 

195 Raised when an empty server response is retrieved. 

196 ''' 

197 def __init__(self, url): 

198 DownloadError.__init__(self) 

199 self._url = url 

200 

201 def __str__(self): 

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

203 

204 

205class RequestEntityTooLarge(DownloadError): 

206 ''' 

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

208 ''' 

209 def __init__(self, url): 

210 DownloadError.__init__(self) 

211 self._url = url 

212 

213 def __str__(self): 

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

215 

216 

217class InvalidRequest(DownloadError): 

218 ''' 

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

220 ''' 

221 pass 

222 

223 

224class Timeout(DownloadError): 

225 ''' 

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

227 ''' 

228 pass 

229 

230 

231def _request( 

232 url, 

233 post=False, 

234 user=None, 

235 passwd=None, 

236 allow_TLSv1=False, 

237 timeout=g_timeout, 

238 **kwargs): 

239 

240 url_values = urlencode(kwargs) 

241 if url_values: 

242 url += '?' + url_values 

243 

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

245 url_args = { 

246 'timeout': timeout 

247 } 

248 

249 if allow_TLSv1: 

250 url_args['context'] = ssl.SSLContext(ssl.PROTOCOL_TLSv1) 

251 

252 url_args['cafile'] = g_cafile 

253 

254 opener = None 

255 

256 req = Request(url) 

257 if post: 

258 if isinstance(post, newstr): 

259 post = post.encode('utf8') 

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

261 req.data = post 

262 

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

264 

265 itry = 0 

266 while True: 

267 itry += 1 

268 try: 

269 urlopen_ = opener.open if opener else urlopen 

270 try: 

271 resp = urlopen_(req, **url_args) 

272 except TypeError: 

273 # context and cafile not avail before 3.4.3, 2.7.9 

274 url_args.pop('context', None) 

275 url_args.pop('cafile', None) 

276 resp = urlopen_(req, **url_args) 

277 

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

279 if resp.getcode() == 204: 

280 raise EmptyResult(url) 

281 return resp 

282 

283 except HTTPError as e: 

284 if e.code == 413: 

285 raise RequestEntityTooLarge(url) 

286 

287 elif e.code == 401: 

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

289 

290 realm = get_realm_from_auth_header(headers) 

291 

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

293 auth_handler = HTTPDigestAuthHandler() 

294 auth_handler.add_password( 

295 realm=realm, 

296 uri=url, 

297 user=user, 

298 passwd=passwd or '') 

299 

300 opener = build_opener(auth_handler) 

301 continue 

302 else: 

303 raise DownloadError( 

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

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

306 realm, url, str(e))) 

307 

308 else: 

309 raise DownloadError( 

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

311 ' Original error was: %s' % ( 

312 indent( 

313 strip_html(e.read()), 

314 ' ! '), 

315 str(e))) 

316 

317 except socket.timeout: 

318 raise Timeout( 

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

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

321 

322 break 

323 

324 

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

326 return url % dict( 

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

328 service=service, 

329 majorversion=majorversion, 

330 method=method) 

331 

332 

333def fix_params(d): 

334 

335 params = dict(d) 

336 for k in ['starttime', 

337 'endtime', 

338 'startbefore', 

339 'startafter', 

340 'endbefore', 

341 'endafter', 

342 'updatedafter']: 

343 

344 if k in params: 

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

346 

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

348 params['location'] = '--' 

349 

350 for k in params: 

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

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

353 

354 return params 

355 

356 

357def make_data_selection( 

358 stations, tmin, tmax, 

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

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

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

362 

363 selection = [] 

364 for station in stations: 

365 wanted = [] 

366 for group in channel_prio: 

367 gchannels = [] 

368 for channel in station.get_channels(): 

369 if channel.name in group: 

370 gchannels.append(channel) 

371 if gchannels: 

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

373 wanted.append(gchannels[0]) 

374 

375 if wanted: 

376 for channel in wanted: 

377 selection.append((station.network, station.station, 

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

379 

380 return selection 

381 

382 

383def station( 

384 site=g_default_site, 

385 url=g_url, 

386 majorversion=1, 

387 timeout=g_timeout, 

388 check=True, 

389 selection=None, 

390 parsed=True, 

391 **kwargs): 

392 

393 ''' 

394 Query FDSN web service for station metadata. 

395 

396 :param site: 

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

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

399 :type site: str, optional 

400 :param url: 

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

402 :type url: str, optional 

403 :param majorversion: 

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

405 writing). 

406 :type majorversion: int, optional 

407 :param timeout: 

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

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

410 :type timeout: float, optional 

411 :param check: 

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

413 the queried web service if available or FDSN specification. 

414 :type check: bool, optional 

415 :param selection: 

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

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

418 detailed queries. 

419 :type selection: list of tuples, optional 

420 :param parsed: 

421 If ``True`` parse received content into 

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

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

424 :type parsed: bool, optional 

425 :param \\*\\*kwargs: 

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

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

428 

429 :returns: 

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

431 

432 :raises: 

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

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

435 raised. 

436 ''' 

437 

438 service = 'station' 

439 

440 if check: 

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

442 

443 params = fix_params(kwargs) 

444 

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

446 if selection: 

447 lst = [] 

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

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

450 

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

452 if location == '': 

453 location = '--' 

454 

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

456 sdatetime(tmin), sdatetime(tmax)))) 

457 

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

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

460 

461 if parsed: 

462 from pyrocko.io import stationxml 

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

464 if format == 'text': 

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

466 return stationxml.load_channel_table( 

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

468 else: 

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

470 'level="channel" is required') 

471 

472 elif format == 'xml': 

473 return stationxml.load_xml( 

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

475 else: 

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

477 else: 

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

479 

480 

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

482 

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

484 

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

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

487 try: 

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

489 except ValueError: 

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

491 

492 return user, passwd 

493 

494 

495def dataselect( 

496 site=g_default_site, 

497 url=g_url, 

498 majorversion=1, 

499 timeout=g_timeout, 

500 check=True, 

501 user=None, 

502 passwd=None, 

503 token=None, 

504 selection=None, 

505 **kwargs): 

506 

507 ''' 

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

509 

510 :param site: 

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

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

513 :type site: str, optional 

514 :param url: 

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

516 :type url: str, optional 

517 :param majorversion: 

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

519 writing). 

520 :type majorversion: int, optional 

521 :param timeout: 

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

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

524 :type timeout: float, optional 

525 :param check: 

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

527 the queried web service if available or FDSN specification. 

528 :type check: bool, optional 

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

530 :type user: str, optional 

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

532 :type passwd: str, optional 

533 :param token: Token for `token authentication 

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

535 :type token: str, optional 

536 :param selection: 

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

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

539 :type selection: list of tuples, optional 

540 :param \\*\\*kwargs: 

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

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

543 

544 :returns: 

545 Open file-like object providing raw miniSEED data. 

546 ''' 

547 

548 service = 'dataselect' 

549 

550 if user or token: 

551 method = 'queryauth' 

552 else: 

553 method = 'query' 

554 

555 if token is not None: 

556 user, passwd = get_auth_credentials( 

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

558 

559 if check: 

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

561 

562 params = fix_params(kwargs) 

563 

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

565 if selection: 

566 lst = [] 

567 

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

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

570 

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

572 if location == '': 

573 location = '--' 

574 

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

576 sdatetime(tmin), sdatetime(tmax)))) 

577 

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

579 return _request( 

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

581 else: 

582 return _request( 

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

584 

585 

586def event( 

587 site=g_default_site, 

588 url=g_url, 

589 majorversion=1, 

590 timeout=g_timeout, 

591 check=True, 

592 user=None, 

593 passwd=None, 

594 token=None, 

595 parsed=False, 

596 **kwargs): 

597 

598 ''' 

599 Query FDSN web service for events. 

600 

601 :param site: 

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

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

604 :type site: str, optional 

605 :param url: 

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

607 :type url: str, optional 

608 :param majorversion: 

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

610 writing). 

611 :type majorversion: int, optional 

612 :param timeout: 

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

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

615 :type timeout: float, optional 

616 :param check: 

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

618 the queried web service if available or FDSN specification. 

619 :type check: bool, optional 

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

621 :type user: str, optional 

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

623 :type passwd: str, optional 

624 :param token: Token for `token authentication 

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

626 :type token: str, optional 

627 :param parsed: 

628 If ``True`` parse received content into 

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

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

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

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

633 :type parsed: bool, optional 

634 :param \\*\\*kwargs: 

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

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

637 

638 :returns: 

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

640 ''' 

641 

642 service = 'event' 

643 

644 if user or token: 

645 method = 'queryauth' 

646 else: 

647 method = 'query' 

648 

649 if token is not None: 

650 user, passwd = get_auth_credentials( 

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

652 

653 if check: 

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

655 

656 params = fix_params(kwargs) 

657 

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

659 

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

661 if parsed: 

662 from pyrocko.io import quakeml 

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

664 if format != 'xml': 

665 raise InvalidRequest( 

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

667 

668 return quakeml.load_xml(stream=fh) 

669 

670 else: 

671 return fh 

672 

673 

674def availability( 

675 method='query', 

676 site=g_default_site, 

677 url=g_url, 

678 majorversion=1, 

679 timeout=g_timeout, 

680 check=True, 

681 user=None, 

682 passwd=None, 

683 token=None, 

684 selection=None, 

685 **kwargs): 

686 

687 ''' 

688 Query FDSN web service for time series data availablity. 

689 

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

691 :param site: 

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

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

694 :type site: str, optional 

695 :param url: 

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

697 :type url: str, optional 

698 :param majorversion: 

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

700 writing). 

701 :type majorversion: int, optional 

702 :param timeout: 

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

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

705 :type timeout: float, optional 

706 :param check: 

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

708 the queried web service if available or FDSN specification. 

709 :type check: bool, optional 

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

711 :type user: str, optional 

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

713 :type passwd: str, optional 

714 :param token: Token for `token authentication 

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

716 :type token: str, optional 

717 :param selection: 

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

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

720 :type selection: list of tuples, optional 

721 :param \\*\\*kwargs: 

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

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

724 

725 :returns: 

726 Open file-like object providing raw response. 

727 ''' 

728 

729 service = 'availability' 

730 

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

732 

733 if user or token: 

734 method += 'auth' 

735 

736 if token is not None: 

737 user, passwd = get_auth_credentials( 

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

739 

740 if check: 

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

742 

743 params = fix_params(kwargs) 

744 

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

746 if selection: 

747 lst = [] 

748 

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

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

751 

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

753 if location == '': 

754 location = '--' 

755 

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

757 sdatetime(tmin), sdatetime(tmax)))) 

758 

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

760 return _request( 

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

762 else: 

763 return _request( 

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

765 

766 

767def check_params( 

768 service, 

769 site=g_default_site, 

770 url=g_url, 

771 majorversion=1, 

772 timeout=g_timeout, 

773 method='query', 

774 **kwargs): 

775 

776 ''' 

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

778 

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

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

781 

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

783 ``'availability'``. 

784 :param site: 

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

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

787 :type site: str, optional 

788 :param url: 

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

790 :type url: str, optional 

791 :param majorversion: 

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

793 writing). 

794 :type majorversion: int, optional 

795 :param timeout: 

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

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

798 :type timeout: float, optional 

799 :param \\*\\*kwargs: 

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

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

802 

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

804 encountered. 

805 ''' 

806 

807 avail = supported_params_wadl( 

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

809 

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

811 if unavail: 

812 raise ValueError( 

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

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

815 service, 

816 site, 

817 ', '.join(unavail))) 

818 

819 

820def supported_params_wadl( 

821 service, 

822 site=g_default_site, 

823 url=g_url, 

824 majorversion=1, 

825 timeout=g_timeout, 

826 method='query'): 

827 

828 ''' 

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

830 

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

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

833 

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

835 ``'availability'``. 

836 :param site: 

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

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

839 :type site: str, optional 

840 :param url: 

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

842 :type url: str, optional 

843 :param majorversion: 

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

845 writing). 

846 :type majorversion: int, optional 

847 :param timeout: 

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

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

850 :type timeout: float, optional 

851 

852 :returns: Supported parameter names. 

853 :rtype: set of str 

854 ''' 

855 

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

857 

858 if wadl: 

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

860 return set(wadl.supported_param_names(url)) 

861 else: 

862 return g_default_query_args[service] 

863 

864 

865def patch_geonet_wadl(wadl): 

866 for r in wadl.resources_list: 

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

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

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

870 

871 

872def wadl( 

873 service, 

874 site=g_default_site, 

875 url=g_url, 

876 majorversion=1, 

877 timeout=g_timeout): 

878 

879 ''' 

880 Retrieve self-description of a specific FDSN service. 

881 

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

883 ``'availability'``. 

884 :param site: 

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

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

887 :type site: str, optional 

888 :param url: 

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

890 :type url: str, optional 

891 :param majorversion: 

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

893 writing). 

894 :type majorversion: int, optional 

895 :param timeout: 

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

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

898 :type timeout: float, optional 

899 ''' 

900 

901 from pyrocko.client.wadl import load_xml 

902 

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

904 

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

906 

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

908 patch_geonet_wadl(wadl) 

909 

910 return wadl 

911 

912 

913g_wadls = {} 

914 

915 

916def cached_wadl( 

917 service, 

918 site=g_default_site, 

919 url=g_url, 

920 majorversion=1, 

921 timeout=g_timeout): 

922 

923 ''' 

924 Get self-description of a specific FDSN service. 

925 

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

927 ''' 

928 

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

930 if k not in g_wadls: 

931 try: 

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

933 

934 except Timeout: 

935 raise 

936 

937 except DownloadError: 

938 logger.info( 

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

940 % (service, site)) 

941 

942 g_wadls[k] = None 

943 

944 return g_wadls[k] 

945 

946 

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

948 

949 

950if __name__ == '__main__': 

951 import sys 

952 

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

954 

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

956 sites = get_sites() 

957 else: 

958 sites = sys.argv[1:] 

959 

960 for site in sites: 

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

962 

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

964 try: 

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

966 print(indent(str(app))) 

967 

968 except Timeout as e: 

969 logger.error(str(e)) 

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

971 

972 except util.DownloadError as e: 

973 logger.error(str(e)) 

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