Подтвердить что ты не робот

Jsonify набор результатов SQLAlchemy в Flask

Я пытаюсь jsonify набор результатов SQLAlchemy в Flask/Python.

Список рассылок Flask предложил следующий метод http://librelist.com/browser//flask/2011/2/16/jsonify-sqlalchemy-pagination-collection-result/#04a0754b63387f87e59dda564bde426e:

return jsonify(json_list = qryresult)

Однако я получаю следующую ошибку:

TypeError: <flaskext.sqlalchemy.BaseQuery object at 0x102c2df90> 
is not JSON serializable

Что я здесь вижу?

Я нашел этот вопрос: Как сериализовать результат SqlAlchemy для JSON?, который кажется очень похожим, но я не знал, есть ли у Флакса какая-то магия, чтобы облегчить его как предложено почтовое сообщение.

Изменить: для уточнения, это то, что моя модель выглядит как

class Rating(db.Model):

    __tablename__ = 'rating'

    id = db.Column(db.Integer, primary_key=True)
    fullurl = db.Column(db.String())
    url = db.Column(db.String())
    comments = db.Column(db.Text)
    overall = db.Column(db.Integer)
    shipping = db.Column(db.Integer)
    cost = db.Column(db.Integer)
    honesty = db.Column(db.Integer)
    communication = db.Column(db.Integer)
    name = db.Column(db.String())
    ipaddr = db.Column(db.String())
    date = db.Column(db.String())

    def __init__(self, fullurl, url, comments, overall, shipping, cost, honesty, communication, name, ipaddr, date):
        self.fullurl = fullurl
        self.url = url
        self.comments = comments
        self.overall = overall
        self.shipping = shipping
        self.cost = cost
        self.honesty = honesty
        self.communication = communication
        self.name = name
        self.ipaddr = ipaddr
        self.date = date
4b9b3361

Ответ 1

Похоже, что вы на самом деле не выполнили свой запрос. Попробуйте следующее:

return jsonify(json_list = qryresult.all())

[Редактировать]: проблема с jsonify заключается в том, что обычно объекты не могут быть jsonified автоматически. Даже Python datetime не работает;)

В прошлом я делал добавление дополнительного свойства (например, serialize) к классам, которые необходимо сериализовать.

def dump_datetime(value):
    """Deserialize datetime object into string form for JSON processing."""
    if value is None:
        return None
    return [value.strftime("%Y-%m-%d"), value.strftime("%H:%M:%S")]

class Foo(db.Model):
    # ... SQLAlchemy defs here..
    def __init__(self, ...):
       # self.foo = ...
       pass

    @property
    def serialize(self):
       """Return object data in easily serializable format"""
       return {
           'id'         : self.id,
           'modified_at': dump_datetime(self.modified_at),
           # This is an example how to deal with Many2Many relations
           'many2many'  : self.serialize_many2many
       }
    @property
    def serialize_many2many(self):
       """
       Return object relations in easily serializable format.
       NB! Calls many2many serialize property.
       """
       return [ item.serialize for item in self.many2many]

А теперь для просмотра я могу просто сделать:

return jsonify(json_list=[i.serialize for i in qryresult.all()])

Надеюсь, это поможет;)

[Изменить 2019]: Если у вас есть более сложные объекты или циклические ссылки, используйте такую библиотеку, как marshmallow).

Ответ 2

У меня была такая же потребность, чтобы сериализоваться в json. Взгляните на этот вопрос. Он показывает, как обнаруживать столбцы программно. Итак, из этого я создал код ниже. Он работает для меня, и я буду использовать его в своем веб-приложении. Счастливое кодирование!


def to_json(inst, cls):
    """
    Jsonify the sql alchemy query result.
    """
    convert = dict()
    # add your coversions for things like datetime 
    # and what-not that aren't serializable.
    d = dict()
    for c in cls.__table__.columns:
        v = getattr(inst, c.name)
        if c.type in convert.keys() and v is not None:
            try:
                d[c.name] = convert[c.type](v)
            except:
                d[c.name] = "Error:  Failed to covert using ", str(convert[c.type])
        elif v is None:
            d[c.name] = str()
        else:
            d[c.name] = v
    return json.dumps(d)

class Person(base):
    __tablename__ = 'person'
    id = Column(Integer, Sequence('person_id_seq'), primary_key=True)
    first_name = Column(Text)
    last_name = Column(Text)
    email = Column(Text)

    @property
    def json(self):
        return to_json(self, self.__class__)

Ответ 3

Вот то, что обычно для меня достаточно:

Я создаю сериализованный mixin, который я использую с моими моделями. Функция сериализации в основном извлекает все атрибуты, которые инспектор SQLAlchemy предоставляет и помещает в dict.

