4. pynest homepage https://pythonnest.github.io/PyNest
최종 업데이트 : 2025-01-18
기존에 하나의 소스로 설명했다면 지금부터는 로직의 흐름으로 설명하고 진행하겠습니다.
fastapi의 개념을 어느정도 이해하신후에 보시면 더 좋습니다.
if __name__ == '__main__':
import uvicorn
uvicorn.run(
'src.app_module:http_server',
host="localhost",
port=8000,
reload=True
)
mian.py파일에서 uvicorn을 import하고 localhost로 8000포트로 지정해서 실행해줍니다.
reload는 소스변경시 다시 로딩해주는 기능이나 가볍기때문에 재시작해주셔도 크게 상관없습니다.
src폴더안의 app_module에서 정의하는 http_server를 구동시킵니다.
src/
├── __init__.py
├── app_module.py
├── app_controller.py
├── app_service.py
최초 pynest 프로젝트를 생성하면 src폴더안에 세개의 파일이 생깁니다.
모듈을 정의할때 사용할 컨트롤러와 서비스를 정의하고
엔드포인트를 controller에 정의하여
사용될 서비스를 service에 구현하여 모듈로서 가동하게됩니다.
__init__.py는 해당 폴더를 패키지임을 인식시키기 위해 추가됩니다.
from nest.core import PyNestFactory, Module
from .app_controller import AppController
from .app_service import AppService
@Module(imports=[], controllers=[AppController], providers=[AppService])
class AppModule:
pass
app = PyNestFactory.create(
AppModule,
description="This is my PyNest app.",
title="PyNest Application",
version="1.0.0",
debug=True,
)
http_server = app.get_server()
src/app_module.py파일부터 살펴보겠습니다.
PyNestFactory을 이용하여 pynest를 구성하고
Module을 통해서 사용될 컨트롤러와 서비스를 추가합니다.
get_server를 통해 fastapi서버를 가져옵니다.
즉, fastapi를 그대로 사용하되 dependency injection(DI)이 추가되었다 보시면 좋습니다.
-src/app_controller.py
from nest.core import Controller, Get, Post
from .app_service import AppService
@Controller("/")
class AppController:
def __init__(self, service: AppService):
self.service = service
@Get("/")
def get_app_info(self):
return self.service.get_app_info()
-src/app_service.py
from nest.core import Injectable
@Injectable
class AppService:
def __init__(self):
self.app_name = "Pynest App"
self.app_version = "1.0.0"
def get_app_info(self):
return {"app_name": self.app_name, "app_version": self.app_version}
app_controller.py에 service를 import하여
/ 경로의 get endpoint를 정의하였습니다.
service의 get_app_info를 이용할수있습니다.
실행하여 확인해봅니다.
fastapi기반이기에 docs와 redoc의 스웨거가 지원됩니다.
이제 회원가입을 구성해봅시다.
pynest generate resource --name siginup
signup/
├──__init__.py
├──signup_controller.py
├──signup_model.py
├──signup_module.py
└──signup_service.py
app_module.py
from .signup.signup_module import SignupModule
imports=[SignupModule],
signup을 추가해서 확인합니다.
app_module.py파일에 자동으로 추가된것을 확인합니다.
from nest.core import Module
from .signup_controller import SignupController
from .signup_service import SignupService
@Module(
controllers=[SignupController],
providers=[SignupService],
imports=[]
)
class SignupModule:
pass
모듈구조는 app_module구조와 같습니다.
signuo_model.py
from pydantic import BaseModel
class Signup(BaseModel):
id: str
password: str
우선 모델을 먼저 설정해주시고
signup_controller.py
@Post("/signup")
def signup_request(self, signup: Signup):
return {"id": signup.id, "password": signup.password}
Post로 signup endpoint를 만들어서 스웨거로 테스트해봅니다.
fastapi와 동일하게 http://localhost:8000/docs, http://localhost:8000/redoc을 지원합니다.
signup_service.py
def add_signup(self, signup: Signup):
id = signup.id
password = signup.password
return {"id": id, "password": password}
signup_controller.py
def signup_request(self, signup: Signup):
return self.signup_service.add_signup(signup)
서비스에 코드를 짜고 이를 컨트롤러에서 제대로 호출하여 리턴하는지 확인합니다.
signup_service.py
from hashlib import sha256
from typing import Dict
from http import HTTPStatus
users_db: Dict[str, str] = {}
class CustomExceptionResponse(Exception):
def __init__(self, status_code: int, message: str):
self.status_code = status_code
self.message = message
super().__init__(message)
패스워드 암호화, 딕셔너리, HTTPStatus를 위한 패키지를 선언합니다.
임시로 사용할 users_db 딕셔너리를 선언하고
예외처리를 위한 CustomExceptionResponse 를 선언합니다.
CustomExceptionResponse의 경우 별도로 만드는게 좋으나 교육용임으로 임시로 진행합니다.
signup_service.py
def add_signup(self, signup: Signup):
id = signup.id
password = signup.password
if id in users_db:
raise CustomExceptionResponse(
status_code=HTTPStatus.CONFLICT.value,
message="이미 사용 중인 ID입니다."
)
hashed_password = sha256(password.encode()).hexdigest()
users_db[id] = hashed_password
return "회원가입 성공"
signup_controller.py
from .signup_service import SignupService, CustomExceptionResponse
@Post("/signup")
def signup_request(self, signup: Signup):
try:
result = self.signup_service.add_signup(signup)
return {
"status_code": HTTPStatus.CREATED.value,
"content": result
}
except CustomExceptionResponse as e:
return {
"status_code": e.status_code,
"content": e.message
}
서비스 로직과 컨트롤러 로직을 예제를 따라 참고하여 작성한 후
스웨거를 통해 테스트해봅니다.
app_module.py
from nest.core import Module, PyNestFactory
from .app_controller import AppController
from .app_service import AppService
from .signup.signup_module import SignupModule
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request
import logging
# 로깅 설정
logging_mode = "Y"
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# FastAPI 미들웨어 정의
async def log_requests_middleware(request: Request, call_next):
if logging_mode == "Y":
logger.info(f"Request: {request.method} {request.url}")
response = await call_next(request)
if logging_mode == "Y":
logger.info(f"Response: {response.status_code}")
return response
@Module(
imports=[SignupModule],
controllers=[AppController],
providers=[AppService],
)
class AppModule():
pass
app = PyNestFactory.create(
AppModule,
description="This is my PyNest app",
title="My App",
version="1.0.0",
debug=True,
)
http_server: FastAPI = app.get_server()
http_server.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@http_server.middleware("http")
async def log_request(request: Request, call_next):
if logging_mode == "Y":
logger.info(f"Request: {request.method} {request.url}")
response = await call_next(request)
if logging_mode == "Y":
logger.info(f"Response: {response.status_code}")
return response
@http_server.options("/{rest_of_path:path}")
async def preflight_handler():
return {"message": "Preflight check passed"}
미들웨어 설정을 해줍니다.
앞선 fastapi를 보셨으면 이해하시는데 큰 문제는 없으실겁니다.
http_server: FastAPI = app.get_server()
구동자체를 FastAPI를 이용합니다
다음 내용부터는 fastapi에 pynest-api를 이용한 예제들로 구성됩니다.
nestjs와 javaspringboot의 구조화등을 학습하기 쉽게 하기 위해 pynest를 이용했고
가져가서 손쉽게 쓸수있도록 구조화를 진행하면서 할 예정입니다.
리펙토링을 해가면서 본인만의 코드를 만들어가면 좋겠습니다.