인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

네오스카이님의 프로필 이미지
네오스카이

작성한 질문수

실전! FastAPI 입문

(실습) ORM 적용 - HTTP Response 처리

(실습) ORM 적용 - HTTP Response 처리

작성

·

969

·

수정됨

0

from typing import List
from fastapi import FastAPI, Body, HTTPException, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from database.orm import ToDo

from database.connection import get_db
from database.repository import get_todos
from schema.response import ListToDoResponse, ToDoSchema

app = FastAPI()


@app.get("/")
def heath_check_handler():
    return {"ping": "pong"}

todo_data = {
    1: {
        "id": 1,
        "contents": "실전! FastAPI 섹션 0 수강",
        "is_done": True,
    },
    2: {
        "id": 2,
        "contents": "실전! FastAPI 섹션 1 수강",
         "is_done": False,
    },
    3: {
        "id": 3,
        "contents": "실전! FastAPI 섹션 2 수강",
        "is_done": False,
    },
}

@app.get("/todos", status_code=200)
def get_todos_handler(
        order: str | None = None,
        session: Session = Depends(get_db),
) -> ListToDoResponse:
    todos: List[ToDo] = get_todos(session=session)

    if order and order == "DESC":
        return ListToDoResponse(
            todos=[ToDoSchema.from_orm(todo) for todo in todos[::-1]]
        )
    return ListToDoResponse(
        todos=[ToDoSchema.from_orm(todo) for todo in todos]
    )

@app.get("/todos/{todo_id}", status_code=200)
def get_todo_handler(todo_id: int):
    todo = todo_data.get(todo_id)
    if todo:
        return todo
    raise HTTPException(status_code=404, detail="ToDo Not Found")

class CreateToDoRequest(BaseModel):
    id: int
    contents: str
    is_done: bool

@app.post("/todos", status_code=201)
def create_todo_handler(request: CreateToDoRequest):
    todo_data[request.id] = request.dict()
    return todo_data[request.id]

@app.patch("/todos/{todo_id}", status_code=200)
def update_todo_handler(
        todo_id: int,
        is_done: bool = Body(..., embed=True)
):
    todo = todo_data.get(todo_id)
    if todo:
        todo["is_done"] = is_done
        return todo
    raise HTTPException(status_code=404, detail="ToDo Not Found")

@app.delete("/todos/{todo_id}")
def delete_todo_handler(todo_id: int):
    todo = todo_data.pop(todo_id, None)
    if todo:
        return
    raise HTTPException(status_code=404, detail="ToDo Not Found")

 

from typing import List
from pydantic import BaseModel


class ToDoSchema(BaseModel):
    id: int
    contents: str
    is_done: bool

    class Config:
        orm_mode = True

class ListToDoResponse(BaseModel):
    todos: List[ToDoSchema]

 

from typing import List
from sqlalchemy import select
from sqlalchemy.orm import Session

from database.orm import ToDo

def get_todos(session: Session) -> List[ToDo]:
    return list(session.scalars(select(ToDo)))

 

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "mysql+pymysql://root:todos@127.0.0.1:3306/todos"

engine = create_engine(DATABASE_URL, echo=True)
SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    session = SessionFactory()
    try:
        yield session
    finally:
        session.close()

 

from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class ToDo(Base):
    __tablename__ = 'todo'

    id = Column(Integer, primary_key=True, index=True)
    contents = Column(String(256), nullable=False)
    is_done = Column(Boolean, nullable=False)

    def __repr__(self):
        return f"ToDo(id={self.id}, contents={self.contents}, is_done={self.is_done})"

 

에러가 납니다.

 

 

 

 

 

파이썬 콘솔

 

<input>:1: PydanticDeprecatedSince20: The from_orm method is deprecated;

 

 

from schema.response import ToDoSchema

from datagbase.orm import ToDo

Traceback (most recent call last):

File "C:\Program Files\JetBrains\PyCharm 2023.3.2\plugins\python\helpers\pydev\pydevconsole.py", line 364, in runcode

coro = func()

^^^^^^

File "<input>", line 1, in <module>

File "C:\Program Files\JetBrains\PyCharm 2023.3.2\plugins\python\helpers\pydev\_pydev_bundle\pydev_import_hook.py", line 21, in do_import

module = self._system_import(name, args, *kwargs)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ModuleNotFoundError: No module named 'datagbase'

from database.orm import ToDo

todo = ToDo(id=100, contents="test", is_done=True)

ToDoSchema.from_orm(todo)

<input>:1: PydanticDeprecatedSince20: The from_orm method is deprecated; set model_config['from_attributes']=True and use model_validate instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/

Traceback (most recent call last):

File "C:\Program Files\JetBrains\PyCharm 2023.3.2\plugins\python\helpers\pydev\pydevconsole.py", line 364, in runcode

coro = func()

^^^^^^

File "<input>", line 1, in <module>

File "C:\Users\manag\pyProject\todos\Lib\site-packages\typing_extensions.py", line 2499, in wrapper

return arg(*args, **kwargs)

^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\pydantic\main.py", line 1126, in from_orm

raise PydanticUserError(

pydantic.errors.PydanticUserError: You must set the config attribute from_attributes=True to use from_orm

 

 

http://localhost:8000/docs#/default/get_todos_handler_todos_get

접속시 터미널

INFO: 127.0.0.1:56312 - "GET /docs HTTP/1.1" 200 OK

INFO: 127.0.0.1:56312 - "GET /openapi.json HTTP/1.1" 200 OK

2024-01-23 16:06:15,108 INFO sqlalchemy.engine.Engine BEGIN (implicit)

2024-01-23 16:06:15,109 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done

FROM todo

2024-01-23 16:06:15,109 INFO sqlalchemy.engine.Engine [cached since 826.5s ago] {}

2024-01-23 16:06:15,113 INFO sqlalchemy.engine.Engine ROLLBACK

INFO: 127.0.0.1:56312 - "GET /todos HTTP/1.1" 500 Internal Server Error

ERROR: Exception in ASGI application

Traceback (most recent call last):

File "C:\Users\manag\pyProject\todos\Lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 404, in run_asgi

result = await app( # type: ignore[func-returns-value]

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in call

return await self.app(scope, receive, send)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\fastapi\applications.py", line 1054, in call

await super().__call__(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\applications.py", line 123, in call

await self.middleware_stack(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\middleware\errors.py", line 186, in call

raise exc

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\middleware\errors.py", line 164, in call

await self.app(scope, receive, _send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\middleware\exceptions.py", line 62, in call

await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app

raise exc

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app

await app(scope, receive, sender)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\routing.py", line 762, in call

await self.middleware_stack(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\routing.py", line 782, in app

await route.handle(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\routing.py", line 297, in handle

await self.app(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\routing.py", line 77, in app

await wrap_app_handling_exceptions(app, request)(scope, receive, send)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app

raise exc

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app

await app(scope, receive, sender)

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\routing.py", line 72, in app

response = await func(request)

^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\fastapi\routing.py", line 299, in app

raise e

File "C:\Users\manag\pyProject\todos\Lib\site-packages\fastapi\routing.py", line 294, in app

raw_response = await run_endpoint_function(

^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\fastapi\routing.py", line 193, in run_endpoint_function

return await run_in_threadpool(dependant.call, **values)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\starlette\concurrency.py", line 40, in run_in_threadpool

return await anyio.to_thread.run_sync(func, *args)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\anyio\to_thread.py", line 56, in run_sync

return await get_async_backend().run_sync_in_worker_thread(

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\anyio\_backends\_asyncio.py", line 2134, in run_sync_in_worker_thread

return await future

^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\anyio\_backends\_asyncio.py", line 851, in run

result = context.run(func, *args)

^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\src\main.py", line 48, in get_todos_handler

todos=[ToDoSchema.from_orm(todo) for todo in todos]

^^^^^^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\typing_extensions.py", line 2499, in wrapper

return arg(*args, **kwargs)

^^^^^^^^^^^^^^^^^^^^

File "C:\Users\manag\pyProject\todos\Lib\site-packages\pydantic\main.py", line 1126, in from_orm

raise PydanticUserError(

pydantic.errors.PydanticUserError: You must set the config attribute from_attributes=True to use from_orm

 

답변 2

0

신동현님의 프로필 이미지
신동현
지식공유자

안녕하세요! AI 인턴이 답변을 제법 잘 해주었네요. 잘 해결하셨다니 다행입니다!

pydantic 버전 때문에 발생하는 문제로, 자세한 내용은 강의 공지에 있습니다.

생각보다 어렵지 않으니 v2로 마이그레이션 하는 연습을 해보셔도 좋을 것 같습니다!

남은 강의도 화이팅하세요!

 

안녕하세요. from_attributes=True로 설정하고, model_validate(todo)를 사용해서 응답 값을 래핑하려고 했는데, 왜인지 작동하지 않네요. 따로 설정해주어야 하는게 있는건가요?

신동현님의 프로필 이미지
신동현
지식공유자

안녕하세요. Pydantic V2를 적용하시는 상황에서 발생하는 문제인거죠? 좀 더 구체적으로 어떻게 동작하지 않는 상황일까요?

@app.get("/todos/{todo_id}", status_code=200)
def get_todo_handler(todo_id: int, session: Session = Depends(get_db)):
    todo = get_todo_by_todo_id(session=session, todo_id=todo_id)

    if todo:
        return ToDoSchema.model_validate(todo)
    raise HTTPException(status_code=404, detail="ToDo Not Found")

컨트롤러는 이렇게 작성하고

from typing import List

from pydantic import BaseModel


class ToDoSchema(BaseModel):
    id: int
    contents: str
    is_done: bool

    class Config:
        from_attributes = True


class ListToDoResponse(BaseModel):
    todos: List[ToDoSchema]

response는 이렇게

from typing import List

from sqlalchemy import select
from sqlalchemy.orm import Session

from database.orm import ToDo


def get_todos(session: Session) -> List[ToDo]:
    return list(session.scalars(select(ToDo)))


def get_todo_by_todo_id(session: Session, todo_id: int) -> ToDo | None:
    return session.scalar(select(ToDo).where(ToDo.id == todo_id))

repository는 이렇게 작성을 했는데,

제가 원하는건 응답값이

{
    todo: {
      "id": 1,
      "contents": "FastAPI Section 0",
      "is_done": true
    }
}

이런식으로 래핑을 한건데 저는 결과가

{
  "id": 1,
  "contents": "FastAPI Section 0",
  "is_done": true
}

이렇게 나와요 ㅠ

신동현님의 프로필 이미지
신동현
지식공유자

지금처럼 TodoSchema의 필드가 모두 풀어져서 응답하는게 정상 동작이 맞습니다. 원하시는대로 todo를 key로 갖는 JSON object를 반환하게 하기 위해서는 아래처럼 응답값에 별도로 todo key를 지정해주시거나 별도의 BaseModel을 다시 정의해주셔야 합니다 :)

return {"todo": ToDoSchema.model_validate(todo)}
class TodoResponse(BaseModel):
    todo: TodoSchema
return {"todo": ToDoSchema.model_validate(todo)}

이 방식으로 해보려고 했었는데, 뭔가 잘못했었나보네요 ㅠ 감사합니다!

0

안녕하세요, 인프런 AI 인턴입니다.

질문자님께서 겪고 계신 문제는 Pydantic 라이브러리의 버전 업데이트에 따라서 from_orm 메서드의 사용이 지원 중단(deprecated)되었기 때문에 발생하는 것으로 보입니다. Pydantic 버전 2.0부터 from_orm 메서드 대신 model_config['from_attributes']=True를 설정하고 model_validate를 사용해야 한다는 경고 메시지가 나옵니다.

이를 해결하기 위해서는 Pydantic 모델의 Config 내에 from_attributes=True 설정이 필요하며, 기존의 from_orm 사용 부분을 model_validate로 변경해야 합니다.

이 문제에 대한 유사한 질문 중 하나가 있으며, 아래 링크에서 확인하실 수 있습니다:

본 질문에서는 pydantic 버전 문제에 대한 답변도 함께 있으니 참고하시면 좋겠습니다. 기존의 from_orm 메서드 사용에서 발생한 문제를 해결하기 위해서는 Pydantic 모델을 업데이트하거나, Pydantic 버전을 downgrade하여 해결이 가능합니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

네오스카이님의 프로필 이미지
네오스카이

작성한 질문수

질문하기