Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper autogenerate of __str__() implementation also for sqlalchemy classes?

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 iterate instance attributes using vars, dir, ...:... helps in the case of simple classes.

When I try to apply it to a Sqlalchemy class (like the one from Introductory Tutorial of Python’s SQLAlchemy - see below), I get - apart from the member variables also the following entry as a member variable:

_sa_instance_state=<sqlalchemy.orm.state.InstanceState object at 0x000000004CEBCC0>

How can I avoid that this entry appears in the __str__ representation?

For the sake of completeness, I put the solution of the linked stackoverflow question below, too.

import os
import sys
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'
    # Here we define columns for the table person
    # Notice that each column is also a normal Python instance attribute.
    id = Column(Integer, primary_key=True)
    name = Column(String(250), nullable=False)

As mentioned, this is the solution from Is there a way to auto generate a __str__() implementation in python?:

def auto_str(cls):
    def __str__(self):
        return '%s(%s)' % (
            type(self).__name__,
            ', '.join('%s=%s' % item for item in vars(self).items())
        )
    cls.__str__ = __str__
    return cls

@auto_str
class Foo(object):
    def __init__(self, value_1, value_2):
        self.attribute_1 = value_1
         self.attribute_2 = value_2

Applied:

>>> str(Foo('bar', 'ping'))
'Foo(attribute_2=ping, attribute_1=bar)'
like image 842
user7468395 Avatar asked Jan 03 '19 16:01

user7468395


People also ask

What is __ repr __ SQLAlchemy?

The __repr__ function is defined by the designer of a type, in order to provide a means for users of the type to represent values of that type unambiguously, with a string.

What does all () do in SQLAlchemy?

all() method. The Query object, when asked to return full entities, will deduplicate entries based on primary key, meaning if the same primary key value would appear in the results more than once, only one object of that primary key would be present.

What does ORM stand for SQLAlchemy?

ORM, which stands for Object Relational Mapper, is the specialization of the Data Mapper design pattern that addresses relational databases like MySQL, Oracle, and PostgreSQL.


2 Answers

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.

like image 157
SuperShoot Avatar answered Oct 19 '22 20:10

SuperShoot


I define this __repr__ method on my base model:

def __repr__(self):
    fmt = '{}.{}({})'
    package = self.__class__.__module__
    class_ = self.__class__.__name__
    attrs = sorted(
        (k, getattr(self, k)) for k in self.__mapper__.columns.keys()
    )
    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.

Sample code

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(
            (k, getattr(self, k)) for k in self.__mapper__.columns.keys()
        )
        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('bar_id', 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')

The original version of this answer used the model's __table__ attribute to access the column names. I've changed it to use the model's __mapper__ instead because SQLAlchemy allows the model attribute name (stored in the mapper) to be different from the column name in the database (stored in the table). MyModel.bar demonstrates this.

like image 26
snakecharmerb Avatar answered Oct 19 '22 19:10

snakecharmerb