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'''
28import re
29import logging
30import socket
33from pyrocko import util
34from pyrocko.util import DownloadError
35from pyrocko import config
37from pyrocko.util import \
38 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError
41logger = logging.getLogger('pyrocko.client.fdsn')
43g_url = '%(site)s/fdsnws/%(service)s/%(majorversion)i/%(method)s'
45g_site_abbr = {
46 'geofon': 'https://geofon.gfz-potsdam.de',
47 'iris': 'http://service.iris.edu',
48 'orfeus': 'http://www.orfeus-eu.org',
49 'bgr': 'http://eida.bgr.de',
50 'geonet': 'http://service.geonet.org.nz',
51 'knmi': 'http://rdsa.knmi.nl',
52 'ncedc': 'http://service.ncedc.org',
53 'scedc': 'http://service.scedc.caltech.edu',
54 'usgs': 'http://earthquake.usgs.gov',
55 'koeri': 'http://eida-service.koeri.boun.edu.tr',
56 'ethz': 'http://eida.ethz.ch',
57 'icgc': 'http://ws.icgc.cat',
58 'ipgp': 'http://eida.ipgp.fr',
59 'ingv': 'http://webservices.ingv.it',
60 'isc': 'http://www.isc.ac.uk',
61 'lmu': 'http://erde.geophysik.uni-muenchen.de',
62 'noa': 'http://eida.gein.noa.gr',
63 'resif': 'http://ws.resif.fr',
64 'usp': 'http://seisrequest.iag.usp.br',
65 'niep': 'http://eida-sc3.infp.ro'
66}
68g_default_site = 'geofon'
71g_default_query_args = {
72 'station': {
73 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore',
74 'endafter', 'network', 'station', 'location', 'channel', 'minlatitude',
75 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude',
76 'minradius', 'maxradius', 'level', 'includerestricted',
77 'includeavailability', 'updatedafter', 'matchtimeseries', 'format',
78 'nodata'},
79 'dataselect': {
80 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
81 'quality', 'minimumlength', 'longestonly', 'format', 'nodata'},
82 'event': {
83 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude',
84 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius',
85 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype',
86 'includeallorigins', 'includeallmagnitudes', 'includearrivals',
87 'eventid', 'limit', 'offset', 'orderby', 'catalog', 'contributor',
88 'updatedafter', 'format', 'nodata'},
89 'availability': {
90 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
91 'quality', 'merge', 'orderby', 'limit', 'includerestricted', 'format',
92 'nodata', 'mergegaps', 'show'}}
95def doc_escape_slist(li):
96 return ', '.join("``'%s'``" % s for s in li)
99def doc_table_dict(d, khead, vhead, indent=''):
100 keys, vals = zip(*sorted(d.items()))
102 lk = max(max(len(k) for k in keys), len(khead))
103 lv = max(max(len(v) for v in vals), len(vhead))
105 hr = '=' * lk + ' ' + '=' * lv
107 lines = [
108 hr,
109 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)),
110 hr]
112 for k, v in zip(keys, vals):
113 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv)))
115 lines.append(hr)
116 return '\n'.join(indent + line for line in lines)
119def strip_html(s):
120 s = s.decode('utf-8')
121 s = re.sub(r'<[^>]+>', '', s)
122 s = re.sub(r'\r', '', s)
123 s = re.sub(r'\s*\n', '\n', s)
124 return s
127def indent(s, ind=' '):
128 return '\n'.join(ind + line for line in s.splitlines())
131def get_sites():
132 '''
133 Get sorted list of registered site names.
134 '''
135 return sorted(g_site_abbr.keys())
138if config.config().fdsn_timeout is None:
139 g_timeout = 20.
140else:
141 g_timeout = config.config().fdsn_timeout
143re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?')
146class CannotGetRealmFromAuthHeader(DownloadError):
147 '''
148 Raised when failing to parse server response during authentication.
149 '''
150 pass
153class CannotGetCredentialsFromAuthRequest(DownloadError):
154 '''
155 Raised when failing to parse server response during token authentication.
156 '''
157 pass
160def get_realm_from_auth_header(headers):
161 realm = dict(re_realm_from_auth_header.findall(
162 headers['WWW-Authenticate'])).get('realm', None)
164 if realm is None:
165 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers))
167 return realm
170def sdatetime(t):
171 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S')
174class EmptyResult(DownloadError):
175 '''
176 Raised when an empty server response is retrieved.
177 '''
178 def __init__(self, url):
179 DownloadError.__init__(self)
180 self._url = url
182 def __str__(self):
183 return 'No results for request %s' % self._url
186class RequestEntityTooLarge(DownloadError):
187 '''
188 Raised when the server indicates that too much data was requested.
189 '''
190 def __init__(self, url):
191 DownloadError.__init__(self)
192 self._url = url
194 def __str__(self):
195 return 'Request entity too large: %s' % self._url
198class InvalidRequest(DownloadError):
199 '''
200 Raised when an invalid request would be sent / has been sent.
201 '''
202 pass
205class Timeout(DownloadError):
206 '''
207 Raised when the server does not respond within the allowed timeout period.
208 '''
209 pass
212def _request(
213 url,
214 post=False,
215 user=None,
216 passwd=None,
217 timeout=g_timeout,
218 **kwargs):
220 url_values = urlencode(kwargs)
221 if url_values:
222 url += '?' + url_values
224 logger.debug('Accessing URL %s' % url)
225 url_args = {
226 'timeout': timeout
227 }
229 if util.g_ssl_context:
230 url_args['context'] = util.g_ssl_context
232 opener = None
234 req = Request(url)
235 if post:
236 if isinstance(post, str):
237 post = post.encode('utf8')
238 logger.debug('POST data: \n%s' % post.decode('utf8'))
239 req.data = post
241 req.add_header('Accept', '*/*')
243 itry = 0
244 while True:
245 itry += 1
246 try:
247 urlopen_ = opener.open if opener else urlopen
248 try:
249 resp = urlopen_(req, **url_args)
250 except TypeError:
251 # context and cafile not avail before 3.4.3, 2.7.9
252 url_args.pop('context', None)
253 url_args.pop('cafile', None)
254 resp = urlopen_(req, **url_args)
256 logger.debug('Response: %s' % resp.getcode())
257 if resp.getcode() == 204:
258 raise EmptyResult(url)
259 return resp
261 except HTTPError as e:
262 if e.code == 413:
263 raise RequestEntityTooLarge(url)
265 elif e.code == 401:
266 headers = getattr(e, 'headers', e.hdrs)
268 realm = get_realm_from_auth_header(headers)
270 if itry == 1 and user is not None:
271 auth_handler = HTTPDigestAuthHandler()
272 auth_handler.add_password(
273 realm=realm,
274 uri=url,
275 user=user,
276 passwd=passwd or '')
278 opener = build_opener(auth_handler)
279 continue
280 else:
281 raise DownloadError(
282 'Authentication failed for realm "%s" when accessing '
283 'url "%s". Original error was: %s' % (
284 realm, url, str(e)))
286 else:
287 raise DownloadError(
288 'Error content returned by server (HTML stripped):\n%s\n'
289 ' Original error was: %s' % (
290 indent(
291 strip_html(e.read()),
292 ' ! '),
293 str(e)))
295 except socket.timeout:
296 raise Timeout(
297 'Timeout error. No response received within %i s. You '
298 'may want to retry with a longer timeout setting.' % timeout)
300 break
303def fillurl(service, site, url, majorversion, method):
304 return url % dict(
305 site=g_site_abbr.get(site, site),
306 service=service,
307 majorversion=majorversion,
308 method=method)
311def fix_params(d):
313 params = dict(d)
314 for k in ['starttime',
315 'endtime',
316 'startbefore',
317 'startafter',
318 'endbefore',
319 'endafter',
320 'updatedafter']:
322 if k in params:
323 params[k] = sdatetime(params[k])
325 if params.get('location', None) == '':
326 params['location'] = '--'
328 for k in params:
329 if isinstance(params[k], bool):
330 params[k] = ['false', 'true'][bool(params[k])]
332 return params
335def make_data_selection(
336 stations, tmin, tmax,
337 channel_prio=[['BHZ', 'HHZ'],
338 ['BH1', 'BHN', 'HH1', 'HHN'],
339 ['BH2', 'BHE', 'HH2', 'HHE']]):
341 selection = []
342 for station in stations:
343 wanted = []
344 for group in channel_prio:
345 gchannels = []
346 for channel in station.get_channels():
347 if channel.name in group:
348 gchannels.append(channel)
349 if gchannels:
350 gchannels.sort(key=lambda a: group.index(a.name))
351 wanted.append(gchannels[0])
353 if wanted:
354 for channel in wanted:
355 selection.append((station.network, station.station,
356 station.location, channel.name, tmin, tmax))
358 return selection
361def station(
362 site=g_default_site,
363 url=g_url,
364 majorversion=1,
365 timeout=g_timeout,
366 check=True,
367 selection=None,
368 parsed=True,
369 **kwargs):
371 '''
372 Query FDSN web service for station metadata.
374 :param site:
375 :ref:`Registered site name <registered-site-names>` or full base URL of
376 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
377 :type site: str, optional
378 :param url:
379 URL template (default should work in 99% of cases).
380 :type url: str, optional
381 :param majorversion:
382 Major version of the service to query (always ``1`` at the time of
383 writing).
384 :type majorversion: int, optional
385 :param timeout:
386 Network timeout in [s]. Global default timeout can be configured in
387 Pyrocko's configuration file under ``fdsn_timeout``.
388 :type timeout: float, optional
389 :param check:
390 If ``True`` arguments are checked against self-description (WADL) of
391 the queried web service if available or FDSN specification.
392 :type check: bool, optional
393 :param selection:
394 If given, selection to be queried as a list of tuples
395 ``(network, station, location, channel, tmin, tmax)``. Useful for
396 detailed queries.
397 :type selection: list of tuples, optional
398 :param parsed:
399 If ``True`` parse received content into
400 :py:class:`~pyrocko.io.stationxml.FDSNStationXML`
401 object, otherwise return open file handle to raw data stream.
402 :type parsed: bool, optional
403 :param \\*\\*kwargs:
404 Parameters passed to the server (see `FDSN web services specification
405 <https://www.fdsn.org/webservices>`_).
407 :returns:
408 See description of ``parsed`` argument above.
410 :raises:
411 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its
412 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is
413 raised.
414 '''
416 service = 'station'
418 if check:
419 check_params(service, site, url, majorversion, timeout, **kwargs)
421 params = fix_params(kwargs)
423 url = fillurl(service, site, url, majorversion, 'query')
424 if selection:
425 lst = []
426 for k, v in params.items():
427 lst.append('%s=%s' % (k, v))
429 for (network, station, location, channel, tmin, tmax) in selection:
430 if location == '':
431 location = '--'
433 lst.append(' '.join((network, station, location, channel,
434 sdatetime(tmin), sdatetime(tmax))))
436 post = '\n'.join(lst)
437 params = dict(post=post.encode())
439 if parsed:
440 from pyrocko.io import stationxml
441 format = kwargs.get('format', 'xml')
442 if format == 'text':
443 if kwargs.get('level', 'station') == 'channel':
444 return stationxml.load_channel_table(
445 stream=_request(url, timeout=timeout, **params))
446 else:
447 raise InvalidRequest('if format="text" shall be parsed, '
448 'level="channel" is required')
450 elif format == 'xml':
451 return stationxml.load_xml(
452 stream=_request(url, timeout=timeout, **params))
453 else:
454 raise InvalidRequest('format must be "xml" or "text"')
455 else:
456 return _request(url, timeout=timeout, **params)
459def get_auth_credentials(service, site, url, majorversion, token, timeout):
461 url = fillurl(service, site, url, majorversion, 'auth')
463 f = _request(url, timeout=timeout, post=token)
464 s = f.read().decode()
465 try:
466 user, passwd = s.strip().split(':')
467 except ValueError:
468 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s)
470 return user, passwd
473def dataselect(
474 site=g_default_site,
475 url=g_url,
476 majorversion=1,
477 timeout=g_timeout,
478 check=True,
479 user=None,
480 passwd=None,
481 token=None,
482 selection=None,
483 **kwargs):
485 '''
486 Query FDSN web service for time series data in miniSEED format.
488 :param site:
489 :ref:`Registered site name <registered-site-names>` or full base URL of
490 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
491 :type site: str, optional
492 :param url:
493 URL template (default should work in 99% of cases).
494 :type url: str, optional
495 :param majorversion:
496 Major version of the service to query (always ``1`` at the time of
497 writing).
498 :type majorversion: int, optional
499 :param timeout:
500 Network timeout in [s]. Global default timeout can be configured in
501 Pyrocko's configuration file under ``fdsn_timeout``.
502 :type timeout: float, optional
503 :param check:
504 If ``True`` arguments are checked against self-description (WADL) of
505 the queried web service if available or FDSN specification.
506 :type check: bool, optional
507 :param user: User name for user/password authentication.
508 :type user: str, optional
509 :param passwd: Password for user/password authentication.
510 :type passwd: str, optional
511 :param token: Token for `token authentication
512 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
513 :type token: str, optional
514 :param selection:
515 If given, selection to be queried as a list of tuples
516 ``(network, station, location, channel, tmin, tmax)``.
517 :type selection: list of tuples, optional
518 :param \\*\\*kwargs:
519 Parameters passed to the server (see `FDSN web services specification
520 <https://www.fdsn.org/webservices>`_).
522 :returns:
523 Open file-like object providing raw miniSEED data.
524 '''
526 service = 'dataselect'
528 if user or token:
529 method = 'queryauth'
530 else:
531 method = 'query'
533 if token is not None:
534 user, passwd = get_auth_credentials(
535 service, site, url, majorversion, token, timeout)
537 if check:
538 check_params(service, site, url, majorversion, timeout, **kwargs)
540 params = fix_params(kwargs)
542 url = fillurl(service, site, url, majorversion, method)
543 if selection:
544 lst = []
546 for k, v in params.items():
547 lst.append('%s=%s' % (k, v))
549 for (network, station, location, channel, tmin, tmax) in selection:
550 if location == '':
551 location = '--'
553 lst.append(' '.join((network, station, location, channel,
554 sdatetime(tmin), sdatetime(tmax))))
556 post = '\n'.join(lst)
557 return _request(
558 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
559 else:
560 return _request(
561 url, user=user, passwd=passwd, timeout=timeout, **params)
564def event(
565 site=g_default_site,
566 url=g_url,
567 majorversion=1,
568 timeout=g_timeout,
569 check=True,
570 user=None,
571 passwd=None,
572 token=None,
573 parsed=False,
574 **kwargs):
576 '''
577 Query FDSN web service for events.
579 :param site:
580 :ref:`Registered site name <registered-site-names>` or full base URL of
581 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
582 :type site: str, optional
583 :param url:
584 URL template (default should work in 99% of cases).
585 :type url: str, optional
586 :param majorversion:
587 Major version of the service to query (always ``1`` at the time of
588 writing).
589 :type majorversion: int, optional
590 :param timeout:
591 Network timeout in [s]. Global default timeout can be configured in
592 Pyrocko's configuration file under ``fdsn_timeout``.
593 :type timeout: float, optional
594 :param check:
595 If ``True`` arguments are checked against self-description (WADL) of
596 the queried web service if available or FDSN specification.
597 :type check: bool, optional
598 :param user: User name for user/password authentication.
599 :type user: str, optional
600 :param passwd: Password for user/password authentication.
601 :type passwd: str, optional
602 :param token: Token for `token authentication
603 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
604 :type token: str, optional
605 :param parsed:
606 If ``True`` parse received content into
607 :py:class:`~pyrocko.io.quakeml.QuakeML`
608 object, otherwise return open file handle to raw data stream. Note:
609 by default unparsed data is retrieved, differently from the default
610 behaviour of :py:func:`station` (for backward compatibility).
611 :type parsed: bool, optional
612 :param \\*\\*kwargs:
613 Parameters passed to the server (see `FDSN web services specification
614 <https://www.fdsn.org/webservices>`_).
616 :returns:
617 See description of ``parsed`` argument above.
618 '''
620 service = 'event'
622 if user or token:
623 method = 'queryauth'
624 else:
625 method = 'query'
627 if token is not None:
628 user, passwd = get_auth_credentials(
629 service, site, url, majorversion, token, timeout)
631 if check:
632 check_params(service, site, url, majorversion, timeout, **kwargs)
634 params = fix_params(kwargs)
636 url = fillurl(service, site, url, majorversion, method)
638 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params)
639 if parsed:
640 from pyrocko.io import quakeml
641 format = kwargs.get('format', 'xml')
642 if format != 'xml':
643 raise InvalidRequest(
644 'If parsed=True is selected, format="xml" must be selected.')
646 return quakeml.QuakeML.load_xml(stream=fh)
648 else:
649 return fh
652def availability(
653 method='query',
654 site=g_default_site,
655 url=g_url,
656 majorversion=1,
657 timeout=g_timeout,
658 check=True,
659 user=None,
660 passwd=None,
661 token=None,
662 selection=None,
663 **kwargs):
665 '''
666 Query FDSN web service for time series data availablity.
668 :param method: Availablility method to call: ``'query'``, or ``'extent'``.
669 :param site:
670 :ref:`Registered site name <registered-site-names>` or full base URL of
671 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
672 :type site: str, optional
673 :param url:
674 URL template (default should work in 99% of cases).
675 :type url: str, optional
676 :param majorversion:
677 Major version of the service to query (always ``1`` at the time of
678 writing).
679 :type majorversion: int, optional
680 :param timeout:
681 Network timeout in [s]. Global default timeout can be configured in
682 Pyrocko's configuration file under ``fdsn_timeout``.
683 :type timeout: float, optional
684 :param check:
685 If ``True`` arguments are checked against self-description (WADL) of
686 the queried web service if available or FDSN specification.
687 :type check: bool, optional
688 :param user: User name for user/password authentication.
689 :type user: str, optional
690 :param passwd: Password for user/password authentication.
691 :type passwd: str, optional
692 :param token: Token for `token authentication
693 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
694 :type token: str, optional
695 :param selection:
696 If given, selection to be queried as a list of tuples
697 ``(network, station, location, channel, tmin, tmax)``.
698 :type selection: list of tuples, optional
699 :param \\*\\*kwargs:
700 Parameters passed to the server (see `FDSN web services specification
701 <https://www.fdsn.org/webservices>`_).
703 :returns:
704 Open file-like object providing raw response.
705 '''
707 service = 'availability'
709 assert method in ('query', 'extent')
711 if user or token:
712 method += 'auth'
714 if token is not None:
715 user, passwd = get_auth_credentials(
716 service, site, url, majorversion, token, timeout)
718 if check:
719 check_params(service, site, url, majorversion, timeout, **kwargs)
721 params = fix_params(kwargs)
723 url = fillurl(service, site, url, majorversion, method)
724 if selection:
725 lst = []
727 for k, v in params.items():
728 lst.append('%s=%s' % (k, v))
730 for (network, station, location, channel, tmin, tmax) in selection:
731 if location == '':
732 location = '--'
734 lst.append(' '.join((network, station, location, channel,
735 sdatetime(tmin), sdatetime(tmax))))
737 post = '\n'.join(lst)
738 return _request(
739 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
740 else:
741 return _request(
742 url, user=user, passwd=passwd, timeout=timeout, **params)
745def check_params(
746 service,
747 site=g_default_site,
748 url=g_url,
749 majorversion=1,
750 timeout=g_timeout,
751 method='query',
752 **kwargs):
754 '''
755 Check query parameters against self-description of web service.
757 Downloads WADL description of the given service and site and checks
758 parameters if they are available. Queried WADLs are cached in memory.
760 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
761 ``'availability'``.
762 :param site:
763 :ref:`Registered site name <registered-site-names>` or full base URL of
764 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
765 :type site: str, optional
766 :param url:
767 URL template (default should work in 99% of cases).
768 :type url: str, optional
769 :param majorversion:
770 Major version of the service to query (always ``1`` at the time of
771 writing).
772 :type majorversion: int, optional
773 :param timeout:
774 Network timeout in [s]. Global default timeout can be configured in
775 Pyrocko's configuration file under ``fdsn_timeout``.
776 :type timeout: float, optional
777 :param \\*\\*kwargs:
778 Parameters that would be passed to the server (see `FDSN web services
779 specification <https://www.fdsn.org/webservices>`_).
781 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
782 encountered.
783 '''
785 avail = supported_params_wadl(
786 service, site, url, majorversion, timeout, method)
788 unavail = sorted(set(kwargs.keys()) - avail)
789 if unavail:
790 raise ValueError(
791 'Unsupported parameter%s for service "%s" at site "%s": %s' % (
792 '' if len(unavail) == 1 else 's',
793 service,
794 site,
795 ', '.join(unavail)))
798def supported_params_wadl(
799 service,
800 site=g_default_site,
801 url=g_url,
802 majorversion=1,
803 timeout=g_timeout,
804 method='query'):
806 '''
807 Get query parameter names supported by a given FDSN site and service.
809 If no WADL is provided by the queried service, default parameter sets from
810 the FDSN standard are returned. Queried WADLs are cached in memory.
812 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
813 ``'availability'``.
814 :param site:
815 :ref:`Registered site name <registered-site-names>` or full base URL of
816 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
817 :type site: str, optional
818 :param url:
819 URL template (default should work in 99% of cases).
820 :type url: str, optional
821 :param majorversion:
822 Major version of the service to query (always ``1`` at the time of
823 writing).
824 :type majorversion: int, optional
825 :param timeout:
826 Network timeout in [s]. Global default timeout can be configured in
827 Pyrocko's configuration file under ``fdsn_timeout``.
828 :type timeout: float, optional
830 :returns: Supported parameter names.
831 :rtype: set of str
832 '''
834 wadl = cached_wadl(service, site, url, majorversion, timeout)
836 if wadl:
837 url = fillurl(service, site, url, majorversion, method)
838 return set(wadl.supported_param_names(url))
839 else:
840 return g_default_query_args[service]
843def patch_geonet_wadl(wadl):
844 for r in wadl.resources_list:
845 r.base = r.base.replace('1/station', 'station/1')
846 r.base = r.base.replace('1/dataselect', 'dataselect/1')
847 r.base = r.base.replace('1/event', 'event/1')
850def wadl(
851 service,
852 site=g_default_site,
853 url=g_url,
854 majorversion=1,
855 timeout=g_timeout):
857 '''
858 Retrieve self-description of a specific FDSN service.
860 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
861 ``'availability'``.
862 :param site:
863 :ref:`Registered site name <registered-site-names>` or full base URL of
864 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
865 :type site: str, optional
866 :param url:
867 URL template (default should work in 99% of cases).
868 :type url: str, optional
869 :param majorversion:
870 Major version of the service to query (always ``1`` at the time of
871 writing).
872 :type majorversion: int, optional
873 :param timeout:
874 Network timeout in [s]. Global default timeout can be configured in
875 Pyrocko's configuration file under ``fdsn_timeout``.
876 :type timeout: float, optional
877 '''
879 from pyrocko.client.wadl import load_xml
881 url = fillurl(service, site, url, majorversion, 'application.wadl')
883 wadl = load_xml(stream=_request(url, timeout=timeout))
885 if site == 'geonet' or site.find('geonet.org.nz') != -1:
886 patch_geonet_wadl(wadl)
888 return wadl
891g_wadls = {}
894def cached_wadl(
895 service,
896 site=g_default_site,
897 url=g_url,
898 majorversion=1,
899 timeout=g_timeout):
901 '''
902 Get self-description of a specific FDSN service.
904 Same as :py:func:`wadl`, but results are cached in memory.
905 '''
907 k = (service, site, url, majorversion)
908 if k not in g_wadls:
909 try:
910 g_wadls[k] = wadl(service, site, url, majorversion, timeout)
912 except Timeout:
913 raise
915 except DownloadError:
916 logger.info(
917 'No service description (WADL) found for "%s" at site "%s".'
918 % (service, site))
920 g_wadls[k] = None
922 return g_wadls[k]
925__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
928if __name__ == '__main__':
929 import sys
931 util.setup_logging('pyrocko.client.fdsn', 'info')
933 if len(sys.argv) == 1:
934 sites = get_sites()
935 else:
936 sites = sys.argv[1:]
938 for site in sites:
939 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
941 for service in ['station', 'dataselect', 'event']:
942 try:
943 app = wadl(service, site=site, timeout=2.0)
944 print(indent(str(app)))
946 except Timeout as e:
947 logger.error(str(e))
948 print('%s: timeout' % (service,))
950 except util.DownloadError as e:
951 logger.error(str(e))
952 print('%s: no wadl' % (service,))