動かざることバグの如し

近づきたいよ 君の理想に

Python3.10からimport Optionalしなくてよくなった

環境

from typing import Optionalしなくて良くなった

昨今のPythonでは型アノテーションが流行っているが、その中でもOptionalは使う機会が多い

例えばPythonの組み込みメソッドであるstr.startswith()は文字列が指定された文字列から開始しているかを調べてくれるが、実は第2引数に整数を渡すことで何文字目から調べるか指定できる。Noneが渡されたときにはなかったことにされる。

'1234567890'.startswith('123')
> True

'1234567890'.startswith('4567')
> False

'1234567890'.startswith('4567', 3)
> True

これをOptionalを使うと

def startswith(prefix:str, start:Optional[int]=None)
  ...

と定義できる。

だが、このままだとOptionalがないと怒られてしまう。

NameError: name 'Optional' is not defined

IDEのオートインポート使えよって話かもしれないが、そんな面倒なことをしなくても気軽に書けるのがPythonのメリットではなかったのか。

ってことでPython 3.10からは簡略記法で記述できる。

def startswith(prefix:str, start:int|None=None):
  ...

うーんこっちのほうが見やすい。何よりimport不要になったのはデカい。

はじめからこっちにしておけばよかったのに

参考リンク

fastapiでvalue is not a valid dictエラーになる

環境

  • fastapi v0.75

状況

以下のようなエラーが出て動かない

pydantic.error_wrappers.ValidationError: 1 validation error for User
response -> 0
  value is not a valid dict (type=type_error.dict)

main.pyコードは以下 よくあるコードなので抜粋のみ

from fastapi import Depends, FastAPI
from sqlalchemy.orm import Session

from . import models, schemas
from .database import SessionLocal, engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

def get_db():
  db = SessionLocal()
  try:
    yield db
  finally:
    db.close()

@app.get("/users", response_model=list[schemas.User])
def all_fetch(db: Session = Depends(get_db)):
  users = db.query(models.User).all()
  return users

でschema.pyが以下

from pydantic import BaseModel

class User(BaseModel):
  id: int
  email: str

対処法

schema.pyに追記して、 orm_mode = True にする必要がある。

追記したコードが以下

from pydantic import BaseModel

class User(BaseModel):
  id: int
  email: str

  class Config:
    orm_mode = True

なぜ

response_model=list[schemas.User] に書いてあるように、レスポンスは辞書(dict)を期待している。

が、実際にはSQLAlchemyのモデルが返ってきてしまっているので invalid となりエラーになってしまう

動いたり動かなかったりする

ここがfastapi初心者あるあるで原因がよくわかってないからコードのどこが原因でエラーになるのかが分かりづらい。

原因にもあるようにresponse_modelでdictを指定しているのに反しているからエラーになっているので指定しなければエラーにならない

@app.get("/users")
def all_fetch(db: Session = Depends(get_db)):
  users = db.query(models.User).all()
  return users

また、結果として辞書型になっていればいいので変換すると一応動いてしまう

# 動くが、よくないやり方
@app.get("/users")
def all_fetch(db: Session = Depends(get_db)):
  users = db.query(models.User).all()
  return return [ x.__dict__ for x in users]

ちゃんと定義はschema.pyに書こう

参考リンク

fastapiのmodels.pyとschema.pyのモデルの違い

環境

  • fastapi v0.75

モデルが2つある?

fastapiやってると、多くのチュートリアルでmodels.py、schema.pyが出てくる。

が、その違いがいまいち分からず混乱したのでメモ

models.pyはSQLAlchemy用、schema.pyはPydantic用ファイル

実際のソースコードでは何を継承しているかを見るとわかりやすい

models.pyの一部

from .database import Base

class User(Base):
  __tablename__ = "users"
  id = Column(Integer, primary_key=True, index=True)
  email = Column(String, unique=True, index=True)

database.pyの一部

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

なるほど、models.pyのクラスはSQLAlchemyパッケージの declarative_base() で生成したクラスを継承して生成されている

一方 schema.pyは

from pydantic import BaseModel

class User(BaseModel):
  email: str
  password: str

こっちはシンプルで pydanticクラスを継承して生成されている。

pydanticはデータベースとは直接の関係は一切ない

fastapiの公式ドキュメントには

  • SQLAlchemyは「モデル」という用語を、データベースと相互作用するこれらのクラスやインスタンスを指すのに使用しています。
  • しかしPydanticは「モデル」という用語を、データの検証、変換、ドキュメンタリーのクラスやインスタンスという、別のものを指すのにも使っています。

と書かれている。

原文は以下

SQLAlchemy uses the term "model" to refer to these classes and instances that interact with the database. But Pydantic also uses the term "model" to refer to something different, the data validation, conversion, and documentation classes and instances.

https://fastapi.tiangolo.com/tutorial/sql-databases/

VSCodeにPylanceを入れてみた

環境

最近だとVScodePython Language Serverは「Pylance」がオススメと言う記事をよく見る

現状不満があるわけではないが、せっかくのGWでもあるので導入してみた

インストール

普通に拡張機能としてインストールするだけ。よく見るとMicrosoftが開発元だった

marketplace.visualstudio.com

Pythonの拡張機能が依存しているのでインストール時に一緒にインストールされる。

有効化

デフォルトでは使えないので、VSCodeの設定を開いて

"python.languageServer": "Pylance",

にする。終わり

エラー

VSCodeのremote developmentだとできなかったが、Python拡張機能インストール後にPython自体のアップデートをしていたのが原因だった

拡張機能を再インストールすることで解決した。

Ubuntuにpyenvインストールする手順

最後にpyenvの記事書いてからだいぶ時間経ってしまい状況も変わってしまっているので再度メモ

thr3a.hatenablog.com

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

以下を~/.zshrcに記述

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"

で、source ~/.zshrc

事前にビルドに必要な依存パッケージをインストールしておく

github.com

入れたいバージョンをインストール

pyenv install 3.10.4

インストール完了後、確認

❯ pyenv versions      
* system (set by /home/thr3a/.pyenv/version)
  3.10.4

デフォルトでpyenvでインストールしたPythonを使うようにする

pyenv global 3.10.4