Changeset 515

Show
Ignore:
Timestamp:
11/12/09 14:47:53 (3 years ago)
Author:
ged
Message:

- Added a new collection which can resolve entities relative to the current

entity, for example "..other_module.Class" (based on patches from Johannes
Janssen, closes #93).

- Added a new entity option "resolve_root", which allows one to specify the

root module where your entities are defined. The string will be prepended
to all "absolute" entity paths. It can also be used on a per-entity basis.
This feature is based on a patch from Johannes Janssen, see #93.

Location:
elixir/trunk
Files:
8 added
8 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/AUTHORS

    r504 r515  
    1616- Graham Higgins 
    1717- Jason R. Coombs 
     18- Johannes Janssen 
    1819- Neil Blakey-Milner 
    1920- Paul Johnston 
    2021- Remi Jolin 
    2122- Robin Munn 
     23- Stéphane Klein 
    2224- Valentin Lab 
    2325- some anonymous contributions I couldn't trace to someone in particular 
  • elixir/trunk/CHANGES

    r511 r515  
    55  etc... This allows, among others, an entity to inherit from multiple abstract 
    66  classes (patch from Stéphane Klein, closes #89). 
     7- Added a new collection which can resolve entities relative to the current 
     8  entity, for example "..other_module.Class" (based on patches from Johannes 
     9  Janssen, closes #93). 
     10- Added a new entity option "resolve_root", which allows one to specify the 
     11  root module where your entities are defined. The string will be prepended 
     12  to all "absolute" entity paths. It can also be used on a per-entity basis. 
     13  This feature is based on a patch from Johannes Janssen, see #93. 
    714 
    815Changes: 
  • elixir/trunk/elixir/__init__.py

    r491 r515  
    3636                              Synonym 
    3737from elixir.statements import Statement 
    38 from elixir.collection import EntityCollection 
     38from elixir.collection import EntityCollection, GlobalEntityCollection 
    3939 
    4040 
     
    6868 
    6969# default entity collection 
    70 entities = EntityCollection() 
     70entities = GlobalEntityCollection() 
    7171 
    7272 
  • elixir/trunk/elixir/collection.py

    r490 r515  
    33''' 
    44import sys 
     5import re 
    56 
    6 # default entity collection 
    7 class EntityCollection(list): 
     7class BaseCollection(list): 
    88    def __init__(self, entities=None): 
    9         # _entities is a dict of entities keyed on their name. 
    10         self._entities = {} 
    119        list.__init__(self) 
    1210        if entities is not None: 
     
    1614        for e in entities: 
    1715            self.append(e) 
     16 
     17    def clear(self): 
     18        del self[:] 
     19 
     20    def resolve_absolute(self, key, full_path, entity=None, root=None): 
     21        if root is None: 
     22            root = entity._descriptor.resolve_root 
     23        if root: 
     24            full_path = '%s.%s' % (root, full_path) 
     25        module_path, classname = full_path.rsplit('.', 1) 
     26        module = sys.modules[module_path] 
     27        res = getattr(module, classname, None) 
     28        if res is None: 
     29            if entity is not None: 
     30                raise Exception("Couldn't resolve target '%s' <%s> in '%s'!" 
     31                                % (key, full_path, entity.__name__)) 
     32            else: 
     33                raise Exception("Couldn't resolve target '%s' <%s>!" 
     34                                % (key, full_path)) 
     35        return res 
     36 
     37    def __getattr__(self, key): 
     38        return self.resolve(key) 
     39 
     40# default entity collection 
     41class GlobalEntityCollection(BaseCollection): 
     42    def __init__(self, entities=None): 
     43        # _entities is a dict of entities keyed on their name. 
     44        self._entities = {} 
     45        super(GlobalEntityCollection, self).__init__(entities) 
    1846 
    1947    def append(self, entity): 
     
    3159        "source" entity when resolving relationship targets. 
    3260        ''' 
    33         path = key.rsplit('.', 1) 
    34         classname = path.pop() 
    35         if path: 
    36             # Do we have a fully qualified entity name? 
    37             module = sys.modules[path.pop()] 
    38             return getattr(module, classname, None) 
     61        # Do we have a fully qualified entity name? 
     62        if '.' in key: 
     63            return self.resolve_absolute(key, key, entity) 
    3964        else: 
    4065            # Otherwise we look in the entities of this collection 
     
    4267            if res is None: 
    4368                if entity: 
    44                     raise Exception("Couldn't resolve target '%s' in '%s'" \ 
     69                    raise Exception("Couldn't resolve target '%s' in '%s'" 
    4570                                    % (key, entity.__name__)) 
    4671                else: 
     
    5782    def clear(self): 
    5883        self._entities = {} 
    59         del self[:] 
     84        super(GlobalEntityCollection, self).clear() 
     85 
     86# backward compatible name 
     87EntityCollection = GlobalEntityCollection 
     88 
     89_leading_dots = re.compile('^([.]*).*$') 
     90 
     91class RelativeEntityCollection(BaseCollection): 
     92    # the entity=None does not make any sense with a relative entity collection 
     93    def resolve(self, key, entity): 
     94        ''' 
     95        Resolve a key to an Entity. The optional `entity` argument is the 
     96        "source" entity when resolving relationship targets. 
     97        ''' 
     98        full_path = key 
     99 
     100        if '.' not in key or key.startswith('.'): 
     101            # relative target 
     102 
     103            # any leading dot is stripped and with each dot removed, 
     104            # the entity_module is stripped of one more chunk (starting with 
     105            # the last one). 
     106            num_dots = _leading_dots.match(full_path).end(1) 
     107            full_path = full_path[num_dots:] 
     108            chunks = entity.__module__.split('.') 
     109            chunkstokeep = len(chunks) - num_dots 
     110            if chunkstokeep < 0: 
     111                raise Exception("Couldn't resolve relative target " 
     112                    "'%s' relative to '%s'" % (key, entity.__module__)) 
     113            entity_module = '.'.join(chunks[:chunkstokeep]) 
     114 
     115            if entity_module and entity_module is not '__main__': 
     116                full_path = '%s.%s' % (entity_module, full_path) 
     117 
     118            root = '' 
     119        else: 
     120            root = None 
     121        return self.resolve_absolute(key, full_path, entity, root=root) 
    60122 
    61123    def __getattr__(self, key): 
    62         return self.resolve(key) 
     124        raise NotImplementedError 
    63125 
    64  
  • elixir/trunk/elixir/options.py

    r513 r515  
    226226    allowcoloverride=False, 
    227227    order_by=None, 
     228    resolve_root=None, 
    228229    mapper_options={}, 
    229230    table_options={} 
  • elixir/trunk/setup.py

    r491 r515  
    2424      author="Gaetan de Menten, Daniel Haus and Jonathan LaCour", 
    2525      author_email="sqlelixir@googlegroups.com", 
     26      #TODO: use maintainer field 
    2627      url="http://elixir.ematia.de", 
    2728      license = "MIT License", 
  • elixir/trunk/tests/test_abstract.py

    r513 r515  
    1212 
    1313def setup(): 
     14    # this is an ugly hack because globally defined entities of other tests  
     15    # (a.*, b.*, db1.* and db2.*) leak into this one because of nosetests  
     16    # habit of importing all modules before running the first test.  
     17    # test_abstract is usually the first test to be run, so it gets all the  
     18    # crap. 
     19    cleanup_all() 
     20 
    1421    metadata.bind = 'sqlite://' 
    1522    elixir.options_defaults['shortnames'] = True 
     23 
     24def teardown(): 
     25    elixir.options_defaults['shortnames'] = False 
    1626 
    1727class TestAbstractInheritance(object): 
  • elixir/trunk/tests/test_packages.py

    r490 r515  
    55import sys 
    66 
     7import elixir 
    78from elixir import * 
    89 
    910def setup(): 
    1011    metadata.bind = 'sqlite://' 
    11     sys.modules.pop('tests.a', None) 
    12     sys.modules.pop('tests.b', None) 
     12    for module in ('a', 'b', 'db1', 'db1.a', 'db1.b', 'db1.c', 'db2', 'db2.a'): 
     13        sys.modules.pop('tests.%s' % module, None) 
    1314 
    1415 
     
    4647    def test_ref_to_imported_entity_using_class(self): 
    4748        from tests.a import A 
    48         from tests.b import B 
     49        import tests.b 
    4950 
    5051        class C(Entity): 
     
    5758 
    5859    def test_ref_to_imported_entity_using_name(self): 
    59         from tests.a import A 
    60         from tests.b import B 
     60        import tests.a 
     61        import tests.b 
    6162 
    6263        class C(Entity): 
     
    6869        assert 'a_id' in C.table.columns 
    6970 
     71    def test_resolve_root(self): 
     72        import tests.a 
     73        import tests.b 
     74 
     75        class C(Entity): 
     76            using_options(resolve_root='tests') 
     77 
     78            name = Field(String(30)) 
     79            a = ManyToOne('a.A') 
     80 
     81        setup_all(True) 
     82 
     83        assert 'a_id' in C.table.columns 
     84 
     85    def test_relative_collection(self): 
     86        from elixir.collection import RelativeEntityCollection, \ 
     87                                      GlobalEntityCollection 
     88 
     89        elixir.entities = RelativeEntityCollection() 
     90 
     91        import db1 
     92        import db2 
     93 
     94        setup_all(True) 
     95 
     96        try: 
     97            assert len(elixir.entities) == 5 
     98        finally: 
     99            elixir.entities = GlobalEntityCollection()