join together various class-level and attribute-level functions into a unified interface
proposed: browse()
inspect()
. Usage of this function will replace:
class_mapper()
object_mapper()
object_session()
attribute_history()
instance_state()
SomeClass.someattr.property
SomeClass.someattr.property.columns
mapper.iterate_properties
mapper.get_property()
others ?
Inspector.from_engine()
? (non orm ! what other non-orm things could we inspect ?)
Some ideas:
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()
>>> b = inspect(User)
# column collection
>>> b.columns
[column>, <name column>](<id)
# its a ColumnCollection
>>> b.columns.id
<id column>
# i.e. from mapper
>>> b.primary_key
(<id column>, )
# i.e. from mapper
>>> b.local_table
<user table>
# ColumnProperty
>>> b.attr.id.columns
[column>](<id)
# but perhaps we use a collection with some helpers
>>> b.attr.id.columns.first
<id column>
# and a mapper? its None since this is a column
>>> b.attr.id.mapper
None
# attr is basically the _props
>>> b.attr.keys()
['name', 'name_syn', 'addresses']('id',)
# b itself is likely just the mapper
>>> b
<User mapper>
# get only column attributes
>>> b.column_attrs
[prop>, <name prop>](<id)
# its a namespace
>>> b.column_attrs.id
<id prop>
# get only synonyms
>>> b.synonyms
[syn prop>](<name)
# get only relationships
>>> b.relationships
[prop>](<addresses)
# its a namespace
>>> b.relationships.addresses
<addresses prop>
# point inspect() at a class level attribute,
# basically returns ".property"
>>> b = inspect(User.addresses)
>>> b
<addresses prop>
# mapper
>>> b.mapper
<Address mapper>
# None columns collection, just like columnprop has empty mapper
>>> b.columns
None
# the parent
>>> b.parent
<User mapper>
# __clause_element__()
>>> b.expression
User.id==Address.user_id
>>> inspect(User.id).expression
<id column with ORM annotations>
# inspect works on instances !
>>> u1 = User(id=3, name='x')
>>> b = inspect(u1)
# what's b here ? probably InstanceState
>>> b
<InstanceState>
>>> b.attr.keys()
['name', 'name_syn', 'addresses']('id',)
# this is class level stuff - should this require b.mapper.columns ?
>>> b.columns
[column>, <name column>](<id)
# does this return '3'? or an object?
>>> b.attr.id
<magic attribute inspect thing>
# or does this ?
>>> b.attr.id.value
3
>>> b.attr.id.history
<history object>
>>> b.attr.id.history.unchanged
3
>>> b.attr.id.history.deleted
None
# lets assume the object is persistent
>>> s = Session()
>>> s.add(u1)
>>> s.commit()
# big one - the primary key identity ! always
# works in query.get()
>>> b.identity
[3](3)
# the mapper level key
>>> b.identity_key
(User, [3](3))
>>> b.persistent
True
>>> b.transient
False
>>> b.deleted
False
>>> b.detached
False
>>> b.session
<session>
# the object. this navigates obj()
# of course, would be nice if it was b.obj...
>>> b.object_
<User instance u1>
# should we allow state manipulation?
>>> b.expire()
>>> b.detach()
>>> b.make_transient()
# or is that TMTOWTDI ? otherwise this:
>>> b.session.expire(b.object_)
>>> b.session.detach(b.object_)
>>> make_transient(b.object_)
Comments (13)
-
reporter -
reporter for
inspect()
to be in core, we'd use a subscription system like event:inspection.register_lookup(type, lambda obj: attributes.manager_of_class(obj).mapper) inspection.register_lookup(object, attributes.instance_state) inspection.register_lookup(Engine, Inspector.from_engine) def inspect(obj): # do a more efficient multimethod approach here for reg in registry: if reg.handles_object_type(obj): return reg.getter_function(obj) else: raise InvalidRequestError(...)
-
reporter let's add mapper.properties too, removing the old error message, as mapper.iterate_properties is incorrectly named for an attribute.
-
reporter another idea:
MyClass.someattribute.info
a namespace that unions all the 'info' attributes of the mapped columns.
-
reporter here's part of mapper
diff -r d60bc21fc69f70c8e7b63b7ed88483bbbe42250a lib/sqlalchemy/orm/mapper.py --- a/lib/sqlalchemy/orm/mapper.py Sun Feb 26 19:42:16 2012 -0500 +++ b/lib/sqlalchemy/orm/mapper.py Sun Feb 26 21:30:09 2012 -0500 @@ -33,6 +33,7 @@ import sys sessionlib = util.importlater("sqlalchemy.orm", "session") properties = util.importlater("sqlalchemy.orm", "properties") +descriptor_props = util.importlater("sqlalchemy.orm", "descriptor_props") __all__ = ( 'Mapper', @@ -1392,12 +1393,35 @@ continue yield c - @property + @util.memoized_property def properties(self): - raise NotImplementedError( - "Public collection of MapperProperty objects is " - "provided by the get_property() and iterate_properties " - "accessors.") + if _new_mappers: + configure_mappers() + return util.ImmutableProperties(self._props) + + @_memoized_configured_property + def synonyms(self): + return self._filter_properties(descriptor_props.SynonymProperty) + + @_memoized_configured_property + def column_attrs(self): + return self._filter_properties(properties.ColumnProperty) + + @_memoized_configured_property + def relationships(self): + return self._filter_properties(properties.RelationshipProperty) + + @_memoized_configured_property + def composites(self): + return self._filter_properties(descriptor_props.CompositeProperty) + + def _filter_properties(self, type_): + if _new_mappers: + configure_mappers() + return dict( + (k, v) for k, v in self._props.iteritems() + if isinstance(v, type_) + ) @_memoized_configured_property def _get_clause(self):
-
reporter - marked as critical
-
reporter this is begun at https://bitbucket.org/zzzeek/sqlalchemy-2208
-
reporter OK I've got as far we need to go with this all up in that branch, a patch is attached which illustrates it.
-
reporter this is merged as of 54017d9de202ed67072a352ce2f6dbfd74bf48f3
-
reporter needs documentation, can close when that's somewhat on board
-
reporter we will also need inspect() to work for an AliasedClass instance, and it will need to return a new inspection object where "mapped_table" returns the alias() object. "local_table" and such shouldn't be present, it will have "mapper" that leads to the mapper object. Some kind of clue that it's "aliased" as well so that it can be distinguished from inspect() on a regular mapped class.
also consider AliasedClass.table returning the alias, though this is controversial....
-
reporter - changed status to resolved
-
reporter - removed milestone
Removing milestone: 0.8.0b1 (automated comment)
- Log in to comment
sent this up to devel at http://groups.google.com/group/sqlalchemy-devel/browse_thread/thread/e91da7e28245507d .