root / elixir / trunk / elixir / collection.py

Revision 515, 4.4 kB (checked in by ged, 2 years ago)

- 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.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1'''
2Default entity collection implementation
3'''
4import sys
5import re
6
7class BaseCollection(list):
8    def __init__(self, entities=None):
9        list.__init__(self)
10        if entities is not None:
11            self.extend(entities)
12
13    def extend(self, entities):
14        for e in entities:
15            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)
46
47    def append(self, entity):
48        '''
49        Add an entity to the collection.
50        '''
51        super(EntityCollection, self).append(entity)
52
53        existing_entities = self._entities.setdefault(entity.__name__, [])
54        existing_entities.append(entity)
55
56    def resolve(self, key, entity=None):
57        '''
58        Resolve a key to an Entity. The optional `entity` argument is the
59        "source" entity when resolving relationship targets.
60        '''
61        # Do we have a fully qualified entity name?
62        if '.' in key:
63            return self.resolve_absolute(key, key, entity)
64        else:
65            # Otherwise we look in the entities of this collection
66            res = self._entities.get(key, None)
67            if res is None:
68                if entity:
69                    raise Exception("Couldn't resolve target '%s' in '%s'"
70                                    % (key, entity.__name__))
71                else:
72                    raise Exception("This collection does not contain any "
73                                    "entity corresponding to the key '%s'!"
74                                    % key)
75            elif len(res) > 1:
76                raise Exception("'%s' resolves to several entities, you should"
77                                " use the full path (including the full module"
78                                " name) to that entity." % key)
79            else:
80                return res[0]
81
82    def clear(self):
83        self._entities = {}
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)
122
123    def __getattr__(self, key):
124        raise NotImplementedError
Note: See TracBrowser for help on using the browser.