1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Low-level FDSN web service client.
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.
15.. _registered-site-names:
17Registered site names
18.....................
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:
23%s
25Any other site can be specified by providing its full URL.
26'''
28from __future__ import absolute_import
30import re
31import logging
32import ssl
33import socket
35try:
36 import certifi
37except ImportError:
38 certifi = None
41from pyrocko import util
42from pyrocko.util import DownloadError
43from pyrocko import config
45from pyrocko.util import \
46 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError
48try:
49 newstr = unicode
50except NameError:
51 newstr = str
54if certifi:
55 g_cafile = certifi.where()
56else:
57 g_cafile = None
60logger = logging.getLogger('pyrocko.client.fdsn')
62g_url = '%(site)s/fdsnws/%(service)s/%(majorversion)i/%(method)s'
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}
87g_default_site = 'geofon'
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'}}
114def doc_escape_slist(li):
115 return ', '.join("``'%s'``" % s for s in li)
118def doc_table_dict(d, khead, vhead, indent=''):
119 keys, vals = zip(*sorted(d.items()))
121 lk = max(max(len(k) for k in keys), len(khead))
122 lv = max(max(len(v) for v in vals), len(vhead))
124 hr = '=' * lk + ' ' + '=' * lv
126 lines = [
127 hr,
128 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)),
129 hr]
131 for k, v in zip(keys, vals):
132 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv)))
134 lines.append(hr)
135 return '\n'.join(indent + line for line in lines)
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
146def indent(s, ind=' '):
147 return '\n'.join(ind + line for line in s.splitlines())
150def get_sites():
151 '''
152 Get sorted list of registered site names.
153 '''
154 return sorted(g_site_abbr.keys())
157if config.config().fdsn_timeout is None:
158 g_timeout = 20.
159else:
160 g_timeout = config.config().fdsn_timeout
162re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?')
165class CannotGetRealmFromAuthHeader(DownloadError):
166 '''
167 Raised when failing to parse server response during authentication.
168 '''
169 pass
172class CannotGetCredentialsFromAuthRequest(DownloadError):
173 '''
174 Raised when failing to parse server response during token authentication.
175 '''
176 pass
179def get_realm_from_auth_header(headers):
180 realm = dict(re_realm_from_auth_header.findall(
181 headers['WWW-Authenticate'])).get('realm', None)
183 if realm is None:
184 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers))
186 return realm
189def sdatetime(t):
190 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S')
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
201 def __str__(self):
202 return 'No results for request %s' % self._url
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
213 def __str__(self):
214 return 'Request entity too large: %s' % self._url
217class InvalidRequest(DownloadError):
218 '''
219 Raised when an invalid request would be sent / has been sent.
220 '''
221 pass
224class Timeout(DownloadError):
225 '''
226 Raised when the server does not respond within the allowed timeout period.
227 '''
228 pass
231def _request(
232 url,
233 post=False,
234 user=None,
235 passwd=None,
236 allow_TLSv1=False,
237 timeout=g_timeout,
238 **kwargs):
240 url_values = urlencode(kwargs)
241 if url_values:
242 url += '?' + url_values
244 logger.debug('Accessing URL %s' % url)
245 url_args = {
246 'timeout': timeout
247 }
249 if allow_TLSv1:
250 url_args['context'] = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
252 url_args['cafile'] = g_cafile
254 opener = None
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
263 req.add_header('Accept', '*/*')
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)
278 logger.debug('Response: %s' % resp.getcode())
279 if resp.getcode() == 204:
280 raise EmptyResult(url)
281 return resp
283 except HTTPError as e:
284 if e.code == 413:
285 raise RequestEntityTooLarge(url)
287 elif e.code == 401:
288 headers = getattr(e, 'headers', e.hdrs)
290 realm = get_realm_from_auth_header(headers)
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 '')
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)))
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)))
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)
322 break
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)
333def fix_params(d):
335 params = dict(d)
336 for k in ['starttime',
337 'endtime',
338 'startbefore',
339 'startafter',
340 'endbefore',
341 'endafter',
342 'updatedafter']:
344 if k in params:
345 params[k] = sdatetime(params[k])
347 if params.get('location', None) == '':
348 params['location'] = '--'
350 for k in params:
351 if isinstance(params[k], bool):
352 params[k] = ['false', 'true'][bool(params[k])]
354 return params
357def make_data_selection(
358 stations, tmin, tmax,
359 channel_prio=[['BHZ', 'HHZ'],
360 ['BH1', 'BHN', 'HH1', 'HHN'],
361 ['BH2', 'BHE', 'HH2', 'HHE']]):
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])
375 if wanted:
376 for channel in wanted:
377 selection.append((station.network, station.station,
378 station.location, channel.name, tmin, tmax))
380 return selection
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):
393 '''
394 Query FDSN web service for station metadata.
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>`_).
429 :returns:
430 See description of ``parsed`` argument above.
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 '''
438 service = 'station'
440 if check:
441 check_params(service, site, url, majorversion, timeout, **kwargs)
443 params = fix_params(kwargs)
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))
451 for (network, station, location, channel, tmin, tmax) in selection:
452 if location == '':
453 location = '--'
455 lst.append(' '.join((network, station, location, channel,
456 sdatetime(tmin), sdatetime(tmax))))
458 post = '\n'.join(lst)
459 params = dict(post=post.encode())
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')
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)
481def get_auth_credentials(service, site, url, majorversion, token, timeout):
483 url = fillurl(service, site, url, majorversion, 'auth')
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)
492 return user, passwd
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):
507 '''
508 Query FDSN web service for time series data in miniSEED format.
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>`_).
544 :returns:
545 Open file-like object providing raw miniSEED data.
546 '''
548 service = 'dataselect'
550 if user or token:
551 method = 'queryauth'
552 else:
553 method = 'query'
555 if token is not None:
556 user, passwd = get_auth_credentials(
557 service, site, url, majorversion, token, timeout)
559 if check:
560 check_params(service, site, url, majorversion, timeout, **kwargs)
562 params = fix_params(kwargs)
564 url = fillurl(service, site, url, majorversion, method)
565 if selection:
566 lst = []
568 for k, v in params.items():
569 lst.append('%s=%s' % (k, v))
571 for (network, station, location, channel, tmin, tmax) in selection:
572 if location == '':
573 location = '--'
575 lst.append(' '.join((network, station, location, channel,
576 sdatetime(tmin), sdatetime(tmax))))
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)
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):
598 '''
599 Query FDSN web service for events.
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>`_).
638 :returns:
639 See description of ``parsed`` argument above.
640 '''
642 service = 'event'
644 if user or token:
645 method = 'queryauth'
646 else:
647 method = 'query'
649 if token is not None:
650 user, passwd = get_auth_credentials(
651 service, site, url, majorversion, token, timeout)
653 if check:
654 check_params(service, site, url, majorversion, timeout, **kwargs)
656 params = fix_params(kwargs)
658 url = fillurl(service, site, url, majorversion, method)
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.')
668 return quakeml.load_xml(stream=fh)
670 else:
671 return fh
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):
687 '''
688 Query FDSN web service for time series data availablity.
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>`_).
725 :returns:
726 Open file-like object providing raw response.
727 '''
729 service = 'availability'
731 assert method in ('query', 'extent')
733 if user or token:
734 method += 'auth'
736 if token is not None:
737 user, passwd = get_auth_credentials(
738 service, site, url, majorversion, token, timeout)
740 if check:
741 check_params(service, site, url, majorversion, timeout, **kwargs)
743 params = fix_params(kwargs)
745 url = fillurl(service, site, url, majorversion, method)
746 if selection:
747 lst = []
749 for k, v in params.items():
750 lst.append('%s=%s' % (k, v))
752 for (network, station, location, channel, tmin, tmax) in selection:
753 if location == '':
754 location = '--'
756 lst.append(' '.join((network, station, location, channel,
757 sdatetime(tmin), sdatetime(tmax))))
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)
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):
776 '''
777 Check query parameters against self-description of web service.
779 Downloads WADL description of the given service and site and checks
780 parameters if they are available. Queried WADLs are cached in memory.
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>`_).
803 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
804 encountered.
805 '''
807 avail = supported_params_wadl(
808 service, site, url, majorversion, timeout, method)
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)))
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'):
828 '''
829 Get query parameter names supported by a given FDSN site and service.
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.
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
852 :returns: Supported parameter names.
853 :rtype: set of str
854 '''
856 wadl = cached_wadl(service, site, url, majorversion, timeout)
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]
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')
872def wadl(
873 service,
874 site=g_default_site,
875 url=g_url,
876 majorversion=1,
877 timeout=g_timeout):
879 '''
880 Retrieve self-description of a specific FDSN service.
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 '''
901 from pyrocko.client.wadl import load_xml
903 url = fillurl(service, site, url, majorversion, 'application.wadl')
905 wadl = load_xml(stream=_request(url, timeout=timeout))
907 if site == 'geonet' or site.find('geonet.org.nz') != -1:
908 patch_geonet_wadl(wadl)
910 return wadl
913g_wadls = {}
916def cached_wadl(
917 service,
918 site=g_default_site,
919 url=g_url,
920 majorversion=1,
921 timeout=g_timeout):
923 '''
924 Get self-description of a specific FDSN service.
926 Same as :py:func:`wadl`, but results are cached in memory.
927 '''
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)
934 except Timeout:
935 raise
937 except DownloadError:
938 logger.info(
939 'No service description (WADL) found for "%s" at site "%s".'
940 % (service, site))
942 g_wadls[k] = None
944 return g_wadls[k]
947__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
950if __name__ == '__main__':
951 import sys
953 util.setup_logging('pyrocko.client.fdsn', 'info')
955 if len(sys.argv) == 1:
956 sites = get_sites()
957 else:
958 sites = sys.argv[1:]
960 for site in sites:
961 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
963 for service in ['station', 'dataselect', 'event']:
964 try:
965 app = wadl(service, site=site, timeout=2.0)
966 print(indent(str(app)))
968 except Timeout as e:
969 logger.error(str(e))
970 print('%s: timeout' % (service,))
972 except util.DownloadError as e:
973 logger.error(str(e))
974 print('%s: no wadl' % (service,))