Changeset 323

Show
Ignore:
Timestamp:
04/28/08 18:26:51 (5 years ago)
Author:
ged
Message:

- Added experimental (!) support for concrete table inheritance (both

polymorphic or not).

- Added new "identity" option which can be used to set a custom polymorphic

identity for an entity. It also accepts a callable so that you can generate
the identity name automatically from the class itself.

- Improved some options documentation.
- Bump version to 0.6

Location:
elixir/trunk
Files:
7 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/CHANGES

    r322 r323  
    1 0.5.3 
     10.6.0 
    22 
    33New features: 
     
    66  simple JSON-like dictionary notation (patch from Paul Johnston, 
    77  closes ticket #40). 
     8- Added experimental (!) support for concrete table inheritance (both  
     9  polymorphic or not). 
     10- Added new "identity" option which can be used to set a custom polymorphic 
     11  identity for an entity. It also accepts a callable so that you can generate 
     12  the identity name automatically from the class itself. 
    813 
    914Bug fixes: 
  • elixir/trunk/elixir/__init__.py

    r314 r323  
    3737 
    3838 
    39 __version__ = '0.5.2' 
     39__version__ = '0.6.0' 
    4040 
    4141__all__ = ['Entity', 'EntityMeta', 
  • elixir/trunk/elixir/entity.py

    r322 r323  
    1313                                          desc, ForeignKey, and_, \ 
    1414                                          ForeignKeyConstraint 
    15 from sqlalchemy.orm                import Query, MapperExtension,\ 
    16                                           mapper, object_session, EXT_PASS 
     15from sqlalchemy.orm                import Query, MapperExtension, \ 
     16                                          mapper, object_session, EXT_PASS, \ 
     17                                          polymorphic_union 
    1718from sqlalchemy.ext.sessioncontext import SessionContext 
    1819 
     
    114115                                  elixir.entities) 
    115116 
    116         for option in ('autosetup', 'inheritance', 'polymorphic', 
     117        for option in ('autosetup', 'inheritance', 'polymorphic', 'identity', 
    117118                       'autoload', 'tablename', 'shortnames',  
    118119                       'auto_primarykey', 'version_id_col',  
     
    152153 
    153154        entity = self.entity 
    154         if self.inheritance == 'concrete' and self.polymorphic: 
    155             raise NotImplementedError("Polymorphic concrete inheritance is " 
    156                                       "not yet implemented.") 
    157  
    158155        if self.parent: 
    159156            if self.inheritance == 'single': 
     
    169166        elif callable(self.tablename): 
    170167            self.tablename = self.tablename(entity) 
     168 
     169        if not self.identity: 
     170            if 'polymorphic_identity' in self.mapper_options: 
     171                self.identity = self.mapper_options['polymorphic_identity'] 
     172            else: 
     173                #TODO: include module name 
     174                self.identity = entity.__name__.lower() 
     175        elif 'polymorphic_identity' in kwargs: 
     176            raise Exception('You cannot use the "identity" option and the ' 
     177                            'polymorphic_identity mapper option at the same ' 
     178                            'time.') 
     179        elif callable(self.identity): 
     180            self.identity = self.identity(entity) 
     181 
     182        if self.polymorphic: 
     183            if not isinstance(self.polymorphic, basestring): 
     184                self.polymorphic = options.DEFAULT_POLYMORPHIC_COL_NAME 
    171185 
    172186    #--------------------- 
     
    282296                            use_alter=con.use_alter)) 
    283297 
    284         if self.polymorphic and self.inheritance in ('single', 'multi') and \ 
     298        if self.polymorphic and \ 
     299           self.inheritance in ('single', 'multi') and \ 
    285300           self.children and not self.parent: 
    286             if not isinstance(self.polymorphic, basestring): 
    287                 self.polymorphic = options.DEFAULT_POLYMORPHIC_COL_NAME 
    288                  
    289301            self.add_column(Column(self.polymorphic,  
    290302                                   options.POLYMORPHIC_COL_TYPE)) 
     
    379391        if self.entity.mapper: 
    380392            return 
    381          
     393 
     394        # for now we don't support the "abstract" parent class in a concrete 
     395        # inheritance scenario as demonstrated in 
     396        # sqlalchemy/test/orm/inheritance/concrete.py 
     397        # this should be added along other 
    382398        kwargs = self.mapper_options 
    383399        if self.order_by: 
     
    396412                                self.parent._descriptor.primary_keys) 
    397413                kwargs['inherit_condition'] = \ 
    398                     and_(*[pc == c for c,pc in col_pairs]) 
     414                    and_(*[pc == c for c, pc in col_pairs]) 
    399415 
    400416            if self.polymorphic: 
    401417                if self.children and not self.parent: 
    402                     kwargs['polymorphic_on'] = \ 
    403                         self.get_column(self.polymorphic) 
     418                    if self.inheritance == 'concrete': 
     419                        keys = [(self.identity, self.entity.table)] 
     420                        keys.extend([(child._descriptor.identity, child.table)  
     421                                     for child in self._get_children()]) 
     422                        pjoin = polymorphic_union( 
     423                                    dict(keys), self.polymorphic, 'pjoin') 
     424                        kwargs['select_table'] = pjoin 
     425                        kwargs['polymorphic_on'] = \ 
     426                            getattr(pjoin.c, self.polymorphic) 
     427                    else: 
     428                        kwargs['polymorphic_on'] = \ 
     429                            self.get_column(self.polymorphic) 
    404430                    #TODO: this is an optimization, and it breaks the multi 
    405431                    # table polymorphic inheritance test with a relation.  
     
    412438#                            join = join.outerjoin(child.table) 
    413439#                        kwargs['select_table'] = join 
    414                      
     440 
    415441                if self.children or self.parent: 
    416                     #TODO: make this customizable (both callable and string) 
    417                     #TODO: include module name 
    418                     if 'polymorphic_identity' not in kwargs: 
    419                         kwargs['polymorphic_identity'] = \ 
    420                             self.entity.__name__.lower() 
    421  
    422                 if self.inheritance == 'concrete': 
     442                    kwargs['polymorphic_identity'] = self.identity 
     443 
     444                if self.parent and self.inheritance == 'concrete': 
    423445                    kwargs['concrete'] = True 
    424446 
     
    494516                            (name, self.entity.__name__)) 
    495517        self.properties[name] = property 
     518 
     519#FIXME: something like this is needed to propagate the relationships from  
     520# parent entities to their children in a concrete inheritance scenario. But  
     521# this doesn't work because of the backref matching code. 
     522#        if self.children and self.inheritance == 'concrete': 
     523#            for child in self.children: 
     524#                child._descriptor.add_property(name, property) 
    496525 
    497526        mapper = self.entity.mapper 
  • elixir/trunk/elixir/options.py

    r321 r323  
    3939|                     | column to this argument.                              | 
    4040+---------------------+-------------------------------------------------------+ 
     41| ``identity``        | Specify a custom polymorphic identity. When using     | 
     42|                     | polymorphic inheritance, this value (usually a        | 
     43|                     | string) will represent this particular entity (class) | 
     44|                     | . It will be used to differentiate it from other      | 
     45|                     | entities (classes) in your inheritance hierarchy when | 
     46|                     | loading from the database instances of different      | 
     47|                     | entities in that hierarchy at the same time.          | 
     48|                     | This value will be stored by default in the           | 
     49|                     | "row_type" column of the entity's table (see above).  | 
     50|                     | You can either provide a                              | 
     51|                     | plain string or a callable. The callable will be      | 
     52|                     | given the entity (ie class) as argument and must      | 
     53|                     | return a value (usually a string) representing the    | 
     54|                     | polymorphic identity of that entity.                  | 
     55|                     | By default, this value is automatically generated: it | 
     56|                     | is the name of the entity lower-cased.                | 
     57+---------------------+-------------------------------------------------------+ 
    4158| ``metadata``        | Specify a custom MetaData for this entity.            | 
    4259|                     | By default, entities uses the global                  | 
     
    5370|                     | given the entity (ie class) as argument and must      | 
    5471|                     | return a string representing the name of the table    | 
    55 |                     | for that entity.                                      | 
    56 +---------------------+-------------------------------------------------------+ 
    57 | ``shortnames``      | Usually tablenames include the full module-path       | 
    58 |                     | to the entity, but lower-cased and separated by       | 
    59 |                     | underscores ("_"), eg.: "project1_model_myentity"     | 
    60 |                     | for an entity named "MyEntity" in the module          | 
    61 |                     | "project1.model".  If shortnames is ``True``, the     | 
    62 |                     | tablename will just be the entity's classname         | 
    63 |                     | lower-cased, ie. "myentity".                          | 
     72|                     | for that entity. By default, the tablename is         | 
     73|                     | automatically generated: it is a concatenation of the | 
     74|                     | full module-path to the entity and the entity (class) | 
     75|                     | name itself. The result is lower-cased and separated  | 
     76|                     | by underscores ("_"), eg.: for an entity named        | 
     77|                     | "MyEntity" in the module "project1.model", the        | 
     78|                     | generated table name will be                          | 
     79|                     | "project1_model_myentity".                            | 
     80+---------------------+-------------------------------------------------------+ 
     81| ``shortnames``      | Specify whether or not the automatically generated    | 
     82|                     | table names include the full module-path              | 
     83|                     | to the entity. Defaults to ``True``.                  | 
    6484+---------------------+-------------------------------------------------------+ 
    6585| ``auto_primarykey`` | If given as string, it will represent the             | 
     
    6989|                     | corresponding entity.  If this option is False,       | 
    7090|                     | it will disallow auto-creation of a primary key.      | 
     91|                     | Defaults to ``True``.                                 | 
    7192+---------------------+-------------------------------------------------------+ 
    7293| ``version_id_col``  | If this option is True, it will create a version      | 
     
    7596|                     | This can be used to prevent concurrent modifications  | 
    7697|                     | to the entity's table rows (i.e. it will raise an     | 
    77 |                     | exception if it happens).                             | 
     98|                     | exception if it happens). Defaults to ``False``.      | 
    7899+---------------------+-------------------------------------------------------+ 
    79100| ``order_by``        | How to order select results. Either a string or a     | 
     
    90111|                     | in which case your entity will be mapped using a      | 
    91112|                     | non-contextual mapper. This option can also be set    | 
    92 |                     | for all entities of a module via by setting the       | 
     113|                     | for all entities of a module by setting the           | 
    93114|                     | ``__session__`` attribute of that module.             | 
    94115+---------------------+-------------------------------------------------------+ 
     
    168189    inheritance='single', 
    169190    polymorphic=True, 
     191    identity=None, 
    170192    autoload=False, 
    171193    tablename=None, 
  • elixir/trunk/setup.cfg

    r314 r323  
    99modules = elixir, elixir.ext.associable, elixir.ext.versioned, 
    1010          elixir.ext.encrypted, elixir.ext.list 
    11 trac_browser_url = http://elixir.ematia.de/trac/browser/elixir/tags/0.5.2 
     11trac_browser_url = http://elixir.ematia.de/trac/browser/elixir/tags/0.6.0 
    1212 
  • elixir/trunk/setup.py

    r314 r323  
    22 
    33setup(name="Elixir", 
    4       version="0.5.2", 
     4      version="0.6.0", 
    55      description="Declarative Mapper for SQLAlchemy", 
    66      long_description=""" 
  • elixir/trunk/tests/test_inherit.py

    r321 r323  
    5555 
    5656    for query_class in ('A', 'B', 'C', 'D', 'E'): 
     57#        print res[query_class], expected_res[query_class] 
    5758        assert len(res[query_class]) == len(expected_res[query_class]) 
    5859        for real, expected in zip(res[query_class], expected_res[query_class]): 
     
    172173        }) 
    173174 
     175    def test_polymorphic_concrete_inheritance(self): 
     176        # to get this test to work, I need to duplicate parent relationships in 
     177        # the children. The problem is that the properties are setup post  
     178        # mapper setup, so I'll need to add some logic into the add_property  
     179        # method which I'm reluctant to do. 
     180        do_tst('concrete', True, { 
     181            'A': ('A', 'B', 'C', 'D', 'E'), 
     182            'B': ('B', 'C'), 
     183            'C': ('C',), 
     184            'D': ('D',), 
     185            'E': ('E',), 
     186        }) 
     187 
    174188    def test_multitable_inheritance(self): 
    175189        do_tst('multi', False, {