I would like to display / print my sqlalchemy classes nice and clean.
In Is there a way to auto generate a __str__() implementation in python? the answer You can it
I define this __repr__
method on my base model:
def __repr__(self):
fmt = '{}.{}({})'
package = self.__class__.__module__
class_ = self.__class__.__name__
attrs = sorted((col.name, getattr(self, col.name)) for col in self.__table__.columns)
sattrs = ', '.join('{}={!r}'.format(*x) for x in attrs)
return fmt.format(package, class_, sattrs)
The method displays the names of all of a table's columns (but not relationships), and the repr
of their values, in alphabetical order. I don't usually define a __str__
unless I need a particular form - perhaps str(User(name='Alice'))
would just be Alice
- so str(model_instance)
will call the __repr__
method.
import datetime
import sqlalchemy as sa
from sqlalchemy.ext import declarative
class BaseModel(object):
__abstract__ = True
def __repr__(self):
fmt = u'{}.{}({})'
package = self.__class__.__module__
class_ = self.__class__.__name__
attrs = sorted((c.name, getattr(self, c.name)) for c in self.__table__.columns)
sattrs = u', '.join('{}={!r}'.format(*x) for x in attrs)
return fmt.format(package, class_, sattrs)
Base = declarative.declarative_base(cls=BaseModel)
class MyModel(Base):
__tablename__ = 'mytable'
foo = sa.Column(sa.Unicode(32))
bar = sa.Column(sa.Integer, primary_key=True)
baz = sa.Column(sa.DateTime)
>>> mm = models.MyModel(foo='Foo', bar=42, baz=datetime.datetime.now())
>>> mm
models.MyModel(bar=42, baz=datetime.datetime(2019, 1, 4, 7, 37, 59, 350432), foo='Foo')
This is what I use:
def todict(obj):
""" Return the object's dict excluding private attributes,
sqlalchemy state and relationship attributes.
"""
excl = ('_sa_adapter', '_sa_instance_state')
return {k: v for k, v in vars(obj).items() if not k.startswith('_') and
not any(hasattr(v, a) for a in excl)}
class Base:
def __repr__(self):
params = ', '.join(f'{k}={v}' for k, v in todict(self).items())
return f"{self.__class__.__name__}({params})"
Base = declarative_base(cls=Base)
Any models that inherit from Base
will have the default __repr__()
method defined and if I need to do something different I can just override the method on that particular class.
It excludes the value of any private attributes denoted with a leading underscore, the SQLAlchemy instance state object, and any relationship attributes from the string. I exclude the relationship attributes as I most often don't want the repr to cause a relationship to lazy load, and where the relationship is bi-directional, including relationship attribs can cause infinite recursion.
The result looks like: ClassName(attr=val, ...)
.
--EDIT--
The todict()
func that I mention above is a helper that I often call upon to construct a dict
out of a SQLA object, mostly for serialisation. I was lazily using it in this context but it isn't very efficient as it's constructing a dict
(in todict()
) to construct a dict
(in __repr__()
). I've since modified the pattern to call upon a generator:
def keyvalgen(obj):
""" Generate attr name/val pairs, filtering out SQLA attrs."""
excl = ('_sa_adapter', '_sa_instance_state')
for k, v in vars(obj).items():
if not k.startswith('_') and not any(hasattr(v, a) for a in excl):
yield k, v
Then the base Base looks like this:
class Base:
def __repr__(self):
params = ', '.join(f'{k}={v}' for k, v in keyvalgen(self))
return f"{self.__class__.__name__}({params})"
The todict()
func leverages off of the keyvalgen()
generator as well but isn't needed to construct the repr anymore.