※当サイトの記事には、広告・プロモーションが含まれます。

Python用のWebフレームワークFastAPIでMVC構成を試みる

www.itmedia.co.jp

 米Microsoftは7月31日(現地時間)、30日に世界の広い地域で「Microsoft 365」と「Azure」のサービスが停止した約9時間にわたった障害は、DDoS(分散型サービス拒否)攻撃によって引き起こされたと発表した。

Microsoft、Azureの大規模障害はDDoS攻撃によるものと発表 - ITmedia NEWS

 「最初のきっかけはDDoS攻撃で、これによりわれわれのDDoS防御機構が作動したが、初期調査の結果、この防御策の実装における誤りが攻撃の影響を軽減するどころか、むしろ増幅させたことがわかった」という。

Microsoft、Azureの大規模障害はDDoS攻撃によるものと発表 - ITmedia NEWS

⇧障害が多いと「SLA(Service Level Agreements)」を維持するのが大変そうね...

Python用のWebフレームワークFastAPIでMVC構成を試みる

とりあえず、Python用のWebフレームワーク「FastAPI」で、フロントエンド側からリクエストを受け付けられるか試してみる。

ただ、今回はデータベース周りの連携は行わないので(データベースとの連携は別の記事で行っていく予定です)、イメージとしては、

medium.com

⇧ 上記サイト様の上図のような処理の流れの内、

Client Application ⇔ Controller Layer ⇔ Service Layer

の部分を試してみる感じになるかと。

ちなみに、

qiita.com

⇧「.py」ファイル名の命名規則としては、長くなる場合は「スネークケース」になるってことみたい。Javaだと「パスカルケース」だったから気を付けねば。

「.yml」ファイル名とかは、ハイフン区切りでも良いんかな?

とりあえず、「FastAPI 」の導入を試してみる。

環境はWindows環境で試してみますが、事前に、

  1. Python
  2. VS CodeVisual Studio Code

はインストール済みです。

Pythonのバージョンについては、

⇧ 上記のキャプチャ画像のような感じですが、

⇧ 新しいバージョンのPythonが公開されているようなので、インストールすることにしました。

zenn.dev

⇧ 上記サイト様によりますと、マイナーバージョンは複数インストールできないそうですが、メジャーバージョンであれば複数インストールできるようです。

では、wingetでPython 3.12をインストールします。

Python 3.12がインストールできているようです。

事前準備はOK。

「FastAPI」を動作させる手順としては、

  1. Pythonプロジェクト用のディレクトリ作成
  2. venvでPython仮想環境を作成
  3. Python仮想環境にログイン
  4. Python仮想環境内で作業
    1. 「FastAPI」などインストール
    2. 必要なファイルを追加
    3. 環境変数PYTHONPATHを追加する
    4. コーディング
    5. アプリケーションサーバーを起動
  5. エンドポイント(「FastAPI」のrouterのメソッド)にアクセス

のような流れになるかと。

それでは、進めていきます。

■1. Pythonプロジェクト用のディレクトリ作成

適当な場所にPythonプロジェクト用のディレクトリを作成します。

■2. venvでPython仮想環境を作成

Pythonプロジェクト用のディレクトリに移動し、Python仮想環境を作成します。

⇧「.venv」フォルダが作成されおり、諸々のフォルダ、ファイルが作成されていればOK。

■3. Python仮想環境にログイン

Python仮想環境にログインするためのスクリプトファイルを実行します。

コマンドプロンプトの場合は、「[Pythonプロジェクト用のディレクトリ]\[Python仮想環境用のディレクトリ]\Scripts\activate.bat」を実行すれば、Python仮想環境にログインできます。

■4. Python仮想環境内で作業

①「FastAPI」などインストール

インストール可能な「FastAPI」のバージョンとか確認しようと思ったのだけど、

stackoverflow.com

⇧ stackoverflowの情報によると、「pip search」が機能しないということで、

pip index versions [パッケージ名]    

⇧ 上記のコマンドで確認できるそうな。

とりあえず、現時点で最新版の「FastAPI」をインストール。

pip install fastapi==0.111.1  

⇧「FastAPI」以外のライブラリも漏れなくインストールされましたと。

で、

zenn.dev

⇧ 上記サイト様によりますと、Python用のWebフレームワークだと「依存性注入(DI:Dependency Injection)」を実現するライブラリは含まれていないらしく、別途ライブラリのインストールが必要らしい。

