[Sqlalchemy-commits] sqlalchemy: - [feature] New declarative reflection example
Brought to you by:
zzzeek
From: <co...@sq...> - 2012-01-28 22:41:35
|
details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/c41057ac6c45 changeset: 8070:c41057ac6c45 user: Mike Bayer <mi...@zz...> date: Sat Jan 28 17:31:39 2012 -0500 description: - [feature] New declarative reflection example added, illustrates how best to mix table reflection with declarative as well as uses some new features from [ticket:2356]. Subject: sqlalchemy: declarative reflection example details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/5b04465d0fd5 changeset: 8071:5b04465d0fd5 user: Mike Bayer <mi...@zz...> date: Sat Jan 28 17:41:10 2012 -0500 description: declarative reflection example diffstat: CHANGES | 5 + doc/build/orm/examples.rst | 8 + examples/declarative_reflection/__init__.py | 46 +++++++++ examples/declarative_reflection/declarative_reflection.py | 76 +++++++++++++++ lib/sqlalchemy/ext/declarative.py | 35 ++++++- 5 files changed, 169 insertions(+), 1 deletions(-) diffs (216 lines): diff -r a6884508d31b -r 5b04465d0fd5 CHANGES --- a/CHANGES Sat Jan 28 17:22:04 2012 -0500 +++ b/CHANGES Sat Jan 28 17:41:10 2012 -0500 @@ -79,6 +79,11 @@ relationship(). Thanks to Kent Bower for tests. [ticket:2374] + - [feature] New declarative reflection example + added, illustrates how best to mix table reflection + with declarative as well as uses some new features + from [ticket:2356]. + - sql - [feature] New reflection feature "autoload_replace"; when set to False on Table, the Table can be autoloaded diff -r a6884508d31b -r 5b04465d0fd5 doc/build/orm/examples.rst --- a/doc/build/orm/examples.rst Sat Jan 28 17:22:04 2012 -0500 +++ b/doc/build/orm/examples.rst Sat Jan 28 17:41:10 2012 -0500 @@ -44,6 +44,14 @@ .. automodule:: beaker_caching +.. _examples_declarative_reflection: + +Declarative Reflection +---------------------- + +Location: /examples/declarative_reflection + +.. automodule:: declarative_reflection Directed Graphs --------------- diff -r a6884508d31b -r 5b04465d0fd5 examples/declarative_reflection/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/declarative_reflection/__init__.py Sat Jan 28 17:41:10 2012 -0500 @@ -0,0 +1,46 @@ +""" +Illustrates how to mix table reflection with Declarative, such that +the reflection process itself can take place **after** all classes +are defined. Declarative classes can also override column +definitions loaded from the database. + +At the core of this example is the ability to change how Declarative +assigns mappings to classes. The ``__mapper_cls__`` special attribute +is overridden to provide a function that gathers mapping requirements +as they are established, without actually creating the mapping. +Then, a second class-level method ``prepare()`` is used to iterate +through all mapping configurations collected, reflect the tables +named within and generate the actual mappers. + +The example is new in 0.7.5 and makes usage of the new +``autoload_replace`` flag on :class:`.Table` to allow declared +classes to override reflected columns. + +Usage example:: + + Base = declarative_base(cls=DeclarativeReflectedBase) + + class Foo(Base): + __tablename__ = 'foo' + bars = relationship("Bar") + + class Bar(Base): + __tablename__ = 'bar' + + # illustrate overriding of "bar.foo_id" to have + # a foreign key constraint otherwise not + # reflected, such as when using MySQL + foo_id = Column(Integer, ForeignKey('foo.id')) + + Base.prepare(e) + + s = Session(e) + + s.add_all([ + Foo(bars=[Bar(data='b1'), Bar(data='b2')], data='f1'), + Foo(bars=[Bar(data='b3'), Bar(data='b4')], data='f2') + ]) + s.commit() + + +""" diff -r a6884508d31b -r 5b04465d0fd5 examples/declarative_reflection/declarative_reflection.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/declarative_reflection/declarative_reflection.py Sat Jan 28 17:41:10 2012 -0500 @@ -0,0 +1,76 @@ +from sqlalchemy import * +from sqlalchemy.orm import * +from sqlalchemy.ext.declarative import declarative_base, declared_attr + +class DeclarativeReflectedBase(object): + _mapper_args = [] + + @classmethod + def __mapper_cls__(cls, *args, **kw): + """Declarative will use this function in lieu of + calling mapper() directly. + + Collect each series of arguments and invoke + them when prepare() is called. + """ + + cls._mapper_args.append((args, kw)) + + @classmethod + def prepare(cls, engine): + """Reflect all the tables and map !""" + for args, kw in cls._mapper_args: + klass = args[0] + klass.__table__ = table = Table( + klass.__tablename__, + cls.metadata, + extend_existing=True, + autoload_replace=False, + autoload=True, + autoload_with=engine, + ) + klass.__mapper__ = mapper(klass, table, **kw) + + +if __name__ == '__main__': + Base= declarative_base(cls=DeclarativeReflectedBase) + + class Foo(Base): + __tablename__ = 'foo' + bars = relationship("Bar") + + class Bar(Base): + __tablename__ = 'bar' + + # illustrate overriding of "bar.foo_id" to have + # a foreign key constraint otherwise not + # reflected, such as when using MySQL + foo_id = Column(Integer, ForeignKey('foo.id')) + + e = create_engine('sqlite://', echo=True) + e.execute(""" + create table foo( + id integer primary key, + data varchar(30) + ) + """) + + e.execute(""" + create table bar( + id integer primary key, + data varchar(30), + foo_id integer + ) + """) + + Base.prepare(e) + + s = Session(e) + + s.add_all([ + Foo(bars=[Bar(data='b1'), Bar(data='b2')], data='f1'), + Foo(bars=[Bar(data='b3'), Bar(data='b4')], data='f2') + ]) + s.commit() + for f in s.query(Foo): + print f.data, ",".join([b.data for b in f.bars]) \ No newline at end of file diff -r a6884508d31b -r 5b04465d0fd5 lib/sqlalchemy/ext/declarative.py --- a/lib/sqlalchemy/ext/declarative.py Sat Jan 28 17:22:04 2012 -0500 +++ b/lib/sqlalchemy/ext/declarative.py Sat Jan 28 17:41:10 2012 -0500 @@ -264,7 +264,7 @@ ``__table__`` provides a more focused point of control for establishing table metadata, while still getting most of the benefits of using declarative. An application that uses reflection might want to load table metadata elsewhere -and simply pass it to declarative classes:: +and pass it to declarative classes:: from sqlalchemy.ext.declarative import declarative_base @@ -313,6 +313,39 @@ def name(self): return "Name: %s" % _name +Using Reflection with Declarative +================================= + +It's easy to set up a :class:`.Table` that uses ``autoload=True`` +in conjunction with a mapped class:: + + class MyClass(Base): + __table__ = Table('mytable', Base.metadata, + autoload=True, autoload_with=some_engine) + +However, one improvement that can be made here is to not +require the :class:`.Engine` to be available when classes are +being first declared. To achieve this, use the example +described at :ref:`examples_declarative_reflection` to build a +declarative base that sets up mappings only after a special +``prepare(engine)`` step is called:: + + Base = declarative_base(cls=DeclarativeReflectedBase) + + class Foo(Base): + __tablename__ = 'foo' + bars = relationship("Bar") + + class Bar(Base): + __tablename__ = 'bar' + + # illustrate overriding of "bar.foo_id" to have + # a foreign key constraint otherwise not + # reflected, such as when using MySQL + foo_id = Column(Integer, ForeignKey('foo.id')) + + Base.prepare(e) + Mapper Configuration ==================== |