4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

DIをflaskで使う方法を記事にしました。
本記事ではflask-injectorを使用します。
併せて、DI・DIコンテナについても説明していきます。

DIとは?

まずは前提知識となるDIについて説明をしていきます。
Dependency Injectionは依存性の注入と訳されますが、
依存性の注入では何を表現しているのか?が掴みづらいと思います。

この依存性を言い換えるなら、依存関係にある要素・オブジェクトです。

「依存関係にあるオブジェクトを注入する」ことがDIパターンとなります。

DIパターンを使用することの利点

依存関係にあるオブジェクトを外部から注入することにより、
依存元のモジュールから依存しているオブジェクトを外部に出すことができます。
つまり、モジュール結合度を弱くすることができます。

DIパターンを使用していない場合

モジュール結合度を弱くすることで保守性の強いソースコードを作ることができます。
例えば、下記のようなコードがあるとします。

test.py
from dataclasses import dataclass

@dataclass
class Cat:
  name: str
 
class Pet:

  def call_name(self) -> None:
    cat_cls = Cat("hoge")
    print(cat_cls.name)
  
if __name__ == "__main__":
  pet = Pet()
  pet.call_name()

Petクラスの内部でCatクラスのインスタンスを生成しています。
PetクラスはCatクラスに強く依存している状態です。

依存状態が強いと、何が問題なのか?
上記を説明するためにCatクラスのインスタンス変数に変更を加えます。

@dataclass
class Cat:
  name: str
  age: int  

変更点として、Catクラスのインスタンス変数にageという変数を追加しました。
変更はCatクラスに行ったものですが、
PetクラスでCatクラスのインスタンスを生成しています。

Petクラスでも修正が必要になるのです。

  def call_name(self) -> None:
    cat_cls = Cat("hoge") # <-- この箇所
    print(cat_cls.name)

下記ではモジュールが少ないのでイメージが付きづらいと思います。
極論にはなりますが、100個のモジュールがCatクラスに依存しているとします。
その場合、依存している100個のモジュール全てを修正していく必要があります

変更したかったのはCatクラスのインスタンス変数であるにも関わらずです。

このようにモジュール結合度が高いと、変更に弱いソースコードになってしまいます。

DIパターンを使用する場合

ここでDIパターンを使用していきます。
CatクラスをPetクラスの中で呼び出すのではなく、
外部から注入します。

test.py
from dataclasses import dataclass

@dataclass
class Cat:
  name: str
  age: int
 
class Pet:
  def __init__(self, cat: Cat):
    self.cat = cat

  def call_name(self) -> None:
    print(self.cat.name)
  
if __name__ == "__main__":
  cat = Cat("taro", 3)
  pet = Pet(cat)
  pet.call_name()

こうすることてCatクラスに変更があった場合でも
修正箇所を特定しやすくできます。

※依存関係においてはSOLID原則の依存性逆転の原則などもあります。
 取り扱っている事柄などが似通っていますが、
 DIパターンと依存性逆転の原則は別のものです。

DIコンテナについて

DIコンテナはDIを使用する上では必須ではありません。
複数のインスタンスを渡すときに呼び出しが煩雑になってきます。
インスタンスを管理する場所が欲しくなります。

そこでDIコンテナを使用しています。
DIコンテナの使用用途は煩雑になるインスタンスの管理です。

flaskでDIを使用する

ここからが本題です。
今回はFlaskにInjectorを追加するため、Flask-Injectorを使用していきます。

Flask-Injector使用時にはFlaskのバージョンも確認しておく必要があります。

PyPiのFlask-Injectorから引用

Flask-Injector is compatible with CPython 3.7+. As of version 0.15.0 it requires Injector version 0.20.0 or greater and Flask 2.2.0 or greater.

注意
なお、バージョン0.15.0以降のFlask-Injectorを使用する場合はInjectorのバージョンが0.20.0であることFlaskのバージョンが2.2.0以上である必要があります。

pip install Flask-Injector

Flask-Injectorをインストールできたら、
コードを書き進めていきます。

Flask-Injectorは内部でInjectorモジュールを使用しております。
実際に使う場合にはInjectorモジュールの学習も必要になります。

かなり簡易的な実装になります。
そちらを留意頂けると幸いでございます。

app.py
from flask import Flask
from flask_injector import FlaskInjector, request, Binder, inject
from model import AbstractUserProfileTable, UserProfileTable
from dataclasses import dataclass

app = Flask(__name__)

@dataclass
class Hoge:
    name: str = "taro"

class Fuga:
    @inject
    def __init__(self, hoge: Hoge):
        self.hoge = hoge

    def test_func(self):
        return self.hoge.name

@app.route("/foo")
def foo(test_cls: Fuga):
    return test_cls.test_func()

@app.route("/bar")
def bar(user_profile: AbstractUserProfileTable):
    users = user_profile.find_all()
    return users

def configure(binder: Binder):
    binder.bind(
        AbstractUserProfileTable,
        to=UserProfileTable,
        scope=request
    )

injecter = FlaskInjector(app=app, modules=[configure])

if __name__ == "__main__":
  app.run()
model.py
import os
from dotenv import load_dotenv
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, String
from abc import ABC, abstractmethod
from sqlalchemy.orm import sessionmaker

load_dotenv()
engine = create_engine(
        f"postgresql+psycopg2://{os.getenv('USER')}:{os.getenv('PASSWORD')}@{os.getenv('HOST')}:{os.getenv('PORT')}/{os.getenv('DATABASE')}")
Base = declarative_base()
session_maker = sessionmaker(engine)
session = session_maker()

class UserProfile(Base):
    __tablename__ = "user_profile"
    user_id = Column(Integer, primary_key=True)
    first_name = Column(String)
    last_name = Column(String)

class AbstractUserProfileTable(ABC):
    @abstractmethod
    def find_all(self):
        pass

class UserProfileTable(AbstractUserProfileTable):
    def find_all(self):
        return session.query(UserProfile).all()

コードの解説をしていきます。

class Fuga:
    @inject
    def __init__(self, hoge: Hoge):
        self.hoge = hoge

外部からオブジェクトを注入する際には@injectでデコレートする必要があります。
@injectでデコレートしないとFugaクラスのインスタンス生成時にエラーになります。
また、型アノテーションが必須になるので忘れずに宣言しておいてください。

    binder.bind(
        AbstractUserProfileTable,
        to=UserProfileTable,
        scope=request
    )

上記のコードは抽象クラスと具象クラスの紐づけに使用しています。

@app.route("/bar")
def bar(user_profile: AbstractUserProfileTable):
    users = user_profile.find_all()
    return users

上記ではAbstractUserProfileTableインスタンスを注入しているが、呼びされているfind_allメソッドは具象クラスのメソッドになります。

参考にさせていただいた資料・記事

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?