iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
Software Development

FastAPI 開發系列 第 11

使用者驗證 - 測試儲存使用者資訊 - Makefile

  • 分享至 

  • xImage
  •  

小獅:先裝套件對吧!

老獅:不,老樣子,先寫好測試

0. 創建使用者在資料庫,確認該使用者存在於資料庫
1. 使用者 A 用他的帳號密碼登入以後可以拿到 JWT token 並且解析出該使用者的 username
2. 使用者 A 用錯密碼無法換取可以登入的 JWT token
3. 使用者 A 可以使用 refresh token 換到一樣可以換回一個新的 token 並可以拿回該使用者的 username

老獅:我們可以先試著測試建立帳號,順便熟悉如何使用資料庫,我們會希望存入資料庫以後,可以撈出一樣的使用者

# src/tests/test_units/test_users_crud.py
import pytest
from fastapi import encoders
from sqlalchemy import future as sqlalchemy_future
from sqlalchemy.ext import asyncio as sqlalchemy_asyncio

from app.models import auth as auth_models


@pytest.mark.asyncio
async def test_create_and_read_user(
    db: sqlalchemy_asyncio.AsyncSession,
):
    username = "username"
    password = "password"
    obj_in = {
        "username": username,
        "password": password,
    }
    obj_in_data = encoders.jsonable_encoder(obj_in)
    obj = auth_models.User(**obj_in_data)
    db.add(obj)
    db.add(obj)
    user = await db.commit()
    user = (
        (
            await db.execute(
                sqlalchemy_future.select(auth_models.User).where(
                    auth_models.User.id == id
                )
            )
        )
        .scalars()
        .first()
    )
    assert user.username == "username"
    assert user.password == "password"
export PYTHONPATH=$PWD/src
pytest src
___________ ERROR collecting src/tests/test_units/test_users_crud.py ___________
ImportError while importing test module '/Users/super/project/fastit/src/tests/test_units/test_users_crud.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.pyenv/versions/3.8.13/lib/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
src/tests/test_units/test_users_crud.py:3: in <module>
    from sqlalchemy.ext import asyncio as sqlalchemy_asyncio
E   ModuleNotFoundError: No module named 'sqlalchemy'
=========================== short test summary info ============================
ERROR src/tests/test_units/test_users_crud.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 0.44s ===============================

小獅:就說缺套件吧!

# requirements/base.in
fastapi==0.101.1
uvicorn[standard]==0.23.2
python-jose[cryptography]==3.3.0
pydantic-settings==2.0.3
sqlalchemy[asyncio]==2.0.20

小獅:話說你跑套件安裝的指令好長喔 @@

老獅:你終於發現了喔?你可以用 Makefile + make 指令簡化他啊!

小獅:恩?(OS: 你是不會早說喔)

# Makefile
pip-tools:  ## Install pip-tools
        pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
.PHONY: pip-tools

pip-build: pip-tools  ## Recompile all pip packages
        ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
.PHONY: pip-build

pip-install: pip-tools  ## Install all pip packages
        pip-sync `ls requirements/*.txt`
.PHONY: pip-install

pip: pip-build pip-install  ## Recompile and install all pip packages
.PHONY: pip

老獅: 注意,以下檔案結構

指令名稱: 依賴的指令
<tab>指令本人
.PHONY: 指令名稱

小獅:意思是說,pip 會先跑 pip-build 再去跑 pip-install 嗎?

老獅:對的,你下看看 make pip

make pip

pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
Name: pip-tools
Version: 7.3.0
Summary: pip-tools keeps your pinned dependencies fresh.
Home-page:
Author:
Author-email: Vincent Driessen <me@nvie.com>
License: BSD
Location: /Users/super/project/fastit/venv/lib/python3.8/site-packages
Requires: build, click, pip, setuptools, tomli, wheel
Required-by:
pip-tools is installed
ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
#
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
#    pip-compile --strip-extras requirements/base.in
#
...省略
pip-sync `ls requirements/*.txt`
Collecting greenlet==2.0.2 (from -r /var/folders/8h/sl5tf4310dgffkts7_rf4dn40000gn/T/tmpyxw_ird2 (line 1))
  Using cached greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl (241 kB)
