Правильная автогенерация реализации __str __ () также для классов sqlalchemy? - PullRequest
0 голосов
/ 03 января 2019

Я хотел бы отобразить / напечатать мои классы sqlalchemy красиво и чисто.

В Есть ли способ автоматически сгенерировать реализацию __str__() в python? ответ Вы можете перебирать атрибуты экземпляра, используя vars, dir, ...: ... помогает в случае простых классов.

Когда я пытаюсь применить его к классу Sqlalchemy (например, из Вводное руководство по SQLAlchemy Python - см. Ниже), я получаю - помимо переменных-членов также следующеезапись в качестве переменной-члена:

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

Как можно избежать появления этой записи в представлении __str__?

Ради полноты я поставлюрешение связанного вопроса stackoverflow ниже тоже.

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)

Как уже упоминалось, это решение из Есть ли способ автоматически сгенерировать реализацию __str__ () в 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

Применено:

>>> str(Foo('bar', 'ping'))
'Foo(attribute_2=ping, attribute_1=bar)'

Ответы [ 2 ]

0 голосов
/ 04 января 2019

Я определяю этот __repr__ метод на моей базовой модели:

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)

Метод отображает имена всех столбцов таблицы (но не отношений) и repr их значений в алфавитном порядке. Я обычно не определяю __str__, если только мне не нужна конкретная форма - возможно, str(User(name='Alice')) будет просто Alice - поэтому str(model_instance) вызовет метод __repr__.

Пример кода

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')
0 голосов
/ 04 января 2019

Это то, что я использую:

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)

Для любых моделей, которые наследуются от Base, будет определен метод по умолчанию __repr__(), и если мне нужно сделать что-то другое, я могу просто переопределить метод наэтот конкретный класс.

Он исключает значение любых частных атрибутов, обозначенных начальным подчеркиванием, объекта состояния экземпляра SQLAlchemy и любых атрибутов отношений из строки.Я исключаю атрибуты отношений, так как чаще всего не хочу, чтобы repr вызывал ленивую загрузку отношений, а когда отношения двунаправленные, в том числе атрибуты отношений могут вызывать бесконечную рекурсию.

Результат выглядит следующим образом: ClassName(attr=val, ...).

- EDIT -

Функция todict(), о которой я упоминал выше, является помощником, к которому я часто обращаюсь для создания dict из объекта SQLAВ основном для сериализации.Я лениво использовал его в этом контексте, но он не очень эффективен, поскольку он создает dicttodict()) для построения dict__repr__()).С тех пор я изменил шаблон для вызова генератора:

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

Тогда базовая база выглядит следующим образом:

class Base:

    def __repr__(self):
        params = ', '.join(f'{k}={v}' for k, v in keyvalgen(self))
        return f"{self.__class__.__name__}({params})"

Функцию todict() можно использовать вне keyvalgen() Генератор также, но больше не нужен для создания repr.

...