summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--lib/sqlalchemy/schema.py52
-rw-r--r--test/sql/test_defaults.py30
3 files changed, 68 insertions, 19 deletions
diff --git a/CHANGES b/CHANGES
index 0ca4ae0ed..adb59a671 100644
--- a/CHANGES
+++ b/CHANGES
@@ -442,6 +442,11 @@ underneath "0.7.xx".
name. The deprecated fold_equivalents() feature is
removed [ticket:1729].
+ - [bug] Fixes to the interpretation of the
+ Column "default" parameter as a callable
+ to not pass ExecutionContext into a keyword
+ argument parameter. [ticket:2520]
+
- [bug] All of UniqueConstraint, ForeignKeyConstraint,
CheckConstraint, and PrimaryKeyConstraint will
attach themselves to their parent table automatically
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index f2ca45894..4ce27582b 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -771,11 +771,12 @@ class Column(SchemaItem, expression.ColumnClause):
at :ref:`post_update`.
:param default: A scalar, Python callable, or
- :class:`~sqlalchemy.sql.expression.ClauseElement` representing the
+ :class:`.ColumnElement` expression representing the
*default value* for this column, which will be invoked upon insert
if this column is otherwise not specified in the VALUES clause of
the insert. This is a shortcut to using :class:`.ColumnDefault` as
- a positional argument.
+ a positional argument; see that class for full detail on the
+ structure of the argument.
Contrast this argument to ``server_default`` which creates a
default generator on the database side.
@@ -1532,6 +1533,31 @@ class ColumnDefault(DefaultGenerator):
"""
def __init__(self, arg, **kwargs):
+ """"Construct a new :class:`.ColumnDefault`.
+
+
+ :param arg: argument representing the default value.
+ May be one of the following:
+
+ * a plain non-callable Python value, such as a
+ string, integer, boolean, or other simple type.
+ The default value will be used as is each time.
+ * a SQL expression, that is one which derives from
+ :class:`.ColumnElement`. The SQL expression will
+ be rendered into the INSERT or UPDATE statement,
+ or in the case of a primary key column when
+ RETURNING is not used may be
+ pre-executed before an INSERT within a SELECT.
+ * A Python callable. The function will be invoked for each
+ new row subject to an INSERT or UPDATE.
+ The callable must accept exactly
+ zero or one positional arguments. The one-argument form
+ will receive an instance of the :class:`.ExecutionContext`,
+ which provides contextual information as to the current
+ :class:`.Connection` in use as well as the current
+ statement and parameters.
+
+ """
super(ColumnDefault, self).__init__(**kwargs)
if isinstance(arg, FetchedValue):
raise exc.ArgumentError(
@@ -1555,8 +1581,18 @@ class ColumnDefault(DefaultGenerator):
not self.is_sequence
def _maybe_wrap_callable(self, fn):
- """Backward compat: Wrap callables that don't accept a context."""
+ """Wrap callables that don't accept a context.
+ The alternative here is to require that
+ a simple callable passed to "default" would need
+ to be of the form "default=lambda ctx: datetime.now".
+ That is the more "correct" way to go, but the case
+ of using a zero-arg callable for "default" is so
+ much more prominent than the context-specific one
+ I'm having trouble justifying putting that inconvenience
+ on everyone.
+
+ """
if inspect.isfunction(fn):
inspectable = fn
elif inspect.isclass(fn):
@@ -1571,7 +1607,8 @@ class ColumnDefault(DefaultGenerator):
except TypeError:
return lambda ctx: fn()
- positionals = len(argspec[0])
+ defaulted = argspec[3] is not None and len(argspec[3]) or 0
+ positionals = len(argspec[0]) - defaulted
# Py3K compat - no unbound methods
if inspect.ismethod(inspectable) or inspect.isclass(fn):
@@ -1579,13 +1616,12 @@ class ColumnDefault(DefaultGenerator):
if positionals == 0:
return lambda ctx: fn()
-
- defaulted = argspec[3] is not None and len(argspec[3]) or 0
- if positionals - defaulted > 1:
+ elif positionals == 1:
+ return fn
+ else:
raise exc.ArgumentError(
"ColumnDefault Python function takes zero or one "
"positional arguments")
- return fn
def _visit_name(self):
if self.for_update:
diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py
index 7a6c6d009..55aa86633 100644
--- a/test/sql/test_defaults.py
+++ b/test/sql/test_defaults.py
@@ -132,10 +132,13 @@ class DefaultTest(fixtures.TestBase):
def test_bad_arg_signature(self):
ex_msg = \
- "ColumnDefault Python function takes zero or one positional arguments"
+ "ColumnDefault Python function takes zero "\
+ "or one positional arguments"
- def fn1(x, y): pass
- def fn2(x, y, z=3): pass
+ def fn1(x, y):
+ pass
+ def fn2(x, y, z=3):
+ pass
class fn3(object):
def __init__(self, x, y):
pass
@@ -150,28 +153,33 @@ class DefaultTest(fixtures.TestBase):
sa.ColumnDefault, fn)
def test_arg_signature(self):
- def fn1(): pass
- def fn2(): pass
- def fn3(x=1): pass
- def fn4(x=1, y=2, z=3): pass
+ def fn1():
+ pass
+ def fn2():
+ pass
+ def fn3(x=1):
+ eq_(x, 1)
+ def fn4(x=1, y=2, z=3):
+ eq_(x, 1)
fn5 = list
class fn6(object):
def __init__(self, x):
- pass
+ eq_(x, "context")
class fn6(object):
def __init__(self, x, y=3):
- pass
+ eq_(x, "context")
class FN7(object):
def __call__(self, x):
- pass
+ eq_(x, "context")
fn7 = FN7()
class FN8(object):
def __call__(self, x, y=3):
- pass
+ eq_(x, "context")
fn8 = FN8()
for fn in fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8:
c = sa.ColumnDefault(fn)
+ c.arg("context")
@testing.fails_on('firebird', 'Data type unknown')
def test_standalone(self):