diff options
author | Jason Kirtland <jek@discorporate.us> | 2008-03-18 02:16:15 +0000 |
---|---|---|
committer | Jason Kirtland <jek@discorporate.us> | 2008-03-18 02:16:15 +0000 |
commit | c35a7171407568025c49257ea276b32eaf205dba (patch) | |
tree | a16208a817f5bf5653d26824d8930096357de978 /lib/sqlalchemy/ext/declarative.py | |
parent | c462e42dfd705ed781c9ae0505c0c65a71c8936e (diff) | |
download | sqlalchemy-c35a7171407568025c49257ea276b32eaf205dba.tar.gz |
- Start coverage for Class.prop = Column(), promote nameless Columns
Diffstat (limited to 'lib/sqlalchemy/ext/declarative.py')
-rw-r--r-- | lib/sqlalchemy/ext/declarative.py | 166 |
1 files changed, 92 insertions, 74 deletions
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 333b1eaa4..7f30a7374 100644 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -2,62 +2,73 @@ SQLAlchemy object-relational configuration involves the usage of Table, mapper(), and class objects to define the three areas of configuration. -declarative moves these three types of configuration underneath the -individual mapped class. Regular SQLAlchemy schema and ORM -constructs are used in most cases:: +declarative moves these three types of configuration underneath the +individual mapped class. Regular SQLAlchemy schema and ORM constructs are +used in most cases:: from sqlalchemy.ext.declarative import declarative_base - + engine = create_engine('sqlite://') Base = declarative_base(engine) - + class SomeClass(Base): __tablename__ = 'some_table' id = Column('id', Integer, primary_key=True) name = Column('name', String(50)) -Above, the ``declarative_base`` callable produces a new base class from which all -mapped classes inherit from. When the class definition is completed, a new -``Table`` and ``mapper()`` have been generated, accessible via the ``__table__`` -and ``__mapper__`` attributes on the ``SomeClass`` class. +Above, the ``declarative_base`` callable produces a new base class from +which all mapped classes inherit from. When the class definition is +completed, a new ``Table`` and ``mapper()`` have been generated, accessible +via the ``__table__`` and ``__mapper__`` attributes on the ``SomeClass`` +class. + +You may omit the names from the Column definitions. Declarative will fill +them in for you. + + class SomeClass(Base): + __tablename__ = 'some_table' + id = Column(Integer, primary_key=True) + name = Column(String(50)) Attributes may be added to the class after its construction, and they will -be added to the underlying ``Table`` and ``mapper()`` definitions as appropriate:: +be added to the underlying ``Table`` and ``mapper()`` definitions as +appropriate:: SomeClass.data = Column('data', Unicode) SomeClass.related = relation(RelatedInfo) -Classes which are mapped explicitly using ``mapper()`` can interact freely with -declarative classes. The ``declarative_base`` base class contains a ``MetaData`` -object as well as a dictionary of all classes created against the base. -So to access the above metadata and create tables we can say:: +Classes which are mapped explicitly using ``mapper()`` can interact freely +with declarative classes. The ``declarative_base`` base class contains a +``MetaData`` object as well as a dictionary of all classes created against +the base. So to access the above metadata and create tables we can say:: Base.metadata.create_all() - -The ``declarative_base`` can also receive a pre-created ``MetaData`` object:: + +The ``declarative_base`` can also receive a pre-created ``MetaData`` +object:: mymetadata = MetaData() Base = declarative_base(metadata=mymetadata) Relations to other classes are done in the usual way, with the added feature -that the class specified to ``relation()`` may be a string name. The -"class registry" associated with ``Base`` is used at mapper compilation time -to resolve the name into the actual class object, which is expected to have been -defined once the mapper configuration is used:: +that the class specified to ``relation()`` may be a string name. The "class +registry" associated with ``Base`` is used at mapper compilation time to +resolve the name into the actual class object, which is expected to have +been defined once the mapper configuration is used:: class User(Base): __tablename__ = 'users' - id = Column('id', Integer, primary_key=True) - name = Column('name', String(50)) + id = Column(Integer, primary_key=True) + name = Column(String(50)) addresses = relation("Address", backref="user") - + class Address(Base): __tablename__ = 'addresses' - id = Column('id', Integer, primary_key=True) - email = Column('email', String(50)) - user_id = Column('user_id', Integer, ForeignKey('users.id')) + id = Column(Integer, primary_key=True) + email = Column(String(50)) + user_id = Column(Integer, ForeignKey('users.id')) Column constructs, since they are just that, are immediately usable, as below where we define a primary join condition on the ``Address`` class @@ -66,39 +77,41 @@ using them:: class Address(Base) __tablename__ = 'addresses' - id = Column('id', Integer, primary_key=True) - email = Column('email', String(50)) - user_id = Column('user_id', Integer, ForeignKey('users.id')) + id = Column(Integer, primary_key=True) + email = Column(String(50)) + user_id = Column(Integer, ForeignKey('users.id')) user = relation(User, primaryjoin=user_id==User.id) -Synonyms are one area where ``declarative`` needs to slightly change the usual -SQLAlchemy configurational syntax. To define a getter/setter which proxies -to an underlying attribute, use ``synonym`` with the ``instruments`` argument:: +Synonyms are one area where ``declarative`` needs to slightly change the +usual SQLAlchemy configurational syntax. To define a getter/setter which +proxies to an underlying attribute, use ``synonym`` with the ``instruments`` +argument:: class MyClass(Base): __tablename__ = 'sometable' - + _attr = Column('attr', String) - + def _get_attr(self): return self._some_attr def _set_attr(self, attr) self._some_attr = attr attr = synonym('_attr', instruments=property(_get_attr, _set_attr)) - -The above synonym is then usable as an instance attribute as well as a class-level -expression construct:: + +The above synonym is then usable as an instance attribute as well as a +class-level expression construct:: x = MyClass() x.attr = "some value" session.query(MyClass).filter(MyClass.attr == 'some other value').all() - -As an alternative to ``__tablename__``, a direct ``Table`` construct may be used:: + +As an alternative to ``__tablename__``, a direct ``Table`` construct may be +used:: class MyClass(Base): __table__ = Table('my_table', Base.metadata, - Column('id', Integer, primary_key=True), - Column('name', String(50)) + Column(Integer, primary_key=True), + Column(String(50)) ) This is the preferred approach when using reflected tables, as below:: @@ -106,42 +119,44 @@ This is the preferred approach when using reflected tables, as below:: class MyClass(Base): __table__ = Table('my_table', Base.metadata, autoload=True) -Mapper arguments are specified using the ``__mapper_args__`` class variable. -Note that the column objects declared on the class are immediately usable, as -in this joined-table inheritance example:: +Mapper arguments are specified using the ``__mapper_args__`` class variable. +Note that the column objects declared on the class are immediately usable, +as in this joined-table inheritance example:: class Person(Base): __tablename__ = 'people' - id = Column('id', Integer, primary_key=True) - discriminator = Column('type', String(50)) + id = Column(Integer, primary_key=True) + discriminator = Column(String(50)) __mapper_args__ = {'polymorphic_on':discriminator} - + class Engineer(Person): __tablename__ = 'engineers' __mapper_args__ = {'polymorphic_identity':'engineer'} - id = Column('id', Integer, ForeignKey('people.id'), primary_key=True) - primary_language = Column('primary_language', String(50)) - + id = Column(Integer, ForeignKey('people.id'), primary_key=True) + primary_language = Column(String(50)) + For single-table inheritance, the ``__tablename__`` and ``__table__`` class -variables are optional on a class when the class inherits from another mapped -class. +variables are optional on a class when the class inherits from another +mapped class. -As a convenience feature, the ``declarative_base()`` sets a default constructor -on classes which takes keyword arguments, and assigns them to the named attributes:: +As a convenience feature, the ``declarative_base()`` sets a default +constructor on classes which takes keyword arguments, and assigns them to +the named attributes:: e = Engineer(primary_language='python') -Note that ``declarative`` has no integration built in with sessions, and is only -intended as an optional syntax for the regular usage of mappers and Table objects. -A typical application setup using ``scoped_session`` might look like:: +Note that ``declarative`` has no integration built in with sessions, and is +only intended as an optional syntax for the regular usage of mappers and +Table objects. A typical application setup using ``scoped_session`` might +look like:: engine = create_engine('postgres://scott:tiger@localhost/test') Session = scoped_session(sessionmaker(transactional=True, autoflush=False, bind=engine)) Base = declarative_base() - -Mapped instances then make usage of ``Session`` in the usual way. +Mapped instances then make usage of ``Session`` in the usual way. """ + from sqlalchemy.schema import Table, SchemaItem, Column, MetaData from sqlalchemy.orm import synonym as _orm_synonym, mapper, comparable_property from sqlalchemy.orm.interfaces import MapperProperty @@ -151,11 +166,12 @@ from sqlalchemy import util __all__ = ['declarative_base', 'synonym_for', 'comparable_using', 'declared_synonym'] + class DeclarativeMeta(type): def __init__(cls, classname, bases, dict_): if '_decl_class_registry' in cls.__dict__: return type.__init__(cls, classname, bases, dict_) - + cls._decl_class_registry[classname] = cls our_stuff = util.OrderedDict() for k in dict_: @@ -170,7 +186,7 @@ class DeclarativeMeta(type): continue prop = _deferred_relation(cls, value) our_stuff[k] = prop - + table = None if '__table__' not in cls.__dict__: if '__tablename__' in cls.__dict__: @@ -182,28 +198,25 @@ class DeclarativeMeta(type): table_kw = {} cols = [] for key, c in our_stuff.iteritems(): - if not isinstance(c, Column): - continue - if c.key is None: - c.key = key - if c.name is None: - c.name = key - cols.append(c) + if isinstance(c, Column): + _undefer_column_name(key, c) + cols.append(c) cls.__table__ = table = Table(tablename, cls.metadata, *cols, **table_kw) else: table = cls.__table__ - + inherits = cls.__mro__[1] inherits = cls._decl_class_registry.get(inherits.__name__, None) mapper_args = getattr(cls, '__mapper_args__', {}) - + cls.__mapper__ = mapper(cls, table, inherits=inherits, properties=our_stuff, **mapper_args) return type.__init__(cls, classname, bases, dict_) - + def __setattr__(cls, key, value): if '__mapper__' in cls.__dict__: if isinstance(value, Column): + _undefer_column_name(key, value) cls.__table__.append_column(value) cls.__mapper__.add_property(key, value) elif isinstance(value, MapperProperty): @@ -224,13 +237,13 @@ def _deferred_relation(cls, prop): def declared_synonym(prop, name): """deprecated. use synonym(name, instrument=prop).""" - + return _orm_synonym(name, instrument=prop) declared_synonym = util.deprecated(declared_synonym) def synonym_for(name, map_column=False): """Decorator, make a Python @property a query synonym for a column. - + A decorator version of [sqlalchemy.orm#synonym()]. The function being decoratred is the 'instrument', otherwise passes its arguments through to synonym(). @@ -242,7 +255,7 @@ def synonym_for(name, map_column=False): The regular ``synonym()`` is also usable directly in a declarative setting and may be convenient for read/write properties:: - + prop = synonym('col', instrument=property(_read_prop, _write_prop)) """ @@ -287,3 +300,8 @@ def declarative_base(engine=None, metadata=None): setattr(self, k, kwargs[k]) return Base +def _undefer_column_name(key, column): + if column.key is None: + column.key = key + if column.name is None: + column.name = key |