root / elixir / trunk / elixir / __init__.py @ 397

Revision 397, 5.3 kB (checked in by ged, 6 years ago)

New features:
- The local_colname and remote_colname arguments on ManyToMany relationships

can now also be used to set custom names for the ManyToMany table columns.
This effectively replace the column_format on ManyToMany relationships which
is now deprecated. Change based on a patch from Diez B. Roggisch.

- Added (or rather fixed and documented) a "table" argument on ManyToMany

relationships to allow using a manually-defined Table (closes #44).

- Added a "schema" argument on ManyToMany relationship to be able to create the

ManyToMany table in a custom schema and not necessarily the same schema as
the table of the "source" entity (patch from Diez B. Roggisch).

Changes:
- Renamed remote_side and local_side ManyToMany arguments to remote_colname and

local_colname respectively to not collide with the remote_side argument
provided by SA (it doesn't make much sense on ManyToMany relationships but
still).

Bug fixes:
- Changed slightly the algorithm to generate the name of the table for

bidirectional self-referential ManyToMany relationships so that it doesn't
depend on the order of declaration of each side (closes #19). If you are
upgrading an application with existing data from an earlier version of
Elixir, you are STRONGLY ADVISED to read the upgrade notes!
=============================
TEMPORARY BUT IMPORTANT NOTE:
=============================
For now it is even worse: the table works but the relationship's meaning is
reversed. Will be fixed before 0.7 ships (see ticket #69).
=============================

- Added missing documentation for the "filter" argument on OneToMany

relationships

Not logged in CHANGES:
- bumped version
- added a few tests
- completed/fixed the API doc a bit
- added some comments in the code so that I remember why things are done this

way.

Line 
1'''
2Elixir package
3
4A declarative layer on top of the `SQLAlchemy library
5<http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides
6the ability to create simple Python classes that map directly to relational
7database tables (this pattern is often referred to as the Active Record design
8pattern), providing many of the benefits of traditional databases
9without losing the convenience of Python objects.
10
11Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the
12TurboEntity project but does not intend to replace SQLAlchemy's core features,
13and instead focuses on providing a simpler syntax for defining model objects
14when you do not need the full expressiveness of SQLAlchemy's manual mapper
15definitions.
16'''
17
18try:
19    set
20except NameError:
21    from sets import Set as set
22
23import sys
24import warnings
25
26from py23compat import rsplit
27
28import sqlalchemy
29from sqlalchemy.types import *
30
31from elixir.options import using_options, using_table_options, \
32                           using_mapper_options, options_defaults
33from elixir.entity import Entity, EntityMeta, EntityDescriptor, \
34                          setup_entities, cleanup_entities
35from elixir.fields import has_field, Field
36from elixir.relationships import belongs_to, has_one, has_many, \
37                                 has_and_belongs_to_many, \
38                                 ManyToOne, OneToOne, OneToMany, ManyToMany
39from elixir.properties import has_property, GenericProperty, ColumnProperty, \
40                              Synonym
41from elixir.statements import Statement
42
43
44__version__ = '0.7.0'
45
46__all__ = ['Entity', 'EntityMeta', 'EntityCollection', 'entities',
47           'Field', 'has_field',
48           'has_property', 'GenericProperty', 'ColumnProperty', 'Synonym',
49           'belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many',
50           'ManyToOne', 'OneToOne', 'OneToMany', 'ManyToMany',
51           'using_options', 'using_table_options', 'using_mapper_options',
52           'options_defaults', 'metadata', 'session',
53           'create_all', 'drop_all',
54           'setup_all', 'cleanup_all',
55           'setup_entities', 'cleanup_entities'] + \
56           sqlalchemy.types.__all__
57
58__doc_all__ = ['create_all', 'drop_all',
59               'setup_all', 'cleanup_all',
60               'metadata', 'session']
61
62# default session
63session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker())
64
65# default metadata
66metadata = sqlalchemy.MetaData()
67
68metadatas = set()
69
70# default entity collection
71class EntityCollection(list):
72    def __init__(self):
73        # _entities is a dict of entities keyed on their name.
74        self._entities = {}
75        list.__init__(self)
76
77    def append(self, entity):
78        '''
79        Add an entity to the collection.
80        '''
81        super(EntityCollection, self).append(entity)
82
83        key = entity.__name__
84        mapped_entity = self._entities.get(key)
85        if mapped_entity:
86            if isinstance(mapped_entity, list):
87                mapped_entity.append(entity)
88            else:
89                self._entities[key] = [mapped_entity, entity]
90        else:
91            self._entities[key] = entity
92
93    def resolve(self, key, entity=None):
94        '''
95        Resolve a key to an Entity. The optional `entity` argument is the
96        "source" entity when resolving relationship targets.
97        '''
98        path = rsplit(key, '.', 1)
99        classname = path.pop()
100        if path:
101            # Do we have a fully qualified entity name?
102            module = sys.modules[path.pop()]
103            return getattr(module, classname, None)
104        else:
105            # Otherwise we look in the entities of this collection
106            res = self._entities[key]
107            if isinstance(res, list):
108                raise Exception("'%s' resolves to several entities, you should "
109                                "use the full path (including the full module "
110                                "name) to that entity." % key)
111            else:
112                return res
113
114    def clear(self):
115        self._entities = {}
116        del self[:]
117
118    def __getattr__(self, key):
119        return self.resolve(key)
120
121entities = EntityCollection()
122
123
124def create_all(*args, **kwargs):
125    '''Create the necessary tables for all declared entities'''
126    for md in metadatas:
127        md.create_all(*args, **kwargs)
128
129
130def drop_all(*args, **kwargs):
131    '''Drop tables for all declared entities'''
132    for md in metadatas:
133        md.drop_all(*args, **kwargs)
134
135
136def setup_all(create_tables=False, *args, **kwargs):
137    '''Setup the table and mapper of all entities in the default entity
138    collection.
139
140    This is called automatically if any entity of the collection is configured
141    with the `autosetup` option and it is first accessed,
142    instanciated (called) or the create_all method of a metadata containing
143    tables from any of those entities is called.
144    '''
145    setup_entities(entities)
146
147    # issue the "CREATE" SQL statements
148    if create_tables:
149        create_all(*args, **kwargs)
150
151
152def cleanup_all(drop_tables=False, *args, **kwargs):
153    '''Clear all mappers, clear the session, and clear all metadatas.
154    Optionally drops the tables.
155    '''
156    session.close()
157
158    cleanup_entities(entities)
159
160    sqlalchemy.orm.clear_mappers()
161    entities.clear()
162
163    if drop_tables:
164        drop_all(*args, **kwargs)
165
166    for md in metadatas:
167        md.clear()
168    metadatas.clear()
169
Note: See TracBrowser for help on using the browser.