Collecting sqlalchemy==2.0.20 (from -r /var/folders/8h/sl5tf4310dgffkts7_rf4dn40000gn/T/tmpyxw_ird2 (line 2))
  Obtaining dependency information for sqlalchemy==2.0.20 from https://files.pythonhosted.org/packages/d0/cd/2c23739c701a299b22fbb2403aa79a43a40def4064049975e3e5beac40ff/SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl.metadata
  Using cached SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl.metadata (9.4 kB)
Requirement already satisfied: typing-extensions>=4.2.0 in ./venv/lib/python3.8/site-packages (from sqlalchemy==2.0.20->-r /var/folders/8h/sl5tf4310dgffkts7_rf4dn40000gn/T/tmpyxw_ird2 (line 2)) (4.7.1)
Using cached SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl (2.1 MB)
Installing collected packages: greenlet, sqlalchemy
Successfully installed greenlet-2.0.2 sqlalchemy-2.0.20

小獅:舒服多了

老獅:同理,測試指令也可以把他們包起來

Makefile
.EXPORT_ALL_VARIABLES:
PYTHONPATH = ${PWD}/src

test:  ## Run test only
        pytest src
.PHONY: test

pip-tools:  ## Install pip-tools
        pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
.PHONY: pip-tools

pip-build: pip-tools  ## Recompile all pip packages
        ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
.PHONY: pip-build

pip-install: pip-tools  ## Install all pip packages
        pip-sync `ls requirements/*.txt`
.PHONY: pip-install

pip: pip-build pip-install  ## Recompile and install all pip packages
.PHONY: pip
make test
pytest .
============================= test session starts ==============================
platform darwin -- Python 3.8.13, pytest-7.4.0, pluggy-1.3.0
rootdir: /Users/super/project/impl-fastit
plugins: anyio-4.0.0, asyncio-0.21.1
asyncio: mode=strict
collected 2 items / 1 error

==================================== ERRORS ====================================
___________ ERROR collecting src/tests/test_units/test_users_crud.py ___________
ImportError while importing test module '/Users/super/project/impl-fastit/src/tests/test_units/test_users_crud.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.pyenv/versions/3.8.13/lib/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
src/tests/test_units/test_users_crud.py:6: in <module>
    from app.models import auth as auth_models
E   ImportError: cannot import name 'auth' from 'app.models' (unknown location)
=========================== short test summary info ============================
ERROR src/tests/test_units/test_users_crud.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 1.64s ===============================
make: *** [test] Error 2

小獅:喔喔喔,由於已經在 Makefile 裡面設定好 PYTHONPATH 了,就可以不用自己處理了

老獅:對的,之前很長的 lint 我們也可以把他們納入進來

.EXPORT_ALL_VARIABLES:
PYTHONPATH = ${PWD}/src

lint:  ## Run linting
	python -m black --check .
	python -m isort -c .
	python -m flake8 .
.PHONY: lint

lint-fix:  ## Run autoformatters
	python -m black .
	python -m isort .
.PHONY: lint-fix

pip-tools:  ## Install pip-tools
	pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
.PHONY: pip-tools

pip-build: pip-tools  ## Recompile all pip packages
	ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
.PHONY: pip-build

pip-install: pip-tools  ## Install all pip packages
	pip-sync `ls requirements/*.txt`
.PHONY: pip-install

pip: pip-build pip-install  ## Recompile and install all pip packages
.PHONY: pip

test:  ## Run test only
	pytest .
.PHONY: test

老獅:由於指令太多我們也可以利用註解與解析的方式把他們變成 make help 來告知開發者,這邊有哪些東西能用

