sqlmodelを使ってテストデータを投入
pythonで良いseedライブラリのようなものが見つからなかったため、モデルを用意して、スクリプトを書くことにしました。
モデルは、SQLModelを使います。
すでに、productsテーブルはalembicで作成してしまっているので、まずは、DDLからモデルを作ってみようと思います。
ざっと探してみたところ、omymodelsができそうかなと思いやってみました。
poetryを導入したので、poetry addしてみました。
$ poetry add omymodels
Using version ^0.17.0 for omymodels
Updating dependencies
Resolving dependencies... (0.1s)
Because no versions of omymodels match >0.17.0,<0.18.0
and omymodels (0.17.0) depends on pydantic (>=1.8.2,<2.0.0), omymodels (>=0.17.0,<0.18.0) requires pydantic (>=1.8.2,<2.0.0).
So, because langchain-samples depends on both pydantic (2.7.0) and omymodels (^0.17.0), version solving failed.
依存関係が解決できない的なエラーが出ました。
poetry add $( cat requirements.txt )
で片っ端から追加した弊害がここに出てます。
下記の2行をpyproject.tomlから削除することで解決しました。
pydantic = "2.7.0"
pydantic-core = "2.18.1"
ただ、この記事を書いていて気づきましたが、pydanticが1系になってますね。
omymodelsはそこまで便利な感じではないので、後ほど消しておこうかと思いました。
omymodelsが追加できたら、下のようなスクリプトを用意しました。
from omymodels import create_models
# カレントディレクトリにmodels.pyが生成される
ddl = """
CREATE TABLE products (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
description VARCHAR(200),
PRIMARY KEY (id)
);
"""
result = create_models(ddl, models_type='pydantic')['code']
これはこんな感じで、models.pyを生成してくれます。
from typing import Optional
from pydantic import BaseModel
class Products(BaseModel):
id: int
name: str
description: Optional[str]
今回は、sqlmodelを使いたいので、修正すると↓みたいになります。
from sqlmodel import Field, SQLModel
# omymodelsで生成して、ちょっと修正
class Products(SQLModel, table=True):
id: int = Field(primary_key=True)
name: str
description: str | None = None
最近の書き方だと、Optionalも不要なので、omymodelsを使うメリットは、やはり少なそうです。
話はそれますが、一応今回、モデルを作成してから、alembic revision --autogenerate
の後、alembic upgrade head
を実行する方法も試してます。
omymodelsがいまいちだったので、そちらの方法の方が今後メインになりそうな気がしています。
HeroモデルをSQLModelのドキュメントからコピペして、models.pyはこんな感じです↓
それから、alembic revision --autogenerate -m "create helo table"
を実行するんですが、下記を見ながら、script.py.makoを修正する必要があります。
修正した後で、autogenerateすると
$ alembic revision --autogenerate -m "create helo table"
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'hero'
INFO [alembic.autogenerate.compare] Detected type change from VARCHAR(collation='utf8mb4_bin', length=50) to AutoString() on 'products.name'
INFO [alembic.autogenerate.compare] Detected type change from VARCHAR(collation='utf8mb4_bin', length=200) to AutoString() on 'products.description'
ちょうど下記のコミットのようになるかと思います。
↑を見るとわかりますが、今回何も変更してないproductsの方もalter_columnが設定されています。
これは実行すると、下記のようにカラムのサイズだけが変わります。
-- 実行前
CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_bin NOT NULL,
`description` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
-- 実行後
CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_bin NOT NULL,
`description` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
これは、SQLModelのstrがデフォルトでVARCHAR(255)であることが原因だと思われます。
productsテーブルは、alembicのチュートリアルを見ながら作ったので、差分が出るのもしょうがないかと思います。
VARCHARのサイズを変えたくない場合は、下記のようにモデル側で指定しておくと大丈夫でした。
ですが、mysqlのvarcharは255までは大した違いが無いということをよく聞くので、255ままにしておこうかと思いました。
でもここで、クラス名がProductsになっているのに気づきました。
これに関しては、↓に記載がありました。
You can also override the table name. You can read about it in the Advanced User Guide.
オーバーライドしないといけないんですね。↓を見るとやり方が分かりそうです。
さらに、idがOptionalじゃ無いため、model_validateで怒られるということに気づきました。
エラーの内容はこんな感じです↓
pydantic.error_wrappers.ValidationError: 1 validation error for Product
id
field required (type=value_error.missing)
python上は、OptionalでDB上はnot nullというのが分かりづらいですが、そういうものだと納得しました。
※下記に少し書いてあります。
それで、まだ、やり直しが効く段階だったので、alembic downgrade
してversionsのファイルを削除して作り直しました↓
そして、なんだかんだあって、やっとデータ投入が完了しました。お疲れ様でした。
$ py app/seeds/products.py
2024-05-16 11:43:58,990 INFO sqlalchemy.engine.Engine SELECT DATABASE()
2024-05-16 11:43:58,990 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-05-16 11:43:58,991 INFO sqlalchemy.engine.Engine SELECT @@sql_mode
2024-05-16 11:43:58,991 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-05-16 11:43:58,992 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names
2024-05-16 11:43:58,992 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-05-16 11:43:58,993 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-05-16 11:43:58,994 INFO sqlalchemy.engine.Engine INSERT INTO products (name, description) VALUES (%s, %s)
2024-05-16 11:43:58,994 INFO sqlalchemy.engine.Engine [generated in 0.00007s] ('Test Product', 'This is a test product')
2024-05-16 11:43:58,997 INFO sqlalchemy.engine.Engine INSERT INTO products (name, description) VALUES (%s, %s)
2024-05-16 11:43:58,997 INFO sqlalchemy.engine.Engine [cached since 0.002637s ago] ('Another Test Product', 'This is another test product')
2024-05-16 11:43:58,998 INFO sqlalchemy.engine.Engine COMMIT
2024-05-16 11:43:59,011 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-05-16 11:43:59,012 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.description
FROM products
WHERE products.id = %s
2024-05-16 11:43:59,012 INFO sqlalchemy.engine.Engine [generated in 0.00009s] (1,)
2024-05-16 11:43:59,013 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.description
FROM products
WHERE products.id = %s
2024-05-16 11:43:59,013 INFO sqlalchemy.engine.Engine [cached since 0.001187s ago] (2,)
2024-05-16 11:43:59,014 INFO sqlalchemy.engine.Engine ROLLBACK
INFO:sqlalchemy.engine.Engine:ROLLBACK
Discussion