136

Flask/Python で SQLAlchemy の結果セットを jsonify しようとしています。

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 にシリアル化する方法は? これは非常に似ているように見えますが、メーリングリストの投稿が示唆しているように、Flask に簡単にする魔法があるかどうかはわかりませんでした。

編集:明確にするために、これは私のモデルがどのように見えるかです

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
4

15 に答える 15

181

実際にはクエリを実行していないようです。以下を試してください:

return jsonify(json_list = qryresult.all())

[編集] : jsonify の問題は、通常、オブジェクトを自動的に json 化できないことです。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's relations in easily serializable format.
       NB! Calls many2many's serialize property.
       """
       return [ item.serialize for item in self.many2many]

そして今、私ができるビューのために:

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

お役に立てれば ;)

[Edit 2019] : より複雑なオブジェクトまたは循環参照がある場合は、marshmallowなどのライブラリを使用します)。

于 2011-08-18T06:52:15.090 に答える
51

通常、私にとって十分なものは次のとおりです。

モデルで使用するシリアル化 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 モデルをSerializermixin クラスで拡張するだけです。

公開したくないフィールドや特別な書式設定が必要なフィールドがある場合は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))
于 2015-01-14T20:16:05.023 に答える
38

jsonにシリアル化するという同じ必要がありました。この質問を見てください。プログラムで列を検出する方法を示します。そこで、以下のコードを作成しました。それは私にとってはうまくいき、Web アプリで使用します。ハッピーコーディング!


def to_json(inst, cls):
    """
    Jsonify the sql alchemy query result.
    """
    convert = dict()
    # add your coversions for things like datetime's 
    # 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__)
于 2012-03-17T00:42:52.050 に答える
21

これが私のアプローチです: https ://github.com/n0nSmoker/SQLAlchemy-serializer

pipinstallSQLAlchemy-シリアライザー

モデルにミックスインを簡単に追加でき、そのインスタンスで.to_dict()メソッドを呼び出すだけではありません。

SerializerMixinに基づいて独自のミックスインを作成することもできます

于 2013-01-14T16:20:06.183 に答える
7

フラットクエリ(結合なし)の場合、これを行うことができます

@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)
于 2013-05-03T21:24:54.440 に答える
6

わかりました、私はこれに数時間取り組んできましたが、これまでで最も Pythonic なソリューションであると信じているものを開発しました。次のコード スニペットは python3 ですが、必要に応じてバックポートするのにそれほど苦労する必要はありません。

dict最初に、db モデルをs のように動作させる mixin から始めます。

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()

次に、モデルを定義して、ミックスインを継承します。

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

のインスタンスを渡して、そこから実際のライブインスタンスをMyModel()取得できるようにするために必要なことはこれだけです。次に、残りの部分を取得するために拡張する必要があります。dict()dictjsonify()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 出力に含めたい場合)、それも簡単です。計算フィールドを s として定義し、メソッドを次のよう@propertyに拡張するだけです。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()))
于 2015-09-30T09:43:19.300 に答える
5

使用している場合は、 marshalflask-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()]}

何を返しているのか、それがどのタイプなのかを明示的にリストする必要があります. シリアル化は簡単に処理できます ( は必要ありjsonifyません)。日付も問題ありません。フィールドのコンテンツは、エンドポイントと IDuriに基づいて自動的に生成されることに注意してください。topic

于 2015-12-26T10:11:48.160 に答える
4

宣言型ベースを使用している場合の私の答えは次のとおりです(すでに投稿されているいくつかの回答の助けを借りて):

# 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)
于 2016-08-29T23:12:47.307 に答える
2

私は一日の大部分でこの問題を見てきました。

(注: 私はフラスコ 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 ...

Jsonify私の見解では、次のように、SWJsonify を呼び出す場所はどこでも呼び出します。

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

かなりうまくいくようです。関係についても。YMMVなので、まだそこまでは進んでいませんが、これまでのところ、私にとってはかなり「正しい」と感じています.

提案を歓迎します。

于 2012-07-06T01:09:44.640 に答える
1

ActiveRecord to_json で使用されている Rails アプローチのようなものを探していて、他の提案に満足できなかった後、この 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)
于 2016-07-17T05:57:20.540 に答える
0

これを行うためのメソッドを追加したいだけです。

カスタム json エンコーダーを定義して、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)

親には関係がありますが、うまく機能します

于 2016-06-02T07:44:56.613 に答える
-1

何度も何度も有効な答えがありますが、次のコードブロックは機能しているようです:

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

これが完璧な解決策ではなく、他の解決策ほどエレガントではないことは承知していますが、迅速な修正が必要な場合は、これを試すことができます。

于 2014-10-22T13:03:40.897 に答える