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 2024-01-02 13:30 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-01-02 13:30 +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 'auspass': 'http://auspass.edu.au:8080',
47 'bgr': 'http://eida.bgr.de',
48 'emsc': 'http://www.seismicportal.eu',
49 'ethz': 'http://eida.ethz.ch',
50 'geofon': 'https://geofon.gfz-potsdam.de',
51 'geonet': 'http://service.geonet.org.nz',
52 'icgc': 'http://ws.icgc.cat',
53 'iesdmc': 'http://batsws.earth.sinica.edu.tw:8080',
54 'ingv': 'http://webservices.ingv.it',
55 'ipgp': 'http://eida.ipgp.fr',
56 'iris': 'http://service.iris.edu',
57 'isc': 'http://www.isc.ac.uk',
58 'kagsr': 'http://sdis.emsd.ru',
59 'knmi': 'http://rdsa.knmi.nl',
60 'koeri': 'http://eida-service.koeri.boun.edu.tr',
61 'lmu': 'http://erde.geophysik.uni-muenchen.de',
62 'ncedc': 'https://service.ncedc.org',
63 'niep': 'http://eida-sc3.infp.ro',
64 'noa': 'http://eida.gein.noa.gr',
65 'norsar': 'http://eida.geo.uib.no',
66 'nrcan': 'https://earthquakescanada.nrcan.gc.ca',
67 'orfeus': 'http://www.orfeus-eu.org',
68 'raspishake': 'https://data.raspberryshake.org',
69 'resif': 'http://ws.resif.fr',
70 'scedc': 'http://service.scedc.caltech.edu',
71 'usgs': 'http://earthquake.usgs.gov',
72 'usp': 'http://seisrequest.iag.usp.br',
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=None,
225 **kwargs):
227 if timeout is None:
228 timeout = g_timeout
230 url_values = urlencode(kwargs)
231 if url_values:
232 url += '?' + url_values
234 logger.debug('Accessing URL %s' % url)
235 url_args = {
236 'timeout': timeout
237 }
239 if util.g_ssl_context:
240 url_args['context'] = util.g_ssl_context
242 opener = None
244 req = Request(url)
245 if post:
246 if isinstance(post, str):
247 post = post.encode('utf8')
248 logger.debug('POST data: \n%s' % post.decode('utf8'))
249 req.data = post
251 req.add_header('Accept', '*/*')
253 itry = 0
254 while True:
255 itry += 1
256 try:
257 urlopen_ = opener.open if opener else urlopen
258 try:
259 resp = urlopen_(req, **url_args)
260 except TypeError:
261 # context and cafile not avail before 3.4.3, 2.7.9
262 url_args.pop('context', None)
263 url_args.pop('cafile', None)
264 resp = urlopen_(req, **url_args)
266 logger.debug('Response: %s' % resp.getcode())
267 if resp.getcode() == 204:
268 raise EmptyResult(url)
269 return resp
271 except HTTPError as e:
272 if e.code == 413:
273 raise RequestEntityTooLarge(url)
275 elif e.code == 401:
276 headers = getattr(e, 'headers', e.hdrs)
278 realm = get_realm_from_auth_header(headers)
280 if itry == 1 and user is not None:
281 auth_handler = HTTPDigestAuthHandler()
282 auth_handler.add_password(
283 realm=realm,
284 uri=url,
285 user=user,
286 passwd=passwd or '')
288 opener = build_opener(auth_handler)
289 continue
290 else:
291 raise DownloadError(
292 'Authentication failed for realm "%s" when accessing '
293 'url "%s". Original error was: %s' % (
294 realm, url, str(e)))
296 else:
297 raise DownloadError(
298 'Error content returned by server (HTML stripped):\n%s\n'
299 ' Original error was: %s' % (
300 indent(
301 strip_html(e.read()),
302 ' ! '),
303 str(e)))
305 except socket.timeout:
306 raise Timeout(
307 'Timeout error. No response received within %i s. You '
308 'may want to retry with a longer timeout setting.' % timeout)
310 break
313def fillurl(service, site, url, majorversion, method):
314 return url % dict(
315 site=g_site_abbr.get(site, site),
316 service=service,
317 majorversion=majorversion,
318 method=method)
321def fix_params(d):
323 params = dict(d)
324 for k in ['starttime',
325 'endtime',
326 'startbefore',
327 'startafter',
328 'endbefore',
329 'endafter',
330 'updatedafter']:
332 if k in params:
333 params[k] = sdatetime(params[k])
335 if params.get('location', None) == '':
336 params['location'] = '--'
338 for k in params:
339 if isinstance(params[k], bool):
340 params[k] = ['false', 'true'][bool(params[k])]
342 return params
345def make_data_selection(
346 stations, tmin, tmax,
347 channel_prio=[['BHZ', 'HHZ'],
348 ['BH1', 'BHN', 'HH1', 'HHN'],
349 ['BH2', 'BHE', 'HH2', 'HHE']]):
351 selection = []
352 for station in stations:
353 wanted = []
354 for group in channel_prio:
355 gchannels = []
356 for channel in station.get_channels():
357 if channel.name in group:
358 gchannels.append(channel)
359 if gchannels:
360 gchannels.sort(key=lambda a: group.index(a.name))
361 wanted.append(gchannels[0])
363 if wanted:
364 for channel in wanted:
365 selection.append((station.network, station.station,
366 station.location, channel.name, tmin, tmax))
368 return selection
371def station(
372 site=g_default_site,
373 url=g_url,
374 majorversion=1,
375 timeout=None,
376 check=True,
377 selection=None,
378 parsed=True,
379 **kwargs):
381 '''
382 Query FDSN web service for station metadata.
384 :param site:
385 :ref:`Registered site name <registered-site-names>` or full base URL of
386 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
387 :type site: str
388 :param url:
389 URL template (default should work in 99% of cases).
390 :type url: str
391 :param majorversion:
392 Major version of the service to query (always ``1`` at the time of
393 writing).
394 :type majorversion: int
395 :param timeout:
396 Network timeout in [s]. Global default timeout can be configured in
397 Pyrocko's configuration file under ``fdsn_timeout``.
398 :type timeout: float
399 :param check:
400 If ``True`` arguments are checked against self-description (WADL) of
401 the queried web service if available or FDSN specification.
402 :type check: bool
403 :param selection:
404 If given, selection to be queried as a list of tuples
405 ``(network, station, location, channel, tmin, tmax)``. Useful for
406 detailed queries.
407 :type selection: :py:class:`list` of :py:class:`tuple`
408 :param parsed:
409 If ``True`` parse received content into
410 :py:class:`~pyrocko.io.stationxml.FDSNStationXML`
411 object, otherwise return open file handle to raw data stream.
412 :type parsed: bool
413 :param \\*\\*kwargs:
414 Parameters passed to the server (see `FDSN web services specification
415 <https://www.fdsn.org/webservices>`_).
417 :returns:
418 See description of ``parsed`` argument above.
420 :raises:
421 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its
422 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is
423 raised.
424 '''
426 service = 'station'
428 if check:
429 check_params(service, site, url, majorversion, timeout, **kwargs)
431 params = fix_params(kwargs)
433 url = fillurl(service, site, url, majorversion, 'query')
434 if selection:
435 lst = []
436 for k, v in params.items():
437 lst.append('%s=%s' % (k, v))
439 for (network, station, location, channel, tmin, tmax) in selection:
440 if location == '':
441 location = '--'
443 lst.append(' '.join((network, station, location, channel,
444 sdatetime(tmin), sdatetime(tmax))))
446 post = '\n'.join(lst)
447 params = dict(post=post.encode())
449 if parsed:
450 from pyrocko.io import stationxml
451 format = kwargs.get('format', 'xml')
452 if format == 'text':
453 if kwargs.get('level', 'station') == 'channel':
454 return stationxml.load_channel_table(
455 stream=_request(url, timeout=timeout, **params))
456 else:
457 raise InvalidRequest('if format="text" shall be parsed, '
458 'level="channel" is required')
460 elif format == 'xml':
461 return stationxml.load_xml(
462 stream=_request(url, timeout=timeout, **params))
463 else:
464 raise InvalidRequest('format must be "xml" or "text"')
465 else:
466 return _request(url, timeout=timeout, **params)
469def get_auth_credentials(service, site, url, majorversion, token, timeout):
471 url = fillurl(service, site, url, majorversion, 'auth')
473 f = _request(url, timeout=timeout, post=token)
474 s = f.read().decode()
475 try:
476 user, passwd = s.strip().split(':')
477 except ValueError:
478 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s)
480 return user, passwd
483def dataselect(
484 site=g_default_site,
485 url=g_url,
486 majorversion=1,
487 timeout=None,
488 check=True,
489 user=None,
490 passwd=None,
491 token=None,
492 selection=None,
493 **kwargs):
495 '''
496 Query FDSN web service for time series data in miniSEED format.
498 :param site:
499 :ref:`Registered site name <registered-site-names>` or full base URL of
500 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
501 :type site: str
502 :param url:
503 URL template (default should work in 99% of cases).
504 :type url: str
505 :param majorversion:
506 Major version of the service to query (always ``1`` at the time of
507 writing).
508 :type majorversion: int
509 :param timeout:
510 Network timeout in [s]. Global default timeout can be configured in
511 Pyrocko's configuration file under ``fdsn_timeout``.
512 :type timeout: float
513 :param check:
514 If ``True`` arguments are checked against self-description (WADL) of
515 the queried web service if available or FDSN specification.
516 :type check: bool
517 :param user: User name for user/password authentication.
518 :type user: str
519 :param passwd: Password for user/password authentication.
520 :type passwd: str
521 :param token: Token for `token authentication
522 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
523 :type token: str
524 :param selection:
525 If given, selection to be queried as a list of tuples
526 ``(network, station, location, channel, tmin, tmax)``.
527 :type selection: :py:class:`list` of :py:class:`tuple`
528 :param \\*\\*kwargs:
529 Parameters passed to the server (see `FDSN web services specification
530 <https://www.fdsn.org/webservices>`_).
532 :returns:
533 Open file-like object providing raw miniSEED data.
534 '''
536 service = 'dataselect'
538 if user or token:
539 method = 'queryauth'
540 else:
541 method = 'query'
543 if token is not None:
544 user, passwd = get_auth_credentials(
545 service, site, url, majorversion, token, timeout)
547 if check:
548 check_params(service, site, url, majorversion, timeout, **kwargs)
550 params = fix_params(kwargs)
552 url = fillurl(service, site, url, majorversion, method)
553 if selection:
554 lst = []
556 for k, v in params.items():
557 lst.append('%s=%s' % (k, v))
559 for (network, station, location, channel, tmin, tmax) in selection:
560 if location == '':
561 location = '--'
563 lst.append(' '.join((network, station, location, channel,
564 sdatetime(tmin), sdatetime(tmax))))
566 post = '\n'.join(lst)
567 return _request(
568 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
569 else:
570 return _request(
571 url, user=user, passwd=passwd, timeout=timeout, **params)
574def event(
575 site=g_default_site,
576 url=g_url,
577 majorversion=1,
578 timeout=None,
579 check=True,
580 user=None,
581 passwd=None,
582 token=None,
583 parsed=False,
584 **kwargs):
586 '''
587 Query FDSN web service for events.
589 :param site:
590 :ref:`Registered site name <registered-site-names>` or full base URL of
591 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
592 :type site: str
593 :param url:
594 URL template (default should work in 99% of cases).
595 :type url: str
596 :param majorversion:
597 Major version of the service to query (always ``1`` at the time of
598 writing).
599 :type majorversion: int
600 :param timeout:
601 Network timeout in [s]. Global default timeout can be configured in
602 Pyrocko's configuration file under ``fdsn_timeout``.
603 :type timeout: float
604 :param check:
605 If ``True`` arguments are checked against self-description (WADL) of
606 the queried web service if available or FDSN specification.
607 :type check: bool
608 :param user: User name for user/password authentication.
609 :type user: str
610 :param passwd: Password for user/password authentication.
611 :type passwd: str
612 :param token: Token for `token authentication
613 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
614 :type token: str
615 :param parsed:
616 If ``True`` parse received content into
617 :py:class:`~pyrocko.io.quakeml.QuakeML`
618 object, otherwise return open file handle to raw data stream. Note:
619 by default unparsed data is retrieved, differently from the default
620 behaviour of :py:func:`station` (for backward compatibility).
621 :type parsed: bool
622 :param \\*\\*kwargs:
623 Parameters passed to the server (see `FDSN web services specification
624 <https://www.fdsn.org/webservices>`_).
626 :returns:
627 See description of ``parsed`` argument above.
628 '''
630 service = 'event'
632 if user or token:
633 method = 'queryauth'
634 else:
635 method = 'query'
637 if token is not None:
638 user, passwd = get_auth_credentials(
639 service, site, url, majorversion, token, timeout)
641 if check:
642 check_params(service, site, url, majorversion, timeout, **kwargs)
644 params = fix_params(kwargs)
646 url = fillurl(service, site, url, majorversion, method)
648 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params)
649 if parsed:
650 from pyrocko.io import quakeml
651 format = kwargs.get('format', 'xml')
652 if format != 'xml':
653 raise InvalidRequest(
654 'If parsed=True is selected, format="xml" must be selected.')
656 return quakeml.QuakeML.load_xml(stream=fh)
658 else:
659 return fh
662def availability(
663 method='query',
664 site=g_default_site,
665 url=g_url,
666 majorversion=1,
667 timeout=None,
668 check=True,
669 user=None,
670 passwd=None,
671 token=None,
672 selection=None,
673 **kwargs):
675 '''
676 Query FDSN web service for time series data availablity.
678 :param method: Availablility method to call: ``'query'``, or ``'extent'``.
679 :param site:
680 :ref:`Registered site name <registered-site-names>` or full base URL of
681 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
682 :type site: str
683 :param url:
684 URL template (default should work in 99% of cases).
685 :type url: str
686 :param majorversion:
687 Major version of the service to query (always ``1`` at the time of
688 writing).
689 :type majorversion: int
690 :param timeout:
691 Network timeout in [s]. Global default timeout can be configured in
692 Pyrocko's configuration file under ``fdsn_timeout``.
693 :type timeout: float
694 :param check:
695 If ``True`` arguments are checked against self-description (WADL) of
696 the queried web service if available or FDSN specification.
697 :type check: bool
698 :param user: User name for user/password authentication.
699 :type user: str
700 :param passwd: Password for user/password authentication.
701 :type passwd: str
702 :param token: Token for `token authentication
703 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_.
704 :type token: str
705 :param selection:
706 If given, selection to be queried as a list of tuples
707 ``(network, station, location, channel, tmin, tmax)``.
708 :type selection: :py:class:`list` of :py:class:`tuple`
709 :param \\*\\*kwargs:
710 Parameters passed to the server (see `FDSN web services specification
711 <https://www.fdsn.org/webservices>`_).
713 :returns:
714 Open file-like object providing raw response.
715 '''
717 service = 'availability'
719 assert method in ('query', 'extent')
721 if user or token:
722 method += 'auth'
724 if token is not None:
725 user, passwd = get_auth_credentials(
726 service, site, url, majorversion, token, timeout)
728 if check:
729 check_params(service, site, url, majorversion, timeout, **kwargs)
731 params = fix_params(kwargs)
733 url = fillurl(service, site, url, majorversion, method)
734 if selection:
735 lst = []
737 for k, v in params.items():
738 lst.append('%s=%s' % (k, v))
740 for (network, station, location, channel, tmin, tmax) in selection:
741 if location == '':
742 location = '--'
744 lst.append(' '.join((network, station, location, channel,
745 sdatetime(tmin), sdatetime(tmax))))
747 post = '\n'.join(lst)
748 return _request(
749 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout)
750 else:
751 return _request(
752 url, user=user, passwd=passwd, timeout=timeout, **params)
755def check_params(
756 service,
757 site=g_default_site,
758 url=g_url,
759 majorversion=1,
760 timeout=None,
761 method='query',
762 **kwargs):
764 '''
765 Check query parameters against self-description of web service.
767 Downloads WADL description of the given service and site and checks
768 parameters if they are available. Queried WADLs are cached in memory.
770 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
771 ``'availability'``.
772 :param site:
773 :ref:`Registered site name <registered-site-names>` or full base URL of
774 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
775 :type site: str
776 :param url:
777 URL template (default should work in 99% of cases).
778 :type url: str
779 :param majorversion:
780 Major version of the service to query (always ``1`` at the time of
781 writing).
782 :type majorversion: int
783 :param timeout:
784 Network timeout in [s]. Global default timeout can be configured in
785 Pyrocko's configuration file under ``fdsn_timeout``.
786 :type timeout: float
787 :param \\*\\*kwargs:
788 Parameters that would be passed to the server (see `FDSN web services
789 specification <https://www.fdsn.org/webservices>`_).
791 :raises: :py:exc:`ValueError` is raised if unsupported parameters are
792 encountered.
793 '''
795 avail = supported_params_wadl(
796 service, site, url, majorversion, timeout, method)
798 unavail = sorted(set(kwargs.keys()) - avail)
799 if unavail:
800 raise ValueError(
801 'Unsupported parameter%s for service "%s" at site "%s": %s' % (
802 '' if len(unavail) == 1 else 's',
803 service,
804 site,
805 ', '.join(unavail)))
808def supported_params_wadl(
809 service,
810 site=g_default_site,
811 url=g_url,
812 majorversion=1,
813 timeout=None,
814 method='query'):
816 '''
817 Get query parameter names supported by a given FDSN site and service.
819 If no WADL is provided by the queried service, default parameter sets from
820 the FDSN standard are returned. Queried WADLs are cached in memory.
822 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
823 ``'availability'``.
824 :param site:
825 :ref:`Registered site name <registered-site-names>` or full base URL of
826 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
827 :type site: str
828 :param url:
829 URL template (default should work in 99% of cases).
830 :type url: str
831 :param majorversion:
832 Major version of the service to query (always ``1`` at the time of
833 writing).
834 :type majorversion: int
835 :param timeout:
836 Network timeout in [s]. Global default timeout can be configured in
837 Pyrocko's configuration file under ``fdsn_timeout``.
838 :type timeout: float
840 :returns: Supported parameter names.
841 :rtype: :py:class:`set` of :py:class:`str`
842 '''
844 wadl = cached_wadl(service, site, url, majorversion, timeout)
846 if wadl:
847 url = fillurl(service, site, url, majorversion, method)
848 return set(wadl.supported_param_names(url))
849 else:
850 return g_default_query_args[service]
853def patch_geonet_wadl(wadl):
854 for r in wadl.resources_list:
855 r.base = r.base.replace('1/station', 'station/1')
856 r.base = r.base.replace('1/dataselect', 'dataselect/1')
857 r.base = r.base.replace('1/event', 'event/1')
860def wadl(
861 service,
862 site=g_default_site,
863 url=g_url,
864 majorversion=1,
865 timeout=None):
867 '''
868 Retrieve self-description of a specific FDSN service.
870 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or
871 ``'availability'``.
872 :param site:
873 :ref:`Registered site name <registered-site-names>` or full base URL of
874 the service (e.g. ``'https://geofon.gfz-potsdam.de'``).
875 :type site: str
876 :param url:
877 URL template (default should work in 99% of cases).
878 :type url: str
879 :param majorversion:
880 Major version of the service to query (always ``1`` at the time of
881 writing).
882 :type majorversion: int
883 :param timeout:
884 Network timeout in [s]. Global default timeout can be configured in
885 Pyrocko's configuration file under ``fdsn_timeout``.
886 :type timeout: float
887 '''
889 from pyrocko.client.wadl import load_xml
891 url = fillurl(service, site, url, majorversion, 'application.wadl')
893 wadl = load_xml(stream=_request(url, timeout=timeout))
895 if site == 'geonet' or site.find('geonet.org.nz') != -1:
896 patch_geonet_wadl(wadl)
898 return wadl
901g_wadls = {}
904def cached_wadl(
905 service,
906 site=g_default_site,
907 url=g_url,
908 majorversion=1,
909 timeout=None):
911 '''
912 Get self-description of a specific FDSN service.
914 Same as :py:func:`wadl`, but results are cached in memory.
915 '''
917 k = (service, site, url, majorversion)
918 if k not in g_wadls:
919 try:
920 g_wadls[k] = wadl(service, site, url, majorversion, timeout)
922 except Timeout:
923 raise
925 except DownloadError:
926 logger.info(
927 'No service description (WADL) found for "%s" at site "%s".'
928 % (service, site))
930 g_wadls[k] = None
932 return g_wadls[k]
935__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ')
938if __name__ == '__main__':
939 import sys
941 util.setup_logging('pyrocko.client.fdsn', 'info')
943 if len(sys.argv) == 1:
944 sites = get_sites()
945 else:
946 sites = sys.argv[1:]
948 for site in sites:
949 print('=== %s (%s) ===' % (site, g_site_abbr[site]))
951 for service in ['station', 'dataselect', 'event']:
952 try:
953 app = wadl(service, site=site, timeout=2.0)
954 print(indent(str(app)))
956 except Timeout as e:
957 logger.error(str(e))
958 print('%s: timeout' % (service,))
960 except util.DownloadError as e:
961 logger.error(str(e))
962 print('%s: no wadl' % (service,))