FastAPI の簡単なサンプルを作る

結構、参考になる解説サイトがあるもので、
チュートリアル - ユーザーガイド - FastAPI
ここを見て、まずは必要最低限を書きます

import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    a = "sample"
    return {"hello": a}


@app.get("/view/{id}")
def view(id: int):
    # TODO view結果生成
    return {"viewid": f"{id}" }


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

これを起動すれば、
http://localhost:8000/
で動きます。
Swagger 書式のAPI定義は、http://localhost:8000/docs

ReDoc書式のAPI定義は、http://localhost:8000/redoc

で、開きます。このままではAPI定義書として説明が不十分なのと、
POST の列も書いておきたいので、以下のように追記します。

まずは、インポートするもの、、

from fastapi import FastAPI,Path,Query
from pydantic import BaseModel,Field
from typing import Union

FastAPIの他に、
 Path :URIのパスパラメータから取得する定義を記述する為に必要
 Query:クエリパラメータ定義を記述する為に必要
 BaseModel:レスポンスモデル、リクエストモデル定義を記述する為に必要
 Field:モデル定義内のフィールドプロパティ定義を記述する為に必要
 Union:モデル定義内のフィールドプロパティで省略可を記述する為に必要
これらを使います。
クエリパラメータの必須の書き方、、
クエリパラメータのデフォルトを書かなければ、required にはなる。

全体のサンプル
一通り見渡せた方が良いので、以下に書いてます。

import uvicorn
from fastapi import FastAPI,Path,Query
from pydantic import BaseModel,Field
from typing import Union

class ViewBody(BaseModel):
    viewid: int=Field(description="view実行したID")
    message: str=Field(description="メッセージ", default=None )
class QueryBody(BaseModel):
    id: int=Field(description="クエリ結果ID")
    title: str=Field(description="タイトル" )
class AddBody(BaseModel):
    status: int=Field(description="登録したID" )
class Item(BaseModel):
    name: str=Field(description="品名")
    price: int=Field(gt=100, description="価格")
    description: Union[str, None] = None
class Error400(BaseModel):
    errorMessage: str=Field("不正なリクエスト")
class Error401(BaseModel):
    errorMessage: str=Field("認証エラー")
class Error500(BaseModel):
    errorMessage: str=Field("システムエラー")

app = FastAPI();

@app.get("/")
def root():
    a = "sample"
    return {"hello": a}


@app.get("/view/{id}", response_model=ViewBody,
             responses={
                 401: {"model": Error401},
                 500: {"model": Error500}
             }
         )
def view(id: int=Path(gt=0, description="view対象のID")):
    # TODO view結果生成
    return {"viewid": id }


@app.get("/query", response_model=QueryBody,
             responses={
                 400: { "model": Error400 },
                 401: { "model": Error401 },
                 500: { "model": Error500 }
             }
         )
def query(id: int=Query(gt=0, description="クエリID"),
          subno: int=Query(gt=0, description="枝番号"),
          ops: str=Query(None, description="オプション")):
    # TODO クエリ結果生成
    answer = f"Test Query : {subno}"
    return {"id": id, "title": answer}


@app.post("/add", response_model=AddBody,
              responses={
                  400: {"model": Error400},
                  401: {"model": Error401},
                  500: {"model": Error500}
              }
          )
def add(item: Item):
    # TODO 追加登録実行して id をセット
    id = 10
    return {"status": id}


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

これの ReDoc の結果を参照してみる、→ http://localhost:8000/redoc
Swagger と ReDoc 、好みが分かれるところだろう。






PyCharm でFastAPI プロジェクトの準備

PyCharm Professional版には、FastAPIが備わっていて
FastAPIのプロジェクトを新規作成する時に便利ですが、
https://pleiades.io/help/pycharm/fastapi-project.html#coding-assistance
Community版を使っていると、以下に相当するインストールをすれば済みます。

pip install fastapi
pip install "uvicorn[standard]"

PyCharm の設定画面で、、、

uvicorn は、「アッバーコーン」と呼ぶみたいで
ASGI(Asynchronous Server Gateway Interface):非同期のWebアプリケーションサーバインターフェース定義
https://www.uvicorn.org/


PyCharmの Run 設定は
普通の Python
script path -> Module name にして uvicorn を選ぶ
main:app --reload --port 8000
だと思うんですが、
生成した FastAPI を uvicorn の run で起動するように書いて
PyCharm でそのまま実行すれば動きます。

SQL 行番号など、

PostgreSQL 8.4 以上で使えるようになったウィンドウ関数
テーブルのレコード総数を SELECT句に含めたい場合 COUNT(1) OVER() を指定する。

SELECT *,  COUNT(1) OVER()  AS  total
FRON items

これは、MySQL でも使える。

同様に行番号を SELECT句に付与したい場合は、ROW_NUMBER()関数
  ROW_NUMBER() OVER()
か、
  ROW_NUMBER() OVER(ORDER BY ソートしたい列 ASC)
を使用する。

この ROW_NUMBER() を使えば以下の様にも、LIMIT~OFFSET のような目的のクエリを
書くこともできる

SELECT 
   a.*
FROM (
        SELECT 
        *,  ROW_NUMBER() OVER(ORDER BY id ASC) AS rownum
        FROM items
        WHERE 1=1
    ) a 
WHERE a.rownum >=1 AND a.rownum <=15
ORDER BY a.rownum ASC

これは、MySQL でも同じように書ける。

OffsetDateTime , ZonedDateTime

この2つを積極的に使うプロジェクトって、意外と少ない。

LocalDateTime localdatetime = LocalDateTime.now();

// LocalDateTime → ZoneDateTime
ZonedDateTime zoneddatetime = localdatetime.atZone(ZoneId.systemDefault());

// LocalDateTime → OffsetDateTime
OffsetDateTime offsetdatetime = localdatetime.atOffset(ZoneOffset.ofHours(9));

それぞれ、toString() として標準出力すると

2024-04-21T16:49:23.526946600
2024-04-21T16:49:23.526946600+09:00[Asia/Tokyo]
2024-04-21T16:49:23.526946600+09:00

以下は、OffsetDateTime 覚えての以下のインスタンスメソッドは、覚えておくといいかも。
withOffsetSameInstant(ZoneOffset offset)
結果が暗黙の日の同じインスタントを持つようにしながら、指定されたオフセットを使ってこのOffsetTimeのコピーを返します。

withOffsetSameLocal(ZoneOffset offset)
結果が同じローカル時間を持つようにしながら、指定されたオフセットを使ってこのOffsetTimeのコピーを返します。

開始ー終了、あるいは、from-to などのリストの連続性を確認する

任意のfrom-toを表現するオブジェクトとして、

import lombok.Data;

@Data
public class Route{
    private Integer from;
    private Integer to;
}

こんなクラスがあったとする。これのリストのパターンとして、
想定している Route のリスト、連続性が成立している

From To
0 9
10 19
20 29
30 null

重なりがある(=20)

From To
0 9
10 20
20 29
30 null

抜けがある(=19)

From To
0 9
10 18
20 29
30 null

これら Route のリストの連続性、重なりの存在、抜けの存在をチェックする方法で
考えた方法が、Stream の reduce を使う方法、AtomicBoolean を使わなければスッキリするのだが。
もっと綺麗な方法が思いつかない。

List<Route> list; // ←ここに何かの処理で格納される。ソートされている前提で、
AtomicBoolean result = new AtomicBoolean(true);
list.stream().reduce(new Route(), (a, b)->{
    result.set(Boolean.logicalAnd(res.get(), a.getTo()==null
            ? true
            : (b.getFrom()-a.getTo())==1));
    return b;
});

// result.get()==true なら、連結に重なりも抜けはない。連続性が想定どおり
// result.get()==false なら、連結に重なりか抜けがある。

最後の Route の to が null であるかをチェックしたければ、

AtomicBoolean result = new AtomicBoolean(true);
Route lastroute = list.stream().reduce(new Route(), (a, b)->{
    result.set(Boolean.logicalAnd(res.get(), a.getTo()==null
            ? true
            : (b.getFrom()-a.getTo())==1));
    return b;
});
// lastroute.getTo() が null かどうかをチェックする

DBeaver のSQL書式設定

DBeaverを使っているのですが、
select などの文字を大文字にするか、小文字にするか、
これら、SQLの句を圧倒的に小文字に書く人が多いのですが、
個人的には、大文字派です。
DBeaverで自動で補完入力する時、小文字が厭で大文字にするなら、
「設定」画面、SQL書式設定で、次のように、Upper を選択します。

Spring DATA JPA Specification

org.springframework.data.jpa.repository.JpaSpecificationExecutor

Specification を使って以下のメソッドで動的クエリを生成できる。

long count(Specification<T> spec)

boolean exists(Specification<T> spec)

List<T> findAll(Specification<T> spec)

Page<T> findAll(Specification<T> spec, Pageable pageable)

List<T> findAll(Specification<T> spec, Sort sort)

T findOne(Specification<T> spec)

でも、@Query で、native Query の使用と、Specification を同時には使えない。