と言うわけで、

github.com

⇧「injector」というライブラリをインストールします。

pip install injector==0.22.0

⇧ インストールされました。

②必要なファイルを追加

ここからは、「VS CodeVisual Studio Code)」で作業していくことにします。

とりあえず、以下のようなディレクトリとファイルを追加しています。

環境変数PYTHONPATHを追加する

で、Pythonの仕組みがイケていないのか、

web-tech.binarymacaron.xyz

kazuhira-r.hatenablog.com

⇧ importで制約がありますと。

何と言うか、ディレクトリの構成が思いっきり影響してくるのですが、「相対 import」はハッキリ言って欠陥だらけなので、「絶対 import」を使うしかないのだと思うのだけど、「絶対 import」する場合は、環境変数「PYTHONPATH」に追加しないといけないらしい。

正確には、自分たちで追加した「.py」ファイルが認識できるように、パスを追加する必要があるということなので、

  1. 環境変数「PYTHONPATH」で追加
  2. コーディングで、sys.path.append()で追加

のどちらかを実施すれば良いっぽいのだけど、Pythonの流儀が分からんのですが、

qiita.com

⇧ 上記サイト様によりますと、「2. コーディングで、sys.path.append()で追加」を利用するのは悪手であるとのこと。

ただ、環境によっては、

dev.classmethod.jp

dev.classmethod.jp

環境変数「PYTHONPATH」に手を出すなって意見もありますが...

アプリケーションの規模にもよりますが、「sys.path.append()」が至る所にハードコーディングされていくのは辛いので、消去法で、「1. 環境変数「PYTHONPATH」で追加」を行うしかないっぽいですな。

で、自分の環境が、Windows 10 Homeなので、Python仮想環境ではあるものの、パスがWindowsのものになってしまうので、Python仮想環境って結局、めちゃくちゃ環境依存してしまうやん、っていうガッカリ感があるのですが、パスを追加します。

でも、考えてみたら、Javaでも環境変数JAVA_HOME」が、同様に環境依存してしまうので、致し方ないのかなと...

本番環境(Linux環境を想定)だと、「.env」ファイルとかを作成して、環境変数「PYTHONPATH」も「.env」ファイルで設定するんだとは思われますが...

追加するパスについては、ご自身の環境に合わせたものに置き換えてください。

set PYTHONPATH=C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi;C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app

⇧ ちなみに、「venv」によるPython仮想環境の仕組みがイケていないのか、自分の環境では環境変数「PYTHONPATH」自体が存在しなかったので、完全に上書きしてしまってます。

Python仮想環境で環境変数「PYTHONPATH」が追加できたのを確認できました。

今回はコマンドプロンプトで実行していく想定のため、上記のような対応にしていますが、「VS CodeVisual Studio Code)」でデバッグとか行う場合は、「VS CodeVisual Studio Code)」側の設定ファイルを用意する感じになります。

④コーディング

で、JavaSpring FrameworkのSpring Web MVC的な構成っぽく、コーディングしてみましたが、これが正しいのかは分かりません...

Pythonにおける「依存性注入(DI:Dependency Injection)」については、

qiita.com

⇧ 上記サイト様を参考にさせていただきました。

Pythonの「self」のせいで、controllerレイヤーにおける「依存性注入(DI:Dependency Injection)」が他のレイヤーでの「依存性注入(DI:Dependency Injection)」のように実装できなかったので。

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\core\dependency_inject.py

from injector import Injector, inject
#from src.database import AppConfig

class DependencyInjector:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(DependencyInjector, cls).__new__(cls)
#            cls._instance._initialize()
        return cls._instance

#    def _initialize(self):
#        self.di = Injector([AppConfig()])

    @staticmethod
    def update_injector(self, _class):
        self.di = Injector([_class])

    @inject
    def get_class(self, _class):
        self.update_injector(self, _class)
        return self.di.get(_class)

di_injector = DependencyInjector()    

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\entity\user_entity.py

from dataclasses import dataclass
from datetime import date, datetime

@dataclass
class UserEntity:
    id: int
    first_name: str
    last_name: str
    first_name_kana: str
    last_name_kana: str
    email: str
    birth: date | None = None
    gender: int | None = None
    address_id: int | None = None
    tel: str | None = None
    created_at: datetime = datetime.now()
    update_at: datetime | None = None    

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\repository\user_repository.py

