🔖

FlaskでAPIを作成するためのディレクトリ構成とモジュール管理のベストプラクティス

2024/06/01に公開

この記事では、Flaskを使ってAPIを開発する際のベストプラクティスに基づくディレクトリ構成例について解説します。

ディレクトリ構成

まず、Flaskアプリケーションの推奨ディレクトリ構成を示します:

my_flask_app/
│
├── app/
│   ├── __init__.py
│   ├── main/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── models.py
│   │   ├── utils.py
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── models.py
│   │   ├── utils.py
│   ├── config.py
│
├── tests/
│   ├── __init__.py
│   ├── test_main.py
│   ├── test_auth.py
│
├── venv/
│
├── .env
├── requirements.txt
└── run.py

各ファイルの詳細と役割

run.py

このファイルはアプリケーションのエントリーポイントです。Flaskアプリケーションを起動します。

from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

app/__init__.py

Flaskアプリケーションのファクトリ関数を定義し、モジュールごとのブループリントを登録します。

from flask import Flask

def create_app():
    app = Flask(__name__)
    
    # Configuration
    app.config.from_pyfile('config.py')
    
    # Register Blueprints
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint, url_prefix='/main')
    
    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')
    
    return app

app/config.py

アプリケーションの設定を管理します。例えば、データベースやセキュリティキーの設定などを行います。

import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
    DEBUG = True

class ProductionConfig(Config):
    DEBUG = False

app/main/__init__.py

メインモジュールの初期化ファイルです。ブループリントを作成し、ルートをインポートします。

from flask import Blueprint

main = Blueprint('main', __name__)

from . import routes

app/main/routes.py

メインモジュールのルートを定義します。ここでは、エンドポイントの定義を行い、サービスロジックを呼び出します。

from flask import jsonify, request
from . import main
from .services import create_user_service, get_user_service

@main.route('/')
def index():
    return jsonify({'message': 'Welcome to the Main Module!'})

@main.route('/users', methods=['POST'])
def create_user():
    data = request.json
    user = create_user_service(data)
    return jsonify(user), 201

@main.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = get_user_service(user_id)
    if user:
        return jsonify(user)
    else:
        return jsonify({'message': 'User not found'}), 404

app/main/services.py

メインモジュールのビジネスロジックを定義します。ここでは、ユーザーの作成と取得のロジックを実装しています。

from .models import User
from . import db

def create_user_service(data):
    new_user = User(username=data['username'], email=data['email'])
    db.session.add(new_user)
    db.session.commit()
    return {'id': new_user.id, 'username': new_user.username, 'email': new_user.email}

def get_user_service(user_id):
    user = User.query.get(user_id)
    if user:
        return {'id': user.id, 'username': user.username, 'email': user.email}
    return None

app/main/models.py

データベースモデルを定義します。ここでは、ユーザーモデルを定義しています。

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return f'<User {self.username}>'

app/main/utils.py

ユーティリティ関数を定義します。一般的な処理やヘルパー関数はこちらに記述します。

def format_date(date):
    return date.strftime('%Y-%m-%d')

app/auth/__init__.py

認証モジュールの初期化ファイルです。

from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import routes

app/auth/routes.py

認証モジュールのルートを定義します。

from flask import jsonify, request
from . import auth
from .services import login_service, register_service

@auth.route('/register', methods=['POST'])
def register():
    data = request.json
    user = register_service(data)
    return jsonify(user), 201

@auth.route('/login', methods=['POST'])
def login():
    data = request.json
    response = login_service(data)
    return jsonify(response)

app/auth/services.py

認証モジュールのビジネスロジックを定義します。ここでは、ユーザーの登録とログインのロジックを実装しています。

from werkzeug.security import generate_password_hash, check_password_hash
from .models import User
from . import db

def register_service(data):
    hashed_password = generate_password_hash(data['password'], method='sha256')
    new_user = User(username=data['username'], password_hash=hashed_password)
    db.session.add(new_user)
    db.session.commit()
    return {'id': new_user.id, 'username': new_user.username}

def login_service(data):
    user = User.query.filter_by(username=data['username']).first()
    if user and check_password_hash(user.password_hash, data['password']):
        return {'message': 'Login successful'}
    return {'message': 'Invalid credentials'}, 401

app/auth/models.py

認証に関連するデータベースモデルを定義します。

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)

    def __repr__(self):
        return f'<User {self.username}>'

app/auth/utils.py

証モジュールのユーティリティ関数を定義します。

from werkzeug.security import generate_password_hash, check_password_hash

def hash_password(password):
    return generate_password_hash(password)

def verify_password(password_hash, password):
    return check_password_hash(password_hash, password)

テストディレクトリ構成

テストを含めたディレクトリ構成の例です:

my_flask_app/
│
├── app/
│   ├── __init__.py
│   ├── main/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── models.py
│   │   ├── utils.py
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── models.py
│   │   ├── utils.py
│   ├── config.py
│
├── tests/
│   ├── __init__.py
│   ├── test_main.py
│   ├── test_auth.py
│
├── venv/
│
├── .env
├── requirements.txt
└── run.py

tests/__init__.py

テストモジュールの初期化ファイルです。

# tests/__init__.py

tests/test_main.py

メインモジュールのテストを定義します。

import unittest
from app import create_app
from app.main.models import db

class MainTestCase(unittest.TestCase):

    def setUp(self):
        self.app = create_app()
        self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()
        
        with self.app.app_context():
            db.create_all()

    def tearDown(self):
        with self.app.app_context():
            db.session.remove()
            db.drop_all()

    def test_index(self):
        response = self.client.get('/main/')
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'Welcome to the Main Module!', response.data)

    def test_create_user(self):
        response = self.client.post('/main/users', json={
            'username': 'testuser',
            'email': 'test@example.com'
        })
        self.assertEqual(response.status_code, 201)
        self.assertIn(b'testuser', response.data)

    def test_get_user(self):
        response = self.client.post('/main/users', json={
            'username': 'testuser',
            'email': 'test@example.com'
        })
        user_id = response.get_json()['id']
        response = self.client.get(f'/main/users/{user_id}')
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'testuser', response.data)

if __name__ == '__main__':
    unittest.main()

tests/test_auth.py

認証モジュールのテストを定義します。

import unittest
from app import create_app
from app.auth.models import db

class AuthTestCase(unittest.TestCase):

    def setUp(self):
        self.app = create_app()
        self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()
        
        with self.app.app_context():
            db.create_all()

    def tearDown(self):
        with self.app.app_context():
            db.session.remove()
            db.drop_all()

    def test_register(self):
        response = self.client.post('/auth/register', json={
            'username': 'testuser',
            'password': 'password'
        })
        self.assertEqual(response.status_code, 201)
        self.assertIn(b'testuser', response.data)

    def test_login(self):
        self.client.post('/auth/register', json={
            'username': 'testuser',
            'password': 'password'
        })
        response = self.client.post('/auth/login', json={
            'username': 'testuser',
            'password': 'password'
        })
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'Login successful', response.data)

if __name__ == '__main__':
    unittest.main()

環境設定ファイルと依存関係

.env

環境変数を定義します。例えば、秘密鍵やデータベースURLを設定します。

SECRET_KEY=your_secret_key
DATABASE_URL=sqlite:///app.db

requirements.txt

必要なパッケージをリストします。

Flask
Flask-SQLAlchemy
python-dotenv

まとめ

このディレクトリ構成とモジュール分割に従うことで、エンドポイントは簡潔に保たれ、ビジネスロジックやデータベースモデルは独立して管理されるため、プロジェクトの拡張や変更が容易になります。

Discussion