小獅:先裝套件對吧!
老獅:不,老樣子,先寫好測試
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 # 新增,尚未提交