# Makefile
.EXPORT_ALL_VARIABLES:
PYTHONPATH = ${PWD}/src

lint:  ## Run linting
	python -m black --check .
	python -m isort -c .
	python -m flake8 .
.PHONY: lint

lint-fix:  ## Run autoformatters
	python -m black .
	python -m isort .
.PHONY: lint-fix

pip-tools:  ## Install pip-tools
	pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
.PHONY: pip-tools

pip-build: pip-tools  ## Recompile all pip packages
	ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
.PHONY: pip-build

pip-install: pip-tools  ## Install all pip packages
	pip-sync `ls requirements/*.txt`
.PHONY: pip-install

pip: pip-build pip-install  ## Recompile and install all pip packages
.PHONY: pip

test:  ## Run test only
	pytest .
.PHONY: test

help: Makefile  ## Show Makefile help
	@echo "Below shows Makefile targets"
	@grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'
make help
Below shows Makefile targets
lint                           Run linting
lint-fix                       Run autoformatters
pip-tools                      Install pip-tools
pip-build                      Recompile all pip packages
pip-install                    Install all pip packages
pip                            Recompile and install all pip packages
test                           Run test only
help                           Show Makefile help

老獅:甚至可以把它改成 make 的預設指令

# Makefile
.EXPORT_ALL_VARIABLES:
PYTHONPATH = ${PWD}/src

lint:  ## Run linting
	python -m black --check .
	python -m isort -c .
	python -m flake8 .
.PHONY: lint

lint-fix:  ## Run autoformatters
	python -m black .
	python -m isort .
.PHONY: lint-fix

pip-tools:  ## Install pip-tools
	pip3 show pip-tools && echo "pip-tools is installed" || (pip3 install -U pip && pip3 install -U pip-tools)
.PHONY: pip-tools

pip-build: pip-tools  ## Recompile all pip packages
	ls requirements/*.in | xargs -n1 pip-compile --resolver=backtracking --strip-extras
.PHONY: pip-build

pip-install: pip-tools  ## Install all pip packages
	pip-sync `ls requirements/*.txt`
.PHONY: pip-install

pip: pip-build pip-install  ## Recompile and install all pip packages
.PHONY: pip

test:  ## Run test only
	pytest .
.PHONY: test

.DEFAULT_GOAL := help
help: Makefile  ## Show Makefile help
	@echo "Below shows Makefile targets"
	@grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'
make
Below shows Makefile targets
lint                           Run linting
lint-fix                       Run autoformatters
pip-tools                      Install pip-tools
pip-build                      Recompile all pip packages
pip-install                    Install all pip packages
pip                            Recompile and install all pip packages
test                           Run test only
help                           Show Makefile help

小獅:是因為這行對吧

.DEFAULT_GOAL := help

老獅:對的,先把 Makefile 提交,我們再繼續吧

git add Makefile

git commit -m "chore: add Makefile"

本次目錄

.
├── Makefile    # 新增
├── pyproject.toml
├── requirements
│   ├── base.in     # 修改,尚未提交
│   ├── base.txt    # 修改,尚未提交
│   ├── development.in
│   └── development.txt
├── requirements.txt
├── setup.cfg
└── src
    ├── app
    │   ├── api
    │   │   └── v1
    │   │       ├── endpoints
    │   │       │   └── auth
    │   │       │       └── users
    │   │       │           └── tokens.py
    │   │       └── routers.py
    │   ├── crud
    │   ├── db
    │   ├── main.py
    │   ├── migrations
    │   ├── models
    │   └── schemas
    │       └── health_check.py
    ├── core
    │   └── config.py
    ├── scripts
    └── tests
        ├── test_main.py
        ├── test_services
        │   └── test_token.py
        └── test_units
            └── test_users_crud.py    # 新增,尚未提交

上一篇
使用者驗證 - 登入資訊
下一篇
使用者驗證 - 測試儲存使用者資訊 - 除錯
系列文
FastAPI 開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言