Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/client/fdsn.py: 69%
262 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-23 12:35 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-23 12:35 +0000
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=None,
219 **kwargs):
221 if timeout is None:
222 timeout = g_timeout
224 url_values = urlencode(kwargs)
225 if url_values:
226 url += '?' + url_values
228 logger.debug('Accessing URL %s' % url)
229 url_args = {
230 'timeout': timeout
231 }
233 if util.g_ssl_context:
234 url_args['context'] = util.g_ssl_context
236 opener = None
238 req = Request(url)
239 if post:
240 if isinstance(post, str):
241 post = post.encode('utf8')
242 logger.debug('POST data: \n%s' % post.decode('utf8'))
243 req.data = post
245 req.add_header('Accept', '*/*')
247 itry = 0
248 while True:
249 itry += 1
250 try:
251 urlopen_ = opener.open if opener else urlopen
252 try:
253 resp = urlopen_(req, **url_args)
254 except TypeError:
255 # context and cafile not avail before 3.4.3, 2.7.9
256 url_args.pop('context', None)
257 url_args.pop('cafile', None)
258 resp = urlopen_(req, **url_args)
260 logger.debug('Response: %s' % resp.getcode())
261 if resp.getcode() == 204:
262 raise EmptyResult(url)
263 return resp
265 except HTTPError as e:
266 if e.code == 413:
267 raise RequestEntityTooLarge(url)
269 elif e.code == 401:
270 headers = getattr(e, 'headers', e.hdrs)
272 realm = get_realm_from_auth_header(headers)
274 if itry == 1 and user is not None:
275 auth_handler = HTTPDigestAuthHandler()
276 auth_handler.add_password(
277 realm=realm,
278 uri=url,
279 user=user,
280 passwd=passwd or '')
282 opener = build_opener(auth_handler)
283 continue
284 else:
285 raise DownloadError(
286 'Authentication failed for realm "%s" when accessing '
287 'url "%s". Original error was: %s' % (
288 realm, url, str(e)))
290 else:
291 raise DownloadError(
292 'Error content returned by server (HTML stripped):\n%s\n'
293 ' Original error was: %s' % (
294 indent(
295 strip_html(e.read()),
296 ' ! '),
297 str(e)))
299 except socket.timeout:
300 raise Timeout(
301 'Timeout error. No response received within %i s. You '
302 'may want to retry with a longer timeout setting.' % timeout)
304 break
307def fillurl(service, site, url, majorversion, method):
308 return url % dict(
309 site=g_site_abbr.get(site, site),
310 service=service,
311 majorversion=majorversion,
312 method=method)
315def fix_params(d):
317 params = dict(d)
318 for k in ['starttime',
319 'endtime',
320 'startbefore',
321 'startafter',
322 'endbefore',
323 'endafter',
324 'updatedafter']:
326 if k in params:
327 params[k] = sdatetime(params[k])
329 if params.get('location', None) == '':
330 params['location'] = '--'
332 for k in params:
333 if isinstance(params[k], bool):
334 params[k] = ['false', 'true'][bool(params[k])]
336 return params
339def make_data_selection(
340 stations, tmin, tmax,
341 channel_prio=[['BHZ', 'HHZ'],
342 ['BH1', 'BHN', 'HH1', 'HHN'],
343 ['BH2', 'BHE', 'HH2', 'HHE']]):
345 selection = []
346 for station in stations:
347 wanted = []
348 for group in channel_prio:
349 gchannels = []
350 for channel in station.get_channels():
351 if channel.name in group:
352 gchannels.append(channel)
353 if gchannels:
354 gchannels.sort(key=lambda a: group.index(a.name))
355 wanted.append(gchannels[0])
357 if wanted:
358 for channel in wanted:
359 selection.append((station.network, station.station,
360 station.location, channel.name, tmin, tmax))
362 return selection
365def station(
366 site=g_default_site,
367 url=g_url,
368 majorversion=1,
369 timeout=None,
370 check=True,
371 selection=None,
372 parsed=True,
373 **kwargs):
375 '''
376 Query FDSN web service for station metadata.
378 :param site:
379 :ref:`Registered site name <registered-site-names>` or full base URL of
380 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
381 :type site: str
382 :param url:
383 URL template (default should work in 99% of cases).
384 :type url: str
385 :param majorversion:
386 Major version of the service to query (always ``1`` at the time of
387 writing).
388 :type majorversion: int
389 :param timeout:
390 Network timeout in [s]. Global default timeout can be configured in
391 Pyrocko's configuration file under ``fdsn_timeout``.
392 :type timeout: float
393 :param check:
394 If ``True`` arguments are checked against self-description (WADL) of
395 the queried web service if available or FDSN specification.
396 :type check: bool
397 :param selection:
398 If given, selection to be queried as a list of tuples
399 ``(network, station, location, channel, tmin, tmax)``. Useful for
400 detailed queries.
401 :type selection: :py:class:`list` of :py:class:`tuple`
402 :param parsed:
403 If ``True`` parse received content into
404 :py:class:`~pyrocko.io.stationxml.FDSNStationXML`
405 object, otherwise return open file handle to raw data stream.
406 :type parsed: bool
407 :param \\*\\*kwargs:
408 Parameters passed to the server (see `FDSN web services specification
409 <https://www.fdsn.org/webservices>`_).
411 :returns:
412 See description of ``parsed`` argument above.
414 :raises:
415 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its
416 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is
417 raised.
418 '''
420 service = 'station'
422 if check:
423 check_params(service, site, url, majorversion, timeout, **kwargs)
425 params = fix_params(kwargs)
427 url = fillurl(service, site, url, majorversion, 'query')
428 if selection:
429 lst = []
430 for k, v in params.items():
431 lst.append('%s=%s' % (k, v))
433 for (network, station, location, channel, tmin, tmax) in selection:
434 if location == '':
435 location = '--'
437 lst.append(' '.join((network, station, location, channel,
438 sdatetime(tmin), sdatetime(tmax))))
440 post = '\n'.join(lst)
441 params = dict(post=post.encode())
443 if parsed:
444 from pyrocko.io import stationxml
445 format = kwargs.get('format', 'xml')
446 if format == 'text':
447 if kwargs.get('level', 'station') == 'channel':
448 return stationxml.load_channel_table(
449 stream=_request(url, timeout=timeout, **params))
450 else:
451 raise InvalidRequest('if format="text" shall be parsed, '
452 'level="channel" is required')
454 elif format == 'xml':
455 return stationxml.load_xml(
456 stream=_request(url, timeout=timeout, **params))
457 else:
458 raise InvalidRequest('format must be "xml" or "text"')
459 else:
460 return _request(url, timeout=timeout, **params)
463def get_auth_credentials(service, site, url, majorversion, token, timeout):
465 url = fillurl(service, site, url, majorversion, 'auth')
467 f = _request(url, timeout=timeout, post=token)
468 s = f.read().decode()
469 try:
470 user, passwd = s.strip().split(':')
471 except ValueError:
472 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s)
474 return user, passwd
477def dataselect(
478 site=g_default_site,
479 url=g_url,
480 majorversion=1,
481 timeout=None,
482 check=True,
483 user=None,
484 passwd=None,
485 token=None,
486 selection=None,
487 **kwargs):
489 '''
490 Query FDSN web service for time series data in miniSEED format.
492 :param site:
493 :ref:`Registered site name <registered-site-names>` or full base URL of
494 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
495 :type site: str
496 :param url:
497 URL template (default should work in 99% of cases).
498 :type url: str
499 :param majorversion:
500 Major version of the service to query (always ``1`` at the time of
501 writing).
502 :type majorversion: int
503 :param timeout:
504 Network timeout in [s]. Global default timeout can be configured in
505 Pyrocko's configuration file under ``fdsn_timeout``.
506 :type timeout: float
507 :param check:
508 If ``True`` arguments are checked against self-description (WADL) of
509 the queried web service if available or FDSN specification.
510 :type check: bool
511 :param user: User name for user/password authentication.
512 :type user: str
513 :param passwd: Password for user/password authentication.
514 :type passwd: str
515 :param token: Token for `token authentication
516 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
517 :type token: str
518 :param selection:
519 If given, selection to be queried as a list of tuples
520 ``(network, station, location, channel, tmin, tmax)``.
521 :type selection: :py:class:`list` of :py:class:`tuple`
522 :param \\*\\*kwargs:
523 Parameters passed to the server (see `FDSN web services specification
524 <https://www.fdsn.org/webservices>`_).
526 :returns:
527 Open file-like object providing raw miniSEED data.
528 '''
530 service = 'dataselect'
532 if user or token:
533 method = 'queryauth'
534 else:
535 method = 'query'
537 if token is not None:
538 user, passwd = get_auth_credentials(
539 service, site, url, majorversion, token, timeout)
541 if check:
542 check_params(service, site, url, majorversion, timeout, **kwargs)
544 params = fix_params(kwargs)
546 url = fillurl(service, site, url, majorversion, method)
547 if selection:
548 lst = []
550 for k, v in params.items():
551 lst.append('%s=%s' % (k, v))
553 for (network, station, location, channel, tmin, tmax) in selection:
554 if location == '':
555 location = '--'
557 lst.append(' '.join((network, station, location, channel,
558 sdatetime(tmin), sdatetime(tmax))))
560 post = '\n'.join(lst)
561 return _request(
562 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
563 else:
564 return _request(
565 url, user=user, passwd=passwd, timeout=timeout, **params)
568def event(
569 site=g_default_site,
570 url=g_url,
571 majorversion=1,
572 timeout=None,
573 check=True,
574 user=None,
575 passwd=None,
576 token=None,
577 parsed=False,
578 **kwargs):
580 '''
581 Query FDSN web service for events.
583 :param site:
584 :ref:`Registered site name <registered-site-names>` or full base URL of
585 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
586 :type site: str
587 :param url:
588 URL template (default should work in 99% of cases).
589 :type url: str
590 :param majorversion:
591 Major version of the service to query (always ``1`` at the time of
592 writing).
593 :type majorversion: int
594 :param timeout:
595 Network timeout in [s]. Global default timeout can be configured in
596 Pyrocko's configuration file under ``fdsn_timeout``.
597 :type timeout: float
598 :param check:
599 If ``True`` arguments are checked against self-description (WADL) of
600 the queried web service if available or FDSN specification.
601 :type check: bool
602 :param user: User name for user/password authentication.
603 :type user: str
604 :param passwd: Password for user/password authentication.
605 :type passwd: str
606 :param token: Token for `token authentication
607 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
608 :type token: str
609 :param parsed:
610 If ``True`` parse received content into
611 :py:class:`~pyrocko.io.quakeml.QuakeML`
612 object, otherwise return open file handle to raw data stream. Note:
613 by default unparsed data is retrieved, differently from the default
614 behaviour of :py:func:`station` (for backward compatibility).
615 :type parsed: bool
616 :param \\*\\*kwargs:
617 Parameters passed to the server (see `FDSN web services specification
618 <https://www.fdsn.org/webservices>`_).
620 :returns:
621 See description of ``parsed`` argument above.
622 '''
624 service = 'event'
626 if user or token:
627 method = 'queryauth'
628 else:
629 method = 'query'
631 if token is not None:
632 user, passwd = get_auth_credentials(
633 service, site, url, majorversion, token, timeout)
635 if check:
636 check_params(service, site, url, majorversion, timeout, **kwargs)
638 params = fix_params(kwargs)
640 url = fillurl(service, site, url, majorversion, method)
642 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params)
643 if parsed:
644 from pyrocko.io import quakeml
645 format = kwargs.get('format', 'xml')
646 if format != 'xml':
647 raise InvalidRequest(
648 'If parsed=True is selected, format="xml" must be selected.')
650 return quakeml.QuakeML.load_xml(stream=fh)
652 else:
653 return fh
656def availability(
657 method='query',
658 site=g_default_site,
659 url=g_url,
660 majorversion=1,
661 timeout=None,
662 check=True,
663 user=None,
664 passwd=None,
665 token=None,
666 selection=None,
667 **kwargs):
669 '''
670 Query FDSN web service for time series data availablity.
672 :param method: Availablility method to call: ``'query'``, or ``'extent'``.
673 :param site:
674 :ref:`Registered site name <registered-site-names>` or full base URL of
675 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
676 :type site: str
677 :param url:
678 URL template (default should work in 99% of cases).
679 :type url: str
680 :param majorversion:
681 Major version of the service to query (always ``1`` at the time of
682 writing).
683 :type majorversion: int
684 :param timeout:
685 Network timeout in [s]. Global default timeout can be configured in
686 Pyrocko's configuration file under ``fdsn_timeout``.
687 :type timeout: float
688 :param check:
689 If ``True`` arguments are checked against self-description (WADL) of
690 the queried web service if available or FDSN specification.
691 :type check: bool
692 :param user: User name for user/password authentication.
693 :type user: str
694 :param passwd: Password for user/password authentication.
695 :type passwd: str
696 :param token: Token for `token authentication
697 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
698 :type token: str
699 :param selection:
700 If given, selection to be queried as a list of tuples
701 ``(network, station, location, channel, tmin, tmax)``.
702 :type selection: :py:class:`list` of :py:class:`tuple`
703 :param \\*\\*kwargs:
704 Parameters passed to the server (see `FDSN web services specification
705 <https://www.fdsn.org/webservices>`_).
707 :returns:
708 Open file-like object providing raw response.
709 '''
711 service = 'availability'
713 assert method in ('query', 'extent')
715 if user or token:
716 method += 'auth'
718 if token is not None:
719 user, passwd = get_auth_credentials(
720 service, site, url, majorversion, token, timeout)
722 if check:
723 check_params(service, site, url, majorversion, timeout, **kwargs)
725 params = fix_params(kwargs)
727 url = fillurl(service, site, url, majorversion, method)
728 if selection:
729 lst = []
731 for k, v in params.items():
732 lst.append('%s=%s' % (k, v))
734 for (network, station, location, channel, tmin, tmax) in selection:
735 if location == '':
736 location = '--'
738 lst.append(' '.join((network, station, location, channel,
739 sdatetime(tmin), sdatetime(tmax))))
741 post = '\n'.join(lst)
742 return _request(
743 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
744 else:
745 return _request(
746 url, user=user, passwd=passwd, timeout=timeout, **params)
749def check_params(
750 service,
751 site=g_default_site,
752 url=g_url,
753 majorversion=1,
754 timeout=None,
755 method='query',
756 **kwargs):
758 '''
759 Check query parameters against self-description of web service.
761 Downloads WADL description of the given service and site and checks
762 parameters if they are available. Queried WADLs are cached in memory.
764 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
765 ``'availability'``.
766 :param site:
767 :ref:`Registered site name <registered-site-names>` or full base URL of
768 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
769 :type site: str
770 :param url:
771 URL template (default should work in 99% of cases).
772 :type url: str
773 :param majorversion:
774 Major version of the service to query (always ``1`` at the time of
775 writing).
776 :type majorversion: int
777 :param timeout:
778 Network timeout in [s]. Global default timeout can be configured in
779 Pyrocko's configuration file under ``fdsn_timeout``.
780 :type timeout: float
781 :param \\*\\*kwargs:
782 Parameters that would be passed to the server (see `FDSN web services
783 specification <https://www.fdsn.org/webservices>`_).
785 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
786 encountered.
787 '''
789 avail = supported_params_wadl(
790 service, site, url, majorversion, timeout, method)
792 unavail = sorted(set(kwargs.keys()) - avail)
793 if unavail:
794 raise ValueError(
795 'Unsupported parameter%s for service "%s" at site "%s": %s' % (
796 '' if len(unavail) == 1 else 's',
797 service,
798 site,
799 ', '.join(unavail)))
802def supported_params_wadl(
803 service,
804 site=g_default_site,
805 url=g_url,
806 majorversion=1,
807 timeout=None,
808 method='query'):
810 '''
811 Get query parameter names supported by a given FDSN site and service.
813 If no WADL is provided by the queried service, default parameter sets from
814 the FDSN standard are returned. Queried WADLs are cached in memory.
816 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
817 ``'availability'``.
818 :param site:
819 :ref:`Registered site name <registered-site-names>` or full base URL of
820 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
821 :type site: str
822 :param url:
823 URL template (default should work in 99% of cases).
824 :type url: str
825 :param majorversion:
826 Major version of the service to query (always ``1`` at the time of
827 writing).
828 :type majorversion: int
829 :param timeout:
830 Network timeout in [s]. Global default timeout can be configured in
831 Pyrocko's configuration file under ``fdsn_timeout``.
832 :type timeout: float
834 :returns: Supported parameter names.
835 :rtype: :py:class:`set` of :py:class:`str`
836 '''
838 wadl = cached_wadl(service, site, url, majorversion, timeout)
840 if wadl:
841 url = fillurl(service, site, url, majorversion, method)
842 return set(wadl.supported_param_names(url))
843 else:
844 return g_default_query_args[service]
847def patch_geonet_wadl(wadl):
848 for r in wadl.resources_list:
849 r.base = r.base.replace('1/station', 'station/1')
850 r.base = r.base.replace('1/dataselect', 'dataselect/1')
851 r.base = r.base.replace('1/event', 'event/1')
854def wadl(
855 service,
856 site=g_default_site,
857 url=g_url,
858 majorversion=1,
859 timeout=None):
861 '''
862 Retrieve self-description of a specific FDSN service.
864 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
865 ``'availability'``.
866 :param site:
867 :ref:`Registered site name <registered-site-names>` or full base URL of
868 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
869 :type site: str
870 :param url:
871 URL template (default should work in 99% of cases).
872 :type url: str
873 :param majorversion:
874 Major version of the service to query (always ``1`` at the time of
875 writing).
876 :type majorversion: int
877 :param timeout:
878 Network timeout in [s]. Global default timeout can be configured in
879 Pyrocko's configuration file under ``fdsn_timeout``.
880 :type timeout: float
881 '''
883 from pyrocko.client.wadl import load_xml
885 url = fillurl(service, site, url, majorversion, 'application.wadl')
887 wadl = load_xml(stream=_request(url, timeout=timeout))
889 if site == 'geonet' or site.find('geonet.org.nz') != -1:
890 patch_geonet_wadl(wadl)
892 return wadl
895g_wadls = {}
898def cached_wadl(
899 service,
900 site=g_default_site,
901 url=g_url,
902 majorversion=1,
903 timeout=None):
905 '''
906 Get self-description of a specific FDSN service.
908 Same as :py:func:`wadl`, but results are cached in memory.
909 '''
911 k = (service, site, url, majorversion)
912 if k not in g_wadls:
913 try:
914 g_wadls[k] = wadl(service, site, url, majorversion, timeout)
916 except Timeout:
917 raise
919 except DownloadError:
920 logger.info(
921 'No service description (WADL) found for "%s" at site "%s".'
922 % (service, site))
924 g_wadls[k] = None
926 return g_wadls[k]
929__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
932if __name__ == '__main__':
933 import sys
935 util.setup_logging('pyrocko.client.fdsn', 'info')
937 if len(sys.argv) == 1:
938 sites = get_sites()
939 else:
940 sites = sys.argv[1:]
942 for site in sites:
943 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
945 for service in ['station', 'dataselect', 'event']:
946 try:
947 app = wadl(service, site=site, timeout=2.0)
948 print(indent(str(app)))
950 except Timeout as e:
951 logger.error(str(e))
952 print('%s: timeout' % (service,))
954 except util.DownloadError as e:
955 logger.error(str(e))
956 print('%s: no wadl' % (service,))