ISCE_INSAR/components/iscesys/Component/Component.py

488 lines
15 KiB
Python
Raw Normal View History

2019-01-16 19:40:08 +00:00
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copyright 2012 California Institute of Technology. ALL RIGHTS RESERVED.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# United States Government Sponsorship acknowledged. This software is subject to
# U.S. export control laws and regulations and has been classified as 'EAR99 NLR'
# (No [Export] License Required except when exporting to an embargoed country,
# end user, or in support of a prohibited end use). By downloading this software,
# the user agrees to comply with all applicable U.S. export laws and regulations.
# The user has the responsibility to obtain export licenses, or other export
# authority as may be required before exporting this software to any 'EAR99'
# embargoed foreign country or citizen of those countries.
#
# Author: Giangi Sacco
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import logging
import types
from iscesys.Component.Configurable import Configurable
from iscesys.StdOEL.StdOELPy import _WriterInterface
from isceobj.Util.decorators import type_check
## A function transformation to convert func(self, *args) into func(self)(*args)
def curried(func):
"""curried(func) takes function with signature:
func(self, *args)
and makes it:
curried_func(*args) --> func(self, *args)
That is, it makes the self implicit
"""
def curried_func(self, *args):
return func(self, *args)
curried_func.__doc__ = """Curried version of:\n%s""" % func.__doc__
return curried_func
## A function transformation to convert func(self, *args) into func(self)(*args)
def delayed(method):
"""delayed(method) takes method with signature:
func(arg, *args)
and makes a new methodwith signature:
delayed_func(arg)(*args)
That is, it delays the evaluation of the method until the returned
function is called.
"""
from functools import partial
def delayed_func(self, attr):
return partial(method, self, attr)
delayed_func.__doc__ = (
"""Delayed execution (via call) version of:\n%s""" %
method.__doc__
)
return delayed_func
## A mixin for ANY object that need to have attribute access done by metacode.
class StepHelper(object):
"""This Mixin helps get subclasses attributes evalauted at a later time--
during the steps process.
"""
@staticmethod
def compose(f, g, fargs=(), gargs=(), fkwargs={}, gkwargs={}):
"""fog = compose(f, g [fargs=(), gargs=(), fkwargs={}, gkwargs={}])
f g callable objects
fargs, gargs the callable's fixed arguments
fkwargs gkwargs the callable's fixed keywords
fog: a callable object that will signatur:
fog(*args, **kwargs) that will evaluate
f(g(*(args+gargs), **(kwargs+gkwargs)), *fargs, **fkwargs)
"""
from functools import partial
def fog(*args, **kwargs):
return (
partial(f, *fargs, **fkwargs)(
partial(g, *gargs, **gkwargs)(
*args, **kwargs
)
)
)
return fog
## self.attrgetter(attr) --> getattr(self, attr)
attrgetter = curried(getattr)
## self.attrsetter(attr) --> setattr(self, attr, value)
attrsetter = curried(setattr)
## self.delayed_attrgetter(attr) --> partial(self.attrgetter, attr)
delayed_attrgetter = delayed(attrgetter)
## self.delayed_attrsetter(attr) --> partial(self.attrsetter, attr)
delayed_attrsetter = delayed(attrsetter)
## Return a function with no arguments that will copy attrf to attr i,
## when called.
def delayed_attrcopy_from_to(self, attri, attrf):
"""f = self.delayed_attrcopy_from_to(attri, attrif)
attri inital attribute name
attrf final (target) attribute name
f a function of 0 arguements that will do:
self.attrf = self.attri
when called. hasattr(self, attr..) need only be True
when f is called.
"""
return lambda : self.attrsetter(attrf, self.attrgetter(attri))
pass
## Decorator (with arguments) to run a Port method with flow keyword set
def flow(flow):
"""Decorator:
decorator = flow(flow)
The decorator then transforms a method:
method = decorator(method)
so that the "flow" kwarg is set the argument to the decorator.
A nonsense value of "flow" will raise and Exception in
Componenet.__select_flow
"""
from functools import wraps
## flow(flow) returns this (that's the python decorator protocal)
def decorator(method):
## The decorator returns the method with the flow keyword set;
## @wraps makes the docstring and __name__
@wraps(method)
def method_with_flow(self, *args, **kwargs):
kwargs["flow"] = flow
return method(self)(*args, **kwargs)
return method_with_flow
return decorator
class Component(Configurable, StepHelper, _WriterInterface):
def __init__(self, family=None, name=None):
super(Component, self).__init__(family, name)
self._inputPorts = InputPorts()
self._outputPorts = OutputPorts()
self.createPorts()
self.createLogger()
return None
## This is how you call a component:
## args are passed to the method
## kwargs are ports.
def __call__(self, *args, **kwargs):
for key, value in kwargs.items():
self.wireInputPort(name=key, object=value)
return getattr(self, self.__class__.__name__.lower())(*args)
## Default pickle behavior
def __getstate__(self):
d = dict(self.__dict__)
for key in ('logger', '_inputPorts', '_outputPorts'):
try:
del d[key]
except KeyError:
pass
return d
## Default unpickle behavior
def __setstate__(self, d):
from iscesys.Component.Component import InputPorts, OutputPorts
self.__dict__.update(d)
self.createLogger()
self._inputPorts = InputPorts()
self._outputPorts = OutputPorts()
self.createPorts()
return None
## Place holder for portless components.
def createPorts(self):
pass
## Moving all logging to here, you must have a logging_name to get logged.
## this is not optimal-- and indicates a logging decorator is the
## appropriate thing to do.
def createLogger(self):
try:
name = self.__class__.logging_name
self.logger = logging.getLogger(name)
except AttributeError:
pass
pass
@property
def inputPorts(self):
return self._inputPorts
@inputPorts.setter
def inputPorts(self, value):
return setattr(self._inputPorts, value)
@property
def outputPorts(self):
return self._outputPorts
@outputPorts.setter
def outputPorts(self, value):
return setattr(self._outputPorts, value)
## Private helper method (not for humans): Get correct port
## (input or output)
def __select_flow(self, flow):
"""private method get "input" or "output" port depending on keyword
'flow'."""
try:
attr = "_" + flow + "Ports"
result = getattr(self, attr)
except AttributeError as err:
## On exception: figure out what went wrong and explain.
allowed_values = [cls.flow for cls in (InputPorts, OutputPorts)]
if flow not in allowed_values:
raise ValueError(
"flow keyword (%s) must be allowed values:%s" %
(str(flow), str(allowed_values))
)
raise err
return result
## private helper method for WIRING, flow keyword uses __selecet_flow()
def _wirePort(self, name, object, flow):
port_iterator = self.__select_flow(flow)
if name in port_iterator:
port = port_iterator.getPort(name=name)
port.setObject(object)
else:
raise PortError("No %s port named %s" % (port_iterator.flow, name))
return None
## private helper method for LIST, flow keyword uses __selecet_flow()
def _listPorts(self, flow):
for port in self.__select_flow(flow):
print(flow + "Port Name:" + port.getName())
pass
return None
## private help to get ports, flow keyword uses __selecet_flow()
def _getPort(self, name=None, flow=None):
return self.__select_flow(flow).getPort(name)
## helper method to activate a port, flow keyword uses __selecet_flow()
def _activePort(self, flow):
for port in self.__select_flow(flow):
port()
pass
return None
## wire InputPorts using a flow() decorated _wirePort()
@flow("input")
def wireInputPort(self, name=None, object=None):
"""wireInputPort([name=None [, object=None]])
_inputPorts.getPort(name).setObject(object)
"""
return self._wirePort
## wire OutputPorts using a flow() decorated _wirePort()
@flow("output")
def wireOutputPort(self, name=None, object=None):
"""wireOutputPort([name=None [, object=None]])
_outputPorts.getPort(name).setObject(object)
"""
return self._wirePort
## Since wiring a port is getting a string and object-- that a dictionary item,
## this does it via a dictionary of {name:object} pairs, and provides a string
## free interface to wiring ports
def wire_input_ports(**kwargs):
"""wire_input_port(**kwargs) kwargs={name: object, ...}"""
for key, value in kwargs.items():
self.wireInputPort(name=key, object=value)
return self
## list InputPorts using a flow() decorated _listPort()
@flow("input")
def listInputPorts(self):
"""prints items in a list of _.inputPorts """
return self._listPorts
## list OutputPorts using a flow() decorated _listPort()
@flow("output")
def listOutputPorts(self):
"""prints items in a list of _.outputPorts """
return self._listPorts
## get an InputPort with flow() decorated _getPort()
@flow("input")
def getInputPort(self, name=None):
"""getInputPort([name=None]) -->
_inputPorts.getPort(name)
"""
return self._getPort
## get an OutputPort with flow() decorated _getPort()
@flow("output")
def getOutputPort(self, name=None):
"""getOutputPort([name=None]) -->
_outputPorts.getPort(name)
"""
return self._getPort
## get an InputPort with flow() decorated _activePort()
@flow("input")
def activateInputPorts(self):
"""call each port in _inputPorts"""
return self._activePort
## get an OutputPort with flow() decorated _activePort()
@flow("output")
def activateOutputPorts(self):
"""call each port in _outputPorts"""
return self._activePort
pass
class Port(object):
def __init__(self, name, method=None, doc=""):
self.name = name # Name with which to reference the port
self._method = method # Function which implements the port
self._object = None
self.doc = doc # A documentation string for the port
@type_check(str)
def setName(self, name):
self._name = name
def getName(self):
return self._name
# @type_check(new.instancemethod)
def setMethod(self, method=None):
self._method = method
def getMethod(self):
return self._method
def setObject(self, object=None):
self._object = object
def getObject(self):
return self._object
def __str__(self):
return str(self._doc)
def __call__(self):
return self._method()
@property
def doc(self):
return str(self._doc)
@doc.setter
@type_check(str)
def doc(self, value):
self._doc = value
name = property(getName, setName)
object = property(getObject, setObject)
method = property(getMethod, setMethod)
pass
class PortIterator(object):
"""PortIterator() uses:
add() method to add ports. Note: it is also
a port container (__contains__) and mapping (__getitem__)
"""
def __init__(self):
self._last = 0
self._ports = []
self._names = []
def add(self, port):
"""add(port)
appends port to _ports
appends port.getName() to _names"""
if isinstance(port, Port):
self._ports.append(port)
self._names.append(port.getName())
pass
return None
def getPort(self, name=None):
try:
result = self._ports[self._names.index(name)]
except IndexError:
result = None
return result
def hasPort(self, name=None):
return name in self._names
## Make PortIterator a container: name in port_iterator
def __contains__(self, name):
"""name in port --> port.hasPort(name)"""
return self.hasPort(name)
## Make PortIterator a mapping (port_iterator[name] --> port)
def __getitem__(self, name):
"""port[name] --> port.getPort(name).getObject()"""
return self.getPort(name).getObject()
## port_iterator[name]=method --> port.add(Port(name=name, method=method)
def __setitem__(self, name, method):
return self.add(Port(name=name, method=method))
## iter(port_iterator()) returns an iterator over port_iterator._list
def __iter__(self):
return iter(self._ports)
## Len(PortIterator) is the len(PortIterator._ports)
def __len__(self):
return len(self._ports)
def next(self):
if(self._last < len(self._ports)):
next_ = self._ports[self._last]
self._last += 1
return next_
else:
self._last = 0 # This is so that we can restart iteration
raise StopIteration()
pass
class InputPorts(PortIterator):
## flow tells Component's generic methods that this in for input
flow="input"
pass
class OutputPorts(PortIterator):
## flow tells Component's generic methods that this in for output
flow="output"
pass
class PortError(Exception):
"""Raised when an invalid port operation is attempted"""
def __init__(self, value):
self.value = value
return None
def __str__(self):
return repr(self.value)