root / elixir / trunk / elixir / entity.py @ 70

Revision 70, 10.6 kB (checked in by ged, 6 years ago)

Applied patch from Robin Munn to make the code python 2.3 compatible

Line 
1'''
2Entity baseclass, metaclass and descriptor
3'''
4
5from sqlalchemy                     import Table, Integer, desc
6from sqlalchemy.ext.assignmapper    import assign_mapper
7from elixir.statements              import Statement
8from elixir.fields                  import Field
9from elixir.options                 import options_defaults
10
11try:
12    set
13except NameError:
14    from sets import Set as set
15
16import sys
17import elixir
18
19
20__all__ = ['Entity']
21
22__pudge_all__ = __all__
23
24DEFAULT_AUTO_PRIMARYKEY_NAME = "id"
25DEFAULT_AUTO_PRIMARYKEY_TYPE = Integer
26
27
28class EntityDescriptor(object):
29    '''
30    EntityDescriptor describes fields and options needed for table creation.
31    '''
32   
33    uninitialized_rels = set()
34    current = None
35   
36    def __init__(self, entity):
37        entity.table = None
38        entity.mapper = None
39
40        self.entity = entity
41        self.module = sys.modules[entity.__module__]
42
43        self.primary_keys = list()
44
45        self.parent = None
46        for base in entity.__bases__:
47            if issubclass(base, Entity) and base is not Entity:
48                if self.parent:
49                    raise Exception('%s entity inherits from several entities,'
50                                    ' and this is not supported.' 
51                                    % self.entity.__name__)
52                else:
53                    self.parent = base
54
55        self.fields = dict()
56        self.relationships = dict()
57        self.constraints = list()
58
59        #CHECKME: this is a workaround for the "current" descriptor/target
60        # property ugliness. The problem is that this workaround is ugly too.
61        # I'm not sure if this is a safe practice. It works but...?
62#        setattr(self.module, entity.__name__, entity)
63
64        # set default value for options
65        self.order_by = None
66        self.tablename = None
67        self.metadata = getattr(self.module, 'metadata', elixir.metadata)
68
69        for option in ('inheritance', 
70                       'autoload', 'shortnames', 'auto_primarykey'):
71            setattr(self, option, options_defaults[option])
72
73        for option_dict in ('mapper_options', 'table_options'):
74            setattr(self, option_dict, options_defaults[option_dict].copy())
75   
76    def setup_options(self):
77        '''
78        Setup any values that might depend on using_options. For example, the
79        tablename or the metadata.
80        '''
81        elixir.metadatas.add(self.metadata)
82
83        entity = self.entity
84
85        if not self.tablename:
86            if self.shortnames:
87                self.tablename = entity.__name__.lower()
88            else:
89                modulename = entity.__module__.replace('.', '_')
90                tablename = "%s_%s" % (modulename, entity.__name__)
91                self.tablename = tablename.lower()
92   
93    def setup(self):
94        '''
95        Create tables, keys, columns that have been specified so far and
96        assign a mapper. Will be called when an instance of the entity is
97        created or a mapper is needed to access one or many instances of the
98        entity. It will try to initialize the entity's relationships (along
99        with any delayed relationship) but some of them might be delayed.
100        '''
101        if elixir.delay_setup:
102            elixir.delayed_entities.add(self)
103            return
104
105        self.setup_table()
106        self.setup_mapper()
107
108        # This marks all relations of the entity (or, at least those which
109        # have been added so far by statements) as being uninitialized
110        EntityDescriptor.uninitialized_rels.update(
111            self.relationships.values())
112
113        # try to setup all uninitialized relationships
114        EntityDescriptor.setup_relationships()
115   
116    def translate_order_by(self, order_by):
117        if isinstance(order_by, basestring):
118            order_by = [order_by]
119       
120        order = list()
121        for field in order_by:
122            col = self.fields[field.strip('-')].column
123            if field.startswith('-'):
124                col = desc(col)
125            order.append(col)
126        return order
127
128    def setup_mapper(self):
129        '''
130        Initializes and assign an (empty!) mapper to the entity.
131        '''
132        if self.entity.mapper:
133            return
134       
135        session = getattr(self.module, 'session', elixir.objectstore)
136       
137        kwargs = self.mapper_options
138        if self.order_by:
139            kwargs['order_by'] = self.translate_order_by(self.order_by)
140       
141        if self.parent:
142            if self.inheritance == 'single':
143                # at this point, we don't know whether the parent relationships
144                # have already been processed or not. Some of them might be,
145                # some other might not.
146                if not self.parent.mapper:
147                    self.parent._descriptor.setup_mapper()
148                kwargs['inherits'] = self.parent.mapper
149
150        assign_mapper(session.context, self.entity, self.entity.table, 
151                      **kwargs)
152
153
154    def setup_table(self):
155        '''
156        Create a SQLAlchemy table-object with all columns that have been
157        defined up to this point.
158        '''
159        if self.entity.table:
160            return
161       
162        if self.parent:
163            if self.inheritance == 'single':
164                # reuse the parent's table
165                if not self.parent.table:
166                    self.parent._descriptor.setup_table()
167                   
168                self.entity.table = self.parent.table 
169                self.primary_keys = self.parent._descriptor.primary_keys
170
171                # re-add the entity fields to the parent entity so that they
172                # are added to the parent's table (whether the parent's table
173                # is setup already or not).
174                for field in self.fields.itervalues():
175                    self.parent._descriptor.add_field(field)
176
177                return
178#            elif self.inheritance == 'concrete':
179                # do not reuse parent table, but copy all fields
180                # the problem is that, at this points, all "plain" fields
181                # are known, but not those generated by relations
182#                for field in self.fields.itervalues():
183#                    self.add_field(field)
184
185        if not self.autoload:
186            if not self.primary_keys and self.auto_primarykey:
187                self.create_auto_primary_key()
188
189        # create list of columns and constraints
190        args = [field.column for field in self.fields.values()] \
191                    + self.constraints
192       
193        # specify options
194        kwargs = self.table_options
195
196        if self.autoload:
197            kwargs['autoload'] = True
198       
199        self.entity.table = Table(self.tablename, self.metadata, 
200                                  *args, **kwargs)
201   
202    def create_auto_primary_key(self):
203        '''
204        Creates a primary key
205        '''
206       
207        assert not self.primary_keys and self.auto_primarykey
208       
209        if isinstance(self.auto_primarykey, basestring):
210            colname = self.auto_primarykey
211        else:
212            colname = DEFAULT_AUTO_PRIMARYKEY_NAME
213       
214        self.add_field(Field(DEFAULT_AUTO_PRIMARYKEY_TYPE,
215                             colname=colname, primary_key=True))
216       
217    def add_field(self, field):
218        self.fields[field.colname] = field
219       
220        if field.primary_key:
221            self.primary_keys.append(field)
222       
223        table = self.entity.table
224        if table:
225            table.append_column(field.column)
226   
227    def add_constraint(self, constraint):
228        self.constraints.append(constraint)
229       
230        table = self.entity.table
231        if table:
232            table.append_constraint(constraint)
233       
234    def get_inverse_relation(self, rel, reverse=False):
235        '''
236        Return the inverse relation of rel, if any, None otherwise.
237        '''
238
239        matching_rel = None
240        for other_rel in self.relationships.itervalues():
241            if other_rel.is_inverse(rel):
242                if matching_rel is None:
243                    matching_rel = other_rel
244                else:
245                    raise Exception(
246                            "Several relations match as inverse of the '%s' "
247                            "relation in entity '%s'. You should specify "
248                            "inverse relations manually by using the inverse "
249                            "keyword."
250                            % (rel.name, rel.entity.__name__) 
251                          )
252        # When a matching inverse is found, we check that it has only
253        # one relation matching as its own inverse. We don't need the result
254        # of the method though. But we do need to be careful not to start an
255        # infinite recursive loop.
256        if matching_rel and not reverse:
257            rel.entity._descriptor.get_inverse_relation(matching_rel, True)
258
259        return matching_rel
260
261    @property
262    def all_relationships(self):
263        if self.parent:
264            res = self.parent._descriptor.all_relationships
265        else:
266            res = dict()
267        res.update(self.relationships)
268        return res
269
270    def setup_relationships(cls):
271        for relationship in list(EntityDescriptor.uninitialized_rels):
272            if relationship.setup():
273                EntityDescriptor.uninitialized_rels.remove(relationship)
274    setup_relationships = classmethod(setup_relationships)
275
276
277class Entity(object):
278    '''
279    The base class for all entities
280   
281    All Elixir model objects should inherit from this class. Statements can
282    appear within the body of the definition of an entity to define its
283    fields, relationships, and other options.
284   
285    Here is an example:
286
287    ::
288   
289        class Person(Entity):
290            has_field('name', Unicode(128))
291            has_field('birthdate', DateTime, default=datetime.now)
292   
293    Please note, that if you don't specify any primary keys, Elixir will
294    automatically create one called ``id``.
295   
296    For further information, please refer to the provided examples or
297    tutorial.
298    '''
299   
300    class __metaclass__(type):
301        def __init__(cls, name, bases, dict_):
302            # only process subclasses of Entity, not Entity itself
303            if bases[0] is object:
304                return
305
306            # create the entity descriptor
307            desc = cls._descriptor = EntityDescriptor(cls)
308            EntityDescriptor.current = desc
309           
310            # process statements
311            Statement.process(cls)
312           
313            # setup misc options here (like tablename etc.)
314            desc.setup_options()
315           
316            # create table & assign (empty) mapper
317            desc.setup()
Note: See TracBrowser for help on using the browser.