- 初めに
- 概要
- この記事のゴール
- 使用するツール
- 実行環境
- ディレクトリ構成
- テストの作成
- MongoDBのドキュメントモデルを作成する
- ユーザー作成時の入力値の型を設定する
- ユーザー情報をDBに作成する機能を実装する
- DBに接続する際に必要な値を設定ファイルに記載する
- テストに必要な処理を作成する
- 動作確認用のコンテナを作成する
- テストの実行
- パスワードのハッシュ化
- 備考
初めに
普段APIの開発や運用をしていますが、APIがWebアプリでどのように利用されているかを全くイメージできませんでした。
そのため、APIをいくつか作成し、簡単なWebページをhtmlで実装して連携させてみたいと思います。
概要
下記のようなwebページを作りたいです。
- ユーザー登録ページ
- ログインページ
- マイページ
そのために、数回に分けて下記のAPIと、それらを使用したwebページを作成していきます。
- ユーザーAPI
- 作成機能 <-今ここ
- 情報取得機能
- 情報削除機能
- cookieAPI
- 作成機能
- 現在のユーザーAPI
- 取得機能
今回はユーザー作成APIのための、ユーザー情報をDBに作成する機能を作成していきます。
この記事のゴール
ユーザー情報をDBに作成する機能を実装したいです。
ユーザー情報をMongoDBで管理することを考えています。
DBの操作はmongoengineを使用します。
ユーザーのパスワードはハッシュ化したいです。
完了条件は下記の通りです。
- ユーザー情報をDBに作成できる
- DBに作成されるユーザードキュメントにおけるパスワードはハッシュ化されている
使用するツール
pipenv で python 3.10の仮想環境を作成します。
pipenv --python 3.10
実行環境
OS : macOS Monterey 12.6
使用言語 : python 3.10
ディレクトリ構成
.
├── Pipfile
├── Pipfile.lock
├── app
│ ├── __init__.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── security.py
│ ├── crud
│ │ ├── __init__.py
│ │ └── crud_user.py
│ ├── models
│ │ ├── __init__.py
│ │ └── user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ └── user.py
│ └── tests
│ ├── __init__.py
│ ├── conftest.py
│ └── crud
│ ├── __init__.py
│ └── test_user.py
├── container_setup.sh
├── docker-compose.yaml
└── requirements.txt
テストの作成
ユーザー情報をDBに作成する機能を実装するために、テストを作成します。
from app import crud
from app.schemas.user import UserCreate
def test_create_user(random_email, random_lower_string) -> None:
email = random_email() # ランダムなemailアドレスを作成する関数
password = random_lower_string() # ランダムな文字列を作成する関数
user_in = UserCreate(email=email, password=password)
user = crud.user.create(obj_in=user_in)
assert user.email == email
assert hasattr(user, "hashed_password")
MongoDBのドキュメントモデルを作成する
ユーザードキュメントが持つことができるフィールドと、格納するデータの種類を定義します。
from mongoengine import Document
from mongoengine.fields import StringField, BooleanField, EmailField
class User(Document):
uuid = StringField(unique=True, required=True)
email = EmailField(unique=True, required=True)
hashed_password = StringField(required=True)
is_active = BooleanField(required=True)
is_superuser = BooleanField(required=True)
meta = {
'db_alias': 'mongodb', # ドキュメントのエイリアスを定義
'collection': 'my_collection', # ドキュメントを作成するコレクションを定義
}
ユーザー作成時の入力値の型を設定する
BaseModelを使用して、ユーザー作成時の入力値の型を設定します。
from typing import Optional
from pydantic import BaseModel, EmailStr
# ユーザーを操作する際の共通の項目と型
class UserBase(BaseModel):
email: Optional[EmailStr] = None
is_active: Optional[bool] = True
is_superuser: bool = False
# ユーザーを作成する際の入力項目の型
class UserCreate(UserBase):
email: EmailStr
password: str
ユーザー情報をDBに作成する機能を実装する
uuidは情報をDBに登録する際に自動生成します。
from uuid import uuid4
from app.models.user import User
from app.schemas.user import UserCreate
class CRUDUser:
def create(self, obj_in: UserCreate) -> User:
db_obj = User(
uuid=uuid4().hex,
email=obj_in.email,
hashed_password=obj_in.password,
is_active=obj_in.is_active,
is_superuser=obj_in.is_superuser,
)
db_obj.save()
return db_obj
user = CRUDUser()
from .crud_user import user # noqa
DBに接続する際に必要な値を設定ファイルに記載する
DBに接続する際に必要な値を設定ファイルに記載します。
from pydantic import BaseSettings
class Settings(BaseSettings):
# datastore settings
DB_USER: str
DB_PASSWORD: str
DB_HOST: str
DB_PORT: int
DB_NAME: str = 'my_db'
settings = Settings()
テストに必要な処理を作成する
テストに必要な処理をconftest.py
に記載します。
import random
import string
import pytest
from mongoengine import connect, disconnect
from app.core.config import settings
# ランダムな文字列を生成する関数
def _random_lower_string():
return "".join(random.choices(string.ascii_lowercase, k=32))
@pytest.fixture(scope='function')
def random_lower_string():
return _random_lower_string()
@pytest.fixture(scope='function')
def random_email():
return f"{_random_lower_string()}@{_random_lower_string()}.com"
# dbへの接続と切断を行うpytest fixture
@pytest.fixture(scope='session', autouse=True)
def init_db():
connect(
host=settings.DB_HOST,
port=settings.DB_PORT,
username=settings.DB_USER,
password=settings.DB_PASSWORD,
db=settings.DB_NAME,
uuidRepresentation="standard",
alias='mongodb'
)
yield
disconnect(alias='mongodb')
動作確認用のコンテナを作成する
作成した機能の動作確認をするために、MongoDBとそのデータを見るためのツールのコンテナを作成します。
${}
の値は.env
ファイルから読み込まれます。
version: "3.8"
services:
mongo:
image: mongo:5.0
restart: always
ports:
- "${DB_PORT}:${DB_PORT}"
environment:
MONGO_INITDB_ROOT_USERNAME: ${DB_USER}
MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mongo-database:/database
mongo_express:
image: mongo-express:1.0.0-alpha.4
restart: always
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: ${DB_USER}
ME_CONFIG_MONGODB_ADMINPASSWORD: ${DB_PASSWORD}
ME_CONFIG_MONGODB_SERVER: mongo
depends_on:
- mongo # mongoコンテナが起動してから起動させる
volumes:
mongo-database:
コンテナの作成と.env
ファイルの作成を行うshellスクリプトを作成します。
#!/bin/sh
echo DB_USER=root >.env
echo DB_PASSWORD=password >>.env
echo DB_HOST=localhost >>.env
echo DB_PORT=27017 >>.env
docker-compose up -d
schellスクリプトを実行します。
sh container_setup.sh
テストの実行
仮想環境を作成します。
pipenv --python 3.10
テスト実行に必要なパッケージを記載します。
mongoengine
pydantic[email]
typing
# test package
pytest
テスト実行に必要なパッケージをインストールします。
pipenv install -r requirements.txt
作成したテストを実行します。
% pipenv run pytest
Loading .env environment variables...
~~~
app/tests/crud/test_user.py .
テストが成功しました。
dbに作成されたデータを確認してみます。
ブラウザでhttp://localhost:8081にアクセスすると
mongo-express(MongoDBのデータを表示するツールに)のUIが表示されます。
このmy_db
のmy_collection
を確認するとデータが登録されていることがわかります。
パスワードのハッシュ化
パスワードのハッシュ化を実装するためにテストを変更します。
from app import crud
from app.schemas.user import UserCreate
def test_create_user(random_email, random_lower_string) -> None:
email = random_email # ランダムなemailアドレスを作成する関数
password = random_lower_string # ランダムな文字列を作成する関数
user_in = UserCreate(email=email, password=password)
user = crud.user.create(obj_in=user_in)
assert user.email == email
assert hasattr(user, "hashed_password")
+ assert user_in.password != user.hashed_password
入力したパスワードが平文でDBに保存されていないことをテストします。
もちろんテストは失敗します。
% pipenv run pytest
Loading .env environment variables...
~~~
app/tests/crud/test_user.py F
ハッシュ化する機能を実装するために、文字列のハッシュ化を行う関数を作成します。
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
パスワードをハッシュ化する機能を、ユーザー情報作成機能に実装します。
from uuid import uuid4
from app.models.user import User
from app.schemas.user import UserCreate
+from app.core.security import get_password_hash
class CRUDUser:
def create(self, obj_in: UserCreate) -> User:
db_obj = User(
uuid=uuid4().hex,
email=obj_in.email,
+ hashed_password=get_password_hash(obj_in.password),
is_active=obj_in.is_active,
is_superuser=obj_in.is_superuser,
)
db_obj.save()
return db_obj
user = CRUDUser()
動作に必要なパッケージを追加し、インストールします。
mongoengine
pydantic[email]
typing
+passlib[bcrypt]
# test package
pytest
pipenv install -r requirements.txt
pipenv run pytest
テストが成功したので実装は完了です。
備考
作成した仮想環境やコンテナ、コンテナイメージは下記のコマンドで削除できます。
pipenv --rm
docker-compose down --rmi all --volumes --remove-orphans