from sqlalchemy.inspection import inspect

class Serializer(object):

    def serialize(self):
        return {c: getattr(self, c) for c in inspect(self).attrs.keys()}

    @staticmethod
    def serialize_list(l):
        return [m.serialize() for m in l]

Все, что нужно сейчас, - это расширить модель SQLAlchemy с помощью класса Serializer mixin.

Если есть поля, которые вы не хотите раскрывать, или которые нуждаются в специальном форматировании, просто переопределите функцию serialize() в подклассе модели.

class User(db.Model, Serializer):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String)
    password = db.Column(db.String)

    # ...

    def serialize(self):
        d = Serializer.serialize(self)
        del d['password']
        return d

В ваших контроллерах все, что вам нужно сделать, это вызвать функцию serialize() (или serialize_list(l), если запрос приводит к списку):

def get_user(id):
    user = User.query.get(id)
    return json.dumps(user.serialize())

def get_users():
    users = User.query.all()
    return json.dumps(User.serialize_list(users))

Ответ 4

Вот мой подход: https://github.com/n0nSmoker/SQLAlchemy-serializer

pip install SQLAlchemy-serializer

Вы можете легко добавить mixin к вашей модели, а затем просто вызвать метод .to_dict() для ее экземпляра.

Вы также можете написать свой собственный миксин на основе SerializerMixin

Ответ 5

Для плоского запроса (без соединений) вы можете сделать это

@app.route('/results/')
def results():
    data = Table.query.all()
    result = [d.__dict__ for d in data]
    return jsonify(result=result)

и если вы хотите вернуть только определенные столбцы из базы данных, вы можете сделать это

@app.route('/results/')
def results():
    cols = ['id', 'url', 'shipping']
    data = Table.query.all()
    result = [{col: getattr(d, col) for col in cols} for d in data]
    return jsonify(result=result)

Ответ 6

Хорошо, я работал над этим в течение нескольких часов, и я разработал то, что, по моему мнению, является самым питоническим решением. Следующие фрагменты кода представляют собой python3, но не должны быть слишком ужасны для резервного копирования, если вам нужно.

Первое, что мы собираемся сделать, это начать с mixin, который заставляет ваши db-модели действовать как dict s:

from sqlalchemy.inspection import inspect

class ModelMixin:
    """Provide dict-like interface to db.Model subclasses."""

    def __getitem__(self, key):
        """Expose object attributes like dict values."""
        return getattr(self, key)

    def keys(self):
        """Identify what db columns we have."""
        return inspect(self).attrs.keys()

Теперь мы собираемся определить нашу модель, наследуя mixin:

class MyModel(db.Model, ModelMixin):
    id = db.Column(db.Integer, primary_key=True)
    foo = db.Column(...)
    bar = db.Column(...)
    # etc ...

Чтобы получить экземпляр MyModel() до dict(), чтобы получить экземпляр реального dict, из которого он прошел, это дает нам довольно большой путь к тому, чтобы сделать jsonify() понятным. Затем нам нужно расширить JSONEncoder, чтобы получить остальную часть пути:

from flask.json import JSONEncoder
from contextlib import suppress

class MyJSONEncoder(JSONEncoder):
    def default(self, obj):
        # Optional: convert datetime objects to ISO format
        with suppress(AttributeError):
            return obj.isoformat()
        return dict(obj)

app.json_encoder = MyJSONEncoder

Бонусные баллы: если ваша модель содержит вычисленные поля (т.е. вы хотите, чтобы ваш вывод JSON содержал поля, которые фактически не хранятся в базе данных), это тоже легко. Просто определите ваши вычисленные поля как @property s и продолжите метод keys() следующим образом:

class MyModel(db.Model, ModelMixin):
    id = db.Column(db.Integer, primary_key=True)
    foo = db.Column(...)
    bar = db.Column(...)

    @property
    def computed_field(self):
        return 'this value did not come from the db'

    def keys(self):
        return super().keys() + ['computed_field']

Теперь это тривиально для jsonify:

@app.route('/whatever', methods=['GET'])
def whatever():
    return jsonify(dict(results=MyModel.query.all()))

Ответ 7

Если вы используете flask-restful, вы можете использовать маршал:

from flask.ext.restful import Resource, fields, marshal

topic_fields = {
    'title':   fields.String,
    'content': fields.String,
    'uri':     fields.Url('topic'),
    'creator': fields.String,
    'created': fields.DateTime(dt_format='rfc822')
}

class TopicListApi(Resource):
    def get(self):
        return {'topics': [marshal(topic, topic_fields) for topic in DbTopic.query.all()]}

