<%method code autoflush=False>
<%args>
title = None
- syntaxtype = 'myghty'
+ syntaxtype = 'python'
</%args>
<%init>
<%flags>inherit='document_base.myt'</%flags>
<&|doclib.myt:item, name="metadata", description="Database Meta Data" &>
- <&|doclib.myt:item, name="tables", description="Describing Tables with Database Meta Data" &>
+ <&|doclib.myt:item, name="tables", description="Describing Tables with MetaData" &>
+ <p>The core of SQLAlchemy's query and object mapping operations is table metadata, which are Python objects that describe tables. Metadata objects can be created by explicitly naming the table and all its properties, using the Table, Column, and ForeignKey objects: </p>
+ <&|formatting.myt:code&>
+ from sqlalchemy.schema import *
+ import sqlalchemy.sqlite as sqlite
+ engine = sqllite.engine(':memory:', {})
+
+ users = Table('users', engine,
+ Column('user_id', INTEGER, primary_key = True),
+ Column('user_name', VARCHAR(16), nullable = False),
+ Column('email_address', VARCHAR(60), key='email'),
+ Column('password', VARCHAR(20), nullable = False)
+ )
+
+ user_prefs = Table('user_prefs', engine,
+ Column('pref_id', INTEGER, primary_key = True),
+ Column('user_id', INTEGER, nullable = False, foreign_key = ForeignKey(users.c.user_id))
+ Column('pref_name', VARCHAR(40), nullable = False),
+ Column('pref_value', VARCHAR(100))
+ )
+ </&>
+
+ <p>Metadata objects can also be <b>reflected</b> from tables that already exist in the database. Reflection means based on a table name, the names, datatypes, and attributes of all columns, including foreign keys, will be loaded automatically. This feature is supported by all database engines:</p>
+ <&|formatting.myt:code&>
+ >>> messages = Table('messages', engine, autoload = True)
+ >>> [c.name for c in messages.columns]
+ ['message_id', 'message_name', 'date']
+ </&>
+
+ <p>
+ Note that if a reflected table has a foreign key referencing another table, then the metadata for the related table will be loaded as well, even if it has not been defined by the application:
+ </p>
+ <&|formatting.myt:code&>
+ >>> shopping_cart_items = Table('shopping_cart_items', engine, autoload = True)
+ >>> print shopping_cart_items.c.cart_id.table.name
+ shopping_carts
+ </&>
+ <p>To get direct access to 'shopping_carts', simply instantiate it via the Table constructor. You'll get the same instance of the shopping cart Table as the one that is attached to shopping_cart_items:
+ <&|formatting.myt:code&>
+ >>> shopping_carts = Table('shopping_carts', engine)
+ >>> shopping_carts is shopping_cart_items.c.cart_id.table.name
+ True
+ </&>
+ <p>This works because when the Table constructor is called for a particular name and database engine, if the table has already been created then the instance returned will be the same as the original. This is a <b>singleton</b> constructor:</p>
+ <&|formatting.myt:code&>
+ >>> news_articles = Table('news', engine,
+ ... Column('article_id', INTEGER, primary_key = True),
+ ... Column('url', VARCHAR(250), nullable = False)
+ ... )
+ >>> othertable = Table('news', engine)
+ >>> othertable is news_articles
+ True
+ </&>
+
+
+
</&>
<&|doclib.myt:item, name="building", description="Building and Dropping Database Tables" &>
</&>
| |------ Connection Pooling Configuration
| |
| |
- |--- <&formatting.myt:link, path="dbengine_establishing" &> |
+ +--- <&formatting.myt:link, path="dbengine_establishing" &> |
| |
| |
|--------- <&formatting.myt:link, path="dbengine_options" &>
|
|
- |---- <&formatting.myt:link, path="metadata_tables" &>
+ +---- <&formatting.myt:link, path="metadata_tables" &>
|
|
|---- <&formatting.myt:link, path="metadata_building" &>
|---- Basic Data Mapping |
| | |
| | |
- | |----------- Advanced Data Mapping
+ | +----------- Advanced Data Mapping
|
|
- |----- Basic Active Record
+ +----- Basic Active Record
</pre>
</&>
\ No newline at end of file
list of primary keys in the order of the table def's primary keys."""
key = objectstore.get_id_key(ident, self.class_, self.table)
try:
- return objectstore.get(key)
+ return objectstore.uow()._get(key)
except KeyError:
clause = sql.and_()
i = 0
except IndexError:
return None
- def put(self, instance):
- key = self.identity_key(instance)
- objectstore.put(key, instance, self.scope)
- return key
-
def identity_key(self, instance):
return objectstore.get_id_key(tuple([self._getattrbycolumn(instance, column) for column in self.primary_keys[self.selectable]]), self.class_, self.table)
self._setattrbycolumn(obj, col, primary_key)
found = True
- def register_dependencies(self, obj, uow):
+ def delete_obj(self, objects, uow):
+ pass
+
+ def register_dependencies(self, *args, **kwargs):
for prop in self.props.values():
- prop.register_dependencies(obj, uow)
+ prop.register_dependencies(*args, **kwargs)
def transaction(self, f):
return self.table.engine.multi_transaction(self.tables, f)
# been exposed to being modified by the application.
identitykey = self._identity_key(row)
if objectstore.has_key(identitykey):
- instance = objectstore.get(identitykey)
+ instance = objectstore.uow()._get(identitykey)
if result is not None:
result.append_nohistory(instance)
return instance
- def rollback(self, obj):
- objectstore.uow().rollback_object(obj)
class MapperOption:
"""describes a modification to a Mapper in the context of making a copy
def delete(self, object):
"""called when the instance is being deleted"""
pass
- def register_dependencies(self, obj, uow):
+ def register_dependencies(self, *args, **kwargs):
pass
class ColumnProperty(MapperProperty):
else:
return sql.and_(crit)
- def register_dependencies(self, objlist, uow):
+ def register_dependencies(self, uowcommit):
if self.secondaryjoin is not None:
# with many-to-many, set the parent as dependent on us, then the
# list of associations as dependent on the parent
# if only a list changes, the parent mapper is the only mapper that
# gets added to the "todo" list
- uow.register_dependency(self.mapper, self.parent, None, None)
- uow.register_dependency(self.parent, None, self, objlist)
+ uowcommit.register_dependency(self.mapper, self.parent)
+ uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False)
elif self.foreignkey.table == self.target:
- uow.register_dependency(self.parent, self.mapper, self, objlist)
+ uowcommit.register_dependency(self.parent, self.mapper)
+ uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False)
elif self.foreignkey.table == self.parent.table:
- uow.register_dependency(self.mapper, self.parent, self, objlist)
+ uowcommit.register_dependency(self.mapper, self.parent)
+ uowcommit.register_task(self.mapper, self, uowcommit.get_objects(self.parent), False)
else:
raise " no foreign key ?"
-
+
def process_dependencies(self, deplist, uowcommit, delete = False):
-
+ print self.mapper.table.name + " process_dep"
def getlist(obj):
if self.uselist:
return uowcommit.uow.attributes.get_list_history(obj, self.key)
secondary_insert = []
for obj in deplist:
childlist = getlist(obj)
- for child in childlist.added_items():
- associationrow = {}
- self.primaryjoin.accept_visitor(setter)
- self.secondaryjoin.accept_visitor(setter)
- secondary_insert.append(associationrow)
- for child in childlist.deleted_items():
- associationrow = {}
+ if delete:
clearkeys = True
- self.primaryjoin.accept_visitor(setter)
- self.secondaryjoin.accept_visitor(setter)
- secondary_delete.append(associationrow)
- if self.private:
- uowcommit.add_item_to_delete(obj)
- uowcommit.register_saved_list(childlist)
+ for child in childlist.deleted_items() + childlist.unchanged_items():
+ associationrow = {}
+ self.primaryjoin.accept_visitor(setter)
+ self.secondaryjoin.accept_visitor(setter)
+ secondary_delete.append(associationrow)
+ uowcommit.register_removed_list(childlist)
+ else:
+ clearkeys = False
+ for child in childlist.added_items():
+ associationrow = {}
+ self.primaryjoin.accept_visitor(setter)
+ self.secondaryjoin.accept_visitor(setter)
+ secondary_insert.append(associationrow)
+ clearkeys = True
+ for child in childlist.deleted_items():
+ associationrow = {}
+ self.primaryjoin.accept_visitor(setter)
+ self.secondaryjoin.accept_visitor(setter)
+ secondary_delete.append(associationrow)
+ uowcommit.register_saved_list(childlist)
if len(secondary_delete):
statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c]))
statement.execute(*secondary_delete)
for child in childlist.deleted_items() + childlist.current_items():
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
- if self.private:
- uowcommit.add_item_to_delete(child)
else:
clearkeys = False
for child in childlist.added_items():
for child in childlist.deleted_items():
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
- if self.private:
- uowcommit.add_item_to_delete(child)
elif self.foreignkey.table == self.parent.table:
associationrow = {}
for child in deplist:
for obj in childlist.deleted_items() + childlist.current_items():
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
- if self.private:
- uowcommit.add_item_to_delete(obj)
else:
clearkeys = False
for obj in childlist.added_items():
uowcommit.register_saved_list(childlist)
clearkeys = True
for obj in childlist.deleted_items():
- if self.private:
- uowcommit.add_item_to_delete(obj)
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
else:
"""
return (class_, table, tuple([row[column.label] for column in primary_keys]))
-identity_map = {}
-
-def get(key):
- val = identity_map[key]
- if isinstance(val, dict):
- return val[thread.get_ident()]
- else:
- return val
-
-def put(key, obj, scope='thread'):
- if isinstance(obj, dict):
- raise "cant put a dict in the object store"
-
- if scope == 'thread':
- try:
- d = identity_map[key]
- except KeyError:
- d = identity_map.setdefault(key, {})
- d[thread.get_ident()] = obj
- else:
- identity_map[key] = obj
-
-def clear(scope='thread'):
- if scope == 'thread':
- for k in identity_map.keys():
- if isinstance(identity_map[k], dict):
- identity_map[k].clear()
- uow.set(UnitOfWork())
- else:
- for k in identity_map.keys():
- if not isinstance(identity_map[k], dict):
- del identity_map[k]
- uow.set(UnitOfWork(), scope="application")
+def clear():
+ uow.set(UnitOfWork())
def has_key(key):
- if identity_map.has_key(key):
- d = identity_map[key]
- if isinstance(d, dict):
- return d.has_key(thread.get_ident())
- else:
- return True
- else:
- return False
+ return uow().identity_map.has_key(key)
class UOWSmartProperty(attributes.SmartProperty):
def attribute_registry(self):
class UnitOfWork(object):
def __init__(self, parent = None, is_begun = False):
self.is_begun = is_begun
+ if parent is not None:
+ self.identity_map = parent.identity_map
+ else:
+ self.identity_map = {}
self.attributes = UOWAttributeManager(self)
self.new = util.HashSet()
self.dirty = util.HashSet()
self.deleted = util.HashSet()
self.parent = parent
+ def get(self, class_, *id):
+ return sqlalchemy.mapper.object_mapper(class_).get(*id)
+
+ def _get(self, key):
+ return self.identity_map[key]
+
+ def _put(self, key, obj):
+ self.identity_map[key] = obj
+
+ def update(self, obj):
+ """called to add an object to this UnitOfWork as though it were loaded from the DB, but is
+ actually coming from somewhere else, like a web session or similar."""
+ self._put(obj._instance_key, obj)
+ self.register_dirty(obj)
+
def register_attribute(self, class_, key, uselist):
self.attributes.register_attribute(class_, key, uselist)
def attribute_set_callable(self, obj, key, func):
obj.__dict__[key] = func
- def rollback_object(self, obj):
- self.attributes.rollback(obj)
- def register_clean(self, obj, scope="thread"):
+ def register_clean(self, obj):
try:
del self.dirty[obj]
except KeyError:
del self.new[obj]
except KeyError:
pass
- # TODO: figure scope out from what scope of this UOW is
- put(obj._instance_key, obj, scope=scope)
+ self._put(obj._instance_key, obj)
# TODO: get lists off the object and make sure theyre clean too ?
def register_new(self, obj):
if self.parent:
uow.set(self.parent)
+ def rollback_object(self, obj):
+ self.attributes.rollback(obj)
+
def rollback(self):
if not self.is_begun:
raise "UOW transaction is not begun"
self.saved_objects = util.HashSet()
self.saved_lists = util.HashSet()
self.deleted_objects = util.HashSet()
- self.todelete = util.HashSet()
def append_task(self, obj):
mapper = self.object_mapper(obj)
task = self.get_task_by_mapper(mapper)
task.objects.append(obj)
- def get_task_by_mapper(self, mapper):
+ def add_item_to_delete(self, obj):
+ mapper = self.object_mapper(obj)
+ task = self.get_task_by_mapper(mapper)
+ task.todelete.append(obj)
+
+ def get_task_by_mapper(self, mapper, isdelete = False):
try:
- return self.tasks[mapper]
+ return self.tasks[(mapper, isdelete)]
except KeyError:
- return self.tasks.setdefault(mapper, UOWTask(mapper))
+ return self.tasks.setdefault((mapper, isdelete), UOWTask(mapper, isdelete))
+ def get_objects(self, mapper, isdelete = False):
+ try:
+ task = self.tasks[(mapper, isdelete)]
+ except KeyError:
+ return []
+
+ return task.objects
+
# TODO: better interface for tasks with no object save, or multiple dependencies
- def register_dependency(self, mapper, dependency, processor, stuff_to_process):
+ def register_dependency(self, mapper, dependency):
self.dependencies[(mapper, dependency)] = True
- task = self.get_task_by_mapper(mapper)
- if processor is not None:
- task.dependencies.append((processor, stuff_to_process))
+
+ def register_task(self, mapper, processor, objects, isdelete):
+ task = self.get_task_by_mapper(mapper, isdelete)
+ task.dependencies.append((processor, objects))
def register_saved_object(self, obj):
self.saved_objects.append(obj)
def register_deleted(self, obj):
self.deleted_objects.append(obj)
- def add_item_to_delete(self, obj):
- self.todelete.append(obj)
def object_mapper(self, obj):
import sqlalchemy.mapper
def execute(self):
for task in self.tasks.values():
- task.mapper.register_dependencies(task.objects, self)
+ task.mapper.register_dependencies(self)
- mapperlist = self.tasks.values()
+ tasklist = self.tasks.values()
def compare(a, b):
if self.dependencies.has_key((a.mapper, b.mapper)):
return -1
return 1
else:
return 0
- mapperlist.sort(compare)
+ tasklist.sort(compare)
- for task in mapperlist:
+ import string
+ for task in tasklist:
obj_list = task.objects
- task.mapper.save_obj(obj_list, self)
+ if len(obj_list):
+ print "t:" + string.join([o.__class__.__name__ for o in obj_list])
+ if not task.isdelete:
+ task.mapper.save_obj(obj_list, self)
for dep in task.dependencies:
- (processor, stuff_to_process) = dep
- processor.process_dependencies(stuff_to_process, self)
-
+ (processor, stuff) = dep
+ processor.process_dependencies(stuff, self, delete = task.isdelete)
+ if task.isdelete:
+ task.mapper.delete_obj(obj_list, self)
+
def post_exec(self):
for obj in self.saved_objects:
mapper = self.object_mapper(obj)
class UOWTask(object):
- def __init__(self, mapper):
+ def __init__(self, mapper, isdelete = False):
self.mapper = mapper
+ self.isdelete = isdelete
self.objects = util.HashSet()
self.dependencies = []
"""integer datatype"""
pass
+INTEGER = INT
class CHAR:
"""character datatype"""
def __init__(self, length):
u = m.get(7)
u2 = m.get(7)
self.assert_(u is u2)
- objectstore.clear("thread")
+ objectstore.clear()
u2 = m.get(7)
self.assert_(u is not u2)
ECHO = False
DATA = False
execfile("test/tables.py")
-db.echo = True
keywords.insert().execute(
dict(keyword_id=1, name='blue'),
db.connection().commit()
+db.echo = True
+
class HistoryTest(AssertMixin):
def testattr(self):
m = mapper(User, users, properties = dict(addresses = relation(Address, addresses)))
u.addresses[1].email_address = 'there'
print repr(u.__dict__)
print repr(u.addresses)
- m.rollback(u)
+ objectstore.uow().rollback_object(u)
print repr(u.__dict__)
class SaveTest(AssertMixin):
# m = mapper(Address, addresses, properties = dict(
# user = relation(User, users, foreignkey = addresses.c.user_id, primaryjoin = users.c.user_id == addresses.c.user_id, lazy = True, uselist = False)
# ))
+ # TODO: put assertion in here !!!
m = mapper(Address, addresses, properties = dict(
user = relation(User, users, lazy = True, uselist = False)
))