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를 이용했고

가져가서 손쉽게 쓸수있도록 구조화를 진행하면서 할 예정입니다.

리펙토링을 해가면서 본인만의 코드를 만들어가면 좋겠습니다.

Related Pages

© 2024 Coding Stairs. All rights reserved.