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 socket
35from pyrocko import util
36from pyrocko.util import DownloadError
37from pyrocko import config
39from pyrocko.util import \
40 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError
42try:
43 newstr = unicode
44except NameError:
45 newstr = str
48logger = logging.getLogger('pyrocko.client.fdsn')
50g_url = '%(site)s/fdsnws/%(service)s/%(majorversion)i/%(method)s'
52g_site_abbr = {
53 'geofon': 'https://geofon.gfz-potsdam.de',
54 'iris': 'http://service.iris.edu',
55 'orfeus': 'http://www.orfeus-eu.org',
56 'bgr': 'http://eida.bgr.de',
57 'geonet': 'http://service.geonet.org.nz',
58 'knmi': 'http://rdsa.knmi.nl',
59 'ncedc': 'http://service.ncedc.org',
60 'scedc': 'http://service.scedc.caltech.edu',
61 'usgs': 'http://earthquake.usgs.gov',
62 'koeri': 'http://eida-service.koeri.boun.edu.tr',
63 'ethz': 'http://eida.ethz.ch',
64 'icgc': 'http://ws.icgc.cat',
65 'ipgp': 'http://eida.ipgp.fr',
66 'ingv': 'http://webservices.ingv.it',
67 'isc': 'http://www.isc.ac.uk',
68 'lmu': 'http://erde.geophysik.uni-muenchen.de',
69 'noa': 'http://eida.gein.noa.gr',
70 'resif': 'http://ws.resif.fr',
71 'usp': 'http://seisrequest.iag.usp.br',
72 'niep': 'http://eida-sc3.infp.ro'
73}
75g_default_site = 'geofon'
78g_default_query_args = {
79 'station': {
80 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore',
81 'endafter', 'network', 'station', 'location', 'channel', 'minlatitude',
82 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude',
83 'minradius', 'maxradius', 'level', 'includerestricted',
84 'includeavailability', 'updatedafter', 'matchtimeseries', 'format',
85 'nodata'},
86 'dataselect': {
87 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
88 'quality', 'minimumlength', 'longestonly', 'format', 'nodata'},
89 'event': {
90 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude',
91 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius',
92 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype',
93 'includeallorigins', 'includeallmagnitudes', 'includearrivals',
94 'eventid', 'limit', 'offset', 'orderby', 'catalog', 'contributor',
95 'updatedafter', 'format', 'nodata'},
96 'availability': {
97 'starttime', 'endtime', 'network', 'station', 'location', 'channel',
98 'quality', 'merge', 'orderby', 'limit', 'includerestricted', 'format',
99 'nodata', 'mergegaps', 'show'}}
102def doc_escape_slist(li):
103 return ', '.join("``'%s'``" % s for s in li)
106def doc_table_dict(d, khead, vhead, indent=''):
107 keys, vals = zip(*sorted(d.items()))
109 lk = max(max(len(k) for k in keys), len(khead))
110 lv = max(max(len(v) for v in vals), len(vhead))
112 hr = '=' * lk + ' ' + '=' * lv
114 lines = [
115 hr,
116 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)),
117 hr]
119 for k, v in zip(keys, vals):
120 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv)))
122 lines.append(hr)
123 return '\n'.join(indent + line for line in lines)
126def strip_html(s):
127 s = s.decode('utf-8')
128 s = re.sub(r'<[^>]+>', '', s)
129 s = re.sub(r'\r', '', s)
130 s = re.sub(r'\s*\n', '\n', s)
131 return s
134def indent(s, ind=' '):
135 return '\n'.join(ind + line for line in s.splitlines())
138def get_sites():
139 '''
140 Get sorted list of registered site names.
141 '''
142 return sorted(g_site_abbr.keys())
145if config.config().fdsn_timeout is None:
146 g_timeout = 20.
147else:
148 g_timeout = config.config().fdsn_timeout
150re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?')
153class CannotGetRealmFromAuthHeader(DownloadError):
154 '''
155 Raised when failing to parse server response during authentication.
156 '''
157 pass
160class CannotGetCredentialsFromAuthRequest(DownloadError):
161 '''
162 Raised when failing to parse server response during token authentication.
163 '''
164 pass
167def get_realm_from_auth_header(headers):
168 realm = dict(re_realm_from_auth_header.findall(
169 headers['WWW-Authenticate'])).get('realm', None)
171 if realm is None:
172 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers))
174 return realm
177def sdatetime(t):
178 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S')
181class EmptyResult(DownloadError):
182 '''
183 Raised when an empty server response is retrieved.
184 '''
185 def __init__(self, url):
186 DownloadError.__init__(self)
187 self._url = url
189 def __str__(self):
190 return 'No results for request %s' % self._url
193class RequestEntityTooLarge(DownloadError):
194 '''
195 Raised when the server indicates that too much data was requested.
196 '''
197 def __init__(self, url):
198 DownloadError.__init__(self)
199 self._url = url
201 def __str__(self):
202 return 'Request entity too large: %s' % self._url
205class InvalidRequest(DownloadError):
206 '''
207 Raised when an invalid request would be sent / has been sent.
208 '''
209 pass
212class Timeout(DownloadError):
213 '''
214 Raised when the server does not respond within the allowed timeout period.
215 '''
216 pass
219def _request(
220 url,
221 post=False,
222 user=None,
223 passwd=None,
224 timeout=g_timeout,
225 **kwargs):
227 url_values = urlencode(kwargs)
228 if url_values:
229 url += '?' + url_values
231 logger.debug('Accessing URL %s' % url)
232 url_args = {
233 'timeout': timeout
234 }
236 if util.g_ssl_context:
237 url_args['context'] = util.g_ssl_context
239 opener = None
241 req = Request(url)
242 if post:
243 if isinstance(post, newstr):
244 post = post.encode('utf8')
245 logger.debug('POST data: \n%s' % post.decode('utf8'))
246 req.data = post
248 req.add_header('Accept', '*/*')
250 itry = 0
251 while True:
252 itry += 1
253 try:
254 urlopen_ = opener.open if opener else urlopen
255 try:
256 resp = urlopen_(req, **url_args)
257 except TypeError:
258 # context and cafile not avail before 3.4.3, 2.7.9
259 url_args.pop('context', None)
260 url_args.pop('cafile', None)
261 resp = urlopen_(req, **url_args)
263 logger.debug('Response: %s' % resp.getcode())
264 if resp.getcode() == 204:
265 raise EmptyResult(url)
266 return resp
268 except HTTPError as e:
269 if e.code == 413:
270 raise RequestEntityTooLarge(url)
272 elif e.code == 401:
273 headers = getattr(e, 'headers', e.hdrs)
275 realm = get_realm_from_auth_header(headers)
277 if itry == 1 and user is not None:
278 auth_handler = HTTPDigestAuthHandler()
279 auth_handler.add_password(
280 realm=realm,
281 uri=url,
282 user=user,
283 passwd=passwd or '')
285 opener = build_opener(auth_handler)
286 continue
287 else:
288 raise DownloadError(
289 'Authentication failed for realm "%s" when accessing '
290 'url "%s". Original error was: %s' % (
291 realm, url, str(e)))
293 else:
294 raise DownloadError(
295 'Error content returned by server (HTML stripped):\n%s\n'
296 ' Original error was: %s' % (
297 indent(
298 strip_html(e.read()),
299 ' ! '),
300 str(e)))
302 except socket.timeout:
303 raise Timeout(
304 'Timeout error. No response received within %i s. You '
305 'may want to retry with a longer timeout setting.' % timeout)
307 break
310def fillurl(service, site, url, majorversion, method):
311 return url % dict(
312 site=g_site_abbr.get(site, site),
313 service=service,
314 majorversion=majorversion,
315 method=method)
318def fix_params(d):
320 params = dict(d)
321 for k in ['starttime',
322 'endtime',
323 'startbefore',
324 'startafter',
325 'endbefore',
326 'endafter',
327 'updatedafter']:
329 if k in params:
330 params[k] = sdatetime(params[k])
332 if params.get('location', None) == '':
333 params['location'] = '--'
335 for k in params:
336 if isinstance(params[k], bool):
337 params[k] = ['false', 'true'][bool(params[k])]
339 return params
342def make_data_selection(
343 stations, tmin, tmax,
344 channel_prio=[['BHZ', 'HHZ'],
345 ['BH1', 'BHN', 'HH1', 'HHN'],
346 ['BH2', 'BHE', 'HH2', 'HHE']]):
348 selection = []
349 for station in stations:
350 wanted = []
351 for group in channel_prio:
352 gchannels = []
353 for channel in station.get_channels():
354 if channel.name in group:
355 gchannels.append(channel)
356 if gchannels:
357 gchannels.sort(key=lambda a: group.index(a.name))
358 wanted.append(gchannels[0])
360 if wanted:
361 for channel in wanted:
362 selection.append((station.network, station.station,
363 station.location, channel.name, tmin, tmax))
365 return selection
368def station(
369 site=g_default_site,
370 url=g_url,
371 majorversion=1,
372 timeout=g_timeout,
373 check=True,
374 selection=None,
375 parsed=True,
376 **kwargs):
378 '''
379 Query FDSN web service for station metadata.
381 :param site:
382 :ref:`Registered site name <registered-site-names>` or full base URL of
383 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
384 :type site: str, optional
385 :param url:
386 URL template (default should work in 99% of cases).
387 :type url: str, optional
388 :param majorversion:
389 Major version of the service to query (always ``1`` at the time of
390 writing).
391 :type majorversion: int, optional
392 :param timeout:
393 Network timeout in [s]. Global default timeout can be configured in
394 Pyrocko's configuration file under ``fdsn_timeout``.
395 :type timeout: float, optional
396 :param check:
397 If ``True`` arguments are checked against self-description (WADL) of
398 the queried web service if available or FDSN specification.
399 :type check: bool, optional
400 :param selection:
401 If given, selection to be queried as a list of tuples
402 ``(network, station, location, channel, tmin, tmax)``. Useful for
403 detailed queries.
404 :type selection: list of tuples, optional
405 :param parsed:
406 If ``True`` parse received content into
407 :py:class:`~pyrocko.io.stationxml.FDSNStationXML`
408 object, otherwise return open file handle to raw data stream.
409 :type parsed: bool, optional
410 :param \\*\\*kwargs:
411 Parameters passed to the server (see `FDSN web services specification
412 <https://www.fdsn.org/webservices>`_).
414 :returns:
415 See description of ``parsed`` argument above.
417 :raises:
418 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its
419 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is
420 raised.
421 '''
423 service = 'station'
425 if check:
426 check_params(service, site, url, majorversion, timeout, **kwargs)
428 params = fix_params(kwargs)
430 url = fillurl(service, site, url, majorversion, 'query')
431 if selection:
432 lst = []
433 for k, v in params.items():
434 lst.append('%s=%s' % (k, v))
436 for (network, station, location, channel, tmin, tmax) in selection:
437 if location == '':
438 location = '--'
440 lst.append(' '.join((network, station, location, channel,
441 sdatetime(tmin), sdatetime(tmax))))
443 post = '\n'.join(lst)
444 params = dict(post=post.encode())
446 if parsed:
447 from pyrocko.io import stationxml
448 format = kwargs.get('format', 'xml')
449 if format == 'text':
450 if kwargs.get('level', 'station') == 'channel':
451 return stationxml.load_channel_table(
452 stream=_request(url, timeout=timeout, **params))
453 else:
454 raise InvalidRequest('if format="text" shall be parsed, '
455 'level="channel" is required')
457 elif format == 'xml':
458 return stationxml.load_xml(
459 stream=_request(url, timeout=timeout, **params))
460 else:
461 raise InvalidRequest('format must be "xml" or "text"')
462 else:
463 return _request(url, timeout=timeout, **params)
466def get_auth_credentials(service, site, url, majorversion, token, timeout):
468 url = fillurl(service, site, url, majorversion, 'auth')
470 f = _request(url, timeout=timeout, post=token)
471 s = f.read().decode()
472 try:
473 user, passwd = s.strip().split(':')
474 except ValueError:
475 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s)
477 return user, passwd
480def dataselect(
481 site=g_default_site,
482 url=g_url,
483 majorversion=1,
484 timeout=g_timeout,
485 check=True,
486 user=None,
487 passwd=None,
488 token=None,
489 selection=None,
490 **kwargs):
492 '''
493 Query FDSN web service for time series data in miniSEED format.
495 :param site:
496 :ref:`Registered site name <registered-site-names>` or full base URL of
497 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
498 :type site: str, optional
499 :param url:
500 URL template (default should work in 99% of cases).
501 :type url: str, optional
502 :param majorversion:
503 Major version of the service to query (always ``1`` at the time of
504 writing).
505 :type majorversion: int, optional
506 :param timeout:
507 Network timeout in [s]. Global default timeout can be configured in
508 Pyrocko's configuration file under ``fdsn_timeout``.
509 :type timeout: float, optional
510 :param check:
511 If ``True`` arguments are checked against self-description (WADL) of
512 the queried web service if available or FDSN specification.
513 :type check: bool, optional
514 :param user: User name for user/password authentication.
515 :type user: str, optional
516 :param passwd: Password for user/password authentication.
517 :type passwd: str, optional
518 :param token: Token for `token authentication
519 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
520 :type token: str, optional
521 :param selection:
522 If given, selection to be queried as a list of tuples
523 ``(network, station, location, channel, tmin, tmax)``.
524 :type selection: list of tuples, optional
525 :param \\*\\*kwargs:
526 Parameters passed to the server (see `FDSN web services specification
527 <https://www.fdsn.org/webservices>`_).
529 :returns:
530 Open file-like object providing raw miniSEED data.
531 '''
533 service = 'dataselect'
535 if user or token:
536 method = 'queryauth'
537 else:
538 method = 'query'
540 if token is not None:
541 user, passwd = get_auth_credentials(
542 service, site, url, majorversion, token, timeout)
544 if check:
545 check_params(service, site, url, majorversion, timeout, **kwargs)
547 params = fix_params(kwargs)
549 url = fillurl(service, site, url, majorversion, method)
550 if selection:
551 lst = []
553 for k, v in params.items():
554 lst.append('%s=%s' % (k, v))
556 for (network, station, location, channel, tmin, tmax) in selection:
557 if location == '':
558 location = '--'
560 lst.append(' '.join((network, station, location, channel,
561 sdatetime(tmin), sdatetime(tmax))))
563 post = '\n'.join(lst)
564 return _request(
565 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
566 else:
567 return _request(
568 url, user=user, passwd=passwd, timeout=timeout, **params)
571def event(
572 site=g_default_site,
573 url=g_url,
574 majorversion=1,
575 timeout=g_timeout,
576 check=True,
577 user=None,
578 passwd=None,
579 token=None,
580 parsed=False,
581 **kwargs):
583 '''
584 Query FDSN web service for events.
586 :param site:
587 :ref:`Registered site name <registered-site-names>` or full base URL of
588 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
589 :type site: str, optional
590 :param url:
591 URL template (default should work in 99% of cases).
592 :type url: str, optional
593 :param majorversion:
594 Major version of the service to query (always ``1`` at the time of
595 writing).
596 :type majorversion: int, optional
597 :param timeout:
598 Network timeout in [s]. Global default timeout can be configured in
599 Pyrocko's configuration file under ``fdsn_timeout``.
600 :type timeout: float, optional
601 :param check:
602 If ``True`` arguments are checked against self-description (WADL) of
603 the queried web service if available or FDSN specification.
604 :type check: bool, optional
605 :param user: User name for user/password authentication.
606 :type user: str, optional
607 :param passwd: Password for user/password authentication.
608 :type passwd: str, optional
609 :param token: Token for `token authentication
610 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
611 :type token: str, optional
612 :param parsed:
613 If ``True`` parse received content into
614 :py:class:`~pyrocko.io.quakeml.QuakeML`
615 object, otherwise return open file handle to raw data stream. Note:
616 by default unparsed data is retrieved, differently from the default
617 behaviour of :py:func:`station` (for backward compatibility).
618 :type parsed: bool, optional
619 :param \\*\\*kwargs:
620 Parameters passed to the server (see `FDSN web services specification
621 <https://www.fdsn.org/webservices>`_).
623 :returns:
624 See description of ``parsed`` argument above.
625 '''
627 service = 'event'
629 if user or token:
630 method = 'queryauth'
631 else:
632 method = 'query'
634 if token is not None:
635 user, passwd = get_auth_credentials(
636 service, site, url, majorversion, token, timeout)
638 if check:
639 check_params(service, site, url, majorversion, timeout, **kwargs)
641 params = fix_params(kwargs)
643 url = fillurl(service, site, url, majorversion, method)
645 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params)
646 if parsed:
647 from pyrocko.io import quakeml
648 format = kwargs.get('format', 'xml')
649 if format != 'xml':
650 raise InvalidRequest(
651 'If parsed=True is selected, format="xml" must be selected.')
653 return quakeml.QuakeML.load_xml(stream=fh)
655 else:
656 return fh
659def availability(
660 method='query',
661 site=g_default_site,
662 url=g_url,
663 majorversion=1,
664 timeout=g_timeout,
665 check=True,
666 user=None,
667 passwd=None,
668 token=None,
669 selection=None,
670 **kwargs):
672 '''
673 Query FDSN web service for time series data availablity.
675 :param method: Availablility method to call: ``'query'``, or ``'extent'``.
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, optional
680 :param url:
681 URL template (default should work in 99% of cases).
682 :type url: str, optional
683 :param majorversion:
684 Major version of the service to query (always ``1`` at the time of
685 writing).
686 :type majorversion: int, optional
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, optional
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, optional
695 :param user: User name for user/password authentication.
696 :type user: str, optional
697 :param passwd: Password for user/password authentication.
698 :type passwd: str, optional
699 :param token: Token for `token authentication
700 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
701 :type token: str, optional
702 :param selection:
703 If given, selection to be queried as a list of tuples
704 ``(network, station, location, channel, tmin, tmax)``.
705 :type selection: list of tuples, optional
706 :param \\*\\*kwargs:
707 Parameters passed to the server (see `FDSN web services specification
708 <https://www.fdsn.org/webservices>`_).
710 :returns:
711 Open file-like object providing raw response.
712 '''
714 service = 'availability'
716 assert method in ('query', 'extent')
718 if user or token:
719 method += 'auth'
721 if token is not None:
722 user, passwd = get_auth_credentials(
723 service, site, url, majorversion, token, timeout)
725 if check:
726 check_params(service, site, url, majorversion, timeout, **kwargs)
728 params = fix_params(kwargs)
730 url = fillurl(service, site, url, majorversion, method)
731 if selection:
732 lst = []
734 for k, v in params.items():
735 lst.append('%s=%s' % (k, v))
737 for (network, station, location, channel, tmin, tmax) in selection:
738 if location == '':
739 location = '--'
741 lst.append(' '.join((network, station, location, channel,
742 sdatetime(tmin), sdatetime(tmax))))
744 post = '\n'.join(lst)
745 return _request(
746 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
747 else:
748 return _request(
749 url, user=user, passwd=passwd, timeout=timeout, **params)
752def check_params(
753 service,
754 site=g_default_site,
755 url=g_url,
756 majorversion=1,
757 timeout=g_timeout,
758 method='query',
759 **kwargs):
761 '''
762 Check query parameters against self-description of web service.
764 Downloads WADL description of the given service and site and checks
765 parameters if they are available. Queried WADLs are cached in memory.
767 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
768 ``'availability'``.
769 :param site:
770 :ref:`Registered site name <registered-site-names>` or full base URL of
771 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
772 :type site: str, optional
773 :param url:
774 URL template (default should work in 99% of cases).
775 :type url: str, optional
776 :param majorversion:
777 Major version of the service to query (always ``1`` at the time of
778 writing).
779 :type majorversion: int, optional
780 :param timeout:
781 Network timeout in [s]. Global default timeout can be configured in
782 Pyrocko's configuration file under ``fdsn_timeout``.
783 :type timeout: float, optional
784 :param \\*\\*kwargs:
785 Parameters that would be passed to the server (see `FDSN web services
786 specification <https://www.fdsn.org/webservices>`_).
788 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
789 encountered.
790 '''
792 avail = supported_params_wadl(
793 service, site, url, majorversion, timeout, method)
795 unavail = sorted(set(kwargs.keys()) - avail)
796 if unavail:
797 raise ValueError(
798 'Unsupported parameter%s for service "%s" at site "%s": %s' % (
799 '' if len(unavail) == 1 else 's',
800 service,
801 site,
802 ', '.join(unavail)))
805def supported_params_wadl(
806 service,
807 site=g_default_site,
808 url=g_url,
809 majorversion=1,
810 timeout=g_timeout,
811 method='query'):
813 '''
814 Get query parameter names supported by a given FDSN site and service.
816 If no WADL is provided by the queried service, default parameter sets from
817 the FDSN standard are returned. Queried WADLs are cached in memory.
819 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
820 ``'availability'``.
821 :param site:
822 :ref:`Registered site name <registered-site-names>` or full base URL of
823 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
824 :type site: str, optional
825 :param url:
826 URL template (default should work in 99% of cases).
827 :type url: str, optional
828 :param majorversion:
829 Major version of the service to query (always ``1`` at the time of
830 writing).
831 :type majorversion: int, optional
832 :param timeout:
833 Network timeout in [s]. Global default timeout can be configured in
834 Pyrocko's configuration file under ``fdsn_timeout``.
835 :type timeout: float, optional
837 :returns: Supported parameter names.
838 :rtype: set of str
839 '''
841 wadl = cached_wadl(service, site, url, majorversion, timeout)
843 if wadl:
844 url = fillurl(service, site, url, majorversion, method)
845 return set(wadl.supported_param_names(url))
846 else:
847 return g_default_query_args[service]
850def patch_geonet_wadl(wadl):
851 for r in wadl.resources_list:
852 r.base = r.base.replace('1/station', 'station/1')
853 r.base = r.base.replace('1/dataselect', 'dataselect/1')
854 r.base = r.base.replace('1/event', 'event/1')
857def wadl(
858 service,
859 site=g_default_site,
860 url=g_url,
861 majorversion=1,
862 timeout=g_timeout):
864 '''
865 Retrieve self-description of a specific FDSN service.
867 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
868 ``'availability'``.
869 :param site:
870 :ref:`Registered site name <registered-site-names>` or full base URL of
871 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
872 :type site: str, optional
873 :param url:
874 URL template (default should work in 99% of cases).
875 :type url: str, optional
876 :param majorversion:
877 Major version of the service to query (always ``1`` at the time of
878 writing).
879 :type majorversion: int, optional
880 :param timeout:
881 Network timeout in [s]. Global default timeout can be configured in
882 Pyrocko's configuration file under ``fdsn_timeout``.
883 :type timeout: float, optional
884 '''
886 from pyrocko.client.wadl import load_xml
888 url = fillurl(service, site, url, majorversion, 'application.wadl')
890 wadl = load_xml(stream=_request(url, timeout=timeout))
892 if site == 'geonet' or site.find('geonet.org.nz') != -1:
893 patch_geonet_wadl(wadl)
895 return wadl
898g_wadls = {}
901def cached_wadl(
902 service,
903 site=g_default_site,
904 url=g_url,
905 majorversion=1,
906 timeout=g_timeout):
908 '''
909 Get self-description of a specific FDSN service.
911 Same as :py:func:`wadl`, but results are cached in memory.
912 '''
914 k = (service, site, url, majorversion)
915 if k not in g_wadls:
916 try:
917 g_wadls[k] = wadl(service, site, url, majorversion, timeout)
919 except Timeout:
920 raise
922 except DownloadError:
923 logger.info(
924 'No service description (WADL) found for "%s" at site "%s".'
925 % (service, site))
927 g_wadls[k] = None
929 return g_wadls[k]
932__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
935if __name__ == '__main__':
936 import sys
938 util.setup_logging('pyrocko.client.fdsn', 'info')
940 if len(sys.argv) == 1:
941 sites = get_sites()
942 else:
943 sites = sys.argv[1:]
945 for site in sites:
946 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
948 for service in ['station', 'dataselect', 'event']:
949 try:
950 app = wadl(service, site=site, timeout=2.0)
951 print(indent(str(app)))
953 except Timeout as e:
954 logger.error(str(e))
955 print('%s: timeout' % (service,))
957 except util.DownloadError as e:
958 logger.error(str(e))
959 print('%s: no wadl' % (service,))