from sqlalchemy import exceptions
from sqlalchemy import util as sautil
from sqlalchemy.orm.mapper import Mapper, object_mapper, class_mapper, mapper_registry
-from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_PASS, ExtensionOption
+from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_PASS, ExtensionOption, PropComparator
from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty, CompositeProperty, BackRef
from sqlalchemy.orm import mapper as mapperlib
from sqlalchemy.orm import collections, strategies
'compile_mappers', 'class_mapper', 'object_mapper',
'MapperExtension', 'Query', 'polymorphic_union', 'create_session',
'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS',
- 'object_session'
+ 'object_session', 'PropComparator'
]
-def relation(*args, **kwargs):
+def relation(argument, secondary=None, **kwargs):
"""Provide a relationship of a primary Mapper to a secondary Mapper.
- This corresponds to a parent-child or associative table relationship.
+ This corresponds to a parent-child or associative table relationship.
+ The constructed class is an instance of [sqlalchemy.orm.properties#PropertyLoader].
+
+ argument
+ a class or Mapper instance, representing the target of the relation.
+
+ secondary
+ for a many-to-many relationship, specifies the intermediary table. The
+ `secondary` keyword argument should generally only be used for a table
+ that is not otherwise expressed in any class mapping. In particular,
+ using the Association Object Pattern is
+ generally mutually exclusive against using the `secondary` keyword
+ argument.
+
+ \**kwargs follow:
+
+ association
+ Deprecated; as of version 0.3.0 the association keyword is synonomous
+ with applying the "all, delete-orphan" cascade to a "one-to-many"
+ relationship. SA can now automatically reconcile a "delete" and
+ "insert" operation of two objects with the same "identity" in a flush()
+ operation into a single "update" statement, which is the pattern that
+ "association" used to indicate. See the updated example of association
+ mappings in [datamapping_association](rel:datamapping_association).
+
+ backref
+ indicates the name of a property to be placed on the related mapper's
+ class that will handle this relationship in the other direction,
+ including synchronizing the object attributes on both sides of the
+ relation. Can also point to a `backref()` construct for more
+ configurability.
+
+ cascade
+ a string list of cascade rules which determines how persistence
+ operations should be "cascaded" from parent to child.
+
+ collection_class
+ a class or function that returns a new list-holding object. will be
+ used in place of a plain list for storing elements.
+
+ foreign_keys
+ a list of columns which are to be used as "foreign key" columns.
+ this parameter should be used in conjunction with explicit
+ `primaryjoin` and `secondaryjoin` (if needed) arguments, and the
+ columns within the `foreign_keys` list should be present within
+ those join conditions. Normally, `relation()` will inspect the
+ columns within the join conditions to determine which columns are
+ the "foreign key" columns, based on information in the `Table`
+ metadata. Use this argument when no ForeignKey's are present in the
+ join condition, or to override the table-defined foreign keys.
+
+ foreignkey
+ deprecated. use the `foreign_keys` argument for foreign key
+ specification, or `remote_side` for "directional" logic.
+
+ lazy=True
+ specifies how the related items should be loaded. a value of True
+ indicates they should be loaded lazily when the property is first
+ accessed. A value of False indicates they should be loaded by joining
+ against the parent object query, so parent and child are loaded in one
+ round trip (i.e. eagerly). A value of None indicates the related items
+ are not loaded by the mapper in any case; the application will manually
+ insert items into the list in some other way. In all cases, items added
+ or removed to the parent object's collection (or scalar attribute) will
+ cause the appropriate updates and deletes upon flush(), i.e. this
+ option only affects load operations, not save operations.
+
+ order_by
+ indicates the ordering that should be applied when loading these items.
+
+ passive_deletes=False
+ Indicates if lazy-loaders should not be executed during the `flush()`
+ process, which normally occurs in order to locate all existing child
+ items when a parent item is to be deleted. Setting this flag to True is
+ appropriate when `ON DELETE CASCADE` rules have been set up on the
+ actual tables so that the database may handle cascading deletes
+ automatically. This strategy is useful particularly for handling the
+ deletion of objects that have very large (and/or deep) child-object
+ collections.
+
+ post_update
+ this indicates that the relationship should be handled by a second
+ UPDATE statement after an INSERT or before a DELETE. Currently, it also
+ will issue an UPDATE after the instance was UPDATEd as well, although
+ this technically should be improved. This flag is used to handle saving
+ bi-directional dependencies between two individual rows (i.e. each row
+ references the other), where it would otherwise be impossible to INSERT
+ or DELETE both rows fully since one row exists before the other. Use
+ this flag when a particular mapping arrangement will incur two rows
+ that are dependent on each other, such as a table that has a
+ one-to-many relationship to a set of child rows, and also has a column
+ that references a single child row within that list (i.e. both tables
+ contain a foreign key to each other). If a `flush()` operation returns
+ an error that a "cyclical dependency" was detected, this is a cue that
+ you might want to use `post_update` to "break" the cycle.
+
+ primaryjoin
+ a ClauseElement that will be used as the primary join of this child
+ object against the parent object, or in a many-to-many relationship the
+ join of the primary object to the association table. By default, this
+ value is computed based on the foreign key relationships of the parent
+ and child tables (or association table).
+
+ private=False
+ deprecated. setting `private=True` is the equivalent of setting
+ `cascade="all, delete-orphan"`, and indicates the lifecycle of child
+ objects should be contained within that of the parent.
+
+ remote_side
+ used for self-referential relationships, indicates the column or list
+ of columns that form the "remote side" of the relationship.
+
+ secondaryjoin
+ a ClauseElement that will be used as the join of an association table
+ to the child object. By default, this value is computed based on the
+ foreign key relationships of the association and child tables.
+
+ uselist=(True|False)
+ a boolean that indicates if this property should be loaded as a list or
+ a scalar. In most cases, this value is determined automatically by
+ `relation()`, based on the type and direction of the relationship - one
+ to many forms a list, many to one forms a scalar, many to many is a
+ list. If a scalar is desired where normally a list would be present,
+ such as a bi-directional one-to-one relationship, set uselist to False.
+
+ viewonly=False
+ when set to True, the relation is used only for loading objects within
+ the relationship, and has no effect on the unit-of-work flush process.
+ Relations with viewonly can specify any kind of join conditions to
+ provide additional views of related objects onto a parent object. Note
+ that the functionality of a viewonly relationship has its limits -
+ complicated join conditions may not compile into eager or lazy loaders
+ properly. If this is the case, use an alternative method.
+
"""
- if len(args) > 1 and isinstance(args[0], type):
- raise exceptions.ArgumentError("relation(class, table, **kwargs) is deprecated. Please use relation(class, **kwargs) or relation(mapper, **kwargs).")
- return _relation_loader(*args, **kwargs)
+ return PropertyLoader(argument, secondary=secondary, **kwargs)
+
+# return _relation_loader(argument, secondary=secondary, **kwargs)
+
+#def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs):
def column_property(*args, **kwargs):
"""Provide a column-level property for use with a Mapper.
Columns that arent present in the mapper's selectable won't be persisted
by the mapper and are effectively "read-only" attributes.
+
+ \*cols
+ list of Column objects to be mapped.
+
+ group
+ a group name for this property when marked as deferred.
+
+ deferred
+ when True, the column property is "deferred", meaning that
+ it does not load immediately, and is instead loaded when the
+ attribute is first accessed on an instance. See also
+ [sqlalchemy.orm#deferred()].
+
"""
return ColumnProperty(*args, **kwargs)
implement a constructor with positional arguments matching the order of
columns given, as well as a __colset__() method which returns its attributes
in column order.
+
+ class_
+ the "composite type" class.
+
+ \*cols
+ list of Column objects to be mapped.
+
+ group
+ a group name for this property when marked as deferred.
+
+ deferred
+ when True, the column property is "deferred", meaning that
+ it does not load immediately, and is instead loaded when the
+ attribute is first accessed on an instance. See also
+ [sqlalchemy.orm#deferred()].
+
+ comparator
+ an optional instance of [sqlalchemy.orm#PropComparator] which
+ provides SQL expression generation functions for this composite
+ type.
"""
return CompositeProperty(class_, *cols, **kwargs)
-def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs):
- return PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs)
def backref(name, **kwargs):
"""Create a BackRef object with explicit arguments, which are the same arguments one
return ColumnProperty(deferred=True, *columns, **kwargs)
-def mapper(class_, table=None, *args, **params):
- """Return a new ``Mapper`` object.
-
- See the ``Mapper`` class for a description of arguments.
+def mapper(class_, local_table=None, *args, **params):
+ """Return a new [sqlalchemy.orm#Mapper] object.
+
+ class\_
+ The class to be mapped.
+
+ local_table
+ The table to which the class is mapped, or None if this
+ mapper inherits from another mapper using concrete table
+ inheritance.
+
+ entity_name
+ A name to be associated with the `class`, to allow alternate
+ mappings for a single class.
+
+ always_refresh
+ If True, all query operations for this mapped class will
+ overwrite all data within object instances that already
+ exist within the session, erasing any in-memory changes with
+ whatever information was loaded from the database. Usage
+ of this flag is highly discouraged; as an alternative,
+ see the method `populate_existing()` on [sqlalchemy.orm.query#Query].
+
+ allow_column_override
+ If True, allows the usage of a ``relation()`` which has the
+ same name as a column in the mapped table. The table column
+ will no longer be mapped.
+
+ allow_null_pks
+ Indicates that composite primary keys where one or more (but
+ not all) columns contain NULL is a valid primary key.
+ Primary keys which contain NULL values usually indicate that
+ a result row does not contain an entity and should be
+ skipped.
+
+ batch
+ Indicates that save operations of multiple entities can be
+ batched together for efficiency. setting to False indicates
+ that an instance will be fully saved before saving the next
+ instance, which includes inserting/updating all table rows
+ corresponding to the entity as well as calling all
+ ``MapperExtension`` methods corresponding to the save
+ operation.
+
+ column_prefix
+ A string which will be prepended to the `key` name of all
+ Columns when creating column-based properties from the given
+ Table. Does not affect explicitly specified column-based
+ properties
+
+ concrete
+ If True, indicates this mapper should use concrete table
+ inheritance with its parent mapper.
+
+ extension
+ A [sqlalchemy.orm#MapperExtension] instance or list of
+ ``MapperExtension`` instances which will be applied to all
+ operations by this ``Mapper``.
+
+ inherits
+ Another ``Mapper`` for which this ``Mapper`` will have an
+ inheritance relationship with.
+
+ inherit_condition
+ For joined table inheritance, a SQL expression (constructed
+ ``ClauseElement``) which will define how the two tables are
+ joined; defaults to a natural join between the two tables.
+
+ order_by
+ A single ``Column`` or list of ``Columns`` for which
+ selection operations should use as the default ordering for
+ entities. Defaults to the OID/ROWID of the table if any, or
+ the first primary key column of the table.
+
+ non_primary
+ Construct a ``Mapper`` that will define only the selection
+ of instances, not their persistence. Any number of non_primary
+ mappers may be created for a particular class.
+
+ polymorphic_on
+ Used with mappers in an inheritance relationship, a ``Column``
+ which will identify the class/mapper combination to be used
+ with a particular row. requires the polymorphic_identity
+ value to be set for all mappers in the inheritance
+ hierarchy.
+
+ _polymorphic_map
+ Used internally to propigate the full map of polymorphic
+ identifiers to surrogate mappers.
+
+ polymorphic_identity
+ A value which will be stored in the Column denoted by
+ polymorphic_on, corresponding to the *class identity* of
+ this mapper.
+
+ polymorphic_fetch
+ specifies how subclasses mapped through joined-table
+ inheritance will be fetched. options are 'union',
+ 'select', and 'deferred'. if the select_table argument
+ is present, defaults to 'union', otherwise defaults to
+ 'select'.
+
+ properties
+ A dictionary mapping the string names of object attributes
+ to ``MapperProperty`` instances, which define the
+ persistence behavior of that attribute. Note that the
+ columns in the mapped table are automatically converted into
+ ``ColumnProperty`` instances based on the `key` property of
+ each ``Column`` (although they can be overridden using this
+ dictionary).
+
+ primary_key
+ A list of ``Column`` objects which define the *primary key*
+ to be used against this mapper's selectable unit. This is
+ normally simply the primary key of the `local_table`, but
+ can be overridden here.
+
+ select_table
+ A [sqlalchemy.schema#Table] or any [sqlalchemy.sql#Selectable]
+ which will be used to select instances of this mapper's class.
+ usually used to provide polymorphic loading among several
+ classes in an inheritance hierarchy.
+
+ version_id_col
+ A ``Column`` which must have an integer type that will be
+ used to keep a running *version id* of mapped entities in
+ the database. this is used during save operations to ensure
+ that no other thread or process has updated the instance
+ during the lifetime of the entity, else a
+ ``ConcurrentModificationError`` exception is thrown.
"""
- return Mapper(class_, table, *args, **params)
+ return Mapper(class_, local_table, *args, **params)
def synonym(name, proxy=False):
"""Set up `name` as a synonym to another ``MapperProperty``.
column_prefix=None):
"""Construct a new mapper.
- All arguments may be sent to the ``sqlalchemy.orm.mapper()``
- function where they are passed through to here.
-
- class\_
- The class to be mapped.
-
- local_table
- The table to which the class is mapped, or None if this
- mapper inherits from another mapper using concrete table
- inheritance.
-
- properties
- A dictionary mapping the string names of object attributes
- to ``MapperProperty`` instances, which define the
- persistence behavior of that attribute. Note that the
- columns in the mapped table are automatically converted into
- ``ColumnProperty`` instances based on the `key` property of
- each ``Column`` (although they can be overridden using this
- dictionary).
-
- primary_key
- A list of ``Column`` objects which define the *primary key*
- to be used against this mapper's selectable unit. This is
- normally simply the primary key of the `local_table`, but
- can be overridden here.
-
- non_primary
- Construct a ``Mapper`` that will define only the selection
- of instances, not their persistence.
-
- inherits
- Another ``Mapper`` for which this ``Mapper`` will have an
- inheritance relationship with.
-
- inherit_condition
- For joined table inheritance, a SQL expression (constructed
- ``ClauseElement``) which will define how the two tables are
- joined; defaults to a natural join between the two tables.
-
- extension
- A ``MapperExtension`` instance or list of
- ``MapperExtension`` instances which will be applied to all
- operations by this ``Mapper``.
-
- order_by
- A single ``Column`` or list of ``Columns`` for which
- selection operations should use as the default ordering for
- entities. Defaults to the OID/ROWID of the table if any, or
- the first primary key column of the table.
-
- allow_column_override
- If True, allows the usage of a ``relation()`` which has the
- same name as a column in the mapped table. The table column
- will no longer be mapped.
-
- entity_name
- A name to be associated with the `class`, to allow alternate
- mappings for a single class.
-
- always_refresh
- If True, all query operations for this mapped class will
- overwrite all data within object instances that already
- exist within the session, erasing any in-memory changes with
- whatever information was loaded from the database.
-
- version_id_col
- A ``Column`` which must have an integer type that will be
- used to keep a running *version id* of mapped entities in
- the database. this is used during save operations to ensure
- that no other thread or process has updated the instance
- during the lifetime of the entity, else a
- ``ConcurrentModificationError`` exception is thrown.
-
- polymorphic_on
- Used with mappers in an inheritance relationship, a ``Column``
- which will identify the class/mapper combination to be used
- with a particular row. requires the polymorphic_identity
- value to be set for all mappers in the inheritance
- hierarchy.
-
- _polymorphic_map
- Used internally to propigate the full map of polymorphic
- identifiers to surrogate mappers.
-
- polymorphic_identity
- A value which will be stored in the Column denoted by
- polymorphic_on, corresponding to the *class identity* of
- this mapper.
-
- polymorphic_fetch
- specifies how subclasses mapped through joined-table
- inheritance will be fetched. options are 'union',
- 'select', and 'deferred'. if the select_table argument
- is present, defaults to 'union', otherwise defaults to
- 'select'.
-
- concrete
- If True, indicates this mapper should use concrete table
- inheritance with its parent mapper.
-
- select_table
- A ``Table`` or (more commonly) ``Selectable`` which will be
- used to select instances of this mapper's class. usually
- used to provide polymorphic loading among several classes in
- an inheritance hierarchy.
-
- allow_null_pks
- Indicates that composite primary keys where one or more (but
- not all) columns contain NULL is a valid primary key.
- Primary keys which contain NULL values usually indicate that
- a result row does not contain an entity and should be
- skipped.
-
- batch
- Indicates that save operations of multiple entities can be
- batched together for efficiency. setting to False indicates
- that an instance will be fully saved before saving the next
- instance, which includes inserting/updating all table rows
- corresponding to the entity as well as calling all
- ``MapperExtension`` methods corresponding to the save
- operation.
-
- column_prefix
- A string which will be prepended to the `key` name of all
- Columns when creating column-based properties from the given
- Table. Does not affect explicitly specified column-based
- properties
+ Mappers are normally constructed via the [sqlalchemy.orm#mapper()]
+ function. See for details.
"""
if not issubclass(class_, object):
--- /dev/null
+import testbase
+from sqlalchemy import *
+from sqlalchemy.orm import *
+
+from sqlalchemy.orm.shard import ShardedSession
+from sqlalchemy.sql import ColumnOperators
+import datetime, operator, os
+from testlib import PersistTest
+
+# TODO: ShardTest can be turned into a base for further subclasses
+
+class ShardTest(PersistTest):
+ def setUpAll(self):
+ global db1, db2, db3, db4, weather_locations, weather_reports
+
+ db1 = create_engine('sqlite:///shard1.db')
+ db2 = create_engine('sqlite:///shard2.db')
+ db3 = create_engine('sqlite:///shard3.db')
+ db4 = create_engine('sqlite:///shard4.db')
+
+ meta = MetaData()
+ ids = Table('ids', meta,
+ Column('nextid', Integer, nullable=False))
+
+ def id_generator(ctx):
+ # in reality, might want to use a separate transaction for this.
+ c = db1.connect()
+ nextid = c.execute(ids.select(for_update=True)).scalar()
+ c.execute(ids.update(values={ids.c.nextid : ids.c.nextid + 1}))
+ return nextid
+
+ weather_locations = Table("weather_locations", meta,
+ Column('id', Integer, primary_key=True, default=id_generator),
+ Column('continent', String(30), nullable=False),
+ Column('city', String(50), nullable=False)
+ )
+
+ weather_reports = Table("weather_reports", meta,
+ Column('id', Integer, primary_key=True),
+ Column('location_id', Integer, ForeignKey('weather_locations.id')),
+ Column('temperature', Float),
+ Column('report_time', DateTime, default=datetime.datetime.now),
+ )
+
+ for db in (db1, db2, db3, db4):
+ meta.create_all(db)
+
+ db1.execute(ids.insert(), nextid=1)
+
+ self.setup_session()
+ self.setup_mappers()
+
+ def tearDownAll(self):
+ for i in range(1,5):
+ os.remove("shard%d.db" % i)
+
+ def setup_session(self):
+ global create_session
+
+ shard_lookup = {
+ 'North America':'north_america',
+ 'Asia':'asia',
+ 'Europe':'europe',
+ 'South America':'south_america'
+ }
+
+ def shard_chooser(mapper, instance):
+ if isinstance(instance, WeatherLocation):
+ return shard_lookup[instance.continent]
+ else:
+ return shard_chooser(mapper, instance.location)
+
+ def id_chooser(ident):
+ return ['north_america', 'asia', 'europe', 'south_america']
+
+ def query_chooser(query):
+ ids = []
+
+ class FindContinent(sql.ClauseVisitor):
+ def visit_binary(self, binary):
+ if binary.left is weather_locations.c.continent:
+ if binary.operator == operator.eq:
+ ids.append(shard_lookup[binary.right.value])
+ elif binary.operator == ColumnOperators.in_op:
+ for bind in binary.right.clauses:
+ ids.append(shard_lookup[bind.value])
+
+ FindContinent().traverse(query._criterion)
+ if len(ids) == 0:
+ return ['north_america', 'asia', 'europe', 'south_america']
+ else:
+ return ids
+
+ def create_session():
+ s = ShardedSession(shard_chooser, id_chooser, query_chooser)
+ s.bind_shard('north_america', db1)
+ s.bind_shard('asia', db2)
+ s.bind_shard('europe', db3)
+ s.bind_shard('south_america', db4)
+ return s
+
+ def setup_mappers(self):
+ global WeatherLocation, Report
+
+ class WeatherLocation(object):
+ def __init__(self, continent, city):
+ self.continent = continent
+ self.city = city
+
+ class Report(object):
+ def __init__(self, temperature):
+ self.temperature = temperature
+
+ mapper(WeatherLocation, weather_locations, properties={
+ 'reports':relation(Report, backref='location')
+ })
+
+ mapper(Report, weather_reports)
+
+ def test_roundtrip(self):
+ tokyo = WeatherLocation('Asia', 'Tokyo')
+ newyork = WeatherLocation('North America', 'New York')
+ toronto = WeatherLocation('North America', 'Toronto')
+ london = WeatherLocation('Europe', 'London')
+ dublin = WeatherLocation('Europe', 'Dublin')
+ brasilia = WeatherLocation('South America', 'Brasila')
+ quito = WeatherLocation('South America', 'Quito')
+
+ tokyo.reports.append(Report(80.0))
+ newyork.reports.append(Report(75))
+ quito.reports.append(Report(85))
+
+ sess = create_session()
+ for c in [tokyo, newyork, toronto, london, dublin, brasilia, quito]:
+ sess.save(c)
+ sess.flush()
+
+ sess.clear()
+
+ t = sess.query(WeatherLocation).get(tokyo.id)
+ assert t.city == tokyo.city
+ assert t.reports[0].temperature == 80.0
+
+ north_american_cities = sess.query(WeatherLocation).filter(WeatherLocation.continent == 'North America')
+ assert set([c.city for c in north_american_cities]) == set(['New York', 'Toronto'])
+
+ asia_and_europe = sess.query(WeatherLocation).filter(WeatherLocation.continent.in_('Europe', 'Asia'))
+ assert set([c.city for c in asia_and_europe]) == set(['Tokyo', 'London', 'Dublin'])
+
+
+
+if __name__ == '__main__':
+ testbase.main()
+
\ No newline at end of file