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

예컨데님의 프로필 이미지
예컨데

작성한 질문수

배달앱은 어떻게 내 주변의 맛집을 찾을까?

base_document, collection 생성 2

테스트를 실행했을 때 RuntimeError: Event loop is closed 에러가 발생합니다.

해결된 질문

작성

·

1.5K

3

질문하실 땐 https://gist.github.com/ 를 사용하시면 코드를 쉽게 공유할 수 있습니다!

  • 원하고자 하는 것

  • 실제 작성한 코드

  • 실행한 결과

  • 원하는 결과

     

이렇게 4가지를 꼭 적어주셔야 도와드릴 수 있습니다 :)

 

test_shop_insert_one() 함수를 추가하여 테스트를 실행했을 때 RuntimeError: Event loop is closed 에러가 발생하여 테스트가 실패합니다.

 

app/tests/entities/collections/shop/test_shop_collection.py F                                                                                                [100%]

============================================================================= FAILURES =============================================================================
_______________________________________________________________________ test_shop_insert_one _______________________________________________________________________

    @pytest.mark.asyncio
    async def test_shop_insert_one() -> None:
        # Given
        name = "치킨집"
        category_codes = [CategoryCode.CHICKEN]
        delivery_areas = [
            ShopDeliveryAreaSubDocument(
                poly=GeoJsonPolygon(coordinates=[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]),
            )
        ]
    
        # When
>       shop = await ShopCollection.insert_one(name, category_codes, delivery_areas)

