본문 바로가기

Python/FastAPI

[FastAPI] AttributeError - 'ScalarResult' object has no attribute 'field'

문제상황

Repository에서 session.scalar() 함수를 사용하여, 사용자가 입력한 'username'과 일치하는 유저 정보를 조회하려는 상황이다.

request 요청 시, 500 서버 에러를 반환하며, 로그에 다음과 같은 문장이 출력된다.

AttributeError: 'ScalarResult' object has no attribute 'password'

ScalarResult 객체에서 password를 찾을 수 없다고 안내하며, 로그가 표시한 라인을 확인하니 아래와 같다.

verified: bool = user_service.verify_password(
        plain_password=request.password, 
        hashed_password=user.password # 에러 발생
    )

user.password 를 가져올 때, password 속성을 User 객체에서 가져와야했으나 선행된 get_user_by_username() 함수를 통해 ScalarResult 객체를 반환하고 있기 때문에 속성 접근이 되지 않는 것으로 보인다.

그렇다면 돌아가서 핸들러 코드를 살펴보자

해결과정

class UserRepository: 

def  __init__(self, session: Session = Depends(get_db)): 
    self.session = session

def get_user_by_username(self, username: str) -> User | None:
    result = self.session.scalars(select(User).where(User.username == username))
    print(result)
    return result

SQLAlchemy의 scalars 메서드를 사용하여 쿼리 결과를 반환하고 있는데, print()로 scalars 객체를 찍어보면 다음과 같이 출력된다.

ScalarResult 객체는 쿼리의 결과를 포함하고 있지만, 직접적으로 User 객체에 접근할 수는 없다.

.first()를 호출하지 않으면 scalars 메서드는 User 객체가 아닌 ScalarResult 객체를 반환하게 되고 있는 것을 확인할 수 있다.

원인은 쿼리의 결과를 반환했지만, 직접적으로 User 객체가 아닌 ScalarResult 객체이기 때문에 접근할 수 없었기 때문이다. ScalarResult 객체에서 직접적으로 속성에 접근하려고 하면 AttributeError가 발생한다.

쿼리 결과에서 첫 번째 사용자 객체를 반환하도록 first()메소드를 사용하면, 이제 get_user_by_username()는 User 객체 또는 None을 정상적으로 반환한다

def get_user_by_username(self, username: str) -> User | None:
    return self.session.scalars(select(User).where(User.username == username)).first() 
      # 유저는 단일객체이기 때문에 result() 대신 first() 메서드는 쿼리 결과에서 첫 번째 행만 반환
    # 만약 결과가 없으면 None을 반환

요약

ScalarResult 객체를 정상적으로 반환 받았지만, 이는 쿼리의 결과를 감싸고 있는 래퍼(wrapper) 객체이기 때문에 .first()나 .result()로 호출하여 결과를 명시해주지 않으면, 쿼리의 결과로 받아온 엔티티 객체를 사용할 수 없게 된다.

객체를 받기만 한거지. 객체 자체는 User 객체를 직접적으로 제공할 수 없다. 바보같은..