Вам нужно явно указать, что вы возвращаете, и тип, который я предпочитаю в любом случае для api. Сериализация легко позаботится (нет необходимости в jsonify), даты также не являются проблемой. Обратите внимание, что содержимое поля uri автоматически создается на основе конечной точки topic и идентификатора.

Ответ 8

Я рассматривал эту проблему в течение большей части дня, и вот то, что я придумал (кредит fooobar.com/questions/58651/... для указания меня в этом направлении).

(Примечание: я использую flask-sqlalchemy, поэтому формат моей модели объявления немного отличается от прямой sqlalchemy).

В моем models.py файле:

import json

class Serializer(object):
  __public__ = None
  "Must be implemented by implementors"

  def to_serializable_dict(self):
    dict = {}
    for public_key in self.__public__:
      value = getattr(self, public_key)
      if value:
        dict[public_key] = value
    return dict

class SWEncoder(json.JSONEncoder):
  def default(self, obj):
    if isinstance(obj, Serializer):
      return obj.to_serializable_dict()
    if isinstance(obj, (datetime)):
      return obj.isoformat()
    return json.JSONEncoder.default(self, obj)


def SWJsonify(*args, **kwargs):
  return current_app.response_class(json.dumps(dict(*args, **kwargs), cls=SWEncoder, indent=None if request.is_xhr else 2), mimetype='application/json')
  # stolen from https://github.com/mitsuhiko/flask/blob/master/flask/helpers.py

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

class User(db.Model, Serializer):
  __public__ = ['id','username']
  ... field definitions ...

В моих представлениях я вызываю SWJsonify везде, где я бы назвал Jsonify, например:

@app.route('/posts')
def posts():
  posts = Post.query.limit(PER_PAGE).all()
  return SWJsonify({'posts':posts })

Кажется, все работает неплохо. Даже на отношениях. Я далеко не ушел, поэтому YMMV, но до сих пор мне это очень нравится.

Предложения приветствуются.

Ответ 9

Вот способ добавить метод as_dict() для каждого класса, а также любой другой метод, который вы хотите использовать для каждого отдельного класса. Не уверен, что это желаемый способ или нет, но он работает...

class Base(object):
    def as_dict(self):
        return dict((c.name,
                     getattr(self, c.name))
                     for c in self.__table__.columns)


Base = declarative_base(cls=Base)

Ответ 10

Я искал что-то вроде подхода рельсов, используемого в ActiveRecord to_json, и реализовал что-то подобное, используя этот Mixin, после того, как был недоволен другими предложениями. Он обрабатывает вложенные модели и включает или исключает атрибуты верхнего уровня или вложенных моделей.

class Serializer(object):

    def serialize(self, include={}, exclude=[], only=[]):
        serialized = {}
        for key in inspect(self).attrs.keys():
            to_be_serialized = True
            value = getattr(self, key)
            if key in exclude or (only and key not in only):
                to_be_serialized = False
            elif isinstance(value, BaseQuery):
                to_be_serialized = False
                if key in include:
                    to_be_serialized = True
                    nested_params = include.get(key, {})
                    value = [i.serialize(**nested_params) for i in value]

            if to_be_serialized:
                serialized[key] = value

        return serialized

Затем, чтобы получить сериализуемое BaseQuery, я расширил BaseQuery

class SerializableBaseQuery(BaseQuery):

    def serialize(self, include={}, exclude=[], only=[]):
        return [m.serialize(include, exclude, only) for m in self]

Для следующих моделей

class ContactInfo(db.Model, Serializer):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    full_name = db.Column(db.String())
    source = db.Column(db.String())
    source_id = db.Column(db.String())

    email_addresses = db.relationship('EmailAddress', backref='contact_info', lazy='dynamic')
    phone_numbers = db.relationship('PhoneNumber', backref='contact_info', lazy='dynamic')


class EmailAddress(db.Model, Serializer):
    id = db.Column(db.Integer, primary_key=True)
    email_address = db.Column(db.String())
    type = db.Column(db.String())
    contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id'))


class PhoneNumber(db.Model, Serializer):
    id = db.Column(db.Integer, primary_key=True)
    phone_number = db.Column(db.String())
    type = db.Column(db.String())
    contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id'))

    phone_numbers = db.relationship('Invite', backref='phone_number', lazy='dynamic')

Вы можете сделать что-то вроде

