Commit 1e9cdab2 authored by damb's avatar damb
Browse files

Implement first resource

Start implementation of BoreholeHydraulicDataListResource. Currently,
the only supported output format is JSON.
parent 161cdc59
......@@ -36,7 +36,7 @@ class Borehole(CreationInfoMixin('CreationInfo',
*Quantities* are implemented as `QuakeML
<https://quake.ethz.ch/quakeml>`_ quantities.
"""
sections = relationship("BoreholeSection", back_populates="borehole")
_sections = relationship("BoreholeSection", back_populates="_borehole")
class BoreholeSection(EpochMixin('Epoch', epoch_type='open',
......@@ -67,9 +67,9 @@ class BoreholeSection(EpochMixin('Epoch', epoch_type='open',
m_description = Column(String)
m_borehole_oid = Column(Integer, ForeignKey('borehole._oid'))
borehole = relationship("Borehole", back_populates="sections")
_borehole = relationship("Borehole", back_populates="_sections")
hydraulics = relationship("HydraulicSample", back_populates="")
_hydraulics = relationship("HydraulicSample", back_populates="_section")
class HydraulicSample(TimeQuantityMixin('m_datetime'),
......@@ -96,5 +96,4 @@ class HydraulicSample(TimeQuantityMixin('m_datetime'),
m_fluidcomposition = Column(String)
m_boreholesection_oid = Column(Integer, ForeignKey('boreholesection._oid'))
boreholesection = relationship("BoreholeSection",
back_populates="hydraulics")
_section = relationship("BoreholeSection", back_populates="_hydraulics")
......@@ -11,6 +11,8 @@ import traceback
import marshmallow as ma
from flask import make_response as _make_response
from hydws import __version__
from hydws.server.errors import FDSNHTTPError
......@@ -123,3 +125,18 @@ def decode_publicid(s):
return base64.b64decode(s).decode("utf-8")
except Exception:
raise FDSNHTTPError.create(400, service_version=__version__)
def make_response(obj, mimetype):
"""
Return response for :code:`output` and code:`mimetype`.
:param obj: Object the response is created from
:param str mimetype: Response mimetype
:returns: Flask response
:rtype: :py:class:`werkzeug.wrappers.Response`
"""
response = _make_response(obj)
response.headers['Content-Type'] = mimetype
return response
......@@ -16,6 +16,12 @@ FDSN_DEFAULT_NO_CONTENT_ERROR_CODE = 204
FDSN_NO_CONTENT_CODES = (FDSN_DEFAULT_NO_CONTENT_ERROR_CODE, 404)
HYDWS_SERVICE_DOCUMENTATION_URI = 'http://URL/to/hydws/docs/'
HYDWS_DEFAULT_OFORMAT = 'json'
HYDWS_OFORMATS = (HYDWS_DEFAULT_OFORMAT, 'xml')
HYDWS_DEFAULT_LEVEL = 'section'
HYDWS_LEVELS = (HYDWS_DEFAULT_LEVEL, 'borehole')
MIMETYPE_JSON = 'application/json'
MIMETYPE_TEXT = 'text/plain'
ERROR_MIMETYPE = MIMETYPE_TEXT
......
"""
HYDWS datamodel ORM entity de-/serialization facilities.
"""
import datetime
from marshmallow import Schema, fields
from marshmallow.utils import get_value
_ATTR_PREFIX = 'm_'
# XXX(damb): Currently, there are no validation facilities implemented.
#XXX(damb): Finish off docstrings
class QuakeMLQuantityField(fields.Field):
"""
`QuakeML <https://quake.ethz.ch/quakeml/>`_ quantity field type
implementation.
The field creates a nested dict consisting of a base value and
a number of uncertainty measures which are composed from the flat
structure that is stored in the object.
"""
_CHECK_ATTRIBUTE = False # We generate the attribute dynamically
ATTRS = ('value', 'uncertainty', 'loweruncertainty', 'upperuncertainty',
'confidencelevel')
def _serialize(self, value, attr, obj, **kwargs):
retval = {}
for _attr in self.ATTRS:
key = f"{_ATTR_PREFIX}{attr}_{_attr}".lower()
value = get_value(obj, key, default=None)
if isinstance(value, datetime.datetime):
retval[_attr] = value.isoformat()
elif value is not None:
retval[_attr] = value
return retval or None
class SchemaBase(Schema):
"""
Schema base class for object de-/serialization.
"""
publicid = fields.String()
def get_attribute(self, obj, key, default):
"""
Custom accessor method extracting values from objects applying the
:code:`m_` prefix to attribute keys.
"""
if not key.startswith(_ATTR_PREFIX) and not key.startswith('_'):
key = _ATTR_PREFIX + key.lower()
return get_value(obj, key, default)
class HydraulicSampleSchema(SchemaBase):
datetime = QuakeMLQuantityField()
downtemperature = QuakeMLQuantityField()
downflow = QuakeMLQuantityField()
downpressure = QuakeMLQuantityField()
toptemperature = QuakeMLQuantityField()
topflow = QuakeMLQuantityField()
toppressure = QuakeMLQuantityField()
fuiddensity = QuakeMLQuantityField()
fluidviscosity = QuakeMLQuantityField()
fluidph = QuakeMLQuantityField()
fluidcomposition = fields.String()
class BoreholeSectionSchema(SchemaBase):
starttime = fields.DateTime(format='iso')
endtime = fields.DateTime(format='iso')
toplongitude = QuakeMLQuantityField()
toplatitude = QuakeMLQuantityField()
topdepth = QuakeMLQuantityField()
bottomlongitude = QuakeMLQuantityField()
bottomlatitude = QuakeMLQuantityField()
bottomdepth = QuakeMLQuantityField()
holediameter = QuakeMLQuantityField()
casingdiameter = QuakeMLQuantityField()
topclosed = fields.Boolean(allow_none=True, missing=None)
bottomclosed = fields.Boolean()
sectiontype = fields.String()
casingtype = fields.String()
description = fields.String()
hydraulics = fields.Nested(HydraulicSampleSchema, many=True,
attribute='_hydraulics')
class BoreholeSchema(SchemaBase):
# TODO(damb): Provide a hierarchical implementation of sub_types; create
# them dynamically (see: e.g. QuakeMLQuantityField)
longitude = QuakeMLQuantityField()
latitude = QuakeMLQuantityField()
depth = QuakeMLQuantityField()
bedrockdepth = QuakeMLQuantityField()
sections = fields.Nested(BoreholeSectionSchema, many=True,
attribute='_sections')
......@@ -5,12 +5,17 @@ HYDWS resources.
import logging
from flask_restful import Api, Resource
from sqlalchemy.orm.exc import NoResultFound
from webargs.flaskparser import use_kwargs
from hydws import __version__
from hydws.db import orm
from hydws.server import db, settings
from hydws.server.misc import with_fdsnws_exception_handling, decode_publicid
from hydws.server.errors import FDSNHTTPError
from hydws.server.misc import (with_fdsnws_exception_handling, decode_publicid,
make_response)
from hydws.server.v1 import blueprint
from hydws.server.v1.ostream.schema import BoreholeSchema
from hydws.server.v1.parser import BoreholeHydraulicDataListResourceSchema
......@@ -28,7 +33,14 @@ class ResourceBase(Resource):
def get(self):
raise NotImplementedError
def _process(self, **kwargs):
def _handle_nodata(self, kwargs):
raise FDSNHTTPError.create(
int(kwargs.get(
'nodata',
settings.FDSN_DEFAULT_NO_CONTENT_ERROR_CODE)))
def _process_request(self, borehole_id=None, section_id=None,
**query_params):
raise NotImplementedError
......@@ -44,18 +56,6 @@ class BoreholeResource(ResourceBase):
pass
class BoreholeSectionListResource(ResourceBase):
def get(self, borehole_id):
pass
class BoreholeSectionResource(ResourceBase):
def get(self, borehole_id, section_id):
pass
class BoreholeHydraulicDataListResource(ResourceBase):
LOGGER = 'hydws.server.v1.boreholehydraulicdatalistresource'
......@@ -63,17 +63,47 @@ class BoreholeHydraulicDataListResource(ResourceBase):
@with_fdsnws_exception_handling(__version__)
@use_kwargs(BoreholeHydraulicDataListResourceSchema(),
locations=("query", ))
def get(self, borehole_id, **kwargs):
def get(self, borehole_id, **query_params):
borehole_id = decode_publicid(borehole_id)
self.logger.debug(
f"Received request: borehole_id={borehole_id}, kwargs={kwargs}")
f"Received request: borehole_id={borehole_id}, "
f"query_params={query_params}")
resp = self._process_request(db.session, borehole_id=borehole_id,
**query_params)
if not resp:
self._handle_nodata(query_params)
# TODO(damb): Serialize according to query_param format=JSON|XML
# format response
try:
resp = BoreholeSchema().dumps(resp)
except Exception:
raise FDSNHTTPError.create(500, service_version=__version__)
return self._process(db.session, **kwargs)
return make_response(resp, settings.MIMETYPE_JSON)
def _process_request(self, session, borehole_id=None, section_id=None,
**query_params):
if not borehole_id:
raise ValueError(f"Invalid borehole identifier: {borehole_id!r}")
# TODO(damb): Add additional filter criteria
try:
bh = session.query(orm.Borehole).\
join(orm.BoreholeSection).\
filter(orm.Borehole.m_publicid==borehole_id).\
one()
except NoResultFound:
return None
def _process(self, session, **kwargs):
# TODO TODO TODO
return {"bh": 'BOREHOLEID'}
# A borehole at least must have a single borehole-section
return bh
class SectionHydraulicDataListResource(ResourceBase):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment