SQLAlchemyのdatetime型をJSONに変換する

SQLAlchemyで取出したdatetime型のデータをJSONに変換するのは、結構面倒みたいなので、marshmallowを使って簡単に変換します。

SQLAlchemyとmarshmallowは、Flask用のFlask-SQLAlchemy,Flask-Marshmallowmarshmallow-sqlalchemyがあるようですが、今回は使いません。

Flaskと組み合わせるとコードが少し複雑になって、動作が分かりづらくなるので、普通の?SQLAlchemyとmarshmallowを使って動作テストをしていきます。

参考にしたサイトはこちら → https://marshmallow.readthedocs.io/en/stable

インストール

pip install sqlalchemy
pip install marshmallow

marshmallowを使って、JSONに変換します。なぜ、マシュマロって名前なのかよくわかりませんが、ずいぶん可愛らしい名前ですね。マシュマロのスペルをこの時に初めて知りました。

ファイルの階層

.
├── app.py
├── db.py
└── db.sqlite

db.py

SQLAlchemyとsqlite3で簡単なデータベースを作成します。

カラム名タイプ
idInteger
nameString
sample1_dateDate
sample1_datetimeDateTime
sample2_datetimeDateTime
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Integer, String, DateTime, Date
from sqlalchemy.orm import scoped_session, sessionmaker

engine = create_engine('sqlite:///db.sqlite')
Base = declarative_base()

class Sample(Base):
    __tablename__ = 'sample'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    sample1_date = Column(Date)
    sample1_datetime = Column(DateTime)
    sample2_datetime = Column(DateTime)

    def __init__(self,name,sample1_date,sample1_datetime,sample2_datetime):
        self.name = name
        self.sample1_date = sample1_date
        self.sample1_datetime = sample1_datetime
        self.sample2_datetime = sample2_datetime

Base.metadata.create_all(engine)
session_db = scoped_session(sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine
))

Base.query = session_db.query_property()

このデータベースに、こんな感じのデータを2件だけ入力しておきます。

1|dattesar|2022-06-23|2022-03-14 12:00:17.405188|2023-01-21 12:00:17.405188
2|dattesar|2022-07-06|2022-12-28 12:00:18.614196|2022-11-19 12:00:18.614196

app.py

from marshmallow import Schema, fields, pprint

from db import Sample, session_db

class SampleSchema(Schema):
    id = fields.Int()
    name = fields.Str()
    sample1_date = fields.Date()
    sample1_datetime = fields.DateTime()
    sample2_datetime = fields.DateTime('%Y-%m-%d %H:%M:%S')

sample_data = Sample.query.first()
session_db.close()

pprint(SampleSchema().dump(sample_data))

これで、こんな感じに出力されます。

{'id': 1,
 'name': 'dattesar',
 'sample1_date': '2022-06-23',
 'sample1_datetime': '2022-03-14T12:00:17.405188',
 'sample2_datetime': '2023-01-21 12:00:17'}

fields.DateTime()では、フォーマットを指定しないと、日付と時間の間にTが入るようです。

結構、簡単ですね。ただし、これが上手く動くのは、データベースから取出したデータが1件の時だけです。

Sample.query.all()でデータを取出すとエラーが出ます。

複数のデータを取出す場合は、many=Trueを指定する必要があります。

sample_data = Sample.query.all()

pprint(SampleSchema(many=True).dump(sample_data))

すると、こんな感じに出力されます。

[{'id': 1,
  'name': 'dattesar',
  'sample1_date': '2022-06-23',
  'sample1_datetime': '2022-03-14T12:00:17.405188',
  'sample2_datetime': '2023-01-21 12:00:17'},
 {'id': 2,
  'name': 'dattesar',
  'sample1_date': '2022-07-06',
  'sample1_datetime': '2022-12-28T12:00:18.614196',
  'sample2_datetime': '2022-11-19 12:00:18'}]

しかし、Sample.query.first()の場合は、many=Trueを書くとエラーがでます。

簡単にJSONに変換できると思っていたのに、ちょっと、ややこしくなってきました。

データを検証

ちょっと分からなくなって来たので、検証してみます。

SQLAlchemyは、first()all()で何のタイプを返してくるのでしょうか?

first_data = Sample.query.first()
print(first_data)
print(type(first_data))
print(type(first_data) is list)

all_data = Sample.query.all()
print(all_data)
print(type(all_data))
print(type(all_data) is list)

すると、こんな感じに出力されます。

<db.Sample object at 0x000001F839EDB250>
<class 'db.Sample'>
False

[<db.Sample object at 0x000001F839EDB250>, <db.Sample object at 0x000001F839EDBBE0>]
<class 'list'>
True

first()ではdb.Sample?が、all()ではlistが返って来てるのかな?

よくわかりませんが、all()で返ってくるのがリストなのは間違い無いようです。

ということで、ソースコードをちょっと改良します。

if type(sample_data) is list:
    pprint(SampleSchema(many=True).dump(sample_data))
else:
    pprint(SampleSchema().dump(sample_data))

これを追記したら、とりあえず、エラーは出なくなり、all()とall()以外、どちらもJSONに変換できるようになりました。

ソースコード

from marshmallow import Schema, fields, pprint

from db import Sample, session_db

class SampleSchema(Schema):
    id = fields.Int()
    name = fields.Str()
    sample1_date = fields.Date()
    sample1_datetime = fields.DateTime()
    sample2_datetime = fields.DateTime('%Y-%m-%d %H:%M:%S')

sample_data = Sample.query.all()
session_db.close()

if type(sample_data) is list:
    pprint(SampleSchema(many=True).dump(sample_data))
else:
    pprint(SampleSchema().dump(sample_data))

ちなみに、こんな感じでdatetimeのデータをdateに変換して出力することもできるようです。

sample1_datetime = fields.Date()
{'id': 1,
 'name': 'dattesar',
 'sample1_date': '2022-06-23',
 'sample1_datetime': '2022-03-14',
 'sample2_datetime': '2023-01-21 12:00:17'}