@app.route("/contact/search", methods=['GET'])
def contact_search():
    contact_name = request.args.get("name")
    matching_contacts = ContactInfo.query.filter(ContactInfo.full_name.like("%{}%".format(contact_name)))

    serialized_contact_info = matching_contacts.serialize(
        include={
            "phone_numbers" : {
                "exclude" : ["contact_info", "contact_info_id"]
            },
            "email_addresses" : {
                "exclude" : ["contact_info", "contact_info_id"]
            }
        }
    )

    return jsonify(serialized_contact_info)

Ответ 11

Здесь мой ответ, если вы используете декларативную базу (с помощью некоторых из уже опубликованных ответов):

# in your models definition where you define and extend declarative_base()
from sqlalchemy.ext.declarative import declarative_base
...
Base = declarative_base()
Base.query = db_session.query_property()
...

# define a new class (call "Model" or whatever) with an as_dict() method defined
class Model():
    def as_dict(self):
        return { c.name: getattr(self, c.name) for c in self.__table__.columns }

# and extend both the Base and Model class in your model definition, e.g.
class Rating(Base, Model):
    ____tablename__ = 'rating'
    id = db.Column(db.Integer, primary_key=True)
    fullurl = db.Column(db.String())
    url = db.Column(db.String())
    comments = db.Column(db.Text)
    ...

# then after you query and have a resultset (rs) of ratings
rs = Rating.query.all()

# you can jsonify it with
s = json.dumps([r.as_dict() for r in rs], default=alchemyencoder)
print (s)

# or if you have a single row
r = Rating.query.first()

# you can jsonify it with
s = json.dumps(r.as_dict(), default=alchemyencoder)

# you will need this alchemyencoder where your are calling json.dumps to handle datetime and decimal format
# credit to Joonas @ http://codeandlife.com/2014/12/07/sqlalchemy-results-to-json-the-easy-way/
def alchemyencoder(obj):
    """JSON encoder function for SQLAlchemy special classes."""
    if isinstance(obj, datetime.date):
        return obj.isoformat()
    elif isinstance(obj, decimal.Decimal):
        return float(obj)

Ответ 12

Flask-Restful 0.3.6 Запрос на разбор рекомендуем Зефир

marshmallow - это библиотека, независимая от ORM/ODM/framework для преобразования сложных типов данных, таких как объекты, в и из собственных типов данных Python.

Простой пример зефира показан ниже.

from marshmallow import Schema, fields

class UserSchema(Schema):
    name = fields.Str()
    email = fields.Email()
    created_at = fields.DateTime()

from marshmallow import pprint

user = User(name="Monty", email="[email protected]")
schema = UserSchema()
result = schema.dump(user)
pprint(result)
# {"name": "Monty",
#  "email": "[email protected]",
#  "created_at": "2014-08-17T14:54:16.049594+00:00"}

Основные функции содержат

Объявление схем
Сериализация объектов ("Сброс")
Десериализация объектов ("Загрузка")
Обработка коллекций объектов
Проверка
Указание имен атрибутов
Указание ключей сериализации/десериализации
Рефакторинг: создание неявного поля
Порядок вывода
Поля "Только для чтения" и "Только для записи"
Укажите значения сериализации/десериализации по умолчанию
Вложенные схемы
Настраиваемые поля

Ответ 13

Я работал с sql-запросом defaultdict списков объектов RowProxy с именем jobDict Мне потребовалось некоторое время, чтобы выяснить, что такое объекты типа.

Это был очень простой способ быстро решить какой-либо чистый jsonEncoding, просто указав строку в список и изначально определяя dict со значением списка.

    jobDict = defaultdict(list)
    def set_default(obj):
        # trickyness needed here via import to know type
        if isinstance(obj, RowProxy):
            return list(obj)
        raise TypeError


    jsonEncoded = json.dumps(jobDict, default=set_default)

Ответ 14

Я просто хочу добавить свой метод для этого.

просто определите custome json encoder для сериализации ваших моделей db.

class ParentEncoder(json.JSONEncoder):
    def default(self, obj):
        # convert object to a dict
        d = {}
        if isinstance(obj, Parent):
            return {"id": obj.id, "name": obj.name, 'children': list(obj.child)}
        if isinstance(obj, Child):
            return {"id": obj.id, "name": obj.name}

        d.update(obj.__dict__)
        return d

то в вашей функции просмотра

parents = Parent.query.all()
dat = json.dumps({"data": parents}, cls=ParentEncoder)
resp = Response(response=dat, status=200, mimetype="application/json")
return (resp)

он работает хорошо, хотя родитель имеет отношения

Ответ 15

Было много раз, и есть много действительных ответов, но, похоже, работает следующий блок кода:

my_object = SqlAlchemyModel()
my_serializable_obj = my_object.__dict__
del my_serializable_obj["_sa_instance_state"]
print(jsonify(my_serializable_object))

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