summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-11-13 15:53:32 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-11-13 15:53:32 -0500
commit6d630990dd35dad35609171eb003a0f1ef5b945d (patch)
tree080b3b7a7b4e7a25ec1a66612eec22cbd6049cc7
parent8303afc9c002bade1dd2736a26f302475da9c398 (diff)
parentaf4285e6adf2a052ce985f9d3d97cc89778fca96 (diff)
downloadsqlalchemy-6d630990dd35dad35609171eb003a0f1ef5b945d.tar.gz
merge tip
-rw-r--r--CHANGES21
-rw-r--r--README.unittests10
-rw-r--r--doc/build/orm/mapper_config.rst26
-rw-r--r--lib/sqlalchemy/cextension/resultproxy.c8
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py16
-rw-r--r--lib/sqlalchemy/engine/base.py8
-rw-r--r--lib/sqlalchemy/orm/attributes.py7
-rw-r--r--lib/sqlalchemy/orm/mapper.py35
-rw-r--r--lib/sqlalchemy/orm/properties.py3
-rw-r--r--lib/sqlalchemy/orm/session.py5
-rw-r--r--lib/sqlalchemy/orm/strategies.py8
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py10
-rw-r--r--lib/sqlalchemy/orm/util.py33
-rw-r--r--lib/sqlalchemy/schema.py19
-rw-r--r--lib/sqlalchemy/sql/compiler.py7
-rw-r--r--lib/sqlalchemy/sql/expression.py41
-rw-r--r--lib/sqlalchemy/test/requires.py22
-rw-r--r--lib/sqlalchemy/test/util.py2
-rw-r--r--lib/sqlalchemy/types.py7
-rw-r--r--lib/sqlalchemy/util.py46
-rwxr-xr-xsqla_nose.py15
-rw-r--r--test/aaa_profiling/test_compiler.py5
-rw-r--r--test/aaa_profiling/test_resultset.py4
-rw-r--r--test/dialect/test_sqlite.py51
-rw-r--r--test/engine/test_execute.py32
-rw-r--r--test/orm/test_subquery_relations.py55
26 files changed, 364 insertions, 132 deletions
diff --git a/CHANGES b/CHANGES
index 65715de54..7b8749a3f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -19,12 +19,33 @@ CHANGES
is an outer join, thus allowing primary rows without
a referenced child row to be correctly returned
in results. [ticket:1954]
+
+ - Fixed bug regarding "subqueryload" strategy whereby
+ strategy would fail if the entity was an aliased()
+ construct. [ticket:1964]
+
+- engine
+ - Implemented sequence check capability for the C
+ version of RowProxy, as well as 2.7 style
+ "collections.Sequence" registration for RowProxy.
+ [ticket:1871]
- mysql
- Fixed error handling for Jython + zxjdbc, such that
has_table() property works again. Regression from
0.6.3 (we don't have a Jython buildbot, sorry)
[ticket:1960]
+
+- sqlite
+ - The REFERENCES clause in a CREATE TABLE that includes
+ a remote schema to another table with the same schema
+ name now renders the remote name without
+ the schema clause, as required by SQLite. [ticket:1851]
+
+ - On the same theme, the REFERENCES clause in a CREATE TABLE
+ that includes a remote schema to a *different* schema
+ than that of the parent table doesn't render at all,
+ as cross-schema references do not appear to be supported.
0.6.5
=====
diff --git a/README.unittests b/README.unittests
index ca7699743..6b2320907 100644
--- a/README.unittests
+++ b/README.unittests
@@ -68,6 +68,16 @@ intersesting:
$ nosetests -v
+ALTERNATE TEST RUNNER
+---------------------
+
+The script "sqla_nose.py" is a front-end to Nose which manually associates
+the SQLAlchemy testing plugin with Nose at runtime. This script can run the
+tests without any reliance upon setuptools. In 0.7 we'll be removing the
+Nose plugin from setup, so this will be the way going forward to run tests:
+
+ $ python sqla_nose.py -v
+
RUNNING INDIVIDUAL TESTS
-------------------------
Any directory of test modules can be run at once by specifying the directory
diff --git a/doc/build/orm/mapper_config.rst b/doc/build/orm/mapper_config.rst
index 15377436f..954d43646 100644
--- a/doc/build/orm/mapper_config.rst
+++ b/doc/build/orm/mapper_config.rst
@@ -570,6 +570,7 @@ passed in to a mapper as the table.
.. sourcecode:: python+sql
+ from sqlalchemy.orm import mapper
from sqlalchemy.sql import join
class AddressUser(object):
@@ -584,9 +585,29 @@ passed in to a mapper as the table.
'user_id': [users_table.c.user_id, addresses_table.c.user_id]
})
-A second example:
+Note that the list of columns is equivalent to the usage of :func:`.column_property`
+with multiple columns::
-.. sourcecode:: python+sql
+ from sqlalchemy.orm import mapper, column_property
+
+ mapper(AddressUser, j, properties={
+ 'user_id': column_property(users_table.c.user_id, addresses_table.c.user_id)
+ })
+
+The usage of :func:`.column_property` is required when using declarative to map
+to multiple columns, since the declarative class parser won't recognize a plain
+list of columns::
+
+ from sqlalchemy.ext.declarative import declarative_base
+
+ Base = declarative_base()
+
+ class AddressUser(Base):
+ __table__ = j
+
+ user_id = column_property(users_table.c.user_id, addresses_table.c.user_id)
+
+A second example::
from sqlalchemy.sql import join
@@ -611,6 +632,7 @@ mappers; these are aggregations of multiple columns into one mapper property,
which instructs the mapper to keep both of those columns set at the same
value.
+
Mapping a Class against Arbitrary Selects
------------------------------------------
diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c
index 7404b9ed2..73e127345 100644
--- a/lib/sqlalchemy/cextension/resultproxy.c
+++ b/lib/sqlalchemy/cextension/resultproxy.c
@@ -327,6 +327,12 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
}
static PyObject *
+BaseRowProxy_getitem(PyObject *self, Py_ssize_t i)
+{
+ return BaseRowProxy_subscript((BaseRowProxy*)self, PyInt_FromSsize_t(i));
+}
+
+static PyObject *
BaseRowProxy_getattro(BaseRowProxy *self, PyObject *name)
{
PyObject *tmp;
@@ -506,7 +512,7 @@ static PySequenceMethods BaseRowProxy_as_sequence = {
(lenfunc)BaseRowProxy_length, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
- 0, /* sq_item */
+ (ssizeargfunc)BaseRowProxy_getitem, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index b84b18e68..994904b6a 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -270,7 +270,21 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
return super(SQLiteDDLCompiler, self).\
visit_primary_key_constraint(constraint)
-
+
+ def visit_foreign_key_constraint(self, constraint):
+
+ local_table = constraint._elements.values()[0].parent.table
+ remote_table = list(constraint._elements.values())[0].column.table
+
+ if local_table.schema != remote_table.schema:
+ return None
+ else:
+ return super(SQLiteDDLCompiler, self).visit_foreign_key_constraint(constraint)
+
+ def define_constraint_remote_table(self, constraint, table, preparer):
+ """Format the remote table clause of a CREATE CONSTRAINT clause."""
+
+ return preparer.format_table(table, use_schema=False)
def visit_create_index(self, create):
index = create.element
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 9d80af7a7..326c33f2d 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -2142,6 +2142,14 @@ class RowProxy(BaseRowProxy):
def itervalues(self):
return iter(self)
+try:
+ # Register RowProxy with Sequence,
+ # so sequence protocol is implemented
+ from collections import Sequence
+ Sequence.register(RowProxy)
+except ImportError:
+ pass
+
class ResultMetaData(object):
"""Handle cursor.description, applying additional info from an execution
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index fcaabfddd..6872dd645 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -19,10 +19,7 @@ from sqlalchemy import util, event
from sqlalchemy.orm import interfaces, collections, events
import sqlalchemy.exceptions as sa_exc
-# lazy imports
-_entity_info = None
-identity_equal = None
-state = None
+mapperutil = util.importlater("sqlalchemy.orm", "util")
PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT')
ATTR_WAS_SET = util.symbol('ATTR_WAS_SET')
@@ -542,7 +539,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
compare_function=compare_function,
**kwargs)
if compare_function is None:
- self.is_equal = identity_equal
+ self.is_equal = mapperutil.identity_equal
def delete(self, state, dict_):
old = self.get(state, dict_)
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 7fdf21c6c..75ba0b5c0 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -31,6 +31,8 @@ from sqlalchemy.orm.util import _INSTRUMENTOR, _class_to_mapper, \
_state_mapper, class_mapper, instance_str, state_str
import sys
+sessionlib = util.importlater("sqlalchemy.orm", "session")
+properties = util.importlater("sqlalchemy.orm", "properties")
__all__ = (
'Mapper',
@@ -54,13 +56,6 @@ NO_ATTRIBUTE = util.symbol('NO_ATTRIBUTE')
# lock used to synchronize the "mapper compile" step
_COMPILE_MUTEX = util.threading.RLock()
-# initialize these lazily
-ColumnProperty = None
-RelationshipProperty = None
-ConcreteInheritedProperty = None
-_expire_state = None
-_state_session = None
-
class Mapper(object):
"""Define the correlation of class attributes to database table
columns.
@@ -590,7 +585,7 @@ class Mapper(object):
self._configure_property(
col.key,
- ColumnProperty(col, _instrument=instrument),
+ properties.ColumnProperty(col, _instrument=instrument),
init=False, setparent=True)
def _adapt_inherited_property(self, key, prop, init):
@@ -599,7 +594,7 @@ class Mapper(object):
elif key not in self._props:
self._configure_property(
key,
- ConcreteInheritedProperty(),
+ properties.ConcreteInheritedProperty(),
init=init, setparent=True)
def _configure_property(self, key, prop, init=True, setparent=True):
@@ -607,7 +602,7 @@ class Mapper(object):
if not isinstance(prop, MapperProperty):
# we were passed a Column or a list of Columns;
- # generate a ColumnProperty
+ # generate a properties.ColumnProperty
columns = util.to_list(prop)
column = columns[0]
if not expression.is_column(column):
@@ -617,12 +612,12 @@ class Mapper(object):
prop = self._props.get(key, None)
- if isinstance(prop, ColumnProperty):
+ if isinstance(prop, properties.ColumnProperty):
# TODO: the "property already exists" case is still not
# well defined here. assuming single-column, etc.
if prop.parent is not self:
- # existing ColumnProperty from an inheriting mapper.
+ # existing properties.ColumnProperty from an inheriting mapper.
# make a copy and append our column to it
prop = prop.copy()
else:
@@ -637,9 +632,9 @@ class Mapper(object):
# this hypothetically changes to
# prop.columns.insert(0, column) when we do [ticket:1892]
prop.columns.append(column)
- self._log("appending to existing ColumnProperty %s" % (key))
+ self._log("appending to existing properties.ColumnProperty %s" % (key))
- elif prop is None or isinstance(prop, ConcreteInheritedProperty):
+ elif prop is None or isinstance(prop, properties.ConcreteInheritedProperty):
mapped_column = []
for c in columns:
mc = self.mapped_table.corresponding_column(c)
@@ -660,7 +655,7 @@ class Mapper(object):
"force this column to be mapped as a read-only "
"attribute." % (key, self, c))
mapped_column.append(mc)
- prop = ColumnProperty(*mapped_column)
+ prop = properties.ColumnProperty(*mapped_column)
else:
raise sa_exc.ArgumentError(
"WARNING: when configuring property '%s' on %s, "
@@ -674,7 +669,7 @@ class Mapper(object):
"columns get mapped." %
(key, self, column.key, prop))
- if isinstance(prop, ColumnProperty):
+ if isinstance(prop, properties.ColumnProperty):
col = self.mapped_table.corresponding_column(prop.columns[0])
# if the column is not present in the mapped table,
@@ -713,7 +708,7 @@ class Mapper(object):
col not in self._cols_by_table[col.table]:
self._cols_by_table[col.table].add(col)
- # if this ColumnProperty represents the "polymorphic
+ # if this properties.ColumnProperty represents the "polymorphic
# discriminator" column, mark it. We'll need this when rendering
# columns in SELECT statements.
if not hasattr(prop, '_is_polymorphic_discriminator'):
@@ -1887,7 +1882,7 @@ class Mapper(object):
)
if readonly:
- _expire_state(state, state.dict, readonly)
+ sessionlib._expire_state(state, state.dict, readonly)
# if eager_defaults option is enabled,
# refresh whatever has been expired.
@@ -1925,7 +1920,7 @@ class Mapper(object):
self._set_state_attr_by_column(state, dict_, c, params[c.key])
if postfetch_cols:
- _expire_state(state, state.dict,
+ sessionlib._expire_state(state, state.dict,
[self._columntoproperty[c].key
for c in postfetch_cols]
)
@@ -2430,7 +2425,7 @@ def _load_scalar_attributes(state, attribute_names):
"""initiate a column-based attribute refresh operation."""
mapper = _state_mapper(state)
- session = _state_session(state)
+ session = sessionlib._state_session(state)
if not session:
raise orm_exc.DetachedInstanceError(
"Instance %s is not bound to a Session; "
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 0cbbf630d..feee041ce 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -1483,6 +1483,3 @@ class RelationshipProperty(StrategizedProperty):
PropertyLoader = RelationProperty = RelationshipProperty
log.class_logger(RelationshipProperty)
-mapper.ColumnProperty = ColumnProperty
-mapper.RelationshipProperty = RelationshipProperty
-mapper.ConcreteInheritedProperty = ConcreteInheritedProperty
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 710d3213a..c384cfc3d 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -1695,8 +1695,3 @@ def _state_session(state):
pass
return None
-# Lazy initialization to avoid circular imports
-unitofwork._state_session = _state_session
-from sqlalchemy.orm import mapper
-mapper._expire_state = _expire_state
-mapper._state_session = _state_session
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 1de2dc3ee..17f165b64 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -741,14 +741,16 @@ class SubqueryLoader(AbstractRelationshipLoader):
("orig_query", SubqueryLoader),
context.query)
+ subq_mapper = mapperutil._class_to_mapper(subq_path[0])
+
# determine attributes of the leftmost mapper
- if self.parent.isa(subq_path[0]) and self.key==subq_path[1]:
+ if self.parent.isa(subq_mapper) and self.key==subq_path[1]:
leftmost_mapper, leftmost_prop = \
self.parent, self.parent_property
else:
leftmost_mapper, leftmost_prop = \
- subq_path[0], \
- subq_path[0].get_property(subq_path[1])
+ subq_mapper, \
+ subq_mapper.get_property(subq_path[1])
leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop)
leftmost_attr = [
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index a9808e6ba..673591e8e 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -16,9 +16,7 @@ from sqlalchemy import util, topological
from sqlalchemy.orm import attributes, interfaces
from sqlalchemy.orm import util as mapperutil
from sqlalchemy.orm.util import _state_mapper
-
-# Load lazily
-_state_session = None
+session = util.importlater("sqlalchemy.orm", "session")
class UOWEventHandler(interfaces.AttributeExtension):
"""An event handler added to all relationship attributes which handles
@@ -34,7 +32,7 @@ class UOWEventHandler(interfaces.AttributeExtension):
# process "save_update" cascade rules for when
# an instance is appended to the list of another instance
- sess = _state_session(state)
+ sess = session._state_session(state)
if sess:
prop = _state_mapper(state).get_property(self.key)
if prop.cascade.save_update and \
@@ -44,7 +42,7 @@ class UOWEventHandler(interfaces.AttributeExtension):
return item
def remove(self, state, item, initiator):
- sess = _state_session(state)
+ sess = session._state_session(state)
if sess:
prop = _state_mapper(state).get_property(self.key)
# expunge pending orphans
@@ -59,7 +57,7 @@ class UOWEventHandler(interfaces.AttributeExtension):
if oldvalue is newvalue:
return newvalue
- sess = _state_session(state)
+ sess = session._state_session(state)
if sess:
prop = _state_mapper(state).get_property(self.key)
if newvalue is not None and \
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index c2b79666c..9447eed30 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -13,7 +13,7 @@ from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\
AttributeExtension
from sqlalchemy.orm import attributes, exc
-mapperlib = None
+mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
all_cascades = frozenset(("delete", "delete-orphan", "all", "merge",
"expunge", "save-update", "refresh-expire",
@@ -460,10 +460,6 @@ def _entity_info(entity, compile=True):
if isinstance(entity, AliasedClass):
return entity._AliasedClass__mapper, entity._AliasedClass__alias, True
- global mapperlib
- if mapperlib is None:
- from sqlalchemy.orm import mapperlib
-
if isinstance(entity, mapperlib.Mapper):
mapper = entity
@@ -558,24 +554,28 @@ def class_mapper(class_, compile=True):
def _class_to_mapper(class_or_mapper, compile=True):
if _is_aliased_class(class_or_mapper):
return class_or_mapper._AliasedClass__mapper
+
elif isinstance(class_or_mapper, type):
- return class_mapper(class_or_mapper, compile=compile)
- elif hasattr(class_or_mapper, 'compile'):
- if compile:
- return class_or_mapper.compile()
- else:
- return class_or_mapper
+ try:
+ class_manager = attributes.manager_of_class(class_or_mapper)
+ mapper = class_manager.mapper
+ except exc.NO_STATE:
+ raise exc.UnmappedClassError(class_or_mapper)
+ elif isinstance(class_or_mapper, mapperlib.Mapper):
+ mapper = class_or_mapper
else:
raise exc.UnmappedClassError(class_or_mapper)
+
+ if compile:
+ return mapper.compile()
+ else:
+ return mapper
def has_identity(object):
state = attributes.instance_state(object)
return state.has_identity
def _is_mapped_class(cls):
- global mapperlib
- if mapperlib is None:
- from sqlalchemy.orm import mapperlib
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
return True
if isinstance(cls, expression.ClauseElement):
@@ -618,8 +618,3 @@ def identity_equal(a, b):
return False
return state_a.key == state_b.key
-
-# TODO: Avoid circular import.
-attributes.identity_equal = identity_equal
-attributes._is_aliased_class = _is_aliased_class
-attributes._entity_info = _entity_info
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 15b58e953..607b55d3f 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -33,7 +33,9 @@ from sqlalchemy import exc, util, dialects
from sqlalchemy.sql import expression, visitors
from sqlalchemy import event, events
-URL = None
+sqlutil = util.importlater("sqlalchemy.sql", "util")
+url = util.importlater("sqlalchemy.engine", "url")
+
__all__ = ['SchemaItem', 'Table', 'Column', 'ForeignKey', 'Sequence', 'Index',
'ForeignKeyConstraint', 'PrimaryKeyConstraint', 'CheckConstraint',
@@ -1957,11 +1959,7 @@ class MetaData(SchemaItem):
def _bind_to(self, bind):
"""Bind this MetaData to an Engine, Connection, string or URL."""
- global URL
- if URL is None:
- from sqlalchemy.engine.url import URL
-
- if isinstance(bind, (basestring, URL)):
+ if isinstance(bind, (basestring, url.URL)):
from sqlalchemy import create_engine
self._bind = create_engine(bind)
else:
@@ -1985,8 +1983,7 @@ class MetaData(SchemaItem):
"""Returns a list of ``Table`` objects sorted in order of
dependency.
"""
- from sqlalchemy.sql.util import sort_tables
- return sort_tables(self.tables.itervalues())
+ return sqlutil.sort_tables(self.tables.itervalues())
def reflect(self, bind=None, schema=None, views=False, only=None):
"""Load all available table definitions from the database.
@@ -2156,11 +2153,7 @@ class ThreadLocalMetaData(MetaData):
def _bind_to(self, bind):
"""Bind to a Connectable in the caller's thread."""
- global URL
- if URL is None:
- from sqlalchemy.engine.url import URL
-
- if isinstance(bind, (basestring, URL)):
+ if isinstance(bind, (basestring, url.URL)):
try:
self.context._engine = self.__engines[bind]
except KeyError:
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index d3b8bf023..4b41c6ed3 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -1306,7 +1306,7 @@ class DDLCompiler(engine.Compiled):
text += "FOREIGN KEY(%s) REFERENCES %s (%s)" % (
', '.join(preparer.quote(f.parent.name, f.parent.quote)
for f in constraint._elements.values()),
- preparer.format_table(remote_table),
+ self.define_constraint_remote_table(constraint, remote_table, preparer),
', '.join(preparer.quote(f.column.name, f.column.quote)
for f in constraint._elements.values())
)
@@ -1314,6 +1314,11 @@ class DDLCompiler(engine.Compiled):
text += self.define_constraint_deferrability(constraint)
return text
+ def define_constraint_remote_table(self, constraint, table, preparer):
+ """Format the remote table clause of a CREATE CONSTRAINT clause."""
+
+ return preparer.format_table(table)
+
def visit_unique_constraint(self, constraint):
text = ""
if constraint.name is not None:
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 625893a68..c3dc339a5 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -29,13 +29,15 @@ to stay the same in future releases.
import itertools, re
from operator import attrgetter
-from sqlalchemy import util, exc #, types as sqltypes
+from sqlalchemy import util, exc
from sqlalchemy.sql import operators
from sqlalchemy.sql.visitors import Visitable, cloned_traverse
import operator
-functions, sql_util, sqltypes = None, None, None
-DefaultDialect = None
+functions = util.importlater("sqlalchemy.sql", "functions")
+sqlutil = util.importlater("sqlalchemy.sql", "util")
+sqltypes = util.importlater("sqlalchemy", "types")
+default = util.importlater("sqlalchemy.engine", "default")
__all__ = [
'Alias', 'ClauseElement', 'ColumnCollection', 'ColumnElement',
@@ -957,9 +959,6 @@ class _FunctionGenerator(object):
o = self.opts.copy()
o.update(kwargs)
if len(self.__names) == 1:
- global functions
- if functions is None:
- from sqlalchemy.sql import functions
func = getattr(functions, self.__names[-1].lower(), None)
if func is not None and \
isinstance(func, type) and \
@@ -1205,10 +1204,7 @@ class ClauseElement(Visitable):
dictionary.
"""
- global sql_util
- if sql_util is None:
- from sqlalchemy.sql import util as sql_util
- return sql_util.Annotated(self, values)
+ return sqlutil.Annotated(self, values)
def _deannotate(self):
"""return a copy of this ClauseElement with an empty annotations
@@ -1389,10 +1385,7 @@ class ClauseElement(Visitable):
dialect = self.bind.dialect
bind = self.bind
else:
- global DefaultDialect
- if DefaultDialect is None:
- from sqlalchemy.engine.default import DefaultDialect
- dialect = DefaultDialect()
+ dialect = default.DefaultDialect()
compiler = self._compiler(dialect, bind=bind, **kw)
compiler.compile()
return compiler
@@ -2154,10 +2147,7 @@ class FromClause(Selectable):
"""
- global sql_util
- if sql_util is None:
- from sqlalchemy.sql import util as sql_util
- return sql_util.ClauseAdapter(alias).traverse(self)
+ return sqlutil.ClauseAdapter(alias).traverse(self)
def correspond_on_equivalents(self, column, equivalents):
"""Return corresponding_column for the given column, or if None
@@ -3098,10 +3088,7 @@ class Join(FromClause):
columns = [c for c in self.left.columns] + \
[c for c in self.right.columns]
- global sql_util
- if not sql_util:
- from sqlalchemy.sql import util as sql_util
- self._primary_key.extend(sql_util.reduce_columns(
+ self._primary_key.extend(sqlutil.reduce_columns(
(c for c in columns if c.primary_key), self.onclause))
self._columns.update((col._label, col) for col in columns)
self._foreign_keys.update(itertools.chain(
@@ -3118,14 +3105,11 @@ class Join(FromClause):
return self.left, self.right, self.onclause
def _match_primaries(self, left, right):
- global sql_util
- if not sql_util:
- from sqlalchemy.sql import util as sql_util
if isinstance(left, Join):
left_right = left.right
else:
left_right = None
- return sql_util.join_condition(left, right, a_subset=left_right)
+ return sqlutil.join_condition(left, right, a_subset=left_right)
def select(self, whereclause=None, fold_equivalents=False, **kwargs):
"""Create a :class:`Select` from this :class:`Join`.
@@ -3145,11 +3129,8 @@ class Join(FromClause):
underlying :func:`select()` function.
"""
- global sql_util
- if not sql_util:
- from sqlalchemy.sql import util as sql_util
if fold_equivalents:
- collist = sql_util.folded_equivalents(self)
+ collist = sqlutil.folded_equivalents(self)
else:
collist = [self.left, self.right]
diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py
index 14c548f12..d29b7abc2 100644
--- a/lib/sqlalchemy/test/requires.py
+++ b/lib/sqlalchemy/test/requires.py
@@ -252,6 +252,12 @@ def sane_rowcount(fn):
skip_if(lambda: not testing.db.dialect.supports_sane_rowcount)
)
+def cextensions(fn):
+ return _chain_decorators_on(
+ fn,
+ skip_if(lambda: not _has_cextensions(), "C extensions not installed")
+ )
+
def dbapi_lastrowid(fn):
return _chain_decorators_on(
fn,
@@ -279,7 +285,23 @@ def python2(fn):
"Python version 2.xx is required."
)
)
+
+def python26(fn):
+ return _chain_decorators_on(
+ fn,
+ skip_if(
+ lambda: sys.version_info < (2, 6),
+ "Python version 2.6 or greater is required"
+ )
+ )
+def _has_cextensions():
+ try:
+ from sqlalchemy import cresultproxy, cprocessors
+ return True
+ except ImportError:
+ return False
+
def _has_sqlite():
from sqlalchemy import create_engine
try:
diff --git a/lib/sqlalchemy/test/util.py b/lib/sqlalchemy/test/util.py
index 98667d8c2..e5277f076 100644
--- a/lib/sqlalchemy/test/util.py
+++ b/lib/sqlalchemy/test/util.py
@@ -22,8 +22,6 @@ else:
def lazy_gc():
pass
-
-
def picklers():
picklers = set()
# Py2K
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index ee1fdc67f..9f322d1eb 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -34,8 +34,8 @@ from sqlalchemy.sql.visitors import Visitable
from sqlalchemy import util
from sqlalchemy import processors
import collections
+default = util.importlater("sqlalchemy.engine", "default")
-DefaultDialect = None
NoneType = type(None)
if util.jython:
import array
@@ -143,10 +143,7 @@ class AbstractType(Visitable):
mod = ".".join(tokens)
return getattr(__import__(mod).dialects, tokens[-1]).dialect()
else:
- global DefaultDialect
- if DefaultDialect is None:
- from sqlalchemy.engine.default import DefaultDialect
- return DefaultDialect()
+ return default.DefaultDialect()
def __str__(self):
# Py3K
diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py
index 7ac7660a5..c897d30f9 100644
--- a/lib/sqlalchemy/util.py
+++ b/lib/sqlalchemy/util.py
@@ -1563,7 +1563,51 @@ class group_expirable_memoized_property(object):
self.attributes.append(fn.__name__)
return memoized_property(fn)
-
+class importlater(object):
+ """Deferred import object.
+
+ e.g.::
+
+ somesubmod = importlater("mypackage.somemodule", "somesubmod")
+
+ is equivalent to::
+
+ from mypackage.somemodule import somesubmod
+
+ except evaluted upon attribute access to "somesubmod".
+
+ """
+ def __init__(self, path, addtl=None):
+ self._il_path = path
+ self._il_addtl = addtl
+
+ @memoized_property
+ def _il_module(self):
+ m = __import__(self._il_path)
+ for token in self._il_path.split(".")[1:]:
+ m = getattr(m, token)
+ if self._il_addtl:
+ try:
+ return getattr(m, self._il_addtl)
+ except AttributeError:
+ raise AttributeError(
+ "Module %s has no attribute '%s'" %
+ (self._il_path, self._il_addtl)
+ )
+ else:
+ return m
+
+ def __getattr__(self, key):
+ try:
+ attr = getattr(self._il_module, key)
+ except AttributeError:
+ raise AttributeError(
+ "Module %s has no attribute '%s'" %
+ (self._il_path, key)
+ )
+ self.__dict__[key] = attr
+ return attr
+
class WeakIdentityMapping(weakref.WeakKeyDictionary):
"""A WeakKeyDictionary with an object identity index.
diff --git a/sqla_nose.py b/sqla_nose.py
index 6d76978a4..302fb5b08 100755
--- a/sqla_nose.py
+++ b/sqla_nose.py
@@ -2,9 +2,8 @@
"""
nose runner script.
-Only use this script if setuptools is not available, i.e. such as
-on Python 3K. Otherwise consult README.unittests for the
-recommended methods of running tests.
+This script is a front-end to "nosetests" which doesn't
+require that SQLA's testing plugin be installed via setuptools.
"""
import sys
@@ -18,13 +17,5 @@ except ImportError:
import nose
-
if __name__ == '__main__':
- py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
- if py3k:
- # this version breaks verbose output,
- # but is the only API that nose3 currently supports
- nose.main(plugins=[NoseSQLAlchemy()])
- else:
- # this is the "correct" API
- nose.main(addplugins=[NoseSQLAlchemy()])
+ nose.main(addplugins=[NoseSQLAlchemy()])
diff --git a/test/aaa_profiling/test_compiler.py b/test/aaa_profiling/test_compiler.py
index a7f64410b..bc589c0b2 100644
--- a/test/aaa_profiling/test_compiler.py
+++ b/test/aaa_profiling/test_compiler.py
@@ -5,6 +5,7 @@ from sqlalchemy.test import *
class CompileTest(TestBase, AssertsExecutionResults):
@classmethod
def setup_class(cls):
+
global t1, t2, metadata
metadata = MetaData()
t1 = Table('t1', metadata,
@@ -15,6 +16,10 @@ class CompileTest(TestBase, AssertsExecutionResults):
Column('c1', Integer, primary_key=True),
Column('c2', String(30)))
+ # do a "compile" ahead of time to load
+ # deferred imports
+ t1.insert().compile()
+
# go through all the TypeEngine
# objects in use and pre-load their _type_affinity
# entries.
diff --git a/test/aaa_profiling/test_resultset.py b/test/aaa_profiling/test_resultset.py
index d71b8dab8..bd9d3ae50 100644
--- a/test/aaa_profiling/test_resultset.py
+++ b/test/aaa_profiling/test_resultset.py
@@ -30,13 +30,13 @@ class ResultSetTest(TestBase, AssertsExecutionResults):
metadata.drop_all()
@profiling.function_call_count(14416, versions={'2.4': 13214,
- '2.6+cextension': 409})
+ '2.6+cextension': 409, '2.7+cextension':438})
def test_string(self):
[tuple(row) for row in t.select().execute().fetchall()]
# sqlite3 returns native unicode. so shouldn't be an increase here.
@profiling.function_call_count(14396, versions={'2.4': 13214,
- '2.6+cextension': 409})
+ '2.6+cextension': 409, '2.7+cextension':409})
def test_unicode(self):
[tuple(row) for row in t2.select().execute().fetchall()]
diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py
index 0cdd3848e..19ec260d3 100644
--- a/test/dialect/test_sqlite.py
+++ b/test/dialect/test_sqlite.py
@@ -384,6 +384,57 @@ class SQLTest(TestBase, AssertsCompiledSQL):
"SELECT CAST(STRFTIME('%s', t.col1) AS "
"INTEGER) AS anon_1 FROM t" % subst)
+ def test_constraints_with_schemas(self):
+ metadata = MetaData()
+ t1 = Table('t1', metadata,
+ Column('id', Integer, primary_key=True),
+ schema='master')
+ t2 = Table('t2', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('t1_id', Integer, ForeignKey('master.t1.id')),
+ schema='master'
+ )
+ t3 = Table('t3', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('t1_id', Integer, ForeignKey('master.t1.id')),
+ schema='alternate'
+ )
+ t4 = Table('t4', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('t1_id', Integer, ForeignKey('master.t1.id')),
+ )
+
+ # schema->schema, generate REFERENCES with no schema name
+ self.assert_compile(
+ schema.CreateTable(t2),
+ "CREATE TABLE master.t2 ("
+ "id INTEGER NOT NULL, "
+ "t1_id INTEGER, "
+ "PRIMARY KEY (id), "
+ "FOREIGN KEY(t1_id) REFERENCES t1 (id)"
+ ")"
+ )
+
+ # schema->different schema, don't generate REFERENCES
+ self.assert_compile(
+ schema.CreateTable(t3),
+ "CREATE TABLE alternate.t3 ("
+ "id INTEGER NOT NULL, "
+ "t1_id INTEGER, "
+ "PRIMARY KEY (id)"
+ ")"
+ )
+
+ # same for local schema
+ self.assert_compile(
+ schema.CreateTable(t4),
+ "CREATE TABLE t4 ("
+ "id INTEGER NOT NULL, "
+ "t1_id INTEGER, "
+ "PRIMARY KEY (id)"
+ ")"
+ )
+
class InsertTest(TestBase, AssertsExecutionResults):
diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py
index 5e6656431..9df23c92e 100644
--- a/test/engine/test_execute.py
+++ b/test/engine/test_execute.py
@@ -290,6 +290,38 @@ class ResultProxyTest(TestBase):
finally:
engine.dialect.execution_ctx_cls = execution_ctx_cls
+ @testing.requires.python26
+ def test_rowproxy_is_sequence(self):
+ import collections
+ from sqlalchemy.engine import RowProxy
+
+ row = RowProxy(object(), ['value'], [None], {'key'
+ : (None, 0), 0: (None, 0)})
+ assert isinstance(row, collections.Sequence)
+
+ @testing.requires.cextensions
+ def test_row_c_sequence_check(self):
+ import csv
+ import collections
+ from StringIO import StringIO
+
+ metadata = MetaData()
+ metadata.bind = 'sqlite://'
+ users = Table('users', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('name', String(40)),
+ )
+ users.create()
+
+ users.insert().execute(name='Test')
+ row = users.select().execute().fetchone()
+
+ s = StringIO()
+ writer = csv.writer(s)
+ # csv performs PySequenceCheck call
+ writer.writerow(row)
+ assert s.getvalue().strip() == '1,Test'
+
class EngineEventsTest(TestBase):
def _assert_stmts(self, expected, received):
diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py
index 71f87a726..5eba68e13 100644
--- a/test/orm/test_subquery_relations.py
+++ b/test/orm/test_subquery_relations.py
@@ -4,7 +4,7 @@ from sqlalchemy.test.schema import Table, Column
from sqlalchemy import Integer, String, ForeignKey, bindparam
from sqlalchemy.orm import backref, subqueryload, subqueryload_all, \
mapper, relationship, clear_mappers, create_session, lazyload, \
- aliased, joinedload, deferred, undefer
+ aliased, joinedload, deferred, undefer, eagerload_all
from sqlalchemy.test.testing import eq_, assert_raises, \
assert_raises_message
from sqlalchemy.test.assertsql import CompiledSQL
@@ -43,6 +43,59 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
self.assert_sql_count(testing.db, go, 2)
@testing.resolve_artifact_names
+ def test_from_aliased(self):
+ mapper(Dingaling, dingalings)
+ mapper(Address, addresses, properties={
+ 'dingalings':relationship(Dingaling, order_by=Dingaling.id)
+ })
+ mapper(User, users, properties={
+ 'addresses':relationship(
+ Address,
+ order_by=Address.id)
+ })
+ sess = create_session()
+
+ u = aliased(User)
+
+ q = sess.query(u).options(subqueryload(u.addresses))
+
+ def go():
+ eq_(
+ [User(id=7, addresses=[
+ Address(id=1, email_address='jack@bean.com')])],
+ q.filter(u.id==7).all()
+ )
+
+ self.assert_sql_count(testing.db, go, 2)
+
+ def go():
+ eq_(
+ self.static.user_address_result,
+ q.order_by(u.id).all()
+ )
+ self.assert_sql_count(testing.db, go, 2)
+
+ q = sess.query(u).\
+ options(subqueryload_all(u.addresses, Address.dingalings))
+
+ def go():
+ eq_(
+ [
+ User(id=8, addresses=[
+ Address(id=2, email_address='ed@wood.com', dingalings=[Dingaling()]),
+ Address(id=3, email_address='ed@bettyboop.com'),
+ Address(id=4, email_address='ed@lala.com'),
+ ]),
+ User(id=9, addresses=[
+ Address(id=5, dingalings=[Dingaling()])
+ ]),
+ ],
+ q.filter(u.id.in_([8, 9])).all()
+ )
+ self.assert_sql_count(testing.db, go, 3)
+
+
+ @testing.resolve_artifact_names
def test_from_get(self):
mapper(User, users, properties={
'addresses':relationship(