diff -r 8f7fcbe11879 -r 3f6dfc312760 entity.py --- a/entity.py Tue Aug 11 17:26:51 2009 +0200 +++ b/entity.py Tue Aug 11 17:27:27 2009 +0200 @@ -132,14 +132,20 @@ return super(_metaentity, mcs).__new__(mcs, name, bases, classdict) -class Entity(AppObject, dict): - """an entity instance has e_schema automagically set on - the class and instances has access to their issuing cursor. +_CWDB_CLASSES = {} + +def cwdb___init__(self, entity): + self.entity = entity - A property is set for each attribute and relation on each entity's type - class. Becare that among attributes, 'eid' is *NEITHER* stored in the - dict containment (which acts as a cache for other attributes dynamically - fetched) +class Entity(AppObject, dict): + """an entity instance has e_schema automagically set on the class. + + Also its special cwdb attribute provides access to persistent attributes and + relation using properties which are set for each attribute and relation + according to entity's type schema. + + Beware that among attributes, 'eid' is *NEITHER* stored in the dict + containment (which acts as a cache for other attributes dynamically fetched) :type e_schema: `cubicweb.schema.EntitySchema` :ivar e_schema: the entity's schema @@ -177,24 +183,30 @@ etype = cls.id assert etype != 'Any', etype cls.e_schema = eschema = cls.schema.eschema(etype) + cwdbclsdict = {'__init__': cwdb___init__} for rschema, _ in eschema.attribute_definitions(): - if rschema.type == 'eid': + rtype = rschema.type + if rtype == 'eid': continue - setattr(cls, rschema.type, Attribute(rschema.type)) + setattr(cls, rtype, DeprecatedAttribute(rtype)) + cwdbclsdict[rtype] = Attribute(rtype) mixins = [] - for rschema, _, x in eschema.relation_definitions(): - if (rschema, x) in MI_REL_TRIGGERS: - mixin = MI_REL_TRIGGERS[(rschema, x)] + for rschema, _, role in eschema.relation_definitions(): + if (rschema, role) in MI_REL_TRIGGERS: + mixin = MI_REL_TRIGGERS[(rschema, role)] if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ? mixins.append(mixin) for iface in getattr(mixin, '__implements__', ()): if not interface.implements(cls, iface): interface.extend(cls, iface) - if x == 'subject': - setattr(cls, rschema.type, SubjectRelation(rschema)) + rtype = rschema.type + if role == 'object': + attr = 'reverse_%s' % rtype else: - attr = 'reverse_%s' % rschema.type - setattr(cls, attr, ObjectRelation(rschema)) + attr = rtype + setattr(cls, attr, DeprecatedRelation(rtype, role)) + cwdbclsdict[attr] = Relation(rtype, role) + _CWDB_CLASSES[etype] = type(etype + 'CWDB', (object,), cwdbclsdict) if mixins: cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object]) cls.debug('plugged %s mixins on %s', mixins, etype) @@ -299,6 +311,8 @@ else: self.eid = None self._is_saved = True + if self.id != 'Any': + self.cwdb = _CWDB_CLASSES[self.id](self) def __repr__(self): return '' % ( @@ -310,6 +324,15 @@ def __hash__(self): return id(self) + def cwgetattr(self, attr, default=_marker): + """return attribute from either self.cwdb or self""" + try: + return getattr(self.cwdb, attr) + except AttributeError: + if default is _marker: + return getattr(self, attr) + return getattr(self, attr, default) + def pre_add_hook(self): """hook called by the repository before doing anything to add the entity (before_add entity hooks have not been called yet). This give the @@ -392,7 +415,7 @@ etype = str(self.e_schema) path = etype.lower() if mainattr != 'eid': - value = getattr(self, mainattr) + value = getattr(self.cwdb, mainattr) if value is None or unicode(value) == u'': mainattr = 'eid' path += '/eid' @@ -413,7 +436,7 @@ def attr_metadata(self, attr, metadata): """return a metadata for an attribute (None if unspecified)""" - value = getattr(self, '%s_%s' % (attr, metadata), None) + value = self.cwgetattr('%s_%s' % (attr, metadata), None) if value is None and metadata == 'encoding': value = self.vreg.property_value('ui.encoding') return value @@ -425,14 +448,17 @@ """ attr = str(attr) if value is _marker: - value = getattr(self, attr) + value = self.cwgetattr(attr) if isinstance(value, basestring): value = value.strip() if value is None or value == '': # don't use "not", 0 is an acceptable value return u'' if attrtype is None: attrtype = self.e_schema.destination(attr) - props = self.e_schema.rproperties(attr) + try: + props = self.e_schema.rproperties(attr) + except KeyError: + props = {} if attrtype == 'String': # internalinalized *and* formatted string such as schema # description... @@ -473,13 +499,20 @@ assert self.has_eid() execute = self.req.execute for rschema in self.e_schema.subject_relations(): - if rschema.is_final() or rschema.meta: + # skip final, meta or composite relation + if rschema.is_final() or rschema.meta or self.e_schema.subjrproperty(rschema, 'composite'): + continue + # skip relation with card in ?1 else we either change the copied + # object (inlined relation) or inserting some inconsistency + if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1': + continue + # skip if we're told to do so + if rschema.type in self.skip_copy_for: continue # skip already defined relations - if getattr(self, rschema.type): + if self.related(rschema.type, 'subject'): continue - if rschema.type in self.skip_copy_for: - continue + # special case for in_state if rschema.type == 'in_state': # if the workflow is defining an initial state (XXX AND we are # not in the managers group? not done to be more consistent) @@ -487,32 +520,26 @@ if execute('Any S WHERE S state_of ET, ET initial_state S,' 'ET name %(etype)s', {'etype': str(self.e_schema)}): continue - # skip composite relation - if self.e_schema.subjrproperty(rschema, 'composite'): - continue - # skip relation with card in ?1 else we either change the copied - # object (inlined relation) or inserting some inconsistency - if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1': - continue rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % ( - rschema.type, rschema.type) + rschema, rschema) execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) self.clear_related_cache(rschema.type, 'subject') for rschema in self.e_schema.object_relations(): - if rschema.meta: - continue - # skip already defined relations - if getattr(self, 'reverse_%s' % rschema.type): - continue - # skip composite relation - if self.e_schema.objrproperty(rschema, 'composite'): + # skip meta or composite + if rschema.meta or self.e_schema.objrproperty(rschema, 'composite'): continue # skip relation with card in ?1 else we either change the copied # object (inlined relation) or inserting some inconsistency if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1': continue + # skip if we're told to do so + if rschema.type in self.skip_copy_for: + continue + # skip already defined relations + if self.related(rschema.type, 'object'): + continue rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % ( - rschema.type, rschema.type) + rschema, rschema) execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) self.clear_related_cache(rschema.type, 'object') @@ -877,9 +904,9 @@ yielded = False for rschema, target in containers: if target == 'object': - targets = getattr(self, rschema.type) + targets = self.related(rschema.type, 'subject', entities=True) else: - targets = getattr(self, 'reverse_%s' % rschema) + targets = self.related(rschema.type, 'object', entities=True) for entity in targets: if entity.eid in _done: continue @@ -915,61 +942,73 @@ words += tokenize(value) for rschema, role in self.e_schema.fulltext_relations(): - if role == 'subject': - for entity in getattr(self, rschema.type): - words += entity.get_words() - else: # if role == 'object': - for entity in getattr(self, 'reverse_%s' % rschema.type): - words += entity.get_words() + for entity in self.related(rschema.type, role, entities=True): + words += entity.get_words() return words # attribute and relation descriptors ########################################## + class Attribute(object): """descriptor that controls schema attribute access""" - def __init__(self, attrname): - assert attrname != 'eid' - self._attrname = attrname + def __init__(self, rtype): + assert rtype != 'eid' + self._rtype = rtype + + def __get__(self, cwdbobj, eclass): + if cwdbobj is None: + return self + return cwdbobj.entity.get_value(self._rtype) + + def __set__(self, eobj, value): + raise NotImplementedError + + +class Relation(Attribute): + """descriptor that controls schema relation access""" + + def __init__(self, rtype, role): + super(Relation, self).__init__(rtype) + self._role = role + + def __get__(self, cwdbobj, eclass): + if cwdbobj is None: + raise AttributeError('%s cannot be only be accessed from instances' + % self._rtype) + return cwdbobj.entity.related(self._rtype, self._role, entities=True) + + +class DeprecatedAttribute(Attribute): + """descriptor that controls schema attribute access""" def __get__(self, eobj, eclass): + rtype = self._rtype + warn('entity.%s is deprecated, use entity.cwdb.%s' % (rtype, rtype), + DeprecationWarning, stacklevel=2) if eobj is None: return self - return eobj.get_value(self._attrname) + return eobj.get_value(self._rtype) def __set__(self, eobj, value): # XXX bw compat # would be better to generate UPDATE queries than the current behaviour eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)") - eobj[self._attrname] = value + eobj[self._rtype] = value -class Relation(object): +class DeprecatedRelation(Relation): """descriptor that controls schema relation access""" - _role = None # for pylint - - def __init__(self, rschema): - self._rschema = rschema - self._rtype = rschema.type def __get__(self, eobj, eclass): + rtype = self._rtype + warn('entity.[reverse_]%s is deprecated, use entity.cwdb.[reverse_]%s' + % (rtype, rtype), DeprecationWarning, stacklevel=2) if eobj is None: raise AttributeError('%s cannot be only be accessed from instances' % self._rtype) - return eobj.related(self._rtype, self._role, entities=True) - - def __set__(self, eobj, value): - raise NotImplementedError - - -class SubjectRelation(Relation): - """descriptor that controls schema relation access""" - _role = 'subject' - -class ObjectRelation(Relation): - """descriptor that controls schema relation access""" - _role = 'object' + return eobj.related(rtype, self._role, entities=True) from logging import getLogger from cubicweb import set_log_methods