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

288 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-12-04 10:41 +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 

31import io 

32 

33import requests 

34 

35from pyrocko import util 

36from pyrocko.util import DownloadError 

37from pyrocko import config 

38 

39from pyrocko.util import \ 

40 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError 

41 

42 

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

44 

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

46 

47g_site_abbr = { 

48 'auspass': 'http://auspass.edu.au:8080', 

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

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

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

52 'geofon': 'https://geofon.gfz.de', 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

75} 

76 

77g_default_site = 'geofon' 

78 

79 

80g_default_query_args = { 

81 'station': { 

82 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore', 

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

84 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude', 

85 'minradius', 'maxradius', 'level', 'includerestricted', 

86 'includeavailability', 'updatedafter', 'matchtimeseries', 'format', 

87 'nodata'}, 

88 'dataselect': { 

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

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

91 'event': { 

92 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude', 

93 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius', 

94 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype', 

95 'includeallorigins', 'includeallmagnitudes', 'includearrivals', 

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

97 'updatedafter', 'format', 'nodata'}, 

98 'availability': { 

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

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

101 'nodata', 'mergegaps', 'show'}} 

102 

103 

104def doc_escape_slist(li): 

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

106 

107 

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

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

110 

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

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

113 

114 hr = '=' * lk + ' ' + '=' * lv 

115 

116 lines = [ 

117 hr, 

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

119 hr] 

120 

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

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

123 

124 lines.append(hr) 

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

126 

127 

128def strip_html(s): 

129 if isinstance(s, bytes): 

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

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

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

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

134 return s 

135 

136 

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

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

139 

140 

141def get_sites(): 

142 ''' 

143 Get sorted list of registered site names. 

144 ''' 

145 return sorted(g_site_abbr.keys()) 

146 

147 

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

149 g_timeout = 20. 

150else: 

151 g_timeout = config.config().fdsn_timeout 

152 

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

154 

155 

156class CannotGetRealmFromAuthHeader(DownloadError): 

157 ''' 

158 Raised when failing to parse server response during authentication. 

159 ''' 

160 pass 

161 

162 

163class CannotGetCredentialsFromAuthRequest(DownloadError): 

164 ''' 

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

166 ''' 

167 pass 

168 

169 

170def get_realm_from_auth_header(headers): 

171 realm = dict(re_realm_from_auth_header.findall( 

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

173 

174 if realm is None: 

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

176 

177 return realm 

178 

179 

180def sdatetime(t): 

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

182 

183 

184class EmptyResult(DownloadError): 

185 ''' 

186 Raised when an empty server response is retrieved. 

187 ''' 

188 def __init__(self, url): 

189 DownloadError.__init__(self) 

190 self._url = url 

191 

192 def __str__(self): 

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

194 

195 

196class RequestEntityTooLarge(DownloadError): 

197 ''' 

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

199 ''' 

200 def __init__(self, url): 

201 DownloadError.__init__(self) 

202 self._url = url 

203 

204 def __str__(self): 

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

206 

207 

208class InvalidRequest(DownloadError): 

209 ''' 

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

211 ''' 

212 pass 

213 

214 

215class Timeout(DownloadError): 

216 ''' 

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

218 ''' 

219 pass 

220 

221 

222g_session = None 

223 

224 

225def _request( 

226 url, 

227 post=False, 

228 user=None, 

229 passwd=None, 

230 timeout=None, 

231 **kwargs): 

232 

233 global g_session 

234 

235 if g_session is None: 

236 g_session = requests.Session() 

237 

238 if user is not None and passwd is not None: 

239 auth = requests.auth.HTTPDigestAuth(user, passwd) 

240 else: 

241 auth = None 

242 

243 if timeout is None: 

244 timeout = g_timeout 

245 

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

247 

248 try: 

249 if not post: 

250 response = g_session.get( 

251 url, 

252 auth=auth, 

253 timeout=timeout, 

254 params=kwargs) 

255 

256 else: 

257 if isinstance(post, str): 

258 post = post.encode('utf8') 

259 

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

261 

262 response = g_session.post( 

263 url, 

264 auth=auth, 

265 timeout=timeout, 

266 params=kwargs, 

267 data=post) 

268 

269 logger.debug('Response: %s' % response.status_code) 

270 

271 if response.status_code == 204: 

272 raise EmptyResult(url) 

273 

274 elif response.status_code == 413: 

275 raise RequestEntityTooLarge(url) 

276 

277 response.raise_for_status() 

278 

279 except requests.exceptions.ConnectionError as e: 

280 raise DownloadError( 

281 'Failed connection attempt: %s' % str(e)) 

282 

283 except requests.exceptions.HTTPError as e: 

284 raise DownloadError( 

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

286 ' Original error was: %s' % ( 

287 indent( 

288 strip_html(response.text), 

289 ' ! '), 

290 str(e))) 

291 

292 except requests.exceptions.Timeout: 

293 raise Timeout( 

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

295 'may want to retry with a longer timeout setting. The global ' 

296 'timeout can be set with the variable `fdsn_timeout` in ' 

297 '`~/.pyrocko/config.pf`, but this value may be overriden by ' 

298 'the script/application for a specific request.' % timeout) 

299 

300 return io.BytesIO(response.content) 

301 

302 

303def _request_old( 

304 url, 

305 post=False, 

306 user=None, 

307 passwd=None, 

308 timeout=None, 

309 **kwargs): 

310 

311 if timeout is None: 

312 timeout = g_timeout 

313 

314 url_values = urlencode(kwargs) 

315 if url_values: 

316 url += '?' + url_values 

317 

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

319 url_args = { 

320 'timeout': timeout 

321 } 

322 

323 if util.g_ssl_context: 

324 url_args['context'] = util.g_ssl_context 

325 

326 opener = None 

327 

328 req = Request(url) 

329 if post: 

330 if isinstance(post, str): 

331 post = post.encode('utf8') 

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

333 req.data = post 

334 

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

336 

337 itry = 0 

338 while True: 

339 itry += 1 

340 try: 

341 urlopen_ = opener.open if opener else urlopen 

342 try: 

343 resp = urlopen_(req, **url_args) 

344 except TypeError: 

345 # context and cafile not avail before 3.4.3, 2.7.9 

346 url_args.pop('context', None) 

347 url_args.pop('cafile', None) 

348 resp = urlopen_(req, **url_args) 

349 

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

351 if resp.getcode() == 204: 

352 raise EmptyResult(url) 

353 return resp 

354 

355 except HTTPError as e: 

356 if e.code == 413: 

357 raise RequestEntityTooLarge(url) 

358 

359 elif e.code == 401: 

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

361 

362 realm = get_realm_from_auth_header(headers) 

363 

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

365 auth_handler = HTTPDigestAuthHandler() 

366 auth_handler.add_password( 

367 realm=realm, 

368 uri=url, 

369 user=user, 

370 passwd=passwd or '') 

371 

372 opener = build_opener(auth_handler) 

373 continue 

374 else: 

375 raise DownloadError( 

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

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

378 realm, url, str(e))) 

379 

380 else: 

381 raise DownloadError( 

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

383 ' Original error was: %s' % ( 

384 indent( 

385 strip_html(e.read()), 

386 ' ! '), 

387 str(e))) 

388 

389 except socket.timeout: 

390 raise Timeout( 

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

392 'may want to retry with a longer timeout setting. The global ' 

393 'timeout can be set with the variable `fdsn_timeout` in ' 

394 '`~/.pyrocko/config.pf`, but this value may be overriden by ' 

395 'the script/application for a specific request.' % timeout) 

396 

397 break 

398 

399 

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

401 return url % dict( 

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

403 service=service, 

404 majorversion=majorversion, 

405 method=method) 

406 

407 

408def fix_params(d): 

409 

410 params = dict(d) 

411 for k in ['starttime', 

412 'endtime', 

413 'startbefore', 

414 'startafter', 

415 'endbefore', 

416 'endafter', 

417 'updatedafter']: 

418 

419 if k in params: 

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

421 

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

423 params['location'] = '--' 

424 

425 for k in params: 

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

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

428 

429 return params 

430 

431 

432def make_data_selection( 

433 stations, tmin, tmax, 

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

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

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

437 

438 selection = [] 

439 for station in stations: 

440 wanted = [] 

441 for group in channel_prio: 

442 gchannels = [] 

443 for channel in station.get_channels(): 

444 if channel.name in group: 

445 gchannels.append(channel) 

446 if gchannels: 

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

448 wanted.append(gchannels[0]) 

449 

450 if wanted: 

451 for channel in wanted: 

452 selection.append((station.network, station.station, 

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

454 

455 return selection 

456 

457 

458def station( 

459 site=g_default_site, 

460 url=g_url, 

461 majorversion=1, 

462 timeout=None, 

463 check=True, 

464 selection=None, 

465 parsed=True, 

466 **kwargs): 

467 

468 ''' 

469 Query FDSN web service for station metadata. 

470 

471 :param site: 

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

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

474 :type site: str 

475 :param url: 

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

477 :type url: str 

478 :param majorversion: 

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

480 writing). 

481 :type majorversion: int 

482 :param timeout: 

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

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

485 :type timeout: float 

486 :param check: 

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

488 the queried web service if available or FDSN specification. 

489 :type check: bool 

490 :param selection: 

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

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

493 detailed queries. 

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

495 :param parsed: 

496 If ``True`` parse received content into 

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

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

499 :type parsed: bool 

500 :param \\*\\*kwargs: 

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

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

503 

504 :returns: 

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

506 

507 :raises: 

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

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

510 raised. 

511 ''' 

512 

513 service = 'station' 

514 

515 if check: 

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

517 

518 params = fix_params(kwargs) 

519 

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

521 if selection: 

522 lst = [] 

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

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

525 

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

527 if location == '': 

528 location = '--' 

529 

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

531 sdatetime(tmin), sdatetime(tmax)))) 

532 

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

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

535 

536 if parsed: 

537 from pyrocko.io import stationxml 

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

539 if format == 'text': 

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

541 return stationxml.load_channel_table( 

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

543 else: 

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

545 'level="channel" is required') 

546 

547 elif format == 'xml': 

548 return stationxml.load_xml( 

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

550 else: 

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

552 else: 

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

554 

555 

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

557 

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

559 

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

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

562 try: 

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

564 except ValueError: 

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

566 

567 return user, passwd 

568 

569 

570def dataselect( 

571 site=g_default_site, 

572 url=g_url, 

573 majorversion=1, 

574 timeout=None, 

575 check=True, 

576 user=None, 

577 passwd=None, 

578 token=None, 

579 selection=None, 

580 **kwargs): 

581 

582 ''' 

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

584 

585 :param site: 

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

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

588 :type site: str 

589 :param url: 

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

591 :type url: str 

592 :param majorversion: 

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

594 writing). 

595 :type majorversion: int 

596 :param timeout: 

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

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

599 :type timeout: float 

600 :param check: 

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

602 the queried web service if available or FDSN specification. 

603 :type check: bool 

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

605 :type user: str 

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

607 :type passwd: str 

608 :param token: Token for `token authentication 

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

610 :type token: str 

611 :param selection: 

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

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

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

615 :param \\*\\*kwargs: 

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

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

618 

619 :returns: 

620 Open file-like object providing raw miniSEED data. 

621 ''' 

622 

623 service = 'dataselect' 

624 

625 if user or token: 

626 method = 'queryauth' 

627 else: 

628 method = 'query' 

629 

630 if token is not None: 

631 user, passwd = get_auth_credentials( 

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

633 

634 if check: 

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

636 

637 params = fix_params(kwargs) 

638 

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

640 if selection: 

641 lst = [] 

642 

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

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

645 

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

647 if location == '': 

648 location = '--' 

649 

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

651 sdatetime(tmin), sdatetime(tmax)))) 

652 

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

654 return _request( 

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

656 else: 

657 return _request( 

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

659 

660 

661def event( 

662 site=g_default_site, 

663 url=g_url, 

664 majorversion=1, 

665 timeout=None, 

666 check=True, 

667 user=None, 

668 passwd=None, 

669 token=None, 

670 parsed=False, 

671 **kwargs): 

672 

673 ''' 

674 Query FDSN web service for events. 

675 

676 :param site: 

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

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

679 :type site: str 

680 :param url: 

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

682 :type url: str 

683 :param majorversion: 

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

685 writing). 

686 :type majorversion: int 

687 :param timeout: 

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

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

690 :type timeout: float 

691 :param check: 

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

693 the queried web service if available or FDSN specification. 

694 :type check: bool 

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

696 :type user: str 

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

698 :type passwd: str 

699 :param token: Token for `token authentication 

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

701 :type token: str 

702 :param parsed: 

703 If ``True`` parse received content into 

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

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

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

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

708 :type parsed: bool 

709 :param \\*\\*kwargs: 

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

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

712 

713 :returns: 

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

715 ''' 

716 

717 service = 'event' 

718 

719 if user or token: 

720 method = 'queryauth' 

721 else: 

722 method = 'query' 

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 

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

736 if parsed: 

737 from pyrocko.io import quakeml 

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

739 if format != 'xml': 

740 raise InvalidRequest( 

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

742 

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

744 

745 else: 

746 return fh 

747 

748 

749def availability( 

750 method='query', 

751 site=g_default_site, 

752 url=g_url, 

753 majorversion=1, 

754 timeout=None, 

755 check=True, 

756 user=None, 

757 passwd=None, 

758 token=None, 

759 selection=None, 

760 **kwargs): 

761 

762 ''' 

763 Query FDSN web service for time series data availablity. 

764 

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

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 check: 

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

783 the queried web service if available or FDSN specification. 

784 :type check: bool 

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

786 :type user: str 

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

788 :type passwd: str 

789 :param token: Token for `token authentication 

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

791 :type token: str 

792 :param selection: 

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

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

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

796 :param \\*\\*kwargs: 

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

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

799 

800 :returns: 

801 Open file-like object providing raw response. 

802 ''' 

803 

804 service = 'availability' 

805 

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

807 

808 if user or token: 

809 method += 'auth' 

810 

811 if token is not None: 

812 user, passwd = get_auth_credentials( 

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

814 

815 if check: 

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

817 

818 params = fix_params(kwargs) 

819 

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

821 if selection: 

822 lst = [] 

823 

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

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

826 

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

828 if location == '': 

829 location = '--' 

830 

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

832 sdatetime(tmin), sdatetime(tmax)))) 

833 

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

835 return _request( 

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

837 else: 

838 return _request( 

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

840 

841 

842def check_params( 

843 service, 

844 site=g_default_site, 

845 url=g_url, 

846 majorversion=1, 

847 timeout=None, 

848 method='query', 

849 **kwargs): 

850 

851 ''' 

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

853 

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

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

856 

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

858 ``'availability'``. 

859 :param site: 

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

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

862 :type site: str 

863 :param url: 

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

865 :type url: str 

866 :param majorversion: 

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

868 writing). 

869 :type majorversion: int 

870 :param timeout: 

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

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

873 :type timeout: float 

874 :param \\*\\*kwargs: 

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

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

877 

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

879 encountered. 

880 ''' 

881 

882 avail = supported_params_wadl( 

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

884 

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

886 if unavail: 

887 raise ValueError( 

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

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

890 service, 

891 site, 

892 ', '.join(unavail))) 

893 

894 

895def supported_params_wadl( 

896 service, 

897 site=g_default_site, 

898 url=g_url, 

899 majorversion=1, 

900 timeout=None, 

901 method='query'): 

902 

903 ''' 

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

905 

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

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

908 

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

910 ``'availability'``. 

911 :param site: 

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

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

914 :type site: str 

915 :param url: 

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

917 :type url: str 

918 :param majorversion: 

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

920 writing). 

921 :type majorversion: int 

922 :param timeout: 

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

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

925 :type timeout: float 

926 

927 :returns: Supported parameter names. 

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

929 ''' 

930 

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

932 

933 if wadl: 

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

935 return set(wadl.supported_param_names(url)) 

936 else: 

937 return g_default_query_args[service] 

938 

939 

940def patch_geonet_wadl(wadl): 

941 for r in wadl.resources_list: 

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

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

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

945 

946 

947def wadl( 

948 service, 

949 site=g_default_site, 

950 url=g_url, 

951 majorversion=1, 

952 timeout=None): 

953 

954 ''' 

955 Retrieve self-description of a specific FDSN service. 

956 

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

958 ``'availability'``. 

959 :param site: 

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

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

962 :type site: str 

963 :param url: 

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

965 :type url: str 

966 :param majorversion: 

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

968 writing). 

969 :type majorversion: int 

970 :param timeout: 

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

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

973 :type timeout: float 

974 ''' 

975 

976 from pyrocko.client.wadl import load_xml 

977 

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

979 

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

981 

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

983 patch_geonet_wadl(wadl) 

984 

985 return wadl 

986 

987 

988g_wadls = {} 

989 

990 

991def cached_wadl( 

992 service, 

993 site=g_default_site, 

994 url=g_url, 

995 majorversion=1, 

996 timeout=None): 

997 

998 ''' 

999 Get self-description of a specific FDSN service. 

1000 

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

1002 ''' 

1003 

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

1005 if k not in g_wadls: 

1006 try: 

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

1008 

1009 except Timeout: 

1010 raise 

1011 

1012 except DownloadError: 

1013 logger.info( 

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

1015 % (service, site)) 

1016 

1017 g_wadls[k] = None 

1018 

1019 return g_wadls[k] 

1020 

1021 

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

1023 

1024 

1025if __name__ == '__main__': 

1026 import sys 

1027 

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

1029 

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

1031 sites = get_sites() 

1032 else: 

1033 sites = sys.argv[1:] 

1034 

1035 for site in sites: 

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

1037 

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

1039 try: 

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

1041 print(indent(str(app))) 

1042 

1043 except Timeout as e: 

1044 logger.error(str(e)) 

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

1046 

1047 except util.DownloadError as e: 

1048 logger.error(str(e)) 

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