Commit d3191b6a authored by M. Huang's avatar M. Huang
Browse files

MetaData Parameter; optimize product init

parent 75de515c
__version_info__ = (1, 5, 2)
__version_info__ = (1, 5, 3)
__version__ = '.'.join(map(str, __version_info__))
# -*- coding: utf-8 -*-
from .metadataholder import MetaDataHolder
from .metadata import AbstractParameter, Parameter, NumericParameter, StringParameter, DateParameter
from .datatypes import DataTypes
import logging
# create logger
logger = logging.getLogger(__name__)
#logger.debug('level %d' % (logger.getEffectiveLevel()))
MdpInfo = {}
def addMetaDataProperty(cls):
"""mh: Add MDP to a class so that although they are metadata,
they can be accessed by for example, productfoo.creator.
dynamic properties see
https://stackoverflow.com/a/2584050
https://stackoverflow.com/a/1355444
"""
# MdpInfo is evaluated at class import time
for name in MdpInfo:
def g(self, n=name):
return self._meta[name].getValue()
def s(self, value, n=name):
self.setMDP(n, value, MdpInfo)
def d(self, n=name):
logger.warn('Cannot delete MetaData Property ' + n)
setattr(cls, name, property(
g, s, d, 'MetaData Property ' + name))
return cls
class Attributable(MetaDataHolder):
""" An Attributable object is an object that has the
notion of meta data. """
notion of meta data.
MetaData Porperties (MDPs) are Attributes that store their properties in te metadata table.
"""
def __init__(self, meta=None, **kwds):
# pdb.set_trace()
def __init__(self, meta=None, zInfo=None, **kwds):
""" Pick out arguments listed in MdpInfo then put get updated MdpInfo into MetaData meta.
"""
self.zInfo = zInfo if zInfo else {'metadata': {}}
# the list of arg names of the 'and' set
# andset = set(MdpInfo) & set(kwds)
mdps = dict((x, kwds.pop('typ_' if x == 'type' else x))
for x in self.zInfo['metadata'])
super(Attributable, self).__init__(meta=meta, **kwds)
#print('**' + self._meta.toString())
# if not self.hasMeta()
self.setParameters(mdps)
def setParameters(self, params):
""" Set a group of name-value pairs to MetaData.
params: a dictionary of name:value where value is a subclass of
`AbstractParameter`. value can be the value of a registerws MDP.
``type`` will be used if ``typ_`` is given as the name.
"""
for met, value in params.items():
# typ_ in params (from __init__) changed to type
name = 'type' if met == 'typ_' else met
# set to input if given or to default.
self.__setattr__(name, value)
#print('@@@@', name, value)
@property
def meta(self):
......@@ -27,7 +78,150 @@ class Attributable(MetaDataHolder):
def setMeta(self, newMetadata):
""" Replaces the current MetaData with specified argument.
mh: Product will override this to add listener whenevery meta is
mh: Product will override this to add listener when meta is
replaced
"""
self._meta = newMetadata
def __getattribute__(self, name):
""" Returns the named metadata parameter.
Reads meta data table when Attributes are
read, and returns the values only.
"""
# print('getattribute ' + name)
# print('aa ' + selftr(self.getMeta()[name]))
if name != 'zInfo':
try:
if name in self.zInfo['metadata']:
return self._meta[name].getValue()
except AttributeError:
pass
return super(Attributable, self).__getattribute__(name)
def setMDP(self, name, value, met):
m = self.getMeta()
#print('MDP ', name, value, id(m), len(m))
if name in m:
# meta already has a Parameter for name
p = m[name]
if issubclass(value.__class__, AbstractParameter):
tv, tp = value.getType(), p.getType()
if issubclass(tv, tp):
p = value
return
else:
vs = value.value
raise TypeError(
"Parameter %s type is %s, not %s's %s." % (vs, tv, name, tp))
else:
# value is not a Parameter
v_type = type(value)
p_type = type(p.value)
if issubclass(v_type, p_type):
p.setValue(value)
return
else:
vs = value
raise TypeError(
"Value %s type is %s, not %s's %s." % (vs, v_type.__name__, name, p_type.__name__))
else:
# named parameter is not in zInfo
if issubclass(value.__class__, AbstractParameter):
# value is a parameter
m[name] = value
return
# value is not a Parameter make one.
m[name] = value2parameter(name, value, met)
return
def __setattr__(self, name, value):
""" Stores value to attribute with name given.
If name is in the `zInfo` list, store the value in a Parameter in
metadata container. Updates meta data table. Updates value when
an MDP attribute already has its Parameter in metadata.
value: Must be Parameter/NumericParameter if this is normal metadata,
depending on if it is `Number`. `Value` is the value if the attribute
is an MDP
"""
# print('setattr ' + name, value)
try:
met = self.zInfo['metadata']
if name in met:
# an MDP attribute like 'description'. store in meta
self.setMDP(name, value, met)
# must return without updating self.__dict__
return
except AttributeError:
pass
super(Attributable, self).__setattr__(name, value)
def __delattr__(self, name):
""" Refuses deletion of mandatory attributes.
"""
try:
if name in self.zInfo:
logger.warn('Cannot delete MetaData Property ' + name)
return
except AttributeError:
pass
super(Attributable, self).__delattr__(name)
def value2parameter(name, value, descriptor):
""" returns a parameter with correct type and attributes according to its value and name.
value: type must be compatible with data_type. For example [0, 0] is wrong; Vector2d([0, 0)] is right if ``data_type``==``vector2d``.
descriptor: is zInfo('metadata'] or zInfo['dataset'][xxx]
"""
im = descriptor[name] # {'dats_type':..., 'value':....}
# in ['integer','hex','float','vector','quaternion']
fs = im['default'] if 'default' in im else None
gs = im['valid'] if 'valid' in im else None
if im['data_type'] == 'string':
cs = im['typecode'] if 'typecode' in im else 'B'
ret = StringParameter(value=value,
description=im['description'],
default=fs,
valid=gs,
typecode=cs
)
elif im['data_type'] == 'finetime':
cs = im['typecode'] if 'typecode' in im else None
ret = DateParameter(value=value,
description=im['description'],
default=fs,
valid=gs,
typecode=cs
)
elif DataTypes[im['data_type']] in ['int', 'float', 'Vector', 'Vector2D', 'Quaternion']:
us = im['unit'] if 'unit' in im else ''
cs = im['typecode'] if 'typecode' in im else None
ret = NumericParameter(value=value,
description=im['description'],
typ_=im['data_type'],
unit=us,
default=fs,
valid=gs,
typecode=cs,
)
else:
ret = Parameter(value=value,
description=im['description'],
typ_=im['data_type'],
default=fs,
valid=gs,
)
return ret
This diff is collapsed.
......@@ -91,9 +91,12 @@ class Composite(DeepEqual):
""" Returns the number of key - value mappings in this map. """
return len(self._sets)
def __len__(self):
""" Returns the number of key - value mappings in this map. """
return len(self._sets)
def __repr__(self):
return self.__class__.__name__ + \
self._sets.__repr__()
return self.__class__.__name__ + '(' + (self._sets.__repr__() if hasattr(self, '_sets') else 'None') + ')'
def toString(self, level=0, matprint=None, trans=True, **kwds):
return self.__class__.__name__ + \
......
......@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class MetaDataHolder(object):
""" Object holding meta data.
mh: object for compatibility with python2
"""
def __init__(self, meta=None, **kwds):
......
......@@ -7,6 +7,7 @@ from fdi.dataset.baseproduct import BaseProduct
from fdi.dataset.finetime import FineTime
from fdi.dataset.attributable import addMetaDataProperty
from fdi.dataset.readonlydict import ReadOnlyDict
import copy
......@@ -17,125 +18,6 @@ logger = logging.getLogger(__name__)
# logger.debug('level %d' % (logger.getEffectiveLevel()))
class Product(BaseProduct):
""" Product class (level ALL) schema 1.4 inheriting ['BaseProduct'].
Automatically generated from fdi/dataset/resources/Product.yml on 2021-04-28 14:20:36.064667.
Description:
Project level product
Generally a Product (inheriting BaseProduct) has project-wide attributes and can be extended to define a plethora of specialized products.
"""
def __init__(self,
description = 'UNKNOWN',
typ_ = 'Product',
creator = 'UNKNOWN',
creationDate = FineTime(0),
rootCause = 'UNKNOWN',
version = '0.8',
FORMATV = '1.4.0.8',
startDate = FineTime(0),
endDate = FineTime(0),
instrument = 'UNKNOWN',
modelName = 'UNKNOWN',
mission = '_AGS',
**kwds):
""" Initializes instances with more metadata as attributes, set to default values.
Put description keyword argument here to allow e.g. BaseProduct("foo") and description='foo'
"""
if 'metasToBeInstalled' in kwds:
# This class is being called probably from super() in a subclass
metasToBeInstalled = kwds['metasToBeInstalled']
del kwds['metasToBeInstalled']
# must be the first line to initiate meta and get description
super().__init__(
metasToBeInstalled=metasToBeInstalled, **kwds)
return
# this class is being called directly
# list of local variables.
metasToBeInstalled = copy.copy(locals())
for x in ('self', '__class__', 'kwds'):
metasToBeInstalled.pop(x)
global ProductInfo
self.zInfo = ProductInfo
#print('@1 zInfo', id(self.zInfo['metadata']), id(self), id(self.zInfo),
# self.zInfo['metadata']['version'], list(metasToBeInstalled.keys()))
# must be the first line to initiate meta and get description
super().__init__(
metasToBeInstalled=metasToBeInstalled, **kwds)
super().installMetas(mtbi=metasToBeInstalled, prodInfo=ProductInfo)
#print(self.meta.keySet(), id(self.meta))
@property
def type(self): pass
@type.setter
def type(self, p): pass
@property
def startDate(self): pass
@startDate.setter
def startDate(self, p): pass
@property
def endDate(self): pass
@endDate.setter
def endDate(self, p): pass
@property
def instrument(self): pass
@instrument.setter
def instrument(self, p): pass
@property
def modelName(self): pass
@modelName.setter
def modelName(self, p): pass
@property
def mission(self): pass
@mission.setter
def mission(self, p): pass
@property
def version(self): pass
@version.setter
def version(self, p): pass
@property
def FORMATV(self): pass
@FORMATV.setter
def FORMATV(self, p): pass
_Model_Spec = {
'name': 'Product',
'description': 'Project level product',
......@@ -270,3 +152,52 @@ _Model_Spec = {
}
ProductInfo = ReadOnlyDict(_Model_Spec)
class Product(BaseProduct):
""" Product class (level ALL) schema 1.4 inheriting ['BaseProduct'].
Automatically generated from fdi/dataset/resources/Product.yml on 2021-05-11 07:28:00.149739.
Description:
Project level product
Generally a Product (inheriting BaseProduct) has project-wide attributes and can be extended to define a plethora of specialized products.
"""
def __init__(self,
description = 'UNKNOWN',
typ_ = 'Product',
creator = 'UNKNOWN',
creationDate = FineTime(0),
rootCause = 'UNKNOWN',
version = '0.8',
FORMATV = '1.4.0.8',
startDate = FineTime(0),
endDate = FineTime(0),
instrument = 'UNKNOWN',
modelName = 'UNKNOWN',
mission = '_AGS',
zInfo=None,
**kwds):
""" Initializes instances with more metadata as attributes, set to default values.
Put description keyword argument here to allow e.g. BaseProduct("foo") and description='foo'
"""
# collect MDPs from args-turned-local-variables.
metasToBeInstalled = copy.copy(locals())
for x in ('self', '__class__', 'zInfo', 'kwds'):
metasToBeInstalled.pop(x)
global ProductInfo
if zInfo is None:
zInfo = ProductInfo
# print('@1 zInfo', id(self.zInfo['metadata']), id(self), id(self.zInfo),
# self.zInfo['metadata']['version'], list(metasToBeInstalled.keys()))
# must be the first line to initiate meta
super().__init__(zInfo=zInfo, **metasToBeInstalled, **kwds)
#print(self.meta.keySet(), id(self.meta))
......@@ -4,15 +4,14 @@ $WARNING
$IMPORTS
from .readonlydict import ReadOnlyDict
from .serializable import Serializable
from .abstractcomposite import AbstractComposite
from .listener import EventSender, EventType
from .metadata import AbstractParameter, Parameter, NumericParameter, StringParameter, DateParameter
from .datatypes import DataTypes
from .eq import deepcmp
from .copyable import Copyable
from .history import History
from fdi.dataset.attributable import addMetaDataProperty
from fdi.dataset.readonlydict import ReadOnlyDict
from fdi.dataset.serializable import Serializable
from fdi.dataset.abstractcomposite import AbstractComposite
from fdi.dataset.listener import EventSender, EventType
from fdi.dataset.eq import deepcmp
from fdi.dataset.copyable import Copyable
from fdi.dataset.history import History
import copy
from functools import lru_cache
......@@ -22,9 +21,14 @@ import logging
# create logger
logger = logging.getLogger(__name__)
$PRODUCTINFO
ProductInfo = ReadOnlyDict(_Model_Spec)
MdpInfo = ProductInfo['metadata']
# @addMandatoryProductAttrs
@addMetaDataProperty
class $PRODUCTNAME($PARENTS AbstractComposite, Copyable, Serializable, EventSender):
""" A BaseProduct is a generic result that can be passed on between
(standalone) processes.
......@@ -55,53 +59,24 @@ class $PRODUCTNAME($PARENTS AbstractComposite, Copyable, Serializable, EventSen
def __init__(self,
$INITARGS
zInfo=None,
**kwds):
if 'metasToBeInstalled' in kwds:
# This class is being called probably from super() in a subclass
metasToBeInstalled = kwds['metasToBeInstalled']
del kwds['metasToBeInstalled']
# 'description' is consumed in annotatable super class so it is not in.
description = metasToBeInstalled.pop('description') if 'description' in metasToBeInstalled else 'UNKNOWN'
super().__init__(description=description, **kwds)
self.history = History()
return
# this class is being called directly
# list of local variables.
# collect MDPs from args-turned-local-variables.
metasToBeInstalled = copy.copy(locals())
for x in ('self', '__class__', 'kwds'):
for x in ('self', '__class__', 'zInfo', 'kwds'):
metasToBeInstalled.pop(x)
global ProductInfo
self.zInfo = ProductInfo
# 'description' is consumed in annotatable super class so it is not in.
description = metasToBeInstalled.pop('description')
# must be the first line to initiate meta and get description
super().__init__(description=description, **kwds)
if zInfo is None:
zInfo = ProductInfo
self.installMetas(mtbi=metasToBeInstalled)
# must be the first line to initiate meta and zInfo
# :class: `Attributable` will process MDPs
super().__init__(zInfo=zInfo, **metasToBeInstalled, **kwds)
self._history = History()
def installMetas(self, mtbi, prodInfo=None):
""" put parameters in group in product metadata, and updates productInfo. values in mtbi override those default ones in group.
"""
if prodInfo is None:
prodInfo = self.zInfo
for met, params in prodInfo['metadata'].items():
# description has been set by Anotatable.__init__
if met != 'description':
# typ_ in mtbi (from __init__) changed to type
name = 'typ_' if met == 'type' else met
# set to input if given or to default.
if name in mtbi:
value = mtbi[name]
self.__setattr__(met, value)
@property
def history(self):
......@@ -123,82 +98,84 @@ $INITARGS
belonging to this product. """
return list(self._sets.values())[0] if len(self._sets) > 0 else None
def in_pinfo(self, name):
return name in self.zInfo['metadata']
# def x__getattribute__(self, name):
# """ Returns the named metadata parameter. Reads meta data table when Mandatory Attributes are
# read, and returns the values only.
# """
# # print('getattribute ' + name)
# if not (name in ('hasMeta', 'zInfo', 'in_pinfo', 'meta', 'history', 'listeners', 'getMeta') or name.startswith('_')) and hasattr(self, '_meta'):
# # if meta does not exist, inherit Attributable
# # before any class that access mandatory attributes
# # print('aa ' + selftr(self.getMeta()[name]))
# if self.in_pinfo(name):
# return self._meta[name].getValue()
# return super(BaseProduct, self).__getattribute__(name)
# def xsetMDP(self, name, value, met):
# m = self.getMeta()
# if name in m:
# # meta already has a Parameter for name
# p = m[name]
# if issubclass(value.__class__, AbstractParameter):
# t, tt = value.getType(), p.getType()
# if issubclass(t, tt):
# p = value
# return
# else:
# vs = value.value
# raise TypeError(
# "Parameter %s type is %s, not %s's %s." % (vs, t, name, tt))
# else:
# # value is not a Parameter
# t_type = type(value)
# tt_type = type(p.value)
# if issubclass(t_type, tt_type):
# p.setValue(value)
# return
# else:
# vs = value
# raise TypeError(
# "Value %s type is %s, not %s's %s." % (vs, t_type.__name__, name, tt_type.__name__))
# else:
# # parameter with name is not in prod metadata.
# if issubclass(value.__class__, AbstractParameter):
# # value is a parameter
# m[name] = value
# return
# # value is not a Parameter make one.
# m[name] = value2parameter(name, value, met)
# return
# def x__setattr__(self, name, value):
# """ Stores value to attribute with name given.
# If name is in the built-in list, store the value in a Parameter in metadata container. Updates meta data table. Updates value when built-in Attributes already has its Parameter in metadata.
# value: Must be Parameter/NumericParameter if this is normal metadata, depending on if it is Number. Value if mandatory / built-in attribute.
# """
# if self.hasMeta():
# met = self.zInfo['metadata']
# if name in met:
# # a built-in attribute like 'description'. store in meta
# self.setMDP(name, value, met)
# # must return without updating self.__dict__
# return
# # print('setattr ' + name, value)
# super(BaseProduct, self).__setattr__(name, value)
# def x__delattr__(self, name):
# """ Refuses deletion of mandatory attributes.
# """
# if name in self.zInfo['metadata'].keys():
# logger.warn('Cannot delete Mandatory Attribute ' + name)
# return