From fd8d4a45ea1c2087ed3c4a86bf5f889b194fdb48 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 1 Mar 2006 21:20:59 +0000 Subject: [PATCH] made SchemaEngine more prominent as the base of Table association BaseProxyEngine descends from SchemaEngine fixes to sqlite/postgres reflection to use the correct engine for table lookups Table engine can be none which will default to schema.default_engine (although its still positional for now, so still needs to be explicit to make room for Columns) __init__ sets default_engine to be a blank ProxyEngine fixes to test suite to allow --db proxy. to really test proxyengine --- lib/sqlalchemy/__init__.py | 4 ++++ lib/sqlalchemy/databases/postgres.py | 6 ++++-- lib/sqlalchemy/databases/sqlite.py | 4 +++- lib/sqlalchemy/engine.py | 6 +++++- lib/sqlalchemy/ext/proxy.py | 17 +++++++++++------ lib/sqlalchemy/schema.py | 13 +++++++++---- test/engines.py | 8 ++++---- test/inheritance.py | 8 +++++--- test/query.py | 6 +++--- test/testbase.py | 23 ++++++++++++++++++----- 10 files changed, 66 insertions(+), 29 deletions(-) diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index d38a557f97..5efbf6652b 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -11,3 +11,7 @@ from schema import * from exceptions import * import mapping as mapperlib from mapping import * + +import sqlalchemy.schema +import sqlalchemy.ext.proxy +sqlalchemy.schema.default_engine = sqlalchemy.ext.proxy.ProxyEngine() diff --git a/lib/sqlalchemy/databases/postgres.py b/lib/sqlalchemy/databases/postgres.py index 13714ba3e6..92407637f9 100644 --- a/lib/sqlalchemy/databases/postgres.py +++ b/lib/sqlalchemy/databases/postgres.py @@ -199,7 +199,7 @@ class PGSQLEngine(ansisql.ANSISQLEngine): self.opts['port'] = str(self.opts['port']) ansisql.ANSISQLEngine.__init__(self, **params) - + def connect_args(self): return [[], self.opts] @@ -277,7 +277,9 @@ class PGSQLEngine(ansisql.ANSISQLEngine): else: ischema_names = pg1_ischema_names - ischema.reflecttable(self, table, ischema_names) + # give ischema the given table's engine with which to look up + # other tables, not 'self', since it could be a ProxyEngine + ischema.reflecttable(table.engine, table, ischema_names) class PGCompiler(ansisql.ANSICompiler): diff --git a/lib/sqlalchemy/databases/sqlite.py b/lib/sqlalchemy/databases/sqlite.py index bb46578a33..2e366e4324 100644 --- a/lib/sqlalchemy/databases/sqlite.py +++ b/lib/sqlalchemy/databases/sqlite.py @@ -182,7 +182,9 @@ class SQLiteSQLEngine(ansisql.ANSISQLEngine): break (tablename, localcol, remotecol) = (row[2], row[3], row[4]) #print "row! " + repr(row) - remotetable = Table(tablename, self, autoload = True) + # look up the table based on the given table's engine, not 'self', + # since it could be a ProxyEngine + remotetable = Table(tablename, table.engine, autoload = True) table.c[localcol].append_item(schema.ForeignKey(remotetable.c[remotecol])) # check for UNIQUE indexes c = self.execute("PRAGMA index_list(" + table.name + ")", {}) diff --git a/lib/sqlalchemy/engine.py b/lib/sqlalchemy/engine.py index 612cec7517..757d3517ee 100644 --- a/lib/sqlalchemy/engine.py +++ b/lib/sqlalchemy/engine.py @@ -172,6 +172,7 @@ class SQLEngine(schema.SchemaEngine): # get a handle on the connection pool via the connect arguments # this insures the SQLEngine instance integrates with the pool referenced # by direct usage of pool.manager().connect(*args, **params) + schema.SchemaEngine.__init__(self) (cargs, cparams) = self.connect_args() if pool is None: params['echo'] = echo_pool @@ -183,7 +184,6 @@ class SQLEngine(schema.SchemaEngine): self.echo_uow = echo_uow self.convert_unicode = convert_unicode self.context = util.ThreadLocal(raiseerror=False) - self.tables = {} self._ischema = None self._figure_paramstyle() if logger is None: @@ -204,6 +204,10 @@ class SQLEngine(schema.SchemaEngine): def hash_key(self): return "%s(%s)" % (self.__class__.__name__, repr(self.connect_args())) + + def _get_name(self): + return sys.modules[self.__module__].descriptor()['name'] + name = property(_get_name) def dispose(self): """disposes of the underlying pool manager for this SQLEngine.""" diff --git a/lib/sqlalchemy/ext/proxy.py b/lib/sqlalchemy/ext/proxy.py index 783ea969e8..b8351d38fe 100644 --- a/lib/sqlalchemy/ext/proxy.py +++ b/lib/sqlalchemy/ext/proxy.py @@ -6,16 +6,14 @@ except ImportError: from sqlalchemy import sql from sqlalchemy.engine import create_engine from sqlalchemy.types import TypeEngine - +import sqlalchemy.schema as schema import thread, weakref -class BaseProxyEngine(object): +class BaseProxyEngine(schema.SchemaEngine): ''' Basis for all proxy engines ''' - def __init__(self): - self.tables = {} - + def get_engine(self): raise NotImplementedError @@ -24,6 +22,9 @@ class BaseProxyEngine(object): engine = property(get_engine, set_engine) + def reflecttable(self, table): + return self.get_engine().reflecttable(table) + def hash_key(self): return "%s(%s)" % (self.__class__.__name__, id(self)) @@ -83,16 +84,20 @@ class ProxyEngine(BaseProxyEngine): classes for TypeEngine. """ - def __init__(self): + def __init__(self, **kwargs): BaseProxyEngine.__init__(self) # create the local storage for uri->engine map and current engine self.storage = local() self.storage.connection = {} self.storage.engine = None + self.kwargs = kwargs def connect(self, uri, opts=None, **kwargs): """Establish connection to a real engine. """ + kw = self.kwargs.copy() + kw.update(kwargs) + kwargs = kw key = "%s(%s,%s)" % (uri, repr(opts), repr(kwargs)) try: map = self.storage.connection diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 6152537dd1..6d43d8e9d0 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -45,8 +45,10 @@ def _get_table_key(engine, name, schema): class TableSingleton(type): """a metaclass used by the Table object to provide singleton behavior.""" - def __call__(self, name, engine, *args, **kwargs): + def __call__(self, name, engine=None, *args, **kwargs): try: + if engine is None: + engine = default_engine name = str(name) # in case of incoming unicode schema = kwargs.get('schema', None) autoload = kwargs.pop('autoload', False) @@ -146,7 +148,6 @@ class Table(sql.TableClause, SchemaItem): metaclass constructor.""" self._clear() - print "RELOAD VALUES", args self._init_items(*args) def append_item(self, item): @@ -383,7 +384,7 @@ class ForeignKey(SchemaItem): if isinstance(self._colspec, str): return self._colspec elif self._colspec.table.schema is not None: - return "%s.%s.%s" % (self._colspec.table.schema, self._colspec.table.name, self._colspec.column.key) + return "%s.%s.%s" % (self._colspec.table.schema, self._colspec.table.name, self._colspec.key) else: return "%s.%s" % (self._colspec.table.name, self._colspec.key) @@ -412,7 +413,6 @@ class ForeignKey(SchemaItem): self._column = table.c[colname] else: self._column = self._colspec - return self._column column = property(lambda s: s._init_column()) @@ -540,6 +540,11 @@ class Index(SchemaItem): class SchemaEngine(object): """a factory object used to create implementations for schema objects. This object is the ultimate base class for the engine.SQLEngine class.""" + + def __init__(self): + # a dictionary that stores Table objects keyed off their name (and possibly schema name) + self.tables = {} + def reflecttable(self, table): """given a table, will query the database and populate its Column and ForeignKey objects.""" diff --git a/test/engines.py b/test/engines.py index 7cc07dddaf..aeb962c9b3 100644 --- a/test/engines.py +++ b/test/engines.py @@ -58,8 +58,6 @@ class EngineTest(PersistTest): mysql_engine='InnoDB' ) - print repr(users) - print repr(addresses) # users.c.parent_user_id.set_foreign_key(ForeignKey(users.c.user_id)) @@ -69,14 +67,14 @@ class EngineTest(PersistTest): # clear out table registry users.deregister() addresses.deregister() - + try: users = Table('engine_users', testbase.db, autoload = True) addresses = Table('engine_email_addresses', testbase.db, autoload = True) finally: addresses.drop() users.drop() - + users.create() addresses.create() try: @@ -86,6 +84,8 @@ class EngineTest(PersistTest): # we can now as long as we use InnoDB # if testbase.db.engine.__module__.endswith('mysql'): # addresses.c.remote_user_id.append_item(ForeignKey('engine_users.user_id')) + print users + print addresses j = join(users, addresses) print str(j.onclause) self.assert_((users.c.user_id==addresses.c.remote_user_id).compare(j.onclause)) diff --git a/test/inheritance.py b/test/inheritance.py index 82d66e48c7..4400cab891 100644 --- a/test/inheritance.py +++ b/test/inheritance.py @@ -139,8 +139,10 @@ class InheritTest2(testbase.AssertMixin): b = Bar('barfoo') objectstore.commit() - b.foos.append(Foo('subfoo1')) - b.foos.append(Foo('subfoo2')) + f1 = Foo('subfoo1') + f2 = Foo('subfoo2') + b.foos.append(f1) + b.foos.append(f2) objectstore.commit() objectstore.clear() @@ -150,7 +152,7 @@ class InheritTest2(testbase.AssertMixin): print l[0].foos self.assert_result(l, Bar, # {'id':1, 'data':'barfoo', 'bid':1, 'foos':(Foo, [{'id':2,'data':'subfoo1'}, {'id':3,'data':'subfoo2'}])}, - {'id':1, 'data':'barfoo', 'foos':(Foo, [{'id':2,'data':'subfoo1'}, {'id':3,'data':'subfoo2'}])}, + {'id':b.id, 'data':'barfoo', 'foos':(Foo, [{'id':f1.id,'data':'subfoo1'}, {'id':f2.id,'data':'subfoo2'}])}, ) diff --git a/test/query.py b/test/query.py index 8c732b75a5..cf0bc94d32 100644 --- a/test/query.py +++ b/test/query.py @@ -47,7 +47,7 @@ class QueryTest(PersistTest): that PassiveDefault upon insert, even though PassiveDefault says "let the database execute this", because in postgres we must have all the primary key values in memory before insert; otherwise we cant locate the just inserted row.""" - if not db.engine.__module__.endswith('postgres'): + if db.engine.name != 'postgres': return try: db.execute(""" @@ -96,8 +96,8 @@ class QueryTest(PersistTest): x['x'] += 1 return x['x'] - use_function_defaults = db.engine.__module__.endswith('postgres') or db.engine.__module__.endswith('oracle') - is_oracle = db.engine.__module__.endswith('oracle') + use_function_defaults = db.engine.name == 'postgres' or db.engine.name == 'oracle' + is_oracle = db.engine.name == 'oracle' # select "count(1)" from the DB which returns different results # on different DBs diff --git a/test/testbase.py b/test/testbase.py index cdd0d6a33e..2a9094f5d0 100644 --- a/test/testbase.py +++ b/test/testbase.py @@ -1,6 +1,7 @@ import unittest import StringIO import sqlalchemy.engine as engine +import sqlalchemy.ext.proxy import re, sys echo = True @@ -15,14 +16,22 @@ def parse_argv(): global db, db_uri DBTYPE = 'sqlite' - + PROXY = False + if len(sys.argv) >= 3: if sys.argv[1] == '--dburi': (param, db_uri) = (sys.argv.pop(1), sys.argv.pop(1)) elif sys.argv[1] == '--db': (param, DBTYPE) = (sys.argv.pop(1), sys.argv.pop(1)) + if (None == db_uri): + p = DBTYPE.split('.') + if len(p) > 1: + arg = p[0] + DBTYPE = p[1] + if arg == 'proxy': + PROXY = True if DBTYPE == 'sqlite': db_uri = 'sqlite://filename=:memory:' elif DBTYPE == 'sqlite_file': @@ -37,7 +46,11 @@ def parse_argv(): if not db_uri: raise "Could not create engine. specify --db to test runner." - db = engine.create_engine(db_uri, echo=echo, default_ordering=True) + if PROXY: + db = sqlalchemy.ext.proxy.ProxyEngine(echo=echo, default_ordering=True) + db.connect(db_uri) + else: + db = engine.create_engine(db_uri, echo=echo, default_ordering=True) db = EngineAssert(db) class PersistTest(unittest.TestCase): @@ -75,7 +88,7 @@ class AssertMixin(PersistTest): else: self.assert_(getattr(rowobj, key) == value, "attribute %s value %s does not match %s" % (key, getattr(rowobj, key), value)) def assert_sql(self, db, callable_, list, with_sequences=None): - if with_sequences is not None and (db.engine.__module__.endswith('postgres') or db.engine.__module__.endswith('oracle')): + if with_sequences is not None and (db.engine.name == 'postgres' or db.engine.name == 'oracle'): db.set_assert_list(self, with_sequences) else: db.set_assert_list(self, list) @@ -89,13 +102,13 @@ class AssertMixin(PersistTest): callable_() finally: self.assert_(db.sql_count == count, "desired statement count %d does not match %d" % (count, db.sql_count)) - + class EngineAssert(object): """decorates a SQLEngine object to match the incoming queries against a set of assertions.""" def __init__(self, engine): self.engine = engine self.realexec = engine.post_exec - engine.post_exec = self.post_exec + self.realexec.im_self.post_exec = self.post_exec self.logger = engine.logger self.set_assert_list(None, None) self.sql_count = 0 -- 2.47.2