Source code for simkit.contrib.readers

"""
Custom data readers including :class:`simkit.contrib.readers.ArgumentReader`,
:class:`simkit.contrib.readers.DjangoModelReader` and
:class:`simkit.contrib.readers.HDF5Reader`.
"""

from __future__ import (
    absolute_import, division, print_function, unicode_literals)
import numpy as np
import h5py
from simkit.core.data_readers import DataReader
from simkit.core.data_sources import DataParameter
from simkit.core import Q_
import logging

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)


def copy_model_instance(obj):
    """
    Copy Django model instance as a dictionary excluding automatically created
    fields like an auto-generated sequence as a primary key or an auto-created
    many-to-one reverse relation.

    :param obj: Django model object
    :return: copy of model instance as dictionary
    """
    meta = getattr(obj, '_meta')  # make pycharm happy
    # dictionary of model values excluding auto created and related fields
    return {f.name: getattr(obj, f.name)
            for f in meta.get_fields(include_parents=False)
            if not f.auto_created}


# TODO: make parameters consistent for all readers
# TODO: parameters set by attributes in data source model fields
# EG: ghi = FloatField('GHI', units='W/m**2')
# EG: solar_azimuth = FloatField('solar azimuth', units='degrees')
# TODO: some parameters set in class Meta
# EG: class Meta: args = ['GHI', 'azimuth']

[docs]class ArgumentReader(DataReader): """ Read arguments passed directly to a simulation. The argument parameters dictionary should have two keys: `args` and `kwargs` which consist of the names and attributes of the positional and keyword arguments respectively. For example:: { 'GHI': {'units': 'W/m**2', 'isconstant': False, 'argpos': 0}, 'azimuth': {'units': 'degrees', 'isconstant': False, 'argpos': 1}, 'DNI': {'units': 'W/m**2', 'isconstant': False}, 'zenith': {'units': 'degrees', 'isconstant': False} } """ #: True if reader accepts ``filename`` argument is_file_reader = False # not a file reader
[docs] def load_data(self, *args, **kwargs): """ Collects positional and keyword arguments into `data` and applies units. :return: data """ # get positional argument names from parameters and apply them to args # update data with additional kwargs argpos = { v['extras']['argpos']: k for k, v in self.parameters.iteritems() if 'argpos' in v['extras'] } data = dict( {argpos[n]: a for n, a in enumerate(args)}, **kwargs ) return self.apply_units_to_cache(data)
[docs] def apply_units_to_cache(self, data): """ Applies units to data when a proxy reader is used. For example if the data is cached as JSON and retrieved using the :class:`~simkit.core.data_readers.JSONReader`, then units can be applied from the original parameter schema. :param data: Data read by proxy reader. :return: data with units applied """ # if units key exists then apply for k, v in self.parameters.iteritems(): if v and v.get('units'): data[k] = Q_(data[k], v.get('units')) return data
[docs]class DjangoModelReader(ArgumentReader): """ Reads arguments that are Django objects or lists of objects. """ def __init__(self, parameters=None, meta=None): #: Django model self.model = meta.model model_meta = getattr(self.model, '_meta') # make pycharm happy # model fields excluding AutoFields and related fields like one-to-many all_model_fields = [ f for f in model_meta.get_fields(include_parents=False) if not f.auto_created ] all_field_names = [f.name for f in all_model_fields] # field names # use all fields if no parameters given if parameters is None: parameters = DataParameter.fromkeys( all_field_names, {} ) fields = getattr(meta, 'fields', all_field_names) # specified fields LOGGER.debug('fields:\n%r', fields) exclude = getattr(meta, 'exclude', []) # specifically excluded fields for f in all_model_fields: # skip any fields not specified in data source if f.name not in fields or f.name in exclude: LOGGER.debug('skipping %s', f.name) continue # add field to parameters or update parameters with field type param_dict = {'ftype': f.get_internal_type()} if f.name in parameters: parameters[f.name]['extras'].update(param_dict) else: parameters[f.name] = DataParameter(**param_dict) super(DjangoModelReader, self).__init__(parameters, meta)
[docs] def load_data(self, model_instance, *args, **kwargs): """ Apply units to model. :return: data """ model_dict = copy_model_instance(model_instance) return super(DjangoModelReader, self).load_data(**model_dict)
[docs]class HDF5Reader(ArgumentReader): """ Reads data from an HDF5 file """ #: True if reader accepts ``filename`` argument is_file_reader = True # is a file reader
[docs] def load_data(self, h5file, *args, **kwargs): with h5py.File(h5file) as h5f: h5data = dict.fromkeys(self.parameters) for param, attrs in self.parameters.iteritems(): LOGGER.debug('parameter:\n%r', param) node = attrs['extras']['node'] # full name of node # composite datatype member member = attrs['extras'].get('member') if member is not None: # if node is a table then get column/field/description h5data[param] = np.asarray(h5f[node][member]) # copy member else: h5data[param] = np.asarray(h5f[node]) # copy array return super(HDF5Reader, self).load_data(**h5data)