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):
__table= '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
実際にクエリを実行していないようです。以下を試してください:
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()])
お役に立てれば ;)
[2019年編集]:より複雑なオブジェクトまたは循環参照がある場合は、 Marshmallow )のようなライブラリを使用します。
Jsonにシリアル化するという同じニーズがありました。 この質問 を見てください。プログラムで列を検出する方法を示します。そこで、それから以下のコードを作成しました。それは私のために機能し、私は私のウェブアプリでそれを使用します。ハッピーコーディング!
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):
__table= '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__)
これは私にとって通常十分なものです:
モデルで使用するシリアル化ミックスインを作成します。シリアル化関数は基本的に、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]
今必要なのは、Serializer
mixinクラスでSQLAlchemyモデルを拡張することです。
公開したくないフィールドがある場合、または特別なフォーマットが必要なフィールドがある場合は、モデルサブクラスの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))
これが私のアプローチです: https://github.com/n0nSmoker/SQLAlchemy-serializer
pip install SQLAlchemy-serializer
インスタンスに.to_dict()メソッドを呼び出すだけでなく、モデルにmixinを簡単に追加できます。
SerializerMixinをベースに独自のミックスインを書くこともできます
OK、私はこれに数時間取り組んでおり、私はこれまでで最もPython的なソリューションであると信じているものを開発しました。次のコードスニペットはpython3ですが、必要に応じてバックポートするのがひどく痛くないはずです。
最初に行うことは、dbモデルをdict
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()
に渡し、そこから実際のライブ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()))
フラットクエリ(結合なし)の場合、これを行うことができます
@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)
flask-restful
を使用している場合は、 marshal を使用できます。
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
エンドポイントとIDに基づいて自動的に生成されることに注意してください。
私は一日のこの部分でこの問題を見てきましたが、ここに私が思いついたものがあります( https://stackoverflow.com/a/5249214/196358 この方向で私)。
(注: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 ...
私の見解では、Jsonify
を呼び出す場所であればどこでもSWJsonifyを呼び出します。
@app.route('/posts')
def posts():
posts = Post.query.limit(PER_PAGE).all()
return SWJsonify({'posts':posts })
かなりうまくいくようです。関係についても。私はこれまであまり理解していませんでしたので、YMMVですが、今のところ私にはかなり「正しい」と感じています。
提案を歓迎します。
宣言ベースを使用している場合の私の答えは次のとおりです(既に投稿された回答の一部からの助けを借りて)。
# 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):
____table= '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)
すべてのクラスに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)
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)
Flask-Restful
0.3.6
リクエストの解析 マシュマロを推奨
Marshmallowは、オブジェクトなどの複雑なデータ型をネイティブPythonデータ型との間で変換するためのORM/ODM /フレームワークに依存しないライブラリです。
簡単な Marshmallow の例を以下に示します。
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"}
コア機能には
スキーマの宣言
オブジェクトのシリアル化(「ダンプ」)
オブジェクトのデシリアライズ(「ロード」)
オブジェクトのコレクションの処理
検証
属性名の指定
シリアル化/逆シリアル化キーの指定
リファクタリング:暗黙的なフィールド作成
出力の注文
「読み取り専用」および「書き込み専用」フィールド
デフォルトのシリアライゼーション/デシリアライゼーション値を指定します
ネストスキーマ
カスタムフィールド
JobDictという名前のRowProxyオブジェクトのリストのSQLクエリdefaultdictで作業していました。オブジェクトがどのようなタイプであるかを理解するにはしばらく時間がかかりました。
これは、行をリストに型キャストするだけで、最初にlistの値でdictを定義することで、きれいなjsonEncodingに解決するための非常に簡単で迅速な方法でした。
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)
これを行うためのメソッドを追加したいだけです。
カスタム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)
親は関係を持っていますが、それはうまく機能します