app/tests/entities/collections/shop/test_shop_collection.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
app/entities/collections/shop/shop_collection.py:20: in insert_one
    result = await cls._collection.insert_one(
/PATH/OF/POETRY/CACHE/lib/python3.11/site-packages/motor/metaprogramming.py:73: in method
    return framework.run_on_executor(
/PATH/OF/POETRY/CACHE/lib/python3.11/site-packages/motor/frameworks/asyncio/__init__.py:85: in run_on_executor
    return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
/PATH/OF/PYTHON/lib/python3.11/asyncio/base_events.py:816: in run_in_executor
    self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_UnixSelectorEventLoop running=False closed=True debug=False>

    def _check_closed(self):
        if self._closed:
>           raise RuntimeError('Event loop is closed')
E           RuntimeError: Event loop is closed

/PATH/OF/PYTHON/lib/python3.11/asyncio/base_events.py:519: RuntimeError
===================================================================== short test summary info ======================================================================
FAILED app/tests/entities/collections/shop/test_shop_collection.py::test_shop_insert_one - RuntimeError: Event loop is closed
=================================================================== 1 failed, 1 passed in 0.55s ====================================================================

에러로그는 위와 같습니다.

 

--async-mode 를 test로 변경해보면 저 단계까지 가기 전에 에러가 발생하고

@pytest.mark.asyncio 어노테이션을 추가해줘도 결과는 같습니다.

poetry 를 이용한 환경 구성이 처음이라 해당 지식이 많이 부족하여 이 문제를 어떻게 해결해야하는지 잘 모르겠습니다.

 

파이썬 버전은 3.11.3

poetry 버전은 1.4.2

mongodb 버전은 6.0.11 입니다.

 

[tool.poetry.dependencies]
fastapi = "^0.95.2"
gunicorn = "^20.1.0"
httpx = "^0.24.1"
motor = "^3.1.2"
orjson = "^3.8.14"
python = "^3.11"
uvicorn = "^0.22.0"

[tool.poetry.group.dev.dependencies]
black = {extras = ["d"], version = "^23.3.0"}
coverage = "^7.2.3"
isort = "^5.12.0"
mypy = "^1.3.0"
pytest = "^7.3.1"
pytest-asyncio = "^0.21.0"
toml-sort = "^0.23.0"

 

작성한 코드의 차이점은 db가 로컬에 있지 않고 외부에 있는 관계로

def create_mongo_url(host: str = "localhost", port: int = 27017) -> str:
    return f"mongodb://{host}:{port}"


DATABASE_NAME = os.environ.get("MONGO_DATABASE", "yorigin")
HOST = os.environ.get("MONGO_HOST", "localhost")
PORT = os.environ.get("MONGO_PORT", 27017)

client = AsyncIOMotorClient(create_mongo_url(HOST, int(PORT)))

이와 같은 코드를 추가하여 지정된 host의 mongodb와 통신할 수 있도록 한 것이 전부입니다.

 

확인 부탁드릴 수 있을까요?

답변 3

0

정승원님의 프로필 이미지
정승원
지식공유자

ㅎㅎ 다 해결되어서 다행입니다!

어제 버스에서 급하게 내리느라고 이미지만 달랑 올렸었는데 ㅋㅋㅋ

 

요거 미해결로 바뀌어 버리니 답 안주셔도 괜찮습니다!

강의 들으시면서 궁금한 점 있으시면 또 편하게 질문 주셔용

 

파이팅!

 

아 제가 달아도 미해결로 바뀌어 버리네요 으앗

0

정승원님의 프로필 이미지
정승원
지식공유자

혹시 시간이 괜찮으시다면 시간을 따로 잡아서 팀뷰어 원격으로 봐 볼까요? 저도 궁금하네요

다음주 평일 이른 아침, 늦은 저녁 중에 괜찮은 시간 있으세요?

 

예컨데님의 프로필 이미지
예컨데
질문자

https://github.com/Evil-Goblin/delivery_fastapi

코드는 여기 올려두었습니다.

급조한 코드다보니 이쁘지 않아 조금 부끄럽네요.

실행 환경이 사내로컬환경이라서 공유드리기가 어렵습니다.

죄송합니다.

혹시라도 환경정보 확인하고 싶으신 사항 있으시면 말씀 부탁드립니다.

 

저에게 발생한 문제의 경우 단 한번의 성공없이 처음부터 motor의 이벤트루프가 실행되지 않은 것으로 확인되었습니다.

디버그로 따라가봐도 motor 의 find_one 을 수행하려 할 때 이벤트 루프가 닫혀있는 것을 확인할 수 있었습니다.

언급해주신 최초 한번의 테스트는 성공 이후 다른 모든 테스트가 실패한다고 말씀주신 것과 상황이 달라서 저또한 의아했습니다.

결과적으로 해결하긴 했지만 motor 라이브러리를 써본 경험이 없어서 많이 해맸던 것 같습니다.

 

그런데 댓글이 달릴때마다 해결 여부가 미해결로 변경되나보군요.

정승원님의 프로필 이미지
정승원
지식공유자

image

예컨데님의 프로필 이미지
예컨데
질문자

아하 저게 첫번째 이벤트 루프를 먹어버렸나보군요

중간에 삭제하지 않은 것을 까먹고 삭제한 코드였는데...

 

아아아 다 이해가 된 것 같습니다.

정말 감사합니다.

저도 어서 해당 부분을 테스트해보고 싶네요

다시 한번 정말 감사합니다.

0

예컨데님의 프로필 이미지
예컨데
질문자

해결하였습니다.

motor 라이브러리에서 사용되는 event loop 와 pytest 에서 사용되는 event loop 가 달라서 발생하는 것으로 확인됩니다.

실제 어플리케이션 실행시에 어떻게 동작할 지 까지는 좀 더 확인이 필요하겠지만

db 변수를 lazy loading 을 하도록 변경하고 최초 load 시에 event loop 를 주입받을 수 있도록 하여 fixtures 에서 주입해주는 event loop 를 이용해 motor 클라이언트를 생성하였습니다.

AsyncIOMotorClient(io_loop=event_loop)

이 named parameter 를 이용해 사용할 이벤트루프를 지정해줌에 따라서 테스트가 성공할 수 있었습니다.

이게 pytest_asyncio 의 이벤트루프가 특별한 것인지 motor의 그것이 특별한 것인지는 좀 더 확인이 필요할 것 같습니다.

정승원님의 프로필 이미지
정승원
지식공유자

앗 스스로 해결하셨다니 굉장한데요!

 

현재 강의의 어디 지점이신지 모르겠지만, 제가 알기로

imagescope 가 session 이고, 이름이 event_loop 인 픽스쳐를 만든 후, 이 픽스쳐가 loop 를 yield 하게 만들면 해결이 됩니다!

(섹션 3의 pytest 와 fixture 에서 다룹니다)

 

혹시 위 fixture 가 있는데도 에러가 난 것일까요?

 

P.S.

motor 라이브러리에서 사용되는 event loop 와 pytest 에서 사용되는 event loop 가 달라서 발생하는 것으로 확인됩니다.

라고 해주셨는데요, 아주 중요한 통찰입니다.

 

pytest_asyncio 의 기본 event_loop fixture 는 scope 가 Function 인데요, 때문에 매 번 테스트 함수가 실행될 때 마다 기존 루프가 닫히고 새로운 루프가 생깁니다. (눈물)

따라서 다음과 같은 일이 벌어집니다.

  1. 처음 테스트에서 loop 가 생성.

  2. AsyncIOMotorClient() 가 해당 loop 를 이용해서 정상적으로 쿼리.

  3. 처음 테스트가 성공하고 loop 가 닫힘

  4. loop 가 닫혔다는것을 알 방법이 없는 AsyncIOMotorClient 는 이미 닫힌 루프로 쿼리를 계속 시도

  5. 나머지 테스트는 실패

위와 같은 문제를 해결하기 위해서 전체 테스트를 실행하는 동안 loop 를 하나만 쓰도록 session scope 의 event loop fixture 를 새로 정의하는 것입니다!

 

 

 

 

 

 

 

 

정승원님의 프로필 이미지
정승원
지식공유자

imagefixture 가 있을 때 성공

 

imageimage

fixture 를 주석처리하고 테스트를 실행하면 실패

예컨데님의 프로필 이미지
예컨데
질문자

넵 해당 설정까지 해주었으나 에러가 발생하였습니다.

때문에

image이와 같이 db 초기화를 위해 event loop 를 직접 넣어주고

 

image이와 같이 초기화시 AsyncIOMotorClient 의 io_loop named parameter 에 직접 넣어줌으로서 해결하였습니다.

버전정보 등을 픽스해주신 값들을 그대로 사용했음에도 차이가 생기는게 의아하긴 합니다.

관련해서 좀 더 공부해야할 필요는 있을 것 같습니다.

답변 감사드립니다.

예컨데님의 프로필 이미지
예컨데

작성한 질문수

질문하기