Source code for fdi.pal.context

# -*- coding: utf-8 -*-

from ..dataset.serializable import Serializable
from .. import pal
from ..dataset.baseproduct import BaseProduct
from ..dataset.odict import ODict
from ..dataset.metadata import tabulate
from ..utils.common import bstr

from collections import OrderedDict
from collections import UserDict
import logging
# create logger
logger = logging.getLogger(__name__)
#logger.debug('level %d' % (logger.getEffectiveLevel()))


[docs]class ContextRuleException(ValueError): pass
[docs]class RefContainer(ODict): """ A map where Rules of its owner Context are applied when put(k,v) is called, and the owner MapContext's ID can be put to v's parents list. Implemented as an ODict that RefContainer has a _STID when json.loads'ed. A MapContext has a _data, which has a refs:RefContainer, which has a datap, which has a name:ProductRef. when used as context.refs.get('x').product.description, the RefContainer is called with get() or __getitem__(), which calls superclass composite's __getitem__() """
[docs] def __init__(self, **kwds): """ """ super().__init__(**kwds) self.setOwner(None)
@property def owner(self): """ Property """ return self.getOwner() @owner.setter def owner(self, owner): """ """ self.setOwner(owner)
[docs] def getOwner(self): """ Returns the reference container mapping """ return self._owner
[docs] def setOwner(self, owner): """ records who owns this container. Due to reason described in __setitem__ doc, existing refs will be set again upon owner set """ if hasattr(self, '_owner') and self._owner is not None and id(self._owner) == id(owner): return self._owner = owner old = self.data self.data = dict() for k, r in old.items(): self.__setitem__(k, r) del old
def __setitem__(self, key, ref): """ when deserialized contained refs will be set with _owner==None then the following are done when the assembled refContainer is set('refs',refC) to the owner context. """ if ref is None: raise ValueError() # during deserialization __setitem__ is called before _owner is set by Context,setRefs() if hasattr(self, '_owner') and self._owner is not None: from .productref import ProductRef if isinstance(ref, ProductRef): ref.addParent(self._owner) else: raise TypeError(type(ref).__name__ + ' is not a Product Reference') if not self._owner.applyRule(key, ref): ContextRuleException('Ref %s fails rule test of %s.' % (ref.toString(level=2), self._owner.__class__.__name__) ) super().__setitem__(key, ref) def __delitem__(self, key): if key is None: raise KeyError() # during deserialization __setitem__ are called before _owner is set by Context,setRefs() if hasattr(self, '_owner'): from .productref import ProductRef ref = self.__getitem__(key) if isinstance(ref, ProductRef): ref.removeParent(self._owner) super().__delitem__(key)
[docs] def clear(self): """ remove all productRefs """ ks = list(self.keys()) for k in ks: self.__delitem__(k)
[docs] def set(self, key, ref): """ set label-ref pair after validating then add parent to the ref """ self.__setitem__(key, ref)
put = set
[docs] def get(self, key, *args, **kwds): """ """ return self.__getitem__(key, *args, **kwds)
[docs] def size(self): """ """ return len(self.keys())
def __getstate__(self): """ Can be encoded with serializableEncoder """ s = super().__getstate__() # there is no need to persist owner as it is setted by `__setitem__` during deserializing # s.update(_ATTR_owner=self.owner) return s
[docs] def toString(self, level=0, tablefmt='grid', **kwds): cn = self.__class__.__name__ if level > 0: s = f'{cn}(' s += ', '.join('%s: %s' % (n, r.urn) for n, r in self.items()) s += ')' else: dat = [(n, r.urn, '\n'.join(str(id(p)) for p in r.parents), len(r.meta) if r.meta else '(none)') for n, r in self.items()] headers = ['lable', 'urn', 'parents', 'meta'] s = tabulate.tabulate(dat, headers=headers, stralign='center', tablefmt=tablefmt) return s
string = toString txt = toString
[docs]class AbstractContext(): """ A special kind of Product that can hold references to other Products. This abstract product introduces the lazy loading and saving of references to Products or ProductRefs that it is holding. It remembers its state. http://herschel.esac.esa.int/hcss-doc-15.0/load/hcss_drm/api/herschel/ia/pal/Context.html """
[docs] def __init__(self, *args, **kwds): """ Sets ``rule`` to ``None`` if ``zInfo`` does not have ``refs``, else to ``zInfo['refs']``. """ super().__init__(*args, **kwds) self._dirty = False if 'refs' not in self.zInfo: self._rule = None # None means there is no rule. else: self._rule = self.zInfo['refs'] refC = None # this line must stay after _rule is set. self.setRefs(refC)
@property def refs(self): """ Property """ return self.getRefs() @refs.setter def refs(self, refs): """ """ self.setRefs(refs)
[docs] def setRefs(self, refs): """ Changes/Adds the mapping container that holds references. """ self.set('refs', refs)
[docs] def set(self, name, refs): """ add owner to RefContainer or add named references. :name: name of the new `RefContainer`. if is ``refs``, set owner of ``refs`` to `self`. """ if isinstance(refs, RefContainer) and name == 'refs': refs.setOwner(self) super().set(name, refs)
[docs] def getRefs(self): """ Returns the reference container mapping """ return self.get('refs')
[docs] def getAllRefs(self, recursive, includeContexts): """ Provides a set of the unique references stored in this context. """ raise NotImplementedError()
[docs] def hasDirtyReferences(self, storage): """ Returns a logical to specify whether this context has dirty references or not. """ raise NotImplementedError()
[docs] @staticmethod def isContext(cls): """ Yields true if specified class belongs to the family of contexts. """ return issubclass(cls, AbstractContext)
[docs] def isValid(self): """ Provides a mechanism to ensure whether it is valid to store this context in its current state. """ raise NotImplementedError()
[docs] def readDataset(self, storage, table, defaultPoolId): """ Reads a dataset with information within this context that is normally not accessible from the normal Product interface.""" raise NotImplementedError()
[docs] def refsChanged(self): """ Indicates that the references have been changed in memory, which marks this context as dirty. """ self._dirty = True
[docs] def writeDataset(self, storage): """ Creates a dataset with information within this context that is normally not accessible from the normal Product interface. """ raise NotImplementedError()
[docs] def getRule(self): """ Get the rule that controls the products to be added into the context. """ return self._rule
[docs] def setRule(self, rule): """ Set the rule that controls the products to be added into the context. """ self._rule = rule
[docs] def addRule(self, rule): """ Add to the rule that controls the products to be added into the context. The new rule will be old rule AND added rule. """ raise NotImplementedError()
[docs] def applyRule(self, *args): """ returns True if the input ProductRef passes rule. If ``refs`` is not specified in ``zInfo`` return True. If ``zInfo['refs'] is empty, return True. Subclasses can override this method according to what is written in zInfo. """ if self._rule is None: return True if len(self._rule) == 0: return True return True
[docs]class Context(AbstractContext, BaseProduct): """ See docstring of AbstractContext. """
[docs] def __init__(self, *args, **kwds): """ """ super().__init__(*args, **kwds)
[docs] def applyRule(self, *args): """ returns True if the input ProductRef passes rule. Default behavior is return the superclass.applyRule() result. Subclasses can override this method according to what is written in zInfo. """ return super().applyRule(*args)
[docs] def getAllRefs(self, recursive=False, includeContexts=True, seen=None): """ Provides a set of the unique references stored in this `Context`. This includes references that are contexts, but not the contents of these subcontexts. This is equivalent to getAllRefs(recursive=false, includeContexts= true). recursive - if true, include references in subcontexts includeContexts - if true, include references to contexts, not including this one """ if issubclass(self.__class__, pal.productref.ProductRef) and not Context.isContext(self.__class__): raise TypeError('This ref does not point to a context') if seen is None: seen = list() rs = list() surn = self.get('_urn', None) for x in self.refs.values(): if Context.isContext(x.getType()): if includeContexts: if surn and x.getUrn() == surn.urn: # pointing to self pass else: if x not in rs: rs.append(x) if recursive: if x not in seen: seen.append(x) # enter the context rs += x.getProduct().getAllRefs(recursive=recursive, includeContexts=includeContexts, seen=seen) else: pass else: # not contex # records if x not in rs: rs.append(x) return rs
[docs] def hasDirtyReferences(self, storage): """ Returns a logical to specify whether this context has dirty references or not. """ return self._dirty
[docs]class MapContext(Context): """ Allows grouping Products into a map of (String, ProductRef) pairs. New entries can be added if they comply to the adding rules of this context. The default behaviour is to allow adding any (String,ProductRef) given that ``rule`` is not set. An example:: image = ImageProduct(description="hi") spectrum = SpectrumProduct(description="there") simple = Product(description="everyone") context=MapContext() context.refs.put("x",ProductRef(image)) context.refs.put("y",ProductRef(spectrum)) context.refs.put("z",ProductRef(simple)) print context.refs.size() # 3 print context.refs.get('x').product.description # hi print context.refs.get('y').product.description # there print context.refs.get('z').product.description # everyone It is possible to insert a ProductRef at a specific key in the MapContext. The same insertion behaviour is followed as for a Python dict, in that if there is already an existing ProductRef for the given key, that ProductRef is replaced with the new one:: product4=SpectrumProduct(description="everybody") context.refs.put("y", ProductRef(product4)) product5=SpectrumProduct(description="here") context.refs.put("a", ProductRef(product5)) print context.refs.get('x').product.description # hi print context.refs.get('y').product.description # everybody print context.refs.get('z').product.description # everyone print context.refs.get('a').product.description # here Note that the rules are only applied when putting an entry to the map! Be aware that 1. the put() method of the map view may throw a ContextRuleException if the data added to the context violates the rules applied to the context. 2. the put() method of the map view may throw a ValueError if either of the arguments to the put() method are null. BaseProduct--Product \\ AbstractContext------Contex---------MapContext """
[docs] def __init__(self, *args, **kwds): """ """ super().__init__(*args, **kwds) refC = RefContainer() # this line must stay after _rule is set. self.setRefs(refC)
[docs] def addRule(self, rule): """ Add to the rule that controls the products to be added into the context. The new rule will be old rule AND added rule. """ self._rule.update(rule)
[docs] def applyRule(self, key, ref): """ returns True if the input key name and ProductRef pass rule. Default behavior is return True if the fully qualified class name of the product which ref refers to equals ``zInfo['refs'][key]``, OR the superclass.applyRule() result. Subclasses can override this method according to what is written in zInfo. """ if super().applyRule(): return True if 'refs' in self.zInfo: return ref.urnobj.getTypeName() in self.zInfo['refs']