diff --git a/geomagio/webservice/data.py b/geomagio/webservice/data.py index cc47221f6a072fb17c484ce3eb4a4616d13a9e8b..9dc1a5d0673aa9b52bfd04d415d4f42c20c53268 100644 --- a/geomagio/webservice/data.py +++ b/geomagio/webservice/data.py @@ -1,36 +1,183 @@ from datetime import datetime +import os from flask import Blueprint, Flask, jsonify, render_template, request, Response from obspy import UTCDateTime +from collections import OrderedDict +from json import dumps + + from geomagio.edge import EdgeFactory from geomagio.iaga2002 import IAGA2002Writer from geomagio.imfjson import IMFJSONWriter -blueprint = Blueprint('data', __name__) - +DEFAULT_DATA_TYPE = "variation" +DEFAULT_ELEMENTS = ("X", "Y", "Z", "F") +DEFAULT_OUTPUT_FORMAT = "iaga2002" +DEFAULT_SAMPLING_PERIOD = "60" +ERROR_CODE_MESSAGES = { + 204: "No Data", + 400: "Bad Request", + 404: "Not Found", + 409: "Conflict", + 500: "Internal Server Error", + 501: "Not Implemented", + 503: "Service Unavailable", +} +VALID_DATA_TYPES = ["variation", "adjusted", "quasi-definitive", "definitive"] +VALID_OUTPUT_FORMATS = ["iaga2002", "json"] +VALID_SAMPLING_PERIODS = ["1", "60"] + +blueprint = Blueprint("data", __name__) +factory = EdgeFactory( + host='cwbpub.cr.usgs.gov', + port=2060, + write_port=7981) def init_app(app: Flask): global blueprint + global factory app.register_blueprint(blueprint) -@blueprint.route('/data', methods=['GET']) +class WebServiceQuery(object): + """Query parameters for a web service request. + Parameters + ---------- + observatory_id : str + observatory + starttime : obspy.core.UTCDateTime + time of first requested sample + endtime : obspy.core.UTCDateTime + time of last requested sample + elements : array_like + list of requested elements + sampling_period : int + period between samples in seconds + default 60. + data_type : {'variation', 'adjusted', 'quasi-definitive', 'definitive'} + data type + default 'variation'. + output_format : {'iaga2002', 'json'} + output format. + default 'iaga2002'. + """ + + def __init__( + self, + observatory_id=None, + starttime=None, + endtime=None, + elements=None, + sampling_period=60, + data_type="variation", + output_format="iaga2002", + ): + self.observatory_id = observatory_id + self.starttime = starttime + self.endtime = endtime + self.elements = elements + self.sampling_period = sampling_period + self.data_type = data_type + self.output_format = output_format + + def _verify_parameters(self): + """Verify that parameters are valid. + Raises + ------ + WebServiceException + if any parameters are not supported. + """ + if len(self.elements) > 4 and self.output_format == "iaga2002": + raise WebServiceException( + "No more than four elements allowed for iaga2002 format." + ) + if self.starttime > self.endtime: + raise WebServiceException("Starttime must be before endtime.") + if self.data_type not in VALID_DATA_TYPES: + raise WebServiceException( + 'Bad type value "%s".' + " Valid values are: %s" % (self.data_type, VALID_DATA_TYPES) + ) + if self.sampling_period not in VALID_SAMPLING_PERIODS: + raise WebServiceException( + 'Bad sampling_period value "%s".' + " Valid values are: %s" % (self.sampling_period, VALID_SAMPLING_PERIODS) + ) + if self.output_format not in VALID_OUTPUT_FORMATS: + raise WebServiceException( + 'Bad format value "%s".' + " Valid values are: %s" % (self.output_format, VALID_OUTPUT_FORMATS) + ) + + +@blueprint.route("/data", methods=["GET"]) def get_data(): query_params = request.args url = request.url if not query_params: + return render_template("usage.html") + + parsed_query = parse_query(query_params) - return render_template('usage.html') + try: + parsed_query._verify_parameters() + except Exception as e: + message = str(e) + error_body = error(400, message, parsed_query, url) + return error_body - query = parse_query(query_params) + timeseries = get_timeseries(parsed_query) - timeseries = get_timeseries(query) + return format_timeseries(timeseries, parsed_query) - return format_timeseries(timeseries, query) +def error(code, message, query, url): + error_body = http_error(code, message, query, url) + status = str(code) + ' ' + ERROR_CODE_MESSAGES[code] + Response(error_body, mimetype="text/plain") + + return error_body + +def http_error(code, message, query, url): + if query.output_format == 'json': + http_error_body = json_error(code, message, url) + return http_error_body + else: + http_error_body = iaga2002_error(code, message) + return http_error_body + +def json_error(code, message, url): + error_dict = OrderedDict() + error_dict['type'] = "Error" + error_dict['metadata'] = OrderedDict() + error_dict['metadata']['status'] = 400 + date = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + error_dict['metadata']['generated'] = date + error_dict['metadata']['url'] = url + status_message = ERROR_CODE_MESSAGES[code] + error_dict['metadata']['title'] = status_message + error_dict['metadata']['error'] = message + error_body = dumps(error_dict, + ensure_ascii=True).encode('utf8') + + return error_body + +def iaga2002_error(code, message, url): + status_message = ERROR_CODE_MESSAGES[code] + error_body = 'Error ' + str(code) + ': ' + status_message + \ + '\n\n' + message + '\n\n' + \ + 'Usage details are available from ' + \ + 'http://geomag.usgs.gov/ws/edge/ \n\n' + \ + 'Request:\n' + \ + url + '\n\n' + \ + 'Request Submitted:\n' + \ + datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + '\n\n' + + return error_body def parse_query(query): """Parse request arguments into a set of parameters @@ -42,121 +189,85 @@ def parse_query(query): Returns ------- - params: dictionary - query parameters dictionary + WebServiceQuery + parsed query object Raises ------ WebServiceException if any parameters are not supported. """ - params = {} - # Get end time - if not query.get('endtime'): + # Get values + if not query.get("endtime"): now = datetime.now() - today = UTCDateTime( - now.year, - now.month, - now.day, - 0) + today = UTCDateTime(now.year, now.month, now.day, 0) end_time = today - params['End Time'] = end_time else: - params['End Time'] = UTCDateTime(query.get('endtime')) + end_time = UTCDateTime(query.get("endtime")) - # Get start time - if not query.get('starttime'): - start_time = UTCDateTime(params['End Time']) - (24 * 60 * 60 - 1) - params['Start Time'] = UTCDateTime(start_time) + if not query.get("starttime"): + start_time = UTCDateTime(query.get("endtime")) - (24 * 60 * 60 - 1) else: - params['Start Time'] = UTCDateTime(query.get('starttime')) + start_time = UTCDateTime(query.get("starttime")) - # Get sampling period - params['Sampling Period'] = query.get('sampling_period') + if not query.get("sampling_period"): + sampling_period = DEFAULT_SAMPLING_PERIOD + else: + sampling_period = query.get("sampling_period") - if params['Sampling Period'] == '1': - params['Sampling Period'] = 'second' + if not query.get("format"): + format = DEFAULT_OUTPUT_FORMAT + else: + format = query.get("format") - if params['Sampling Period'] == '60': - params['Sampling Period'] = 'minute' + observatory = query.get("observatory") - # Get format - if query.get('format'): - params['Format'] = query.get('format') + if not query.get("channels"): + channels = DEFAULT_ELEMENTS else: - params['Format'] = 'iaga2002' - - # Get observatory - params['Observatory'] = query.get('observatory') + channels = query.get("channels").split(",") - # Get channels - channels = query.get('channels').split(',') - params['Channels'] = channels + type = query.get("type") - # Get data type - params['Type'] = query.get('type') + params = WebServiceQuery() - validate_parameters(params) + params.observatory_id = observatory + params.starttime = start_time + params.endtime = end_time + params.elements = channels + params.sampling_period = sampling_period + params.data_type = type + params.output_format = format return params -def validate_parameters(params): - """Verify that parameters are valid. - - Parameters - ---------- - params: dict - dictionary of parsed query parameters - - Raises - ------ - WebServiceException - if any parameters are not supported. - """ - valid_data_types = ['variation', 'adjusted', - 'quasi-definitive', 'definitive'] - valid_formats = ['iaga2002', 'json'] - valid_sampling_periods = ['second', 'minute'] - - if len(params['Channels']) > 4 and params['Format'] == 'iaga2002': - raise WebServiceException( - 'No more than four elements allowed for Iaga2002 format.') - - if params['Start Time'] > params['End Time']: - raise WebServiceException('Start time must be before end time.') - - if params['Type'] not in valid_data_types: - raise WebServiceException('Bad data type: ' + params['Type'] + - '. Valid values are: ' + ', '.join(valid_data_types) + '.') - - if params['Sampling Period'] not in valid_sampling_periods: - raise WebServiceException('Bad sampling_period value: ' + params['Sampling Period'] + - '. Valid values are: 1, 60.') - - if params['Format'] not in valid_formats: - raise WebServiceException('Bad format value: ' + params['Format'] + - '. Valid values are: ' + ', '.join(valid_formats)) - - def get_timeseries(query): """ Parameters ---------- - query: dict - dictionary of parsed query parameters + WebServiceQuery + parsed query object Returns ------- obspy.core.Stream timeseries object with requested data """ - factory = EdgeFactory() + if query.sampling_period == "1": + query.sampling_period = "second" + + if query.sampling_period == "60": + query.sampling_period = "minute" timeseries = factory.get_timeseries( - query['Start Time'], query['End Time'], query['Observatory'], query['Channels'], - query['Type'], query['Sampling Period']) + query.starttime, + query.endtime, + query.observatory_id, + query.elements, + query.data_type, + query.sampling_period) return timeseries @@ -169,23 +280,23 @@ def format_timeseries(timeseries, query): obspy.core.Stream timeseries object with requested data - query: dict - dictionary of parsed query parameters + WebServiceQuery + parsed query object Returns ------- unicode IAGA2002 or JSON formatted string. """ - if query['Format'] == 'json': - json_output = IMFJSONWriter.format(timeseries, query['Channels']) + if query.output_format == "json": + json_output = IMFJSONWriter.format(timeseries, query.elements) + json_output = Response(json_output, mimetype="application/json") return json_output else: - iaga_output = IAGA2002Writer.format(timeseries, query['Channels']) - - iaga_output = Response(iaga_output, mimetype="text / plain") + iaga_output = IAGA2002Writer.format(timeseries, query.elements) + iaga_output = Response(iaga_output, mimetype="text/plain") return iaga_output