Skip to content

Commit

Permalink
feat: application 초기 작업 (health check, function test)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mincheol Kim committed Jul 5, 2020
1 parent d437063 commit 68d158b
Show file tree
Hide file tree
Showing 22 changed files with 2,425 additions and 0 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ source = src
omit =
.venv/*
tests/*
src/asgi.py

[pytest]
junit_family=xunit1
Expand Down
7 changes: 7 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# pipenv .venv path
PIPENV_VENV_IN_PROJECT=true

APP_NAME=landlords-server

HOST=0.0.0.0
PORT=5001
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: FastAPI",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"src.asgi:app",
"--host",
"0.0.0.0",
"--port",
"5000"
],
"console": "integratedTerminal",
"subProcess": true,
"envFile": "${workspaceFolder}/.env"
}
]
}
44 changes: 44 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/.mypy_cache": true,
"**/.benchmarks": true,
"**/.venv": true,
"**/node_modules": true,
"**/public": true,
"**/.pytest_cache": true,
"**/__pycache__": true
},
"editor.detectIndentation": false,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.formatOnSave": true,
"python.envFile": "${workspaceFolder}/.env",
"python.testing.pytestEnabled": true,
"python.linting.enabled": true,
"python.linting.lintOnSave": true,
"python.linting.mypyEnabled": true,
"python.linting.pycodestyleArgs": [
"--max-line-length=80"
],
"python.linting.pycodestyleEnabled": true,
"python.linting.pylintEnabled": false,
"python.linting.pylintArgs": [
"--errors-only", // mypy, pycodestyle
"--disable=no-member", // mypy
"--enable=unused-import" // 이것 때문에 씀..
],
"python.formatting.provider": "black",
"python.formatting.blackArgs": [
"-l",
"80"
],
"explorer.sortOrder": "modified",
"editor.formatOnSaveTimeout": 3000,
"editor.codeActionsOnSaveTimeout": 3000,
"editor.fontFamily": "D2Coding"
}
104 changes: 104 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
include .env
GREEN=\n\033[1;32;40m
RED=\n\033[1;31;40m
NC=\033[0m # No Color


PYCODESTYLE = pycodestyle
MYPY = mypy
# no-member: mypy
PYLINTFLAGS = --verbose --reports=no --output-format=colorized --errors-only --disable=no-member --enable=unused-import

PYTHONFILES := $(shell find . -name '*.py' | grep -v .venv)
PYTHON_VERSION = py38
PYTHON_LINE_LENGTH = 80
targets:
@echo $(PYTHONFILES)
.PHONY: targets

PIP := $(shell command -v pip 2> /dev/null)
PIPENV := $(shell command -v pipenv 2> /dev/null)

ref:
ifndef PIP
# https://pip.pypa.io/en/stable/installing/
$(error "pip이 설치되어 있지 않습니다.")
endif
@/bin/sh -c "echo \"${GREEN}pip 설치되어 있음${NC}\""

ifndef PIPENV
pip install pipenv
endif
@/bin/sh -c "echo \"${GREEN}pipenv 설치되어 있음${NC}\""

.PHONY: ref

# 의존성 모듈 관리
venv_dir=.venv
venv-dev:
ifneq "$(wildcard $(venv_dir) )" ""
@/bin/sh -c "echo \"${GREEN}Already installation${NC}\""
else
@/bin/sh -c "echo \"${GREEN}pipenv install${NC}\""
export PIPENV_VENV_IN_PROJECT=${PWD} && pipenv install --dev
pipenv graph
endif
.PHONY: venv-dev

pycodestyle: ref venv-dev
@/bin/sh -c "echo \"${GREEN}[pycodestyle 시작]${NC}\""
pipenv run $(PYCODESTYLE) --first $(PYTHONFILES)
.PHONY: pycodestyle

# vscode의 formatting 도구로 black을 사용
black: ref venv-dev
@/bin/sh -c "echo \"${GREEN}[black 시작]${NC}\""
pipenv run black -t $(PYTHON_VERSION) -l $(PYTHON_LINE_LENGTH) $(PYTHONFILES)
.PHONY: black

pylint: ref venv-dev
@/bin/sh -c "echo \"${GREEN}[pylint 시작]${NC}\""
pipenv run pylint $(PYLINTFLAGS) $(PYTHONFILES)
.PHONY: fast-pylint

mypy: ref venv-dev
@/bin/sh -c "echo \"${GREEN}[정적분석 시작]${NC}\""
pipenv run $(MYPY) --config-file mypy.ini $(PYTHONFILES)
.PHONY: mypy

lint: pycodestyle mypy pylint
.PHONY: lint

test: ref venv-dev
pipenv run pytest \
--pdb \
--cov=src tests \
--cov-report=html \
--cov-report=term \
--cov-report=xml \
--disable-warnings
.PHONY: test-coverage

docker: requirements
@/bin/sh -c "echo \"${GREEN}[docker image 빌드를 시작합니다]${NC}\""
@set -ex && docker build --rm -t ${APP_NAME}:latest "."
.PHONY: docker

requirements: ref venv-dev
@/bin/sh -c "echo \"${GREEN}[requirements.txt를 추출합니다]${NC}\""
@pipenv lock -r > requirements.txt
.PHONY: requirements

# yaml에서 colon 버그 존재 이외에도 특문 버그도 존재하므로 script로 처리
# https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30097
__ci_commit:
@git add app/__init__.py
@git commit -m "chore(version): changed version ${AGENT_VERSION}"
.PHONY: __ci_commit

# 마지막 tag로부터 현재까지의 changelog 및 버전 확인 용
current_changelog:
@/bin/sh -c "echo \"${GREEN}[release version] $(shell yarn run standard-version --dry-run | grep tagging | cut -d ' ' -f4)${NC}\""
@/bin/sh -c "echo \"${GREEN}[description] ${NC}\""
@yarn run standard-version --dry-run --silent | grep -v yarn | grep -v Done | grep -v "\-\-\-" | grep -v standard-version
.PHONY: current_changelog
38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "todo-list-fastapi",
"version": "1.8.5",
"description": "practice project",
"main": "src/main.py",
"license": "MIT",
"scripts": {
"start": "pipenv run uvicorn src.main:app --host 0.0.0.0 --port 5000",
"build": "make docker",
"lint": "make lint",
"test": "make test",
"release": "standard-version",
"version": "echo v$npm_package_version",
"next-version": "node ./get-next-version.js"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"husky": "^4.2.5",
"standard-version": "^7.1.0"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"husky": {
"skipCI": false,
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "npm run lint && npm run test && npm run build",
"pre-push": "npm run lint"
}
},
"dependencies": {
"yarn": "^1.22.4"
}
}
1 change: 1 addition & 0 deletions src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 소스코드 패키지
Empty file added src/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions src/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
"""
from .core.config import settings
from .main import create_app
from .routes import api_v1

app = create_app()
app.include_router(api_v1, prefix=f"{settings.API_VERSION_PREFIX}")
Empty file added src/core/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions src/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pylint: disable=no-self-argument
from pydantic import BaseSettings


class Settings(BaseSettings):
"""
application 설정 (환경변수 최우선)
"""

API_VERSION_PREFIX: str = "/api/v1"

class Config:
""" setting의 부가 설정 """

case_sensitive = True


settings = Settings()
37 changes: 37 additions & 0 deletions src/core/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
에러
"""
from typing import Union

from fastapi import status
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.openapi.constants import REF_PREFIX
from fastapi.openapi.utils import validation_error_response_definition
from pydantic import ValidationError
from starlette.requests import Request
from starlette.responses import JSONResponse


async def http_exception_handler(
_: Request, exc: HTTPException
) -> JSONResponse:
""" http exception handling """
return JSONResponse({"errors": [exc.detail]}, status_code=exc.status_code)


async def validation_exception_handler(
_: Request, exc: Union[RequestValidationError, ValidationError]
) -> JSONResponse:
""" client request exception handling """
return JSONResponse(
{"errors": exc.errors()}, status_code=status.HTTP_400_BAD_REQUEST
)


validation_error_response_definition["properties"] = {
"errors": {
"title": "Errors",
"type": "array",
"items": {"$ref": "{0}ValidationError".format(REF_PREFIX)},
}
}
19 changes: 19 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
main.py
"""
from fastapi.applications import FastAPI
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException
from .core.handlers import http_exception_handler, validation_exception_handler


def create_app() -> FastAPI:
""" app factory method """
app = FastAPI()

app.add_exception_handler(
RequestValidationError, handler=validation_exception_handler
)
app.add_exception_handler(HTTPException, handler=http_exception_handler)

return app
10 changes: 10 additions & 0 deletions src/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
routes
"""
from fastapi.routing import APIRouter

from .health import health as health_router

api_v1 = APIRouter()

api_v1.include_router(health_router, prefix="/health", tags=["manage"])
13 changes: 13 additions & 0 deletions src/routes/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
router
"""
from typing import Dict
from fastapi.routing import APIRouter

health = APIRouter()


@health.get("")
async def health_check() -> Dict[str, str]:
""" health check """
return {"status": "up"}
1 change: 1 addition & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 테스트 패키지
Empty file added tests/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest
from starlette.testclient import TestClient
from fastapi.applications import FastAPI

from src.main import create_app
from src.routes import api_v1
from src.core.config import settings


@pytest.fixture
def app() -> FastAPI:
""" test app """
app = create_app()
app.include_router(api_v1, prefix=f"{settings.API_VERSION_PREFIX}")
return app


@pytest.fixture
def client(app: FastAPI) -> TestClient:
""" test client """
return TestClient(app)
1 change: 1 addition & 0 deletions tests/function/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 기능 테스트
Empty file added tests/function/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions tests/function/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
routes/health.py 테스트
"""
from http import HTTPStatus
from starlette.testclient import TestClient
from src.core.config import settings


def test_health(client: TestClient) -> None:
""" test health """
response = client.get(f"{settings.API_VERSION_PREFIX}/health")
assert response.status_code == HTTPStatus.OK
Loading

0 comments on commit 68d158b

Please sign in to comment.