from app.src.entity.user_entity import UserEntity
from injector import singleton

@singleton
class UserRepository:

    def find_by_id(self, user_id: int) -> UserEntity:
        """
        ユーザー検索
        """
        # TODO:DBからのSelect
        return None    

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\service\user_service.py

from app.src.entity.user_entity import UserEntity
from app.src.repository.user_repository import UserRepository
from injector import inject, singleton

@singleton
class UserService:

    @inject
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    def find_user(self, user_id: int) -> UserEntity:
        return self.user_repository.find_by_id(user_id)    

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\controller\user_controller.py

#import asyncio
from fastapi import APIRouter
from injector import inject
from app.src.service.user_service import UserService
from app.src.entity.user_entity import UserEntity
from app.src.core.dependency_inject import DependencyInjector

router: APIRouter = APIRouter()

class UserController:
  user_service: UserService
  
  @inject
  def __init__(self, user_service: UserService):
     self.user_service = user_service
  
  @router.get('/user')
  def root():
     return {"message": "Hello World"}

  @router.get("/user/{id}")
  def find_user(id: int) -> UserEntity | None:
  #async def find_user(id: int) -> UserEntity | None:
      result: UserEntity = DependencyInjector.get_class(DependencyInjector,UserService).find_user(id)
      #result: UserEntity = await DependencyInjector.get_class(DependencyInjector,UserService).find_user(id)
      #result: UserEntity = UserController.user_service.find_user(id)
      return result


■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\main.py

import sys
#import os
#sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from fastapi import FastAPI

print(sys.path)

app = FastAPI()

from app.src.controller import user_controller

app.include_router(user_controller.router)    

⇧ で保存。

アプリケーションサーバーを起動

Python仮想環境で、「FastAPI」をインストールした時に同梱されてた「uvicorn」っていうアプリケーションサーバーを起動する際に、実行するPythonのファイルとして「main.py」を指定します。

⇧ とりあえず、「FastAPI」自体は動いたと。

■5. エンドポイント(routerのメソッド)にアクセス

試しに、Controllerのエンドポイントのメソッドにアクセスしてみたところ、

⇧ 引数ありの方は、Noneを返すだけにしてるから、nullと表示されてますね。

とりあえず、JavaSpring Frameworkとかに慣れしたんできた身からすると、Pythonは、いろいろとイケていない気がして仕方ない...

データベース周りの処理を追加したら、また、上手く動かなくなるように思えて気が滅入りますな...

JavaSpring Frameworkの「@Autowired」で実現できていた、クラスのフィールドについての「依存性注入(DI:Dependency Injection)」ができないのが辛過ぎる...

そして、

javaconceptoftheday.com

⇧ 上記サイト様の情報が正しいとすると、PythonってJavaより歴史が古いはずなんよね...

とりあえず、利用頻度の多そうなimportの仕組みは、公式のドキュメントで詳細に記載しておいて欲しいですかね...

ディレクトリの構成に依存する「相対 import」とか、最早バグと言っても過言では無い気がしてしまうのだが...

う~む、業務でPythonを使っているアプリケーションに触れるかもしれないということで、Pythonを学習せざるを得ないわけなのだけど、Pythonを利用するメリットが全く思い付かないんよね...

データサイエンス系のアプリケーションであれば、科学計算系のライブラリが豊富らしいという理由でPythonを利用するメリットはあると思うのですが、Webアプリケーション系でPythonを利用するメリット、本当に分からない...

扱うのが「サーバーレス」とか利用してるアプリケーションであれば、Pythonを利用するメリットはありそうだけども...

ちなみに、「Datadog」の調査によると、

www.datadoghq.com

⇧「AWSAmazon Web Services)」における「サーバーレス」のサービスである「AWS Lambda」で利用されるプログラミング言語としては、「Node.js」に次いで「Python」の利用が多いらしい。

AWSAmazon Web Services)」以外のクラウドサービスプロバイダーの「サーバーレス」サービスにおけるプログラミング言語の割合も掲載して欲しかったんだが...

どちらにしろ、Pythonの学習のモチベーションが上がらないですな...

毎度モヤモヤ感が半端ない…

今回はこのへんで。