0
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?

More than 1 year has passed since last update.

mongoengineでmongodbにデータを作成する

Last updated at Posted at 2022-10-13

初めに

普段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に作成する機能を実装するために、テストを作成します。

app/tests/crud/test_user.py
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のドキュメントモデルを作成する

ユーザードキュメントが持つことができるフィールドと、格納するデータの種類を定義します。

app/models/user.py
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を使用して、ユーザー作成時の入力値の型を設定します。

app/schemas/user.py
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に登録する際に自動生成します。

app/crud/crud_user.py
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()
app/crud/__init__.py
from .crud_user import user  # noqa

DBに接続する際に必要な値を設定ファイルに記載する

DBに接続する際に必要な値を設定ファイルに記載します。

app/core/config.py
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に記載します。

app/tests/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ファイルから読み込まれます。

docker-compose.yaml
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スクリプトを作成します。

container_setup.sh
#!/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

テスト実行に必要なパッケージを記載します。

requirements.txt
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_dbmy_collectionを確認するとデータが登録されていることがわかります。

パスワードのハッシュ化

パスワードのハッシュ化を実装するためにテストを変更します。

app/tests/crud/test_user.py
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 

ハッシュ化する機能を実装するために、文字列のハッシュ化を行う関数を作成します。

app/core/security.py
from passlib.context import CryptContext


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

パスワードをハッシュ化する機能を、ユーザー情報作成機能に実装します。

diff app/crud/crud_user.py
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()

動作に必要なパッケージを追加し、インストールします。

requirements.txt
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
0
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
0
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?