'''
kabaret.flow.relations
Defines the relations classes used to bind objects together:
Parent: Returns the parent of the object.
If the nb_levels argument is more than 1, a grand parent
is returned:
1 -> parent
2 -> parent's parent
3 -> parent's parent's parent
etc...
Child: Declares a Child in the Object. The Object will be the parent
of the created child.
Param: Declares a Child Value in the Object.
The default_value and value_type arguments control the initial data
returned by the child Value and the type of the child Value.
(a subclass of kabaret.flow.values.Value())
SessionParam: Declare a Child SessionValue in the Object.
This value will be reset to its default at each session and
is not shared with other sessions.
IntParam
FloatParam
StringParam
DictParam
OrderedStringSetParam
HashParam: Declares as Child the corresponding Value subclass.
Separator:
A pre-configured Param acting as a separator in GUI
Computed: Declares a Child ComputedValue in the Object.
See kabaret.flow.values.ComputedValue().
Connection: Declares a Child Ref in the Object.
The ref being a Value returning another Object, this relations
is used to define dependencies between Objects.
See kabaret.flow.values.Ref()
'''
import logging
from .exceptions import WIPException
from .object import _Relation
from .values import (
Value, SessionValue,
IntValue, BoolValue, FloatValue, StringValue, DictValue,
OrderedStringSetValue, HashValue,
ComputedValue, Ref
)
[docs]class Parent(_Relation):
'''
The Parent relation give access to the related object's parent or grand-parents.
'''
_RELATION_TYPE_NAME = 'Parent'
def __init__(self, nb_levels=1):
super(Parent, self).__init__(None)
self.nb_levels = nb_levels
[docs] def get_ui(self, of=None):
'''
Overridden to return the ui of the parented Object.
See the `_Relation.get_ui()` class in `flow.object`.
'''
ui = super(Parent, self).get_ui(None)
if of is not None:
related = self.__get__(of)
related._fill_ui(ui)
return ui
def __get__(self, o, t=None):
if o is None:
return self
parent = o
for i in range(self.nb_levels):
if parent is None:
raise Exception(
'Could not find a parent at level %d for %r' % (i, o._mng.oid()))
parent = parent._mng.parent
return parent
[docs]class Child(_Relation):
'''
The Child relation sets an Object as the Child of the owner of the relation
(wich in turn becomes the parent)
'''
_RELATION_TYPE_NAME = 'Child'
def _create_object(self, parent):
try:
related = self.related_type(parent, self.name)
except:
logging.getLogger('kabaret.flow').error(
'Creating related object: %s(%r, %r)' % (
self.related_type.__name__, parent.oid(), self.name
)
)
raise
return related
[docs]class Param(Child):
'''
A Child relating to a :any:`Value` or one of its subclasses.
'''
_RELATION_TYPE_NAME = 'Param'
_DEFAULT_VALUE_TYPE = Value
def __init__(self, default_value=None, value_type=None):
'''
Beware of the default_value: if it is passed by reference, all
instance of the class owning the created value will share the value!
For example:
class MyObject(Object):
my_dict = Param({})
-> all instances of MyObject will have their my_dict Value sharing
the same dict (initialized when python executed the class definition)
The workaround is to use a callable that creates a new instance of the value:
class MyObject(Object):
my_dict = Param(dict)
'''
if value_type is None:
value_type = self.__class__._DEFAULT_VALUE_TYPE
elif not issubclass(value_type, self.__class__._DEFAULT_VALUE_TYPE):
raise TypeError(
'%r is not a subclass or %r, needed for relation %r' % (
value_type, self.__class__._DEFAULT_VALUE_TYPE,
self
)
)
super(Param, self).__init__(value_type)
if callable(default_value):
self.get_default_value = default_value
else:
self.get_default_value = lambda: default_value
# default for Param is editable:
self._ui['editable'] = True
try:
default_editor = value_type.DEFAULT_EDITOR
except Exception:
pass
else:
self._ui['editor_type'] = default_editor
self._watched = False
[docs] def watched(self, b=True):
'''
Configures the related value to be watched or not (default is False).
Watched value call their parent's child_value_changed() when changed.
'''
self._watched = b
return self
def _create_object(self, parent):
value = self.related_type(parent, self.name)
value.set_default_value(self.get_default_value())
value.set_watched(self._watched)
return value
#---OLD
def __set__(self, o, v):
raise WIPException('Never used this, it is confusing.')
# we must use get() on related values so not using set() feels strange
# (you may end up thinking it's an attribute and not a related value...)
value = self.get_related(o)
value.set(v)
[docs]class SessionParam(Param):
'''
A Param relating to a :any:`SessionValue`.
'''
_DEFAULT_VALUE_TYPE = SessionValue
[docs]class IntParam(Param):
'''
A Param relating to an :any:`IntValue`.
'''
_DEFAULT_VALUE_TYPE = IntValue
[docs]class BoolParam(Param):
'''
A Param relating to a :any:`BoolValue`.
'''
_DEFAULT_VALUE_TYPE = BoolValue
[docs]class FloatParam(Param):
'''
A Param relating to a :any:`FloatValue`.
'''
_DEFAULT_VALUE_TYPE = FloatValue
[docs]class StringParam(Param):
'''
A Param relating to a :any:`StringValue`.
'''
_DEFAULT_VALUE_TYPE = StringValue
[docs]class DictParam(Param):
'''
A Param relating to a :any:`DictValue`.
'''
_DEFAULT_VALUE_TYPE = DictValue
[docs]class OrderedStringSetParam(Param):
'''
A Param relating to an :any:`OrderedStringSetValue`.
'''
_DEFAULT_VALUE_TYPE = OrderedStringSetValue
def __init__(self, value_type=None):
super(OrderedStringSetParam, self).__init__(None, value_type)
[docs]class HashParam(Param):
'''
A Param relating to a :any:`HashValue`.
'''
_DEFAULT_VALUE_TYPE = HashValue
def __init__(self, value_type=None):
super(HashParam, self).__init__({}, value_type)
[docs]def Separator():
'''
Returns a Param relation showing an horizontal line in GUI.
'''
return Param(None).ui(editor='label', text='<hr>').ui(label='')
[docs]def Label(text, label=''):
'''
Returns a Param relation showing a (potentially html) text in GUI.
'''
return Param(None).ui(editor='label', text=text).ui(label=label)
[docs]class Computed(Param):
'''
A Param relating to a :any:`ComputedValue`
The value computation is delegated to the parent's compute_child_value() method.
The 'cached' and 'store_value' constuctor arguments will configure the ComputedValue.
(See kabaret.flow.values.ComputedValue)
You can use a subclass of ComputedValue by specifying computed_value_type in the constructor
'''
_RELATION_TYPE_NAME = 'Computed'
_DEFAULT_VALUE_TYPE = ComputedValue
def __init__(self, cached=False, store_value=False, computed_value_type=None):
super(Computed, self).__init__(None, value_type=computed_value_type)
self._cached = cached
self._store_value = store_value
self.ui(editable=False)
def _create_object(self, parent):
value = self.related_type(parent, self.name)
value.set_watched(self._watched)
value.set_cached(self._cached)
value.set_store_value(self._store_value)
return value
[docs]class Connection(Child):
'''
A Child relating to a :any:`Ref` subclass.
'''
_RELATION_TYPE_NAME = 'Connection'
def __init__(self, related_type=None, ref_type=None):
'''
One of related_type and ref_type must be given:
- if ref_type is not None, it will be used to reference the connected object
- if ref_type is None, related_type.ref_type() will be used
Specifying the ref_type instead of the related_type can be usefull in
obscur situation but should be avoided unless you have a good reason.
(I would explain but I cant remind why I used this in the past :p)
'''
if ref_type is None:
if related_type is not None:
ref_type = related_type.ref_type()
# else:
# This mean the type will be configured later (I hope!)
# It it used for example to have a ref to the same class.
super(Connection, self).__init__(ref_type)
# default for Connection is editable:
self._ui['editable'] = True
self._watched = False
def _create_object(self, parent):
value = self.related_type(parent, self.name)
value.set_watched(self._watched)
return value
[docs] def watched(self, b=True):
'''
Configures the related value to be watched or not (default is False).
Watched value call their parent's child_value_changed() when changed.
'''
self._watched = b
return self
#---OLD
def __set__(self, o, v):
raise WIPException('Never used this, it is confusing.')
# we must use get() on related values so not using set() feels strange
# (you may end up thinking it's an attribute and not a related value...)
value = self.get_related(o)
value.set(v)
#---OLD
class PipelineConnection(Connection):
'''
A Connection with ui(editable=False).
'''
def __init__(self, related_type=None, ref_type=None):
raise WIPException('THIS IS OBSOLETE')
super(PipelineConnection, self).__init__(related_type, ref_type)
# a PipelineConnection is just a not GUI editable connection
self._ui['editable'] = False