🐡

sqlmodelを使ってテストデータを投入

2024/05/16に公開

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はこんな感じです↓
https://github.com/na8esin/LangChain-sample/blob/f34a8ed5eb33d0c0252e6d6eca6fdfd709bfd2b9/app/models.py

それから、alembic revision --autogenerate -m "create helo table"を実行するんですが、下記を見ながら、script.py.makoを修正する必要があります。
https://alembic.sqlalchemy.org/en/latest/autogenerate.html#comparing-and-rendering-types

修正した後で、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'

ちょうど下記のコミットのようになるかと思います。
https://github.com/na8esin/LangChain-sample/commit/9f0b758e917a120c3f353f52ab35a45f74b16515

↑を見るとわかりますが、今回何も変更してない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)であることが原因だと思われます。
https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#text-or-varchar

productsテーブルは、alembicのチュートリアルを見ながら作ったので、差分が出るのもしょうがないかと思います。
VARCHARのサイズを変えたくない場合は、下記のようにモデル側で指定しておくと大丈夫でした。
https://github.com/tiangolo/sqlmodel/issues/126#issuecomment-2035012563

ですが、mysqlのvarcharは255までは大した違いが無いということをよく聞くので、255ままにしておこうかと思いました。

でもここで、クラス名がProductsになっているのに気づきました。
これに関しては、↓に記載がありました。
https://sqlmodel.tiangolo.com/db-to-code/#sql-table-names

You can also override the table name. You can read about it in the Advanced User Guide.

オーバーライドしないといけないんですね。↓を見るとやり方が分かりそうです。
https://github.com/tiangolo/sqlmodel/issues/159

さらに、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というのが分かりづらいですが、そういうものだと納得しました。
※下記に少し書いてあります。
https://sqlmodel.tiangolo.com/tutorial/insert/#whats-next

それで、まだ、やり直しが効く段階だったので、alembic downgradeしてversionsのファイルを削除して作り直しました↓
https://github.com/na8esin/LangChain-sample/blob/4b6a1873d290ededa7d4d63e7f5b82758a9ee1b7/app/alembic/versions/b031d4fe1c3e_create_products_hero_table.py

そして、なんだかんだあって、やっとデータ投入が完了しました。お疲れ様でした。

$ 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