Source code for pyvo.mivot.viewer.mivot_instance

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
MivotInstance is the root of the Python generated classes.
Instances of MivotInstance are built from a dictionary issued
from the XML view of the mapped model.
This dictionary is used to extend the object with all components
(classes, attributes, collections) necessary to reproduce the structure
of the mapped model.
Instances of this class are built by `pyvo.mivot.viewer.mivot_viewer`.
Although attribute values can be changed by users, this class is first
meant to provide a convenient access the mapped VOTable data
"""
from pyvo.mivot.utils.vocabulary import Constant
from pyvo.utils.prototype import prototype_feature
from pyvo.mivot.utils.mivot_utils import MivotUtils
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder


# list of model leaf parameters that must be hidden for the final user
hk_parameters = ["ref"]


[docs] @prototype_feature('MIVOT') class MivotInstance: """ MivotInstance holds the dictionary (__dict__) similar with the mapped model structure where the references have been resolved. The dictionary keeps the hierarchy of the XML : "key" : {not a leaf} means key is the dmtype of an INSTANCE "key" : {leaf} means key is the dmrole of an ATTRIBUTE "key" : "value" means key is an element of ATTRIBUTE "key" : [] means key is the dmtype of a COLLECTION """ def __init__(self, **instance_dict): """ Constructor of the MIVOT class. Parameters ---------- kwargs (dict): Dictionary of the XML object. """ self._create_class(**instance_dict) def __repr__(self): """ return a human readable (json) representation of object """ return DictUtils._get_pretty_json(self.to_dict())
[docs] def to_hk_dict(self): """ return a human readable (dict) representation of object with a few housekeeping data such as column references. This might be used to apply the mapping out of the MivotViewer context """ return self._get_class_dict(self, slim=False)
[docs] def to_dict(self): """ return a human readable (dict) representation of object """ return self._get_class_dict(self, slim=True)
def _create_class(self, **kwargs): """ Recursively initialize the MIVOT class with the dictionary of the XML object got in MivotViewer. For the unit of the ATTRIBUTE, we add the Astropy unit or the Astropy time equivalence by comparing the value of the unit with values in time.TIME_FORMATS.keys() which is the list of time formats. We do the same with the unit_mapping dictionary, which is the list of Astropy units. Parameters ---------- kwargs (dict): Dictionary of the XML object. """ for key, value in kwargs.items(): # roles are used as key and the first element in a TEMPLATE has no role if not key: key = Constant.ROOT_OBJECT if isinstance(value, list): # COLLECTION setattr(self, self._remove_model_name(key), []) for item in value: getattr(self, self._remove_model_name(key)).append(MivotInstance(**item)) elif isinstance(value, dict): # INSTANCE if not self._is_leaf(**value): setattr(self, self._remove_model_name(key), MivotInstance(**value)) if self._is_leaf(**value): setattr(self, self._remove_model_name(key), MivotInstance(**value)) else: # ATTRIBUTE if key == 'value': # We cast the value read in the row setattr(self, self._remove_model_name(key), MivotUtils.cast_type_value(value, getattr(self, 'dmtype'))) elif key not in ["dmtype", "dmrole"]: setattr(self, self._remove_model_name(key), self._remove_model_name(value)) else: setattr(self, self._remove_model_name(key), value) if key == 'unit': # We convert the unit to astropy unit or to astropy time format if possible # The first Vizier implementation used mas/year for the mapped pm unit: let's correct it value = value.replace("year", "yr") if value else None
[docs] def update(self, row, ref=None): """ Update the MIVOT class with the new data row. For each leaf of the MIVOT class, we update the value with the new data row. Parameters ---------- row (astropy.table.row.Row): The new data row. ref (str, optional):The reference of the data row, default is None. """ for key, value in vars(self).items(): if isinstance(value, list): for item in value: item.update(row=row) elif isinstance(value, MivotInstance): if isinstance(vars(value), dict): if 'value' not in vars(value): value.update(row=row) if 'ref' in vars(value): value.update(row=row, ref=getattr(value, 'ref')) else: if key == 'value' and ref is not None and ref != 'null': setattr(self, self._remove_model_name(key), MivotUtils.cast_type_value(row[ref], getattr(self, 'dmtype')))
[docs] def get_SkyCoord(self): """ returns ------- - a SkyCoord instance or None """ return SkyCoordBuilder(self.to_dict()).build_sky_coord()
@staticmethod def _remove_model_name(value): """ Return the last element of a model path built like model:a.b.c Parameters ---------- value (str): The string to process. """ if value: next_index_underscore = value.rfind(".") return value[next_index_underscore + 1:] return value def _is_leaf(self, **kwargs): """ Check if the dictionary is an ATTRIBUTE. Parameters ---------- **kwargs (dict): The dictionary to check. Returns ------- bool: True if the dictionary is an ATTRIBUTE, False otherwise. """ if isinstance(kwargs, dict): for _, value in kwargs.items(): if isinstance(value, dict): return False return True def _get_class_dict(self, obj, classkey=None, slim=False, with_dmtypes=True): """ Recursively displays a serializable dictionary. This function is only used for debugging purposes. Parameters ---------- obj (dict or object): The dictionary or object to display. classkey (str, optional): The key to use for the object's class name in the dictionary, default is None. slim (bool, optional): if true, only @values and @units (if not empty) are attached to model leaves. @dmtype and @ref attributes are ignored with_dmtypes (boolean, optional) : if true dmtypes are added to the primitive types (model leaves) Returns ------- dict or object The serializable dictionary representation of the input. """ if isinstance(obj, dict): data = {} for (k, v) in obj.items(): data[k] = self._get_class_dict(v, classkey, slim=slim) return data elif hasattr(obj, "_ast"): return self._get_class_dict(obj._ast()) elif hasattr(obj, "__iter__") and not isinstance(obj, str): return [self._get_class_dict(v, classkey, slim=slim) for v in obj] elif hasattr(obj, "__dict__"): data = {key: obj._get_class_dict(value, classkey, slim=slim) for key, value in obj.__dict__.items() if not callable(value) and not key.startswith('_')} # remove the house keeping parameters if slim is True: # data is atomic value (e.g. float): the type be hidden if with_dmtypes is False and ("ref" in data or "value" in data): data.pop("dmtype", None) # remove unit when not set if "unit" in data and not data["unit"]: data.pop("unit", None) for hk_parameter in hk_parameters: data.pop(hk_parameter, None) if classkey is not None and hasattr(obj, "__class__"): data[classkey] = obj.__class__.__name__ return data else: return obj