ProxyEngine seems to over-proxy types

Issue #51 resolved
Former user created an issue

With the table definition

users = Table('users', db_engine,
              Column('user_id', Integer, primary_key = True),
              Column('user_name', String(32), nullable = False),
              Column('first_name', String(32), nullable = False),
              Column('last_name', String(32), nullable = False),
              Column('email_address', String(64), nullable = False),
              Column('web_address', String(64), nullable = True),
              Column('password', String(48), nullable = False)
              )

where {{{db_engine}}} is an instance of {{{ProxyEngine}}}, the types are proxied ''twice''---i.e. the type of the first column is set to a {{{ProxyTypeEngine}}} whose typeobj is ''another'' {{{ProxyTypeEngine}}} which actually points at the correct type.

This breaks the schema generators, because they do tests like {{{isinstance(column.type, types.Integer)}}}, which fails because it sees a {{{ProxyTypeEngine}}} object rather than the actual type object. One obvious result of this is that the {{{create()}}} methods can't currently create {{{AUTO_INCREMENT}}} or {{{SERIAL}}} columns automatically for the primary keys of tables.

I'm currently investigating why this is happening and will post more information if/when I work it out.

-- alastair

Comments (5)

  1. Former user Account Deleted

    Interesting. I can't reproduce the double proxying (although it was definitely happening), but the actual problem is still there; because of the {{{ProxyTypeEngine}}}, {{{AUTO_INCREMENT}}} columns aren't being created.

  2. Former user Account Deleted

    It looks to me as if the best fix would be to change the code in the database drivers so that it calls a function on the type object to determine whether the type object is of a particular kind. This should fix the problem, as the proxy can just pass it straight through to the underlying object, which will always return the right thing.

  3. Former user Account Deleted

    Having messed about a bit more with the Python code, I came up with the following fix:

    Index: lib/sqlalchemy/ext/proxy.py
    ===================================================================
    --- lib/sqlalchemy/ext/proxy.py (revision 894)
    +++ lib/sqlalchemy/ext/proxy.py (working copy)
    @@ -100,8 +100,8 @@
    
         engine = property(lambda self: self._engine.engine)
    
    -class ProxyTypeEngine(object):
    -    """Proxy type engine; defers engine access to ProxyEngine
    +class ProxyTypeEngineProxy(object):
    +    """Proxy type engine proxy object
         """
         def __init__(self, engine, typeobj):
             self._engine = engine
    @@ -109,38 +109,26 @@
    
         engine = property(lambda self: self._engine.engine)
    
    -    def __getattr__(self, attr):
    -        # NOTE:
    -        # profiling so far indicates that caching the type_descriptor
    -        # results is more trouble than it's worth
    -        return getattr(self.engine.type_descriptor(self.typeobj), attr)
    +    def __getattribute__(self, name):
    +        if name.startswith('__') and name.endswith('__'):
    +            return object.__getattribute__(self, name)
    +        
    +        engine = object.__getattribute__(self, '_engine').engine
    +        typeobj = object.__getattribute__(self, 'typeobj')
    +        return getattr(engine.type_descriptor(typeobj), name)
    
    +    def __repr__(self):
    +        return '<Proxy %s>' % (object.__getattribute__(self, 'typeobj'))
    
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +class ProxyTypeEngine(object):
    +    """Proxy type engine; defers engine access to ProxyEngine
    +    """
    +    def __new__(oldcls, engine, typeobj):
    +        if isinstance(typeobj, TypeEngine):
    +            thetype = typeobj.__class__
    +        else:
    +            thetype = typeobj
    +            
    +       cls = type('_ProxyTypeEngineProxy_%s' % (thetype.__name__),
    +                   (ProxyTypeEngineProxy, thetype), {})
    +       return cls(engine, typeobj)
    

    You may ask why I used {{{getattribute}}} rather than {{{getattr}}}; the reason is that because the new objects will have classes that derive from the base types (e.g. {{{types.Integer}}}), the {{{getattr}}} function won't get called for any of the functions that the base types implement by raising {{{NotImplementedError()}}}. If you wanted, you could use {{{getattr}}}, but then you'd have to re-implement all of those specific functions.

    (I should say that I haven't been writing Python code for long, so it may be that there is a better way to do the same thing… if so, I defer to those with more experience.)

  4. Mike Bayer repo owner
    • assigned issue to

    so is this one fixed ? feel free to close if so Jason (you should have perms for that, let me know otherwise)

  5. Log in to comment