diff options
-rw-r--r-- | CHANGES | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 24 | ||||
-rw-r--r-- | lib/sqlalchemy/schema.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 63 | ||||
-rw-r--r-- | test/engine/test_transaction.py | 73 |
5 files changed, 140 insertions, 39 deletions
@@ -240,6 +240,12 @@ CHANGES Use the 'bind' keyword argument. - sql + + - the "autocommit" flag on select() and text() as well + as select().autocommit() are deprecated - now call + .execution_options(autocommit=True) on either of those + constructs, also available directly on Connection and orm.Query. + - the autoincrement flag on column now indicates the column which should be linked to cursor.lastrowid, if that method is used. See the API docs for details. @@ -349,14 +355,16 @@ CHANGES - transaction isolation level may be specified with create_engine(... isolation_level="..."); available on postgresql and sqlite. [ticket:443] - + - Connection has execution_options(), generative method which accepts keywords that affect how the statement is executed w.r.t. the DBAPI. Currently supports "stream_results", causes psycopg2 to use a server - side cursor for that statement. Can also be set - upon select() and text() constructs directly as well - as ORM Query(). + side cursor for that statement, as well as + "autocommit", which is the new location for the "autocommit" + option from select() and text(). select() and + text() also have .execution_options() as well as + ORM Query(). - fixed the import for entrypoint-driven dialects to not rely upon silly tb_info trick to determine import diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index e168a5465..e2a03d227 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -229,7 +229,6 @@ class DefaultExecutionContext(base.ExecutionContext): self.dialect = dialect self._connection = self.root_connection = connection self.engine = connection.engine - self.execution_options = connection._execution_options if compiled_ddl is not None: self.compiled = compiled = compiled_ddl @@ -268,9 +267,6 @@ class DefaultExecutionContext(base.ExecutionContext): self.isinsert = compiled.isinsert self.isupdate = compiled.isupdate self.isdelete = compiled.isdelete - if compiled.statement._execution_options: - self.execution_options =\ - compiled.statement._execution_options.union(self.execution_options) if not parameters: self.compiled_parameters = [compiled.construct_params()] @@ -301,18 +297,24 @@ class DefaultExecutionContext(base.ExecutionContext): self.cursor = self.create_cursor() @util.memoized_property - def should_autocommit(self): + def execution_options(self): if self.compiled: - autocommit = self.compiled.statement._autocommit - if autocommit is expression.PARSE_AUTOCOMMIT: + return self.compiled.statement._execution_options.union( + self._connection._execution_options) + else: + return self._connection._execution_options + + @util.memoized_property + def should_autocommit(self): + autocommit = self.execution_options.get('autocommit', expression.PARSE_AUTOCOMMIT) + if autocommit is expression.PARSE_AUTOCOMMIT: + if self.statement: return self.should_autocommit_text(self.statement) else: - return autocommit - elif self.statement: - return self.should_autocommit_text(self.statement) + return False else: - return False + return autocommit @util.memoized_property def _is_explicit_returning(self): diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index f2737ecde..cd9bd4892 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -2035,7 +2035,8 @@ class SchemaVisitor(visitors.ClauseVisitor): class DDLElement(expression._Executable, expression.ClauseElement): """Base class for DDL expression constructs.""" - _autocommit = True + _execution_options = expression._Executable.\ + _execution_options.union({'autocommit':True}) target = None on = None diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index a4504e833..5edb6e47f 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -164,9 +164,8 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs): Additional parameters include: autocommit - indicates this SELECT statement modifies the database, and - should be subject to autocommit behavior if no transaction - has been started. + Deprecated. Use .execution_options(autocommit=<True|False>) + to set the autocommit option. prefixes a list of strings or :class:`ClauseElement` objects to include @@ -805,9 +804,8 @@ def text(text, bind=None, *args, **kwargs): an optional connection or engine to be used for this text query. autocommit=True - indicates this SELECT statement modifies the database, and - should be subject to autocommit behavior if no transaction - has been started. + Deprecated. Use .execution_options(autocommit=<True|False>) + to set the autocommit option. bindparams a list of :func:`bindparam()` instances which can be used to define @@ -2215,9 +2213,26 @@ class _Executable(object): @_generative def execution_options(self, **kw): - """ Set non-SQL options for the statement, such as dialect-specific options. - - The options available are covered in the respective dialect's section. + """ Set non-SQL options for the statement which take effect during execution. + + Current options include: + + * autocommit - when True, a COMMIT will be invoked after execution + when executed in 'autocommit' mode, i.e. when an explicit transaction + is not begun on the connection. Note that DBAPI connections by + default are always in a transaction - SQLAlchemy uses rules applied + to different kinds of statements to determine if COMMIT will be invoked + in order to provide its "autocommit" feature. Typically, all + INSERT/UPDATE/DELETE statements as well as CREATE/DROP statements + have autocommit behavior enabled; SELECT constructs do not. Use this + option when invokving a SELECT or other specific SQL construct + where COMMIT is desired (typically when calling stored procedures + and such). + + * stream_results - indicate to the dialect that results should be + "streamed" and not pre-buffered, if possible. This is a limitation + of many DBAPIs. The flag is currently understood only by the + psycopg2 dialect. """ self._execution_options = self._execution_options.union(kw) @@ -2233,7 +2248,8 @@ class _TextClause(_Executable, ClauseElement): __visit_name__ = 'textclause' _bind_params_regex = re.compile(r'(?<![:\w\x5c]):(\w+)(?!:)', re.UNICODE) - + _execution_options = _Executable._execution_options.union({'autocommit':PARSE_AUTOCOMMIT}) + @property def _select_iterable(self): return (self,) @@ -2242,11 +2258,16 @@ class _TextClause(_Executable, ClauseElement): def __init__(self, text = "", bind=None, bindparams=None, typemap=None, - autocommit=PARSE_AUTOCOMMIT): + autocommit=None): self._bind = bind self.bindparams = {} self.typemap = typemap - self._autocommit = autocommit + + if autocommit is not None: + util.warn_deprecated("autocommit on text() is deprecated. " + "Use .execution_options(autocommit=True)") + self._execution_options = self._execution_options.union({'autocommit':autocommit}) + if typemap is not None: for key in typemap.keys(): typemap[key] = sqltypes.to_instance(typemap[key]) @@ -2856,7 +2877,6 @@ class Alias(FromClause): self.original = baseselectable self.supports_execution = baseselectable.supports_execution if self.supports_execution: - self._autocommit = baseselectable._autocommit self._execution_options = baseselectable._execution_options self.element = selectable if alias is None: @@ -3229,10 +3249,13 @@ class _SelectBaseMixin(_Executable): order_by=None, group_by=None, bind=None, - autocommit=False): + autocommit=None): self.use_labels = use_labels self.for_update = for_update - self._autocommit = autocommit + if autocommit is not None: + util.warn_deprecated("autocommit on select() is deprecated. " + "Use .execution_options(autocommit=True)") + self._execution_options = self._execution_options.union({'autocommit':autocommit}) self._limit = limit self._offset = offset self._bind = bind @@ -3276,10 +3299,12 @@ class _SelectBaseMixin(_Executable): return self.as_scalar().label(name) @_generative + @util.deprecated(message="autocommit() is deprecated. " + "Use .execution_options(autocommit=True)") def autocommit(self): """return a new selectable with the 'autocommit' flag set to True.""" - - self._autocommit = True + + self._execution_options = self._execution_options.union({'autocommit':True}) def _generate(self): s = self.__class__.__new__(self.__class__) @@ -3864,7 +3889,7 @@ class _UpdateBase(_Executable, ClauseElement): __visit_name__ = 'update_base' - _autocommit = True + _execution_options = _Executable._execution_options.union({'autocommit':True}) def _generate(self): s = self.__class__.__new__(self.__class__) @@ -4118,7 +4143,7 @@ class Delete(_UpdateBase): class _IdentifiedClause(_Executable, ClauseElement): __visit_name__ = 'identified' - _autocommit = False + _execution_options = _Executable._execution_options.union({'autocommit':False}) quote = None def __init__(self, ident): diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py index a58d65e68..fa2cc3e59 100644 --- a/test/engine/test_transaction.py +++ b/test/engine/test_transaction.py @@ -369,7 +369,6 @@ class AutoRollbackTest(TestBase): users.drop(conn2) conn2.close() -foo = None class ExplicitAutoCommitTest(TestBase): """test the 'autocommit' flag on select() and text() objects. @@ -382,9 +381,13 @@ class ExplicitAutoCommitTest(TestBase): def setup_class(cls): global metadata, foo metadata = MetaData(testing.db) - foo = Table('foo', metadata, Column('id', Integer, primary_key=True), Column('data', String(100))) + foo = Table('foo', metadata, + Column('id', Integer, primary_key=True), + Column('data', String(100)) + ) metadata.create_all() - testing.db.execute("create function insert_foo(varchar) returns integer as 'insert into foo(data) values ($1);select 1;' language sql") + testing.db.execute("create function insert_foo(varchar) returns integer " + "as 'insert into foo(data) values ($1);select 1;' language sql") def teardown(self): foo.delete().execute().close() @@ -417,6 +420,67 @@ class ExplicitAutoCommitTest(TestBase): conn1 = testing.db.connect() conn2 = testing.db.connect() + conn1.execute(select([func.insert_foo('data1')]).execution_options(autocommit=True)) + assert conn2.execute(select([foo.c.data])).fetchall() == [('data1',)] + + conn1.close() + conn2.close() + + def test_explicit_connection(self): + conn1 = testing.db.connect() + conn2 = testing.db.connect() + + conn1.execution_options(autocommit=True).\ + execute(select([func.insert_foo('data1')])) + eq_( + conn2.execute(select([foo.c.data])).fetchall(), + [('data1',), ] + ) + + # connection supercedes statement + conn1.execution_options(autocommit=False).\ + execute( + select([func.insert_foo('data2')]). + execution_options(autocommit=True) + ) + eq_( + conn2.execute(select([foo.c.data])).fetchall(), + [('data1',), ] + ) + + # ditto + conn1.execution_options(autocommit=True).\ + execute( + select([func.insert_foo('data3')]). + execution_options(autocommit=False) + ) + eq_( + conn2.execute(select([foo.c.data])).fetchall(), + [('data1',), ('data2', ), ('data3',)] + ) + + conn1.close() + conn2.close() + + def test_explicit_text(self): + conn1 = testing.db.connect() + conn2 = testing.db.connect() + + conn1.execute( + text("select insert_foo('moredata')"). + execution_options(autocommit=True) + ) + assert conn2.execute(select([foo.c.data])).fetchall() == [('moredata',)] + + conn1.close() + conn2.close() + + @testing.uses_deprecated(r'autocommit on select\(\) is deprecated', + r'autocommit\(\) is deprecated') + def test_explicit_compiled_deprecated(self): + conn1 = testing.db.connect() + conn2 = testing.db.connect() + conn1.execute(select([func.insert_foo('data1')], autocommit=True)) assert conn2.execute(select([foo.c.data])).fetchall() == [('data1',)] @@ -426,7 +490,8 @@ class ExplicitAutoCommitTest(TestBase): conn1.close() conn2.close() - def test_explicit_text(self): + @testing.uses_deprecated(r'autocommit on text\(\) is deprecated') + def test_explicit_text_deprecated(self): conn1 = testing.db.connect() conn2 = testing.db.connect() |