diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-10-26 19:25:23 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-10-26 19:25:23 -0400 |
commit | d68bf88fa9147591a245f23562154d9562e4b744 (patch) | |
tree | 609931e247dcff1b5f0012b31020afc190e8092b | |
parent | 7f043a9666eecdecc54fe779ffdd50a7d5bb0086 (diff) | |
download | sqlalchemy-d68bf88fa9147591a245f23562154d9562e4b744.tar.gz |
- add class_ to AliasedInsp
- redefine inspect(Class.attrname).parent to be always an inspectable
target; either Mapper or AliasedInsp
- add most major features to 08 migration, document, link
-rw-r--r-- | doc/build/changelog/migration_08.rst | 577 | ||||
-rw-r--r-- | doc/build/conf.py | 5 | ||||
-rw-r--r-- | doc/build/core/inspection.rst | 10 | ||||
-rw-r--r-- | doc/build/core/tutorial.rst | 2 | ||||
-rw-r--r-- | doc/build/core/types.rst | 2 | ||||
-rw-r--r-- | doc/build/glossary.rst | 59 | ||||
-rw-r--r-- | doc/build/index.rst | 1 | ||||
-rw-r--r-- | doc/build/orm/extensions/declarative.rst | 4 | ||||
-rw-r--r-- | doc/build/orm/inheritance.rst | 2 | ||||
-rw-r--r-- | doc/build/orm/internals.rst | 8 | ||||
-rw-r--r-- | doc/build/orm/session.rst | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 21 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 14 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 17 | ||||
-rw-r--r-- | test/orm/test_inspect.py | 10 |
17 files changed, 514 insertions, 230 deletions
diff --git a/doc/build/changelog/migration_08.rst b/doc/build/changelog/migration_08.rst index a02c15f39..33264860c 100644 --- a/doc/build/changelog/migration_08.rst +++ b/doc/build/changelog/migration_08.rst @@ -49,8 +49,8 @@ SQLAlchemy will eventually drop 2.5 support as well - when ``2to3`` tool and maintaining a source base that works with Python 2 and 3 at the same time. -New Features -============ +New ORM Features +================ .. _feature_relationship_08: @@ -179,8 +179,10 @@ entities. The new system includes these features: :ticket:`1401` :ticket:`610` -New Class Inspection System ---------------------------- +.. _feature_orminspection_08: + +New Class/Object Inspection System +---------------------------------- Lots of SQLAlchemy users are writing systems that require the ability to inspect the attributes of a mapped class, @@ -207,131 +209,111 @@ as :class:`.Mapper`, :class:`.InstanceState`, and :class:`.MapperProperty`: :: - class User(Base): - __tablename__ = 'user' - - id = Column(Integer, primary_key=True) - name = Column(String) - name_syn = synonym(name) - addresses = relationship(Address) + >>> class User(Base): + ... __tablename__ = 'user' + ... id = Column(Integer, primary_key=True) + ... name = Column(String) + ... name_syn = synonym(name) + ... addresses = relationship("Address") + ... - # universal entry point is inspect() + >>> # universal entry point is inspect() >>> b = inspect(User) - # column collection - >>> b.columns - [<id column>, <name column>] + >>> # b in this case is the Mapper + >>> b + <Mapper at 0x101521950; User> - # its a ColumnCollection + >>> # Column namespace >>> b.columns.id - <id column> + Column('id', Integer(), table=<user>, primary_key=True, nullable=False) - # i.e. from mapper + >>> # mapper's perspective of the primary key >>> b.primary_key - (<id column>, ) + (Column('id', Integer(), table=<user>, primary_key=True, nullable=False),) - # ColumnProperty - >>> b.attr.id.columns - [<id column>] + >>> # MapperProperties available from .attrs + >>> b.attrs.keys() + ['name_syn', 'addresses', 'id', 'name'] - # get only column attributes - >>> b.column_attrs - [<id prop>, <name prop>] + >>> # .column_attrs, .relationships, etc. filter this collection + >>> b.column_attrs.keys() + ['id', 'name'] - # its a namespace - >>> b.column_attrs.id - <id prop> + >>> list(b.relationships) + [<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>] - # get only relationships - >>> b.relationships - [<addresses prop>] + >>> # they are also namespaces + >>> b.column_attrs.id + <sqlalchemy.orm.properties.ColumnProperty object at 0x101525090> - # its a namespace >>> b.relationships.addresses - <addresses prop> + <sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0> - # point inspect() at a class level attribute, - # basically returns ".property" + >>> # point inspect() at a mapped, class level attribute, + >>> # returns the attribute itself >>> b = inspect(User.addresses) >>> b - <addresses prop> + <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0> - # mapper + >>> # From here we can get the mapper: >>> b.mapper - <Address mapper> + <Mapper at 0x101525810; Address> - # None columns collection, just like columnprop has empty mapper - >>> b.columns - None - - # the parent + >>> # the parent inspector, in this case a mapper >>> b.parent - <User mapper> - - # __clause_element__() - >>> b.expression - User.id==Address.user_id + <Mapper at 0x101521950; User> - >>> inspect(User.id).expression - <id column with ORM annotations> + >>> # an expression + >>> print b.expression + "user".id = address.user_id - # inspect works on instances ! + >>> # inspect works on instances >>> u1 = User(id=3, name='x') >>> b = inspect(u1) - # what's b here ? probably InstanceState + >>> # it returns the InstanceState >>> b - <InstanceState> + <sqlalchemy.orm.state.InstanceState object at 0x10152bed0> - >>> b.attr.keys() - ['id', 'name', 'name_syn', 'addresses'] + >>> # similar attrs accessor refers to the + >>> b.attrs.keys() + ['id', 'name_syn', 'addresses', 'name'] - # attribute interface - >>> b.attr.id - <magic attribute inspect thing> + >>> # attribute interface - from attrs, you get a state object + >>> b.attrs.id + <sqlalchemy.orm.state.AttributeState object at 0x10152bf90> - # value - >>> b.attr.id.value + >>> # this object can give you, current value... + >>> b.attrs.id.value 3 - # history - >>> b.attr.id.history - <history object> + >>> # ... current history + >>> b.attrs.id.history + History(added=[3], unchanged=(), deleted=()) - >>> b.attr.id.history.unchanged - 3 - - >>> b.attr.id.history.deleted - None - - # lets assume the object is persistent + >>> # InstanceState can also provide session state information + >>> # lets assume the object is persistent >>> s = Session() >>> s.add(u1) >>> s.commit() - # big one - the primary key identity ! always - # works in query.get() + >>> # now we can get primary key identity, always + >>> # works in query.get() >>> b.identity - [3] + (3,) - # the mapper level key + >>> # the mapper level key >>> b.identity_key - (User, [3]) - - >>> b.persistent - True + (<class '__main__.User'>, (3,)) - >>> b.transient - False - - >>> b.deleted - False - - >>> b.detached - False + >>> # state within the session + >>> b.persistent, b.transient, b.deleted, b.detached + (True, False, False, False) + >>> # owning session >>> b.session - <session> + <sqlalchemy.orm.session.Session object at 0x101701150> .. seealso:: @@ -339,79 +321,6 @@ as :class:`.Mapper`, :class:`.InstanceState`, and :class:`.MapperProperty`: :ticket:`2208` -Fully extensible, type-level operator support in Core ------------------------------------------------------ - -The Core has to date never had any system of adding support -for new SQL operators to Column and other expression -constructs, other than the :meth:`.ColumnOperators.op` method -which is "just enough" to make things work. There has also -never been any system in place for Core which allows the -behavior of existing operators to be overridden. Up until -now, the only way operators could be flexibly redefined was -in the ORM layer, using :func:`.column_property` given a -``comparator_factory`` argument. Third party libraries -like GeoAlchemy therefore were forced to be ORM-centric and -rely upon an array of hacks to apply new opertions as well -as to get them to propagate correctly. - -The new operator system in Core adds the one hook that's -been missing all along, which is to associate new and -overridden operators with *types*. Since after all, it's -not really a column, CAST operator, or SQL function that -really drives what kinds of operations are present, it's the -*type* of the expression. The implementation details are -minimal - only a few extra methods are added to the core -:class:`.ColumnElement` type so that it consults it's -:class:`.TypeEngine` object for an optional set of operators. -New or revised operations can be associated with any type, -either via subclassing of an existing type, by using -:class:`.TypeDecorator`, or "globally across-the-board" by -attaching a new :class:`.TypeEngine.Comparator` object to an existing type -class. - -For example, to add logarithm support to :class:`.Numeric` types: - -:: - - - from sqlalchemy.types import Numeric - from sqlalchemy.sql import func - - class CustomNumeric(Numeric): - class comparator_factory(Numeric.Comparator): - def log(self, other): - return func.log(self.expr, other) - -The new type is usable like any other type: - -:: - - - data = Table('data', metadata, - Column('id', Integer, primary_key=True), - Column('x', CustomNumeric(10, 5)), - Column('y', CustomNumeric(10, 5)) - ) - - stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value) - print conn.execute(stmt).fetchall() - - -New features which should come from this immediately are -support for Postgresql's HSTORE type, which is ready to go -in a separate library which may be merged, as well as all -the special operations associated with Postgresql's ARRAY -type. It also paves the way for existing types to acquire -lots more operators that are specific to those types, such -as more string, integer and date operators. - - .. seealso:: - - `Postgresql HSTORE <https://bitbucket.org/audriusk/hstore>`_ - support for HSTORE in SQLAlchemy - -:ticket:`2547` - New with_polymorphic() feature, can be used anywhere ---------------------------------------------------- @@ -451,7 +360,6 @@ constructs; also works with most relationship functions like :: - # use eager loading in conjunction with with_polymorphic targets Job_P = with_polymorphic(Job, SubJob, aliased=True) q = s.query(DataContainer).\ @@ -471,8 +379,13 @@ constructs; also works with most relationship functions like q = s.query(Job).join(DataContainer.jobs).\ filter( DataContainer.jobs.of_type(Job_A).\ - any(and_(Job_A.id < Job.id, Job_A.type=='fred')) + any(and_(Job_A.id < Job.id, Job_A.type=='fred') + ) + ) +.. seealso:: + + :ref:`of_type` :ticket:`2438` :ticket:`1106` @@ -511,8 +424,237 @@ in one step: ReflectedOne.prepare(engine_one) ReflectedTwo.prepare(engine_two) +.. seealso:: + + :class:`.DeferredReflection` + :ticket:`2485` +ORM Classes Now Accepted by Core Constructs +------------------------------------------- + +While the SQL expressions used with :class:`.Query.filter`, +such as ``User.id == 5``, have always been compatible for +use with core constructs such as :func:`.select`, the mapped +class itself would not be recognized when passed to :func:`.select`, +:meth:`.Select.select_from`, or :meth:`.Select.correlate`. +A new SQL registration system allows a mapped class to be +accepted as a FROM clause within the core:: + + from sqlalchemy import select + + stmt = select([User]).where(User.id == 5) + +Above, the mapped ``User`` class will expand into +:class:`.Table` to which :class:`.User` is mapped. + +:ticket:`2245` + +Query.update() supports UPDATE..FROM +------------------------------------- + +The new UPDATE..FROM mechanics work in query.update(). +Below, we emit an UPDATE against ``SomeEntity``, adding +a FROM clause (or equivalent, depending on backend) +against ``SomeOtherEntity``:: + + query(SomeEntity).\ + filter(SomeEntity.id==SomeOtherEntity.id).\ + filter(SomeOtherEntity.foo=='bar').\ + update({"data":"x"}) + +In particular, updates to joined-inheritance +entities are supported, provided the target of the UPDATE is local to the +table being filtered on, or if the parent and child tables +are mixed, they are joined explicitly in the query. Below, +given ``Engineer`` as a joined subclass of ``Person``: + +:: + + query(Engineer).\ + filter(Person.id==Engineer.id).\ + filter(Person.name=='dilbert').\ + update({"engineer_data":"java"}) + +would produce: + +:: + + UPDATE engineer SET engineer_data='java' FROM person + WHERE person.id=engineer.id AND person.name='dilbert' + +:ticket:`2365` + +rollback() will only roll back "dirty" objects from a begin_nested() +-------------------------------------------------------------------- + +A behavioral change that should improve efficiency for those +users using SAVEPOINT via ``Session.begin_nested()`` - upon +``rollback()``, only those objects that were made dirty +since the last flush will be expired, the rest of the +``Session`` remains intact. This because a ROLLBACK to a +SAVEPOINT does not terminate the containing transaction's +isolation, so no expiry is needed except for those changes +that were not flushed in the current transaction. + +:ticket:`2452` + +Caching Example now uses dogpile.cache +--------------------------------------- + +The caching example now uses `dogpile.cache <http://dogpilecache.readthedocs.org/>`_. +Dogpile.cache is a rewrite of the caching portion +of Beaker, featuring vastly simpler and faster operation, +as well as support for distributed locking. + +.. seealso:: + + :mod:`dogpile_caching` + +:ticket:`2589` + +New Core Features +================== + +Fully extensible, type-level operator support in Core +----------------------------------------------------- + +The Core has to date never had any system of adding support +for new SQL operators to Column and other expression +constructs, other than the :meth:`.ColumnOperators.op` method +which is "just enough" to make things work. There has also +never been any system in place for Core which allows the +behavior of existing operators to be overridden. Up until +now, the only way operators could be flexibly redefined was +in the ORM layer, using :func:`.column_property` given a +``comparator_factory`` argument. Third party libraries +like GeoAlchemy therefore were forced to be ORM-centric and +rely upon an array of hacks to apply new opertions as well +as to get them to propagate correctly. + +The new operator system in Core adds the one hook that's +been missing all along, which is to associate new and +overridden operators with *types*. Since after all, it's +not really a column, CAST operator, or SQL function that +really drives what kinds of operations are present, it's the +*type* of the expression. The implementation details are +minimal - only a few extra methods are added to the core +:class:`.ColumnElement` type so that it consults it's +:class:`.TypeEngine` object for an optional set of operators. +New or revised operations can be associated with any type, +either via subclassing of an existing type, by using +:class:`.TypeDecorator`, or "globally across-the-board" by +attaching a new :class:`.TypeEngine.Comparator` object to an existing type +class. + +For example, to add logarithm support to :class:`.Numeric` types: + +:: + + + from sqlalchemy.types import Numeric + from sqlalchemy.sql import func + + class CustomNumeric(Numeric): + class comparator_factory(Numeric.Comparator): + def log(self, other): + return func.log(self.expr, other) + +The new type is usable like any other type: + +:: + + + data = Table('data', metadata, + Column('id', Integer, primary_key=True), + Column('x', CustomNumeric(10, 5)), + Column('y', CustomNumeric(10, 5)) + ) + + stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value) + print conn.execute(stmt).fetchall() + + +New features which should come from this immediately are +support for Postgresql's HSTORE type, which is ready to go +in a separate library which may be merged, as well as all +the special operations associated with Postgresql's ARRAY +type. It also paves the way for existing types to acquire +lots more operators that are specific to those types, such +as more string, integer and date operators. + +.. seealso:: + + :ref:`types_operators` + + `Postgresql HSTORE <https://bitbucket.org/audriusk/hstore>`_ - support for HSTORE in SQLAlchemy + +:ticket:`2547` + +Type Expressions +----------------- + +SQL expressions can now be associated with types. Historically, +:class:`.TypeEngine` has always allowed Python-side functions which +receive both bound parameters as well as result row values, passing +them through a Python side conversion function on the way to/back from +the database. The new feature allows similar +functionality, except on the database side:: + + from sqlalchemy.types import String + from sqlalchemy import func, Table, Column, MetaData + + class LowerString(String): + def bind_expression(self, bindvalue): + return func.lower(bindvalue) + + def column_expression(self, col): + return func.lower(col) + + metadata = MetaData() + test_table = Table( + 'test_table', + metadata, + Column('data', LowerString) + ) + +Above, the ``LowerString`` type defines a SQL expression that will be emitted +whenever the ``test_table.c.data`` column is rendered in the columns +clause of a SELECT statement:: + + >>> print select([test_table]).where(test_table.c.data == 'HI') + SELECT lower(test_table.data) AS data + FROM test_table + WHERE test_table.data = lower(:data_1) + +This feature is also used heavily by the new release of GeoAlchemy, +to embed PostGIS expressions inline in SQL based on type rules. + +.. seealso:: + + :ref:`types_sql_value_processing` + +:ticket:`1534` + +Core Inspection System +----------------------- + +The :func:`.inspect` function introduced in :ref:`feature_orminspection_08` +also applies to the core. Applied to an :class:`.Engine` it produces +an :class:`.Inspector` object:: + + from sqlalchemy import inspect + from sqlalchemy import create_engine + + engine = create_engine("postgresql://scott:tiger@localhost/test") + insp = inspect(engine) + print insp.get_table_names() + +It can also be applied to any :class:`.ClauseElement`, which returns +the :class:`.ClauseElement` itself, such as :class:`.Table`, :class:`.Column`, +:class:`.Select`, etc. This allows it to work fluently between Core +and ORM constructs. + New, configurable DATE, TIME types for SQLite --------------------------------------------- @@ -541,46 +683,49 @@ everything else. ) ) +Huge thanks to Nate Dub for the sprinting on this at Pycon 2012. -Huge thanks to Nate Dub for the sprinting on this at Pycon -'12. +.. seealso:: -:ticket:`2363` + :class:`.sqlite.DATETIME` -Query.update() will support UPDATE..FROM ----------------------------------------- + :class:`.sqlite.DATE` -Not 100% sure if this will make it in, the new UPDATE..FROM -mechanics should work in query.update(): + :class:`.sqlite.TIME` -:: +:ticket:`2363` - query(SomeEntity).\ - filter(SomeEntity.id==SomeOtherEntity.id).\ - filter(SomeOtherEntity.foo=='bar').\ - update({"data":"x"}) +New Method :meth:`.Select.correlate_except` +------------------------------------------- -Should also work when used against a joined-inheritance -entity, provided the target of the UPDATE is local to the -table being filtered on, or if the parent and child tables -are mixed, they are joined explicitly in the query. Below, -given ``Engineer`` as a joined subclass of ``Person``: +:func:`.select` now has a method :meth:`.Select.correlate_except` +which specifies "correlate on all FROM clauses except those +specified". It can be used for mapping scenarios where +a related subquery should correlate normally, except +against a particular target selectable:: -:: + class SnortEvent(Base): + __tablename__ = "event" - query(Engineer).\ - filter(Person.id==Engineer.id).\ - filter(Person.name=='dilbert').\ - update({"engineer_data":"java"}) + id = Column(Integer, primary_key=True) + signature = Column(Integer, ForeignKey("signature.id")) -would produce: + signatures = relationship("Signature", lazy=False) -:: + class Signature(Base): + __tablename__ = "signature" - UPDATE engineer SET engineer_data='java' FROM person - WHERE person.id=engineer.id AND person.name='dilbert' + id = Column(Integer, primary_key=True) -:ticket:`2365` + sig_count = column_property( + select([func.count('*')]).\ + where(SnortEvent.signature == id). + correlate_except(SnortEvent) + ) + +.. seealso:: + + :meth:`.Select.correlate_except` Enhanced Postgresql ARRAY type ------------------------------ @@ -600,21 +745,53 @@ results: # to guess how many levels deep to go Column("my_array", postgresql.ARRAY(Integer, dimensions=2)) +.. seealso:: + + :class:`.postgresql.ARRAY` + :ticket:`2441` -rollback() will only roll back "dirty" objects from a begin_nested() --------------------------------------------------------------------- +"COLLATE" supported across all dialects; in particular MySQL, Postgresql, SQLite +-------------------------------------------------------------------------------- -A behavioral change that should improve efficiency for those -users using SAVEPOINT via ``Session.begin_nested()`` - upon -``rollback()``, only those objects that were made dirty -since the last flush will be expired, the rest of the -``Session`` remains intact. This because a ROLLBACK to a -SAVEPOINT does not terminate the containing transaction's -isolation, so no expiry is needed except for those changes -that were not flushed in the current transaction. +The "collate" keyword, long accepted by the MySQL dialect, is now established +on all :class:`.String` types and will render on any backend, including +when features such as :meth:`.MetaData.create_all` and :func:`.cast` is used:: + + >>> stmt = select([cast(sometable.c.somechar, String(20, collation='utf8'))]) + >>> print stmt + SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1 + FROM sometable + +.. seealso:: + + :class:`.String` + +:ticket:`2276` + +"Prefixes" now supported for :func:`.insert`, :func:`.update`, :func:`.delete` +------------------------------------------------------------------------------- + +Geared towards MySQL, a "prefix" can be rendered within any of these +statements:: + + stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql") + + + stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql") + +.. seealso:: + + :meth:`.Insert.prefix_with` + + :meth:`.Update.prefix_with` + + :meth:`.Delete.prefix_with` + + :meth:`.Select.prefix_with` + +:ticket:`2431` -:ticket:`2452` Behavioral Changes ================== diff --git a/doc/build/conf.py b/doc/build/conf.py index b89b81c0e..ca6292adc 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -31,8 +31,9 @@ import sqlalchemy # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', +extensions = [ + 'sphinx.ext.autodoc', + # 'sphinx.ext.doctest', 'builder.autodoc_mods', 'builder.changelog', 'builder.dialect_info', diff --git a/doc/build/core/inspection.rst b/doc/build/core/inspection.rst index 777ee944e..6d36f1015 100644 --- a/doc/build/core/inspection.rst +++ b/doc/build/core/inspection.rst @@ -1,4 +1,5 @@ .. _core_inspection_toplevel: +.. _inspection_toplevel: Runtime Inpection API ===================== @@ -25,8 +26,11 @@ Following is a listing of all inspection targets. * ``type`` (i.e. a class) - a class given will be checked by the ORM for a mapping - if so, a :class:`.Mapper` for that class is returned. * mapped attribute - passing a mapped attribute to :func:`.inspect`, such - as ``inspect(MyClass.some_attribute)``, returns a :class:`.MapperProperty` - object, which usually is an instance of :class:`.ColumnProperty` - or :class:`.RelationshipProperty`. + as ``inspect(MyClass.some_attribute)``, returns a :class:`.QueryableAttribute` + object, which is the :term:`descriptor` associated with a mapped class. + This descriptor refers to a :class:`.MapperProperty`, which is usually + an instance of :class:`.ColumnProperty` + or :class:`.RelationshipProperty`, via its :attr:`.QueryableAttribute.property` + attribute. * :class:`.AliasedClass` - returns an :class:`.AliasedInsp` object. diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst index e6e3dc689..e9ed5c27a 100644 --- a/doc/build/core/tutorial.rst +++ b/doc/build/core/tutorial.rst @@ -1310,6 +1310,8 @@ well: () {stop}[(u'jack', 2), (u'wendy', 2), (u'fred', 0), (u'mary', 0)] +.. _correlated_subqueries: + Correlated Subqueries --------------------- diff --git a/doc/build/core/types.rst b/doc/build/core/types.rst index 2838b722f..745752f0e 100644 --- a/doc/build/core/types.rst +++ b/doc/build/core/types.rst @@ -753,8 +753,6 @@ Base Type API :members: :show-inheritance: - .. autoclass:: TypeEngine.Comparator - .. automethod:: _adapt_expression .. autoclass:: Concatenable :members: diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst index 8c40fcec0..b47ae7131 100644 --- a/doc/build/glossary.rst +++ b/doc/build/glossary.rst @@ -12,6 +12,65 @@ Glossary .. glossary:: + descriptor + In Python, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the `descriptor protocol <http://docs.python.org/howto/descriptor.html>`_. + Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined + for an object, it is said to be a descriptor. + + In SQLAlchemy, descriptors are used heavily in order to provide attribute behavior + on mapped classes. When a class is mapped as such:: + + class MyClass(Base): + __tablename__ = 'foo' + + id = Column(Integer, primary_key=True) + data = Column(String) + + The ``MyClass`` class will be :term:`mapped` when its definition + is complete, at which point the ``id`` and ``data`` attributes, + starting out as :class:`.Column` objects, will be replaced + by the :term:`instrumentation` system with instances + of :class:`.InstrumentedAttribute`, which are descriptors that + provide the above mentioned ``__get__()``, ``__set__()`` and + ``__delete__()`` methods. The :class:`.InstrumentedAttribute` + will generate a SQL expression when used at the class level:: + + >>> print MyClass.data == 5 + data = :data_1 + + and at the instance level, keeps track of changes to values, + and also :term:`lazy loads` unloaded attributes + from the database:: + + >>> m1 = MyClass() + >>> m1.id = 5 + >>> m1.data = "some data" + + >>> from sqlalchemy import inspect + >>> inspect(m1).attrs.data.history.added + "some data" + + instrumentation + instrumented + Instrumentation refers to the process of augmenting the functionality + and attribute set of a particular class. Ideally, the + behavior of the class should remain close to a regular + class, except that additional behviors and features are + made available. The SQLAlchemy :term:`mapping` process, + among other things, adds database-enabled :term:`descriptors` + to a mapped + class which each represent a particular database column + or relationship to a related class. + + mapping + mapped + We say a class is "mapped" when it has been passed through the + :func:`.orm.mapper` function. This process associates the + class with a database table or other :term:`selectable` + construct, so that instances of it can be persisted + using a :class:`.Session` as well as loaded using a + :class:`.Query`. + release releases released diff --git a/doc/build/index.rst b/doc/build/index.rst index 0c8c19b28..cacbf570c 100644 --- a/doc/build/index.rst +++ b/doc/build/index.rst @@ -12,6 +12,7 @@ A high level view and getting set up. :ref:`Overview <overview>` | :ref:`Installation Guide <installation>` | :doc:`Migration from 0.7 <changelog/migration_08>` | +:doc:`Glossary <glossary>` | :doc:`Changelog catalog <changelog/index>` SQLAlchemy ORM diff --git a/doc/build/orm/extensions/declarative.rst b/doc/build/orm/extensions/declarative.rst index c2ca4eabc..35895e8df 100644 --- a/doc/build/orm/extensions/declarative.rst +++ b/doc/build/orm/extensions/declarative.rst @@ -4,7 +4,7 @@ Declarative =========== .. automodule:: sqlalchemy.ext.declarative - + API Reference ------------- @@ -12,7 +12,7 @@ API Reference .. autoclass:: declared_attr -.. autofunction:: _declarative_constructor +.. autofunction:: sqlalchemy.ext.declarative.api._declarative_constructor .. autofunction:: has_inherited_table diff --git a/doc/build/orm/inheritance.rst b/doc/build/orm/inheritance.rst index 50d5c85d0..cf199841b 100644 --- a/doc/build/orm/inheritance.rst +++ b/doc/build/orm/inheritance.rst @@ -330,6 +330,8 @@ what's specified in the :meth:`.Session.query`, :meth:`.Query.filter`, or session.query(engineer.c.id).\ filter(engineer.c.engineer_info==manager.c.manager_data) +.. _of_type: + Creating Joins to Specific Subtypes +++++++++++++++++++++++++++++++++++ diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst index 4f640b265..94f4bfc0f 100644 --- a/doc/build/orm/internals.rst +++ b/doc/build/orm/internals.rst @@ -28,6 +28,10 @@ sections, are listed here. :members: :show-inheritance: +.. autoclass:: sqlalchemy.orm.attributes.InstrumentedAttribute + :members: + :show-inheritance: + .. autoclass:: sqlalchemy.orm.interfaces.MapperProperty :members: :show-inheritance: @@ -47,3 +51,7 @@ sections, are listed here. .. autoclass:: sqlalchemy.orm.query.QueryContext :members: :show-inheritance: + +.. autoclass:: sqlalchemy.orm.attributes.QueryableAttribute + :members: + :show-inheritance:
\ No newline at end of file diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst index 79fdebae0..52cee92fb 100644 --- a/doc/build/orm/session.rst +++ b/doc/build/orm/session.rst @@ -1971,7 +1971,7 @@ those described in :ref:`events_orm_toplevel`. as they each emit an informative exception if the given object is not mapped. -.. autofunction:: is_instrumented +.. autofunction:: sqlalchemy.orm.instrumentation.is_instrumented .. autofunction:: set_attribute diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 7effd8e58..983b4dfec 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -158,9 +158,17 @@ class QueryableAttribute(interfaces._MappedAttribute, # TODO: conditionally attach this method based on clause_element ? return self - @property + @util.memoized_property def parent(self): - return self._parententity + """Return an inspection instance representing the parent. + + This will be either an instance of :class:`.Mapper` + or :class:`.AliasedInsp`, depending upon the nature + of the parent entity which this attribute is associated + with. + + """ + return inspection.inspect(self._parententity) @property def expression(self): @@ -208,6 +216,15 @@ class QueryableAttribute(interfaces._MappedAttribute, @util.memoized_property def property(self): + """Return the :class:`.MapperProperty` associated with this + :class:`.QueryableAttribute`. + + + Return values here will commonly be instances of + :class:`.ColumnProperty` or :class:`.RelationshipProperty`. + + + """ return self.comparator.property inspection._self_inspects(QueryableAttribute) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index a0abb2743..e2b5e94e0 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -386,20 +386,8 @@ class RelationshipProperty(StrategizedProperty): return self.property.mapper @util.memoized_property - def parent(self): - """The parent :class:`.Mapper` or :class:`.AliasedClass` - referred to by this - :class:`.RelationshipProperty.Comparator. - - This is the "parent" or "local" side of the - :func:`.relationship`. - - """ - return self.property.parent - - @util.memoized_property def _parententity(self): - return self.parent + return self.property.parent def _source_selectable(self): elem = self.property.parent._with_polymorphic_selectable diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 02fb7d4f7..9289c01e1 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1751,7 +1751,6 @@ class Query(object): right_entity = onclause.property.mapper left_entity = onclause._parententity - assert left_entity is onclause.parent prop = onclause.property if not isinstance(onclause, attributes.QueryableAttribute): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index fb4197c58..cf3238b15 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -581,11 +581,20 @@ class AliasedInsp(_InspectionAttr, AliasedInsp): * ``polymorphic_on`` - an alternate column or SQL expression which will be used as the "discriminator" for a polymorphic load. + .. seealso:: + + :ref:`inspection_toplevel` + """ is_aliased_class = True "always returns True" + @property + def class_(self): + """Return the mapped class ultimately represented by this + :class:`.AliasedInsp`.""" + return self.mapper.class_ inspection._inspects(AliasedClass)(lambda target: target._aliased_insp) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 1681b26f4..84fe9a82e 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -5535,6 +5535,12 @@ class Select(HasPrefixes, SelectBase): If the fromclause is None, correlation is disabled for the returned select(). + .. seealso:: + + :meth:`.Select.correlate_except` + + :ref:`correlated_subqueries` + """ self._should_correlate = False if fromclauses and fromclauses[0] is None: @@ -5545,6 +5551,17 @@ class Select(HasPrefixes, SelectBase): @_generative def correlate_except(self, *fromclauses): + """"Return a new select() construct which will auto-correlate + on FROM clauses of enclosing selectables, except for those FROM + clauses specified here. + + .. seealso:: + + :meth:`.Select.correlate` + + :ref:`correlated_subqueries` + + """ self._should_correlate = False if fromclauses and fromclauses[0] is None: self._correlate_except = () diff --git a/test/orm/test_inspect.py b/test/orm/test_inspect.py index cd9a30b1a..fc2d2181f 100644 --- a/test/orm/test_inspect.py +++ b/test/orm/test_inspect.py @@ -191,9 +191,10 @@ class TestORMInspection(_fixtures.FixtureTest): prop = inspect(ua.addresses) is_(prop, ua.addresses) - is_(prop.property.parent, class_mapper(User)) + is_(prop.property.parent.mapper, class_mapper(User)) is_(prop.property.mapper, class_mapper(Address)) - is_(prop.parent, ua) + is_(prop.parent.entity, ua) + is_(prop.parent.class_, User) is_(prop._parentmapper, class_mapper(User)) is_(prop.mapper, class_mapper(Address)) @@ -213,9 +214,10 @@ class TestORMInspection(_fixtures.FixtureTest): prop = inspect(ua.name) is_(prop, ua.name) - is_(prop.property.parent, class_mapper(User)) + is_(prop.property.parent.mapper, class_mapper(User)) assert not hasattr(prop.property, "mapper") - is_(prop.parent, ua) + is_(prop.parent.entity, ua) + is_(prop.parent.class_, User) is_(prop._parentmapper, class_mapper(User)) assert not hasattr(prop, "mapper") |