Please make Column easier to subclass

Issue #2284 resolved
Former user created an issue

Hi everyone!

I'm writing my own ORM based on SQLAlchemy and I need to subclass sqlalchemy.schema.Column. The only issue I have is with copy() function. Now I have to copy-paste it to my code and fix it there.

I wanted to use something like:

class MyProperty(sqlalchemy.Column):
  ...
  def copy(self, **kw):
    c = super(MyProperty, self).copy(**kw)
    c.foo = self.foo
    ...
    return c

But in this case a copy will be of type sqlalchemy.Column instead of MyProperty. Is it possible to rewrite sqlalchemy.Column.copy() like this:

def copy(self, **kw):
        ...

        c = type(self)(
                name=self.name, 
                ...
                doc=self.doc,
                *args
                )
        c.dispatch._update(self.dispatch)
        return c

Can this be done in 0.7.x?

Comments (8)

  1. Mike Bayer repo owner

    what's your use case for subclassing Column ? I'd prefer to give you another way to do what you need.

  2. Former user Account Deleted

    I'm creating network ORM and subclass Column to introduce additional introspection to it (7 more attributes). E.g.: is it hidden (from prospective of clients), more information on foreign keys and so on

  3. Mike Bayer repo owner

    So what I can do for you here is in e4c04590a6438f73278675e96dd84b57121eeb5f, where an existing attribute _constructor is used to create the copy - this method defaults to self.__class__, equivalent to type(self) without the function call. _constructor is already used in the _make_proxy() function, which is similar to copy() except it is a much more important method as it is used very frequently within expression transformation - copy() is only used by the tometadata() utility system. it was likely an oversight that _constructor was not used here already. The unit test added mimics exactly the pattern you're asking for here.

    That said, subclassing Column is a little dangerous. I would encourage you to at least store your extra state within the .info dictionary of Column, which is provided for end-user storage of extra attributes. If .info were good enough for you without the need for additional methods/accessors, you could skip the need to subclass Column and just use a function that puts what you want in .info.

    It's also worth noting that the SQLAlchemy ORM adds lots of ORM-specific functionality column-bound attributes, but doesn't need to subclass Column itself, since it uses a proxy pattern via InstrumentedAttribute - which itself implements PropComparator to provide all of the column operators, and proxies those requests down to an actual Column. This pattern, that of composition/proxying rather than direct subclassing, allows a lot more trickery to go on at the ORM layer without being destablized by changes in the schema package.

    Anyway that's my advice on that, the change committed should resolve the specific issue you're having with subclassing - good luck !

  4. Mike Bayer repo owner

    Oh I'd also note, you can subclass _constructor() itself too. That was the original idea behind that method - that subclassing _constructor() would eliminate the need to dig into copy() and particularly the _make_proxy() method:

    class MyColumn(Column):
        def __init__(self, *arg, **kw):
            self.widget = kw.pop('widget', None)
            super(MyColumn, self).__init__(*arg, **kw)
    
        def _constructor(self):
            def copy(*arg, **kw):
                kw['widget']('widget') = self.widget
                return MyColumn(*arg, **kw)
            return copy
    
  5. Former user Account Deleted

    Thank you, that should solve my problems. Also thank you for your notes!

    Waiting for 0.7.3...

  6. Former user Account Deleted

    Please note that your example is wrong, the correct one seems to be:

    class MyColumn(Column):
        def __init__(self, *arg, **kw):
            self.widget = kw.pop('widget', None)
            super(MyColumn, self).__init__(*arg, **kw)
    
        def _constructor(self):
            kw['widget']('widget') = self.widget
            return MyColumn(*arg, **kw)
    
  7. Log in to comment