From: Mike Bayer Date: Tue, 5 Apr 2011 16:04:42 +0000 (-0400) Subject: - The 'useexisting' flag on Table has been superceded X-Git-Tag: rel_0_7b4~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d34bc7da318978f6d0235e5747e94160959eb276;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - The 'useexisting' flag on Table has been superceded by a new pair of flags 'keep_existing' and 'extend_existing'. 'extend_existing' is equivalent to 'useexisting' - the existing Table is returned, and additional constructor elements are added. With 'keep_existing', the existing Table is returned, but additional constructor elements are not added - these elements are only applied when the Table is newly created. [ticket:2109] --- diff --git a/CHANGES b/CHANGES index 5d6d60009f..dc7dba7ada 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,17 @@ CHANGES collection of Sequence objects, list of schema names. [ticket:2104] +- schema + - The 'useexisting' flag on Table has been superceded + by a new pair of flags 'keep_existing' and + 'extend_existing'. 'extend_existing' is equivalent + to 'useexisting' - the existing Table is returned, + and additional constructor elements are added. + With 'keep_existing', the existing Table is returned, + but additional constructor elements are not added - + these elements are only applied when the Table + is newly created. [ticket:2109] + -event - Added @event.listens_for() decorator, given target + event name, applies the decorated diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 1da43c6b16..8b9f1b1fd4 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -89,9 +89,14 @@ class Table(SchemaItem, expression.TableClause): Column('value', String(50)) ) - The Table object constructs a unique instance of itself based on its - name within the given MetaData object. Constructor - arguments are as follows: + The :class:`.Table` object constructs a unique instance of itself based on its + name and optionl schema name within the given :class:`.MetaData` object. + Calling the :class:`.Table` + constructor with the same name and same :class:`.MetaData` argument + a second time will return the *same* :class:`.Table` object - in this way + the :class:`.Table` constructor acts as a registry function. + + Constructor arguments are as follows: :param name: The name of this table as represented in the database. @@ -127,6 +132,14 @@ class Table(SchemaItem, expression.TableClause): or Connection instance to be used for the table reflection. If ``None``, the underlying MetaData's bound connectable will be used. + :param extend_existing: When ``True``, indicates that if this Table is already + present in the given :class:`.MetaData`, apply further arguments within + the constructor to the existing :class:`.Table`. + + If extend_existing or keep_existing are not set, an error is + raised if additional table modifiers are specified when + the given :class:`.Table` is already present in the :class:`.MetaData`. + :param implicit_returning: True by default - indicates that RETURNING can be used by default to fetch newly inserted primary key values, for backends which support this. Note that @@ -141,6 +154,20 @@ class Table(SchemaItem, expression.TableClause): :param info: A dictionary which defaults to ``{}``. A space to store application specific data. This must be a dictionary. + :param keep_existing: When ``True``, indicates that if this Table + is already present in the given :class:`.MetaData`, ignore + further arguments within the constructor to the existing + :class:`.Table`, and return the :class:`.Table` object as + originally created. This is to allow a function that wishes + to define a new :class:`.Table` on first call, but on + subsequent calls will return the same :class:`.Table`, + without any of the declarations (particularly constraints) + being applied a second time. Also see extend_existing. + + If extend_existing or keep_existing are not set, an error is + raised if additional table modifiers are specified when + the given :class:`.Table` is already present in the :class:`.MetaData`. + :param listeners: A list of tuples of the form ``(, )`` which will be passed to :func:`.event.listen` upon construction. This alternate hook to :func:`.event.listen` allows the establishment @@ -149,6 +176,7 @@ class Table(SchemaItem, expression.TableClause): the :meth:`.events.column_reflect` event:: def listen_for_reflect(table, column_info): + "handle the column reflection event" # ... t = Table( @@ -159,7 +187,8 @@ class Table(SchemaItem, expression.TableClause): ]) :param mustexist: When ``True``, indicates that this Table must already - be present in the given :class:`.MetaData`` collection. + be present in the given :class:`.MetaData`` collection, else + an exception is raised. :param prefixes: A list of strings to insert after CREATE in the CREATE TABLE @@ -178,12 +207,8 @@ class Table(SchemaItem, expression.TableClause): :param schema: The *schema name* for this table, which is required if the table resides in a schema other than the default selected schema for the engine's database connection. Defaults to ``None``. - - :param useexisting: When ``True``, indicates that if this Table is already - present in the given :class:`.MetaData`, apply further arguments within - the constructor to the existing :class:`.Table`. If this flag is not - set, an error is raised when the parameters of an existing - :class:`.Table` are overwritten. + + :param useexisting: Deprecated. Use extend_existing. """ @@ -200,17 +225,32 @@ class Table(SchemaItem, expression.TableClause): raise TypeError("Table() takes at least two arguments") schema = kw.get('schema', None) - useexisting = kw.pop('useexisting', False) + keep_existing = kw.pop('keep_existing', False) + extend_existing = kw.pop('extend_existing', False) + if 'useexisting' in kw: + util.warn_deprecated("useexisting is deprecated. Use extend_existing.") + if extend_existing: + raise exc.ArgumentError("useexisting is synonymous " + "with extend_existing.") + extend_existing = kw.pop('useexisting', False) + + if keep_existing and extend_existing: + raise exc.ArgumentError("keep_existing and extend_existing " + "are mutually exclusive.") + mustexist = kw.pop('mustexist', False) key = _get_table_key(name, schema) if key in metadata.tables: - if not useexisting and bool(args): + if not keep_existing and not extend_existing and bool(args): raise exc.InvalidRequestError( "Table '%s' is already defined for this MetaData " - "instance. Specify 'useexisting=True' to redefine " - "options and columns on an existing Table object." % key) + "instance. Specify 'extend_existing=True' " + "to redefine " + "options and columns on an " + "existing Table object." % key) table = metadata.tables[key] - table._init_existing(*args, **kw) + if extend_existing: + table._init_existing(*args, **kw) return table else: if mustexist: diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index eb4f2a8d13..023d7a3e24 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -3,7 +3,7 @@ import StringIO, unicodedata from sqlalchemy import types as sql_types from sqlalchemy import schema, events, event from sqlalchemy.engine.reflection import Inspector -from sqlalchemy import MetaData +from sqlalchemy import MetaData, Integer from test.lib.schema import Table, Column import sqlalchemy as sa from test.lib import ComparesTables, \ @@ -115,7 +115,7 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): meta3 = MetaData(testing.db) foo = Table('foo', meta3, autoload=True) foo = Table('foo', meta3, include_columns=['b', 'f', 'e'], - useexisting=True) + extend_existing=True) eq_([c.name for c in foo.c], ['b', 'e', 'f']) for c in ('b', 'f', 'e'): assert c in foo.c @@ -480,46 +480,6 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): finally: meta.drop_all() - @testing.exclude('mysql', '<', (4, 1, 1), 'innodb funkiness') - def test_use_existing(self): - meta = MetaData(testing.db) - users = Table('users', meta, - Column('id', sa.Integer, primary_key=True), - Column('name', sa.String(30)), - test_needs_fk=True) - addresses = Table( - 'addresses', - meta, - Column('id', sa.Integer, primary_key=True), - Column('user_id', sa.Integer, sa.ForeignKey('users.id')), - Column('data', sa.String(100)), - test_needs_fk=True, - ) - meta.create_all() - try: - meta2 = MetaData(testing.db) - addresses = Table('addresses', meta2, Column('data', - sa.Unicode), autoload=True) - try: - users = Table('users', meta2, Column('name', - sa.Unicode), autoload=True) - assert False - except sa.exc.InvalidRequestError, err: - assert str(err) \ - == "Table 'users' is already defined for this "\ - "MetaData instance. Specify 'useexisting=True' "\ - "to redefine options and columns on an existing "\ - "Table object." - users = Table('users', meta2, Column('name', sa.Unicode), - autoload=True, useexisting=True) - assert isinstance(users.c.name.type, sa.Unicode) - assert not users.quote - users = Table('users', meta2, quote=True, autoload=True, - useexisting=True) - assert users.quote - finally: - meta.drop_all() - def test_pks_not_uniques(self): """test that primary key reflection not tripped up by unique indexes""" diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index 77a75eb5f5..de86580fd5 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -6,7 +6,7 @@ import pickle from sqlalchemy import Integer, String, UniqueConstraint, \ CheckConstraint, ForeignKey, MetaData, Sequence, \ ForeignKeyConstraint, ColumnDefault, Index, event,\ - events + events, Unicode from test.lib.schema import Table, Column from sqlalchemy import schema, exc import sqlalchemy as tsa @@ -146,7 +146,7 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): assert_raises_message( tsa.exc.InvalidRequestError, "Table 'table1' is already defined for this "\ - "MetaData instance. Specify 'useexisting=True' "\ + "MetaData instance. Specify 'extend_existing=True' "\ "to redefine options and columns on an existing "\ "Table object.", go @@ -303,7 +303,7 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): t2 = Table('a', m2, Column('id',Integer,primary_key=True), Column('x', Integer, s2), - useexisting=True) + extend_existing=True) assert m2._sequences['x_seq'] is t2.c.x.default assert m2._sequences['x_seq'] is s2 @@ -322,7 +322,7 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): s2 = Sequence('x_seq') t2 = Table('a', m1, Column('x', Integer, s2), - useexisting=True + extend_existing=True ) assert t.c.x.default is s2 assert m1._sequences['x_seq'] is s2 @@ -336,7 +336,7 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): m2 = pickle.loads(pickle.dumps(m1)) - t2 = Table('a', m2, useexisting=True) + t2 = Table('a', m2, extend_existing=True) eq_(m2._sequences, {'x_seq':t2.c.x.default}) @@ -350,7 +350,7 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): m2 = pickle.loads(pickle.dumps(m1)) t2 = Table('a', m2, schema='y', - useexisting=True) + extend_existing=True) eq_(m2._schemas, m1._schemas) @@ -570,6 +570,222 @@ class TableTest(fixtures.TestBase, AssertsCompiledSQL): assign ) +class UseExistingTest(fixtures.TablesTest): + @classmethod + def define_tables(cls, metadata): + Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(30))) + + def _useexisting_fixture(self): + meta2 = MetaData(testing.db) + Table('users', meta2, autoload=True) + return meta2 + + def _notexisting_fixture(self): + return MetaData(testing.db) + + def test_exception_no_flags(self): + meta2 = self._useexisting_fixture() + def go(): + users = Table('users', meta2, Column('name', + Unicode), autoload=True) + assert_raises_message( + exc.InvalidRequestError, + "Table 'users' is already defined for this "\ + "MetaData instance.", + go + ) + + @testing.uses_deprecated + def test_deprecated_useexisting(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + autoload=True, useexisting=True) + assert isinstance(users.c.name.type, Unicode) + assert not users.quote + users = Table('users', meta2, quote=True, autoload=True, + useexisting=True) + assert users.quote + + def test_keep_plus_existing_raises(self): + meta2 = self._useexisting_fixture() + assert_raises( + exc.ArgumentError, + Table, 'users', meta2, keep_existing=True, + extend_existing=True + ) + + @testing.uses_deprecated + def test_existing_plus_useexisting_raises(self): + meta2 = self._useexisting_fixture() + assert_raises( + exc.ArgumentError, + Table, 'users', meta2, useexisting=True, + extend_existing=True + ) + + def test_keep_existing_no_dupe_constraints(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, + Column('id', Integer), + Column('name', Unicode), + UniqueConstraint('name'), + keep_existing=True + ) + assert 'name' in users.c + assert 'id' in users.c + eq_(len(users.constraints), 2) + + u2 = Table('users', meta2, + Column('id', Integer), + Column('name', Unicode), + UniqueConstraint('name'), + keep_existing=True + ) + eq_(len(u2.constraints), 2) + + def test_extend_existing_dupes_constraints(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, + Column('id', Integer), + Column('name', Unicode), + UniqueConstraint('name'), + extend_existing=True + ) + assert 'name' in users.c + assert 'id' in users.c + eq_(len(users.constraints), 2) + + u2 = Table('users', meta2, + Column('id', Integer), + Column('name', Unicode), + UniqueConstraint('name'), + extend_existing=True + ) + # constraint got duped + eq_(len(u2.constraints), 3) + + def test_keep_existing_coltype(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + autoload=True, keep_existing=True) + assert not isinstance(users.c.name.type, Unicode) + + def test_keep_existing_quote(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, quote=True, autoload=True, + keep_existing=True) + assert not users.quote + + def test_keep_existing_add_column(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + autoload=True, + keep_existing=True) + assert "foo" not in users.c + + def test_keep_existing_coltype_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + autoload=True, keep_existing=True) + assert isinstance(users.c.name.type, Unicode) + + def test_keep_existing_quote_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, quote=True, + autoload=True, + keep_existing=True) + assert users.quote + + def test_keep_existing_add_column_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + autoload=True, + keep_existing=True) + assert "foo" in users.c + + def test_keep_existing_coltype_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + keep_existing=True) + assert not isinstance(users.c.name.type, Unicode) + + def test_keep_existing_quote_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, quote=True, + keep_existing=True) + assert not users.quote + + def test_keep_existing_add_column_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + keep_existing=True) + assert "foo" not in users.c + + def test_extend_existing_coltype(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + autoload=True, extend_existing=True) + assert isinstance(users.c.name.type, Unicode) + + def test_extend_existing_quote(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, quote=True, autoload=True, + extend_existing=True) + assert users.quote + + def test_extend_existing_add_column(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + autoload=True, + extend_existing=True) + assert "foo" in users.c + + def test_extend_existing_coltype_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + autoload=True, extend_existing=True) + assert isinstance(users.c.name.type, Unicode) + + def test_extend_existing_quote_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, quote=True, + autoload=True, + extend_existing=True) + assert users.quote + + def test_extend_existing_add_column_no_orig(self): + meta2 = self._notexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + autoload=True, + extend_existing=True) + assert "foo" in users.c + + def test_extend_existing_coltype_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, Column('name', Unicode), + extend_existing=True) + assert isinstance(users.c.name.type, Unicode) + + def test_extend_existing_quote_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, quote=True, + extend_existing=True) + assert users.quote + + def test_extend_existing_add_column_no_reflection(self): + meta2 = self._useexisting_fixture() + users = Table('users', meta2, + Column('foo', Integer), + extend_existing=True) + assert "foo" in users.c + class ConstraintTest(fixtures.TestBase): def _single_fixture(self): m = MetaData()