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 'emsc': 'http://www.seismicportal.eu',
51 'geonet': 'http://service.geonet.org.nz',
52 'knmi': 'http://rdsa.knmi.nl',
53 'ncedc': 'http://service.ncedc.org',
54 'scedc': 'http://service.scedc.caltech.edu',
55 'usgs': 'http://earthquake.usgs.gov',
56 'koeri': 'http://eida-service.koeri.boun.edu.tr',
57 'ethz': 'http://eida.ethz.ch',
58 'icgc': 'http://ws.icgc.cat',
59 'ipgp': 'http://eida.ipgp.fr',
60 'ingv': 'http://webservices.ingv.it',
61 'isc': 'http://www.isc.ac.uk',
62 'lmu': 'http://erde.geophysik.uni-muenchen.de',
63 'noa': 'http://eida.gein.noa.gr',
64 'resif': 'http://ws.resif.fr',
65 'usp': 'http://seisrequest.iag.usp.br',
66 'niep': 'http://eida-sc3.infp.ro'
67}
69g_default_site = 'geofon'
72g_default_query_args = {
73 'station': {
74 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore',
75 'endafter', 'network', 'station', 'location', 'channel', 'minlatitude',
76 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude',
77 'minradius', 'maxradius', 'level', 'includerestricted',
78 'includeavailability', 'updatedafter', 'matchtimeseries', 'format',
79 'nodata'},
80 'dataselect': {
81 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
82 'quality', 'minimumlength', 'longestonly', 'format', 'nodata'},
83 'event': {
84 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude',
85 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius',
86 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype',
87 'includeallorigins', 'includeallmagnitudes', 'includearrivals',
88 'eventid', 'limit', 'offset', 'orderby', 'catalog', 'contributor',
89 'updatedafter', 'format', 'nodata'},
90 'availability': {
91 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
92 'quality', 'merge', 'orderby', 'limit', 'includerestricted', 'format',
93 'nodata', 'mergegaps', 'show'}}
96def doc_escape_slist(li):
97 return ', '.join("``'%s'``" % s for s in li)
100def doc_table_dict(d, khead, vhead, indent=''):
101 keys, vals = zip(*sorted(d.items()))
103 lk = max(max(len(k) for k in keys), len(khead))
104 lv = max(max(len(v) for v in vals), len(vhead))
106 hr = '=' * lk + ' ' + '=' * lv
108 lines = [
109 hr,
110 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)),
111 hr]
113 for k, v in zip(keys, vals):
114 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv)))
116 lines.append(hr)
117 return '\n'.join(indent + line for line in lines)
120def strip_html(s):
121 s = s.decode('utf-8')
122 s = re.sub(r'<[^>]+>', '', s)
123 s = re.sub(r'\r', '', s)
124 s = re.sub(r'\s*\n', '\n', s)
125 return s
128def indent(s, ind=' '):
129 return '\n'.join(ind + line for line in s.splitlines())
132def get_sites():
133 '''
134 Get sorted list of registered site names.
135 '''
136 return sorted(g_site_abbr.keys())
139if config.config().fdsn_timeout is None:
140 g_timeout = 20.
141else:
142 g_timeout = config.config().fdsn_timeout
144re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?')
147class CannotGetRealmFromAuthHeader(DownloadError):
148 '''
149 Raised when failing to parse server response during authentication.
150 '''
151 pass
154class CannotGetCredentialsFromAuthRequest(DownloadError):
155 '''
156 Raised when failing to parse server response during token authentication.
157 '''
158 pass
161def get_realm_from_auth_header(headers):
162 realm = dict(re_realm_from_auth_header.findall(
163 headers['WWW-Authenticate'])).get('realm', None)
165 if realm is None:
166 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers))
168 return realm
171def sdatetime(t):
172 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S')
175class EmptyResult(DownloadError):
176 '''
177 Raised when an empty server response is retrieved.
178 '''
179 def __init__(self, url):
180 DownloadError.__init__(self)
181 self._url = url
183 def __str__(self):
184 return 'No results for request %s' % self._url
187class RequestEntityTooLarge(DownloadError):
188 '''
189 Raised when the server indicates that too much data was requested.
190 '''
191 def __init__(self, url):
192 DownloadError.__init__(self)
193 self._url = url
195 def __str__(self):
196 return 'Request entity too large: %s' % self._url
199class InvalidRequest(DownloadError):
200 '''
201 Raised when an invalid request would be sent / has been sent.
202 '''
203 pass
206class Timeout(DownloadError):
207 '''
208 Raised when the server does not respond within the allowed timeout period.
209 '''
210 pass
213def _request(
214 url,
215 post=False,
216 user=None,
217 passwd=None,
218 timeout=g_timeout,
219 **kwargs):
221 url_values = urlencode(kwargs)
222 if url_values:
223 url += '?' + url_values
225 logger.debug('Accessing URL %s' % url)
226 url_args = {
227 'timeout': timeout
228 }
230 if util.g_ssl_context:
231 url_args['context'] = util.g_ssl_context
233 opener = None
235 req = Request(url)
236 if post:
237 if isinstance(post, str):
238 post = post.encode('utf8')
239 logger.debug('POST data: \n%s' % post.decode('utf8'))
240 req.data = post
242 req.add_header('Accept', '*/*')
244 itry = 0
245 while True:
246 itry += 1
247 try:
248 urlopen_ = opener.open if opener else urlopen
249 try:
250 resp = urlopen_(req, **url_args)
251 except TypeError:
252 # context and cafile not avail before 3.4.3, 2.7.9
253 url_args.pop('context', None)
254 url_args.pop('cafile', None)
255 resp = urlopen_(req, **url_args)
257 logger.debug('Response: %s' % resp.getcode())
258 if resp.getcode() == 204:
259 raise EmptyResult(url)
260 return resp
262 except HTTPError as e:
263 if e.code == 413:
264 raise RequestEntityTooLarge(url)
266 elif e.code == 401:
267 headers = getattr(e, 'headers', e.hdrs)
269 realm = get_realm_from_auth_header(headers)
271 if itry == 1 and user is not None:
272 auth_handler = HTTPDigestAuthHandler()
273 auth_handler.add_password(
274 realm=realm,
275 uri=url,
276 user=user,
277 passwd=passwd or '')
279 opener = build_opener(auth_handler)
280 continue
281 else:
282 raise DownloadError(
283 'Authentication failed for realm "%s" when accessing '
284 'url "%s". Original error was: %s' % (
285 realm, url, str(e)))
287 else:
288 raise DownloadError(
289 'Error content returned by server (HTML stripped):\n%s\n'
290 ' Original error was: %s' % (
291 indent(
292 strip_html(e.read()),
293 ' ! '),
294 str(e)))
296 except socket.timeout:
297 raise Timeout(
298 'Timeout error. No response received within %i s. You '
299 'may want to retry with a longer timeout setting.' % timeout)
301 break
304def fillurl(service, site, url, majorversion, method):
305 return url % dict(
306 site=g_site_abbr.get(site, site),
307 service=service,
308 majorversion=majorversion,
309 method=method)
312def fix_params(d):
314 params = dict(d)
315 for k in ['starttime',
316 'endtime',
317 'startbefore',
318 'startafter',
319 'endbefore',
320 'endafter',
321 'updatedafter']:
323 if k in params:
324 params[k] = sdatetime(params[k])
326 if params.get('location', None) == '':
327 params['location'] = '--'
329 for k in params:
330 if isinstance(params[k], bool):
331 params[k] = ['false', 'true'][bool(params[k])]
333 return params
336def make_data_selection(
337 stations, tmin, tmax,
338 channel_prio=[['BHZ', 'HHZ'],
339 ['BH1', 'BHN', 'HH1', 'HHN'],
340 ['BH2', 'BHE', 'HH2', 'HHE']]):
342 selection = []
343 for station in stations:
344 wanted = []
345 for group in channel_prio:
346 gchannels = []
347 for channel in station.get_channels():
348 if channel.name in group:
349 gchannels.append(channel)
350 if gchannels:
351 gchannels.sort(key=lambda a: group.index(a.name))
352 wanted.append(gchannels[0])
354 if wanted:
355 for channel in wanted:
356 selection.append((station.network, station.station,
357 station.location, channel.name, tmin, tmax))
359 return selection
362def station(
363 site=g_default_site,
364 url=g_url,
365 majorversion=1,
366 timeout=g_timeout,
367 check=True,
368 selection=None,
369 parsed=True,
370 **kwargs):
372 '''
373 Query FDSN web service for station metadata.
375 :param site:
376 :ref:`Registered site name <registered-site-names>` or full base URL of
377 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
378 :type site: str, optional
379 :param url:
380 URL template (default should work in 99% of cases).
381 :type url: str, optional
382 :param majorversion:
383 Major version of the service to query (always ``1`` at the time of
384 writing).
385 :type majorversion: int, optional
386 :param timeout:
387 Network timeout in [s]. Global default timeout can be configured in
388 Pyrocko's configuration file under ``fdsn_timeout``.
389 :type timeout: float, optional
390 :param check:
391 If ``True`` arguments are checked against self-description (WADL) of
392 the queried web service if available or FDSN specification.
393 :type check: bool, optional
394 :param selection:
395 If given, selection to be queried as a list of tuples
396 ``(network, station, location, channel, tmin, tmax)``. Useful for
397 detailed queries.
398 :type selection: list of tuples, optional
399 :param parsed:
400 If ``True`` parse received content into
401 :py:class:`~pyrocko.io.stationxml.FDSNStationXML`
402 object, otherwise return open file handle to raw data stream.
403 :type parsed: bool, optional
404 :param \\*\\*kwargs:
405 Parameters passed to the server (see `FDSN web services specification
406 <https://www.fdsn.org/webservices>`_).
408 :returns:
409 See description of ``parsed`` argument above.
411 :raises:
412 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its
413 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is
414 raised.
415 '''
417 service = 'station'
419 if check:
420 check_params(service, site, url, majorversion, timeout, **kwargs)
422 params = fix_params(kwargs)
424 url = fillurl(service, site, url, majorversion, 'query')
425 if selection:
426 lst = []
427 for k, v in params.items():
428 lst.append('%s=%s' % (k, v))
430 for (network, station, location, channel, tmin, tmax) in selection:
431 if location == '':
432 location = '--'
434 lst.append(' '.join((network, station, location, channel,
435 sdatetime(tmin), sdatetime(tmax))))
437 post = '\n'.join(lst)
438 params = dict(post=post.encode())
440 if parsed:
441 from pyrocko.io import stationxml
442 format = kwargs.get('format', 'xml')
443 if format == 'text':
444 if kwargs.get('level', 'station') == 'channel':
445 return stationxml.load_channel_table(
446 stream=_request(url, timeout=timeout, **params))
447 else:
448 raise InvalidRequest('if format="text" shall be parsed, '
449 'level="channel" is required')
451 elif format == 'xml':
452 return stationxml.load_xml(
453 stream=_request(url, timeout=timeout, **params))
454 else:
455 raise InvalidRequest('format must be "xml" or "text"')
456 else:
457 return _request(url, timeout=timeout, **params)
460def get_auth_credentials(service, site, url, majorversion, token, timeout):
462 url = fillurl(service, site, url, majorversion, 'auth')
464 f = _request(url, timeout=timeout, post=token)
465 s = f.read().decode()
466 try:
467 user, passwd = s.strip().split(':')
468 except ValueError:
469 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s)
471 return user, passwd
474def dataselect(
475 site=g_default_site,
476 url=g_url,
477 majorversion=1,
478 timeout=g_timeout,
479 check=True,
480 user=None,
481 passwd=None,
482 token=None,
483 selection=None,
484 **kwargs):
486 '''
487 Query FDSN web service for time series data in miniSEED format.
489 :param site:
490 :ref:`Registered site name <registered-site-names>` or full base URL of
491 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
492 :type site: str, optional
493 :param url:
494 URL template (default should work in 99% of cases).
495 :type url: str, optional
496 :param majorversion:
497 Major version of the service to query (always ``1`` at the time of
498 writing).
499 :type majorversion: int, optional
500 :param timeout:
501 Network timeout in [s]. Global default timeout can be configured in
502 Pyrocko's configuration file under ``fdsn_timeout``.
503 :type timeout: float, optional
504 :param check:
505 If ``True`` arguments are checked against self-description (WADL) of
506 the queried web service if available or FDSN specification.
507 :type check: bool, optional
508 :param user: User name for user/password authentication.
509 :type user: str, optional
510 :param passwd: Password for user/password authentication.
511 :type passwd: str, optional
512 :param token: Token for `token authentication
513 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
514 :type token: str, optional
515 :param selection:
516 If given, selection to be queried as a list of tuples
517 ``(network, station, location, channel, tmin, tmax)``.
518 :type selection: list of tuples, optional
519 :param \\*\\*kwargs:
520 Parameters passed to the server (see `FDSN web services specification
521 <https://www.fdsn.org/webservices>`_).
523 :returns:
524 Open file-like object providing raw miniSEED data.
525 '''
527 service = 'dataselect'
529 if user or token:
530 method = 'queryauth'
531 else:
532 method = 'query'
534 if token is not None:
535 user, passwd = get_auth_credentials(
536 service, site, url, majorversion, token, timeout)
538 if check:
539 check_params(service, site, url, majorversion, timeout, **kwargs)
541 params = fix_params(kwargs)
543 url = fillurl(service, site, url, majorversion, method)
544 if selection:
545 lst = []
547 for k, v in params.items():
548 lst.append('%s=%s' % (k, v))
550 for (network, station, location, channel, tmin, tmax) in selection:
551 if location == '':
552 location = '--'
554 lst.append(' '.join((network, station, location, channel,
555 sdatetime(tmin), sdatetime(tmax))))
557 post = '\n'.join(lst)
558 return _request(
559 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
560 else:
561 return _request(
562 url, user=user, passwd=passwd, timeout=timeout, **params)
565def event(
566 site=g_default_site,
567 url=g_url,
568 majorversion=1,
569 timeout=g_timeout,
570 check=True,
571 user=None,
572 passwd=None,
573 token=None,
574 parsed=False,
575 **kwargs):
577 '''
578 Query FDSN web service for events.
580 :param site:
581 :ref:`Registered site name <registered-site-names>` or full base URL of
582 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
583 :type site: str, optional
584 :param url:
585 URL template (default should work in 99% of cases).
586 :type url: str, optional
587 :param majorversion:
588 Major version of the service to query (always ``1`` at the time of
589 writing).
590 :type majorversion: int, optional
591 :param timeout:
592 Network timeout in [s]. Global default timeout can be configured in
593 Pyrocko's configuration file under ``fdsn_timeout``.
594 :type timeout: float, optional
595 :param check:
596 If ``True`` arguments are checked against self-description (WADL) of
597 the queried web service if available or FDSN specification.
598 :type check: bool, optional
599 :param user: User name for user/password authentication.
600 :type user: str, optional
601 :param passwd: Password for user/password authentication.
602 :type passwd: str, optional
603 :param token: Token for `token authentication
604 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
605 :type token: str, optional
606 :param parsed:
607 If ``True`` parse received content into
608 :py:class:`~pyrocko.io.quakeml.QuakeML`
609 object, otherwise return open file handle to raw data stream. Note:
610 by default unparsed data is retrieved, differently from the default
611 behaviour of :py:func:`station` (for backward compatibility).
612 :type parsed: bool, optional
613 :param \\*\\*kwargs:
614 Parameters passed to the server (see `FDSN web services specification
615 <https://www.fdsn.org/webservices>`_).
617 :returns:
618 See description of ``parsed`` argument above.
619 '''
621 service = 'event'
623 if user or token:
624 method = 'queryauth'
625 else:
626 method = 'query'
628 if token is not None:
629 user, passwd = get_auth_credentials(
630 service, site, url, majorversion, token, timeout)
632 if check:
633 check_params(service, site, url, majorversion, timeout, **kwargs)
635 params = fix_params(kwargs)
637 url = fillurl(service, site, url, majorversion, method)
639 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params)
640 if parsed:
641 from pyrocko.io import quakeml
642 format = kwargs.get('format', 'xml')
643 if format != 'xml':
644 raise InvalidRequest(
645 'If parsed=True is selected, format="xml" must be selected.')
647 return quakeml.QuakeML.load_xml(stream=fh)
649 else:
650 return fh
653def availability(
654 method='query',
655 site=g_default_site,
656 url=g_url,
657 majorversion=1,
658 timeout=g_timeout,
659 check=True,
660 user=None,
661 passwd=None,
662 token=None,
663 selection=None,
664 **kwargs):
666 '''
667 Query FDSN web service for time series data availablity.
669 :param method: Availablility method to call: ``'query'``, or ``'extent'``.
670 :param site:
671 :ref:`Registered site name <registered-site-names>` or full base URL of
672 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
673 :type site: str, optional
674 :param url:
675 URL template (default should work in 99% of cases).
676 :type url: str, optional
677 :param majorversion:
678 Major version of the service to query (always ``1`` at the time of
679 writing).
680 :type majorversion: int, optional
681 :param timeout:
682 Network timeout in [s]. Global default timeout can be configured in
683 Pyrocko's configuration file under ``fdsn_timeout``.
684 :type timeout: float, optional
685 :param check:
686 If ``True`` arguments are checked against self-description (WADL) of
687 the queried web service if available or FDSN specification.
688 :type check: bool, optional
689 :param user: User name for user/password authentication.
690 :type user: str, optional
691 :param passwd: Password for user/password authentication.
692 :type passwd: str, optional
693 :param token: Token for `token authentication
694 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
695 :type token: str, optional
696 :param selection:
697 If given, selection to be queried as a list of tuples
698 ``(network, station, location, channel, tmin, tmax)``.
699 :type selection: list of tuples, optional
700 :param \\*\\*kwargs:
701 Parameters passed to the server (see `FDSN web services specification
702 <https://www.fdsn.org/webservices>`_).
704 :returns:
705 Open file-like object providing raw response.
706 '''
708 service = 'availability'
710 assert method in ('query', 'extent')
712 if user or token:
713 method += 'auth'
715 if token is not None:
716 user, passwd = get_auth_credentials(
717 service, site, url, majorversion, token, timeout)
719 if check:
720 check_params(service, site, url, majorversion, timeout, **kwargs)
722 params = fix_params(kwargs)
724 url = fillurl(service, site, url, majorversion, method)
725 if selection:
726 lst = []
728 for k, v in params.items():
729 lst.append('%s=%s' % (k, v))
731 for (network, station, location, channel, tmin, tmax) in selection:
732 if location == '':
733 location = '--'
735 lst.append(' '.join((network, station, location, channel,
736 sdatetime(tmin), sdatetime(tmax))))
738 post = '\n'.join(lst)
739 return _request(
740 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
741 else:
742 return _request(
743 url, user=user, passwd=passwd, timeout=timeout, **params)
746def check_params(
747 service,
748 site=g_default_site,
749 url=g_url,
750 majorversion=1,
751 timeout=g_timeout,
752 method='query',
753 **kwargs):
755 '''
756 Check query parameters against self-description of web service.
758 Downloads WADL description of the given service and site and checks
759 parameters if they are available. Queried WADLs are cached in memory.
761 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
762 ``'availability'``.
763 :param site:
764 :ref:`Registered site name <registered-site-names>` or full base URL of
765 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
766 :type site: str, optional
767 :param url:
768 URL template (default should work in 99% of cases).
769 :type url: str, optional
770 :param majorversion:
771 Major version of the service to query (always ``1`` at the time of
772 writing).
773 :type majorversion: int, optional
774 :param timeout:
775 Network timeout in [s]. Global default timeout can be configured in
776 Pyrocko's configuration file under ``fdsn_timeout``.
777 :type timeout: float, optional
778 :param \\*\\*kwargs:
779 Parameters that would be passed to the server (see `FDSN web services
780 specification <https://www.fdsn.org/webservices>`_).
782 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
783 encountered.
784 '''
786 avail = supported_params_wadl(
787 service, site, url, majorversion, timeout, method)
789 unavail = sorted(set(kwargs.keys()) - avail)
790 if unavail:
791 raise ValueError(
792 'Unsupported parameter%s for service "%s" at site "%s": %s' % (
793 '' if len(unavail) == 1 else 's',
794 service,
795 site,
796 ', '.join(unavail)))
799def supported_params_wadl(
800 service,
801 site=g_default_site,
802 url=g_url,
803 majorversion=1,
804 timeout=g_timeout,
805 method='query'):
807 '''
808 Get query parameter names supported by a given FDSN site and service.
810 If no WADL is provided by the queried service, default parameter sets from
811 the FDSN standard are returned. Queried WADLs are cached in memory.
813 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
814 ``'availability'``.
815 :param site:
816 :ref:`Registered site name <registered-site-names>` or full base URL of
817 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
818 :type site: str, optional
819 :param url:
820 URL template (default should work in 99% of cases).
821 :type url: str, optional
822 :param majorversion:
823 Major version of the service to query (always ``1`` at the time of
824 writing).
825 :type majorversion: int, optional
826 :param timeout:
827 Network timeout in [s]. Global default timeout can be configured in
828 Pyrocko's configuration file under ``fdsn_timeout``.
829 :type timeout: float, optional
831 :returns: Supported parameter names.
832 :rtype: set of str
833 '''
835 wadl = cached_wadl(service, site, url, majorversion, timeout)
837 if wadl:
838 url = fillurl(service, site, url, majorversion, method)
839 return set(wadl.supported_param_names(url))
840 else:
841 return g_default_query_args[service]
844def patch_geonet_wadl(wadl):
845 for r in wadl.resources_list:
846 r.base = r.base.replace('1/station', 'station/1')
847 r.base = r.base.replace('1/dataselect', 'dataselect/1')
848 r.base = r.base.replace('1/event', 'event/1')
851def wadl(
852 service,
853 site=g_default_site,
854 url=g_url,
855 majorversion=1,
856 timeout=g_timeout):
858 '''
859 Retrieve self-description of a specific FDSN service.
861 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
862 ``'availability'``.
863 :param site:
864 :ref:`Registered site name <registered-site-names>` or full base URL of
865 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
866 :type site: str, optional
867 :param url:
868 URL template (default should work in 99% of cases).
869 :type url: str, optional
870 :param majorversion:
871 Major version of the service to query (always ``1`` at the time of
872 writing).
873 :type majorversion: int, optional
874 :param timeout:
875 Network timeout in [s]. Global default timeout can be configured in
876 Pyrocko's configuration file under ``fdsn_timeout``.
877 :type timeout: float, optional
878 '''
880 from pyrocko.client.wadl import load_xml
882 url = fillurl(service, site, url, majorversion, 'application.wadl')
884 wadl = load_xml(stream=_request(url, timeout=timeout))
886 if site == 'geonet' or site.find('geonet.org.nz') != -1:
887 patch_geonet_wadl(wadl)
889 return wadl
892g_wadls = {}
895def cached_wadl(
896 service,
897 site=g_default_site,
898 url=g_url,
899 majorversion=1,
900 timeout=g_timeout):
902 '''
903 Get self-description of a specific FDSN service.
905 Same as :py:func:`wadl`, but results are cached in memory.
906 '''
908 k = (service, site, url, majorversion)
909 if k not in g_wadls:
910 try:
911 g_wadls[k] = wadl(service, site, url, majorversion, timeout)
913 except Timeout:
914 raise
916 except DownloadError:
917 logger.info(
918 'No service description (WADL) found for "%s" at site "%s".'
919 % (service, site))
921 g_wadls[k] = None
923 return g_wadls[k]
926__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
929if __name__ == '__main__':
930 import sys
932 util.setup_logging('pyrocko.client.fdsn', 'info')
934 if len(sys.argv) == 1:
935 sites = get_sites()
936 else:
937 sites = sys.argv[1:]
939 for site in sites:
940 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
942 for service in ['station', 'dataselect', 'event']:
943 try:
944 app = wadl(service, site=site, timeout=2.0)
945 print(indent(str(app)))
947 except Timeout as e:
948 logger.error(str(e))
949 print('%s: timeout' % (service,))
951 except util.DownloadError as e:
952 logger.error(str(e))
953 print('%s: no wadl' % (service,))