Changeset 512

Show
Ignore:
Timestamp:
11/09/09 16:21:19 (3 years ago)
Author:
ged
Message:

- allows abstract classes to set default options
- allow mixed type inheritance (classes can inherit from a concrete class and

abstract classes at the same time).

- cleaned up slightly the abstract patch

Location:
elixir/trunk
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/elixir/entity.py

    r511 r512  
    4747 
    4848    def __init__(self, entity): 
    49  
    5049        self.entity = entity 
    5150        # entity.__module__ is not always reliable (eg in mod_python) 
    5251        self.module = sys.modules.get(entity.__module__) 
    5352 
    54         # used for multi-table inheritance 
    55         self.join_condition = None 
    56         self.has_pk = False 
    57         self._pk_col_done = False 
    58  
    5953        self.builders = [] 
    6054 
     
    6357        self.children = [] 
    6458 
     59        bases = [] 
    6560        for base in entity.__bases__: 
    6661            if isinstance(base, EntityMeta): 
    67                 if ( 
    68                     is_entity(base) and 
    69                     (not is_abstract_entity(base)) 
    70                 ): 
     62                if is_entity(base) and not is_abstract_entity(base): 
    7163                    if self.parent: 
    7264                        raise Exception( 
     
    7668                    else: 
    7769                        self.parent = base 
    78                         self.base = base._descriptor.base 
     70                        bases.extend(base._descriptor.bases) 
    7971                        self.parent._descriptor.children.append(entity) 
    8072                else: 
    81                     self.base = base 
     73                    bases.append(base) 
     74        self.bases = bases 
     75 
     76        if not is_entity(entity): 
     77            return 
     78 
     79        # used for multi-table inheritance 
     80        self.join_condition = None 
     81        self.has_pk = False 
     82        self._pk_col_done = False 
    8283 
    8384        # columns and constraints waiting for a table to exist 
     
    9697        self.table_args = [] 
    9798 
    98         # base class options_defaults 
    99         base_defaults = getattr(self.base, 'options_defaults', {}) 
     99        # base class(es) options_defaults 
     100        base_defaults = {} 
     101        for base in self.bases: 
     102            base_defaults.update(getattr(base, 'options_defaults', {})) 
     103 
    100104        complete_defaults = options.options_defaults.copy() 
    101105        complete_defaults.update({ 
     
    697701 
    698702def is_abstract_entity(dict_or_cls): 
    699     if isinstance(dict_or_cls, dict): 
    700         mutators = dict_or_cls.get(MUTATORS, []) 
    701     else: 
    702         mutators = getattr(dict_or_cls, MUTATORS, []) 
    703          
    704     for m in mutators: 
    705         if 'abstract' in m[2]: 
    706             return m[2]['abstract'] 
     703    if not isinstance(dict_or_cls, dict): 
     704        dict_or_cls = dict_or_cls.__dict__ 
     705    for mutator, args, kwargs in dict_or_cls.get(MUTATORS, []): 
     706        if 'abstract' in kwargs: 
     707            return kwargs['abstract'] 
    707708 
    708709    return False 
     
    713714    the EntityMeta metaclass. 
    714715    """ 
     716    # Create the entity descriptor 
     717    #XXX: we might want to use a simplified descriptor for bases and abstract 
     718    # classes which only need "bases", "options_defaults" and "abstract" 
     719    desc = cls._descriptor = EntityDescriptor(cls) 
     720 
     721    # Process mutators 
     722    # We *do* want mutators to be processed for base/abstract classes 
     723    # (so that statements like using_options_defaults work). 
     724    process_mutators(cls) 
     725 
     726    # We do not want to do any more processing for base/abstract classes 
     727    # (Entity et al.). 
     728    if not is_entity(cls) or is_abstract_entity(cls): 
     729        return 
     730 
    715731    cls.table = None 
    716732    cls.mapper = None 
    717733 
    718     # create the entity descriptor 
    719     desc = cls._descriptor = EntityDescriptor(cls) 
    720  
    721     # Determine whether this entity is a *direct* subclass of its base entity 
    722     entity_bases = [] 
     734    # Copy the properties ('Property' instances) of the entity base class(es). 
     735    # We use getmembers (instead of __dict__) so that we also get the 
     736    # properties from the parents of the base class if any. 
     737    base_props = [] 
    723738    for base in cls.__bases__: 
    724         if isinstance(base, EntityMeta): 
    725             if not is_entity(base) or is_abstract_entity(base): 
    726                 entity_bases.append(base) 
    727   
    728     base_props = [] 
    729     if entity_bases: 
    730         # If so, copy the base entity properties ('Property' instances). 
    731         # We use inspect.getmembers (instead of __dict__) so that we also 
    732         # get the properties from the parents of the base_class if any. 
    733         for base in entity_bases: 
    734             base_props += [(name, deepcopy(attr)) for name, attr in  
     739        if isinstance(base, EntityMeta) and \ 
     740           (not is_entity(base) or is_abstract_entity(base)): 
     741            base_props += [(name, deepcopy(attr)) for name, attr in 
    735742                           getmembers(base, lambda a: isinstance(a, Property))] 
    736743 
     
    744751        prop.attach(cls, name) 
    745752 
    746     # Process mutators 
    747     process_mutators(cls) 
    748  
    749753    # setup misc options here (like tablename etc.) 
    750754    desc.setup_options() 
     
    759763 
    760764    def __init__(cls, name, bases, dict_): 
    761         # Only process further subclasses of the base classes (Entity et al.), 
    762         # not the base classes themselves. We don't want the base entities to 
    763         # be registered in an entity collection, nor to have a table name and 
    764         # so on. 
    765  
    766         if is_abstract_entity(dict_): 
    767             return 
    768  
    769         if not is_entity(cls): 
    770             if isinstance(cls, EntityMeta): 
    771                 process_mutators(cls) 
    772             return 
    773  
    774765        instrument_class(cls) 
    775766 
  • elixir/trunk/tests/test_abstract.py

    r511 r512  
    33""" 
    44 
     5import re 
     6 
    57from elixir import * 
    68import elixir 
     9 
     10def camel_to_underscore(entity): 
     11    return re.sub(r'(.+?)([A-Z])+?', r'\1_\2', entity.__name__).lower() 
    712 
    813def setup(): 
     
    114119        class AbstractDated(Entity): 
    115120            using_options(abstract=True) 
     121            using_options_defaults(tablename=camel_to_underscore) 
    116122 
    117123            #TODO: add defaults 
     
    121127        class AbstractContact(Entity): 
    122128            using_options(abstract=True) 
     129            using_options_defaults(identity=camel_to_underscore) 
    123130 
    124131            first_name = Field(Unicode(50)) 
    125132            last_name = Field(Unicode(50)) 
    126133 
    127         class Contact(AbstractContact, AbstractDated): 
     134        class DatedContact(AbstractContact, AbstractDated): 
    128135            pass 
    129136 
    130137        setup_all(True) 
    131138 
    132         for f in ('created_date', 'modified_date', 
    133                   'first_name', 'last_name'): 
    134             assert f in Contact.table.columns, \ 
    135                    "column '%s' does not exist in table " % f 
     139        assert 'created_date' in DatedContact.table.columns 
     140        assert 'modified_date' in DatedContact.table.columns 
     141        assert 'first_name' in DatedContact.table.columns 
     142        assert 'last_name' in DatedContact.table.columns 
     143        assert DatedContact._descriptor.identity == 'dated_contact' 
     144        assert DatedContact.table.name == 'dated_contact' 
    136145 
    137         contact1 = Contact(first_name=u"Guido", last_name=u"van Rossum") 
     146        contact1 = DatedContact(first_name=u"Guido", last_name=u"van Rossum") 
    138147        session.commit() 
    139148 
     149    def test_mixed_inheritance(self): 
     150        class AbstractDated(Entity): 
     151            using_options(abstract=True) 
     152            using_options_defaults(tablename=camel_to_underscore) 
    140153 
     154            #TODO: add defaults 
     155            created_date = Field(DateTime) 
     156            modified_date = Field(DateTime) 
     157 
     158        class AbstractContact(Entity): 
     159            using_options(abstract=True) 
     160            using_options_defaults(identity=camel_to_underscore) 
     161 
     162            first_name = Field(Unicode(50)) 
     163            last_name = Field(Unicode(50)) 
     164 
     165        class Contact(AbstractContact): 
     166            using_options(inheritance='multi') 
     167 
     168        class DatedContact(AbstractDated, Contact): 
     169            using_options(inheritance='multi') 
     170 
     171        setup_all(True) 
     172 
     173        assert 'created_date' in DatedContact.table.columns 
     174        assert 'modified_date' in DatedContact.table.columns 
     175        assert DatedContact._descriptor.identity == 'dated_contact' 
     176        assert DatedContact.table.name == 'dated_contact' 
     177 
     178        contact1 = DatedContact(first_name=u"Guido", last_name=u"van Rossum") 
     179        session.commit() 
     180