Steven Van Ingelgem
Steven Van Ingelgem

Reputation: 1019

How to ignore an incoming argument to a declarative SQLAlchemy class?

I have a class:

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class DataFromWeb(Base):
    __tablename__ = 'data'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))

Now I'm getting from the web a JSON array which also have several other fields (in which obviously I'm not interested in, and could change but shouldn't break the app).

This is the error: TypeError: 'timestamp' is an invalid keyword argument for DataFromWeb. But there could be more fields. I'm just not interested in them.

Is there a nice way to ignore them? Or tell SQLAlchemy that the Columns I've given are the only ones I'm interested in?

One way I found is this:

    def __init__(self, *args, **kwargs):
        # Gotta clean up the kwargs here :(
        kwargs2 = {k: v for k, v in kwargs.items() if hasattr(self.__class__, k)}
        super().__init__(*args, **kwargs2)

But this is just ugly in my opinion...

Thanks!

Upvotes: 2

Views: 868

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55854

The default constructor for declarative models essentially performs the same hasattr loop as that in the question, but raises a TypeError if a kwargs key doesn't map to the name of an instance attribute, so the ugliness is unavoidable.

However, it can be hidden. declarative_base accepts a constructor argument which will override the default constructor, so you could provide your own implementation which ignores unknown names. This way, you don't have to implement an __init__ method on all your models to filter unwanted keys.

def _declarative_constructor(self, **kwargs):
    """Don't raise a TypeError for unknown attribute names."""
    cls_ = type(self)
    for k in kwargs:
        if not hasattr(cls_, k):
            continue
        setattr(self, k, kwargs[k])


Base = declarative_base(constructor=_declarative_constructor)


class DataFromWeb(Base):
    __tablename__ = 'data'

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)

    def __init__(self, *args, **kwargs):
        # We don't really need an __init__ method, this is just 
        # for demonstration purposes.
        super().__init__(*args, **kwargs)

Upvotes: 1

Related Questions