해결된 질문
작성
·
1.6K
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
해결하였습니다.
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의 그것이 특별한 것인지는 좀 더 확인이 필요할 것 같습니다.
앗 스스로 해결하셨다니 굉장한데요!
현재 강의의 어디 지점이신지 모르겠지만, 제가 알기로
scope 가 session 이고, 이름이 event_loop 인 픽스쳐를 만든 후, 이 픽스쳐가 loop 를 yield 하게 만들면 해결이 됩니다!
(섹션 3의 pytest 와 fixture 에서 다룹니다)
혹시 위 fixture 가 있는데도 에러가 난 것일까요?
motor 라이브러리에서 사용되는 event loop 와 pytest 에서 사용되는 event loop 가 달라서 발생하는 것으로 확인됩니다.
라고 해주셨는데요, 아주 중요한 통찰입니다.
pytest_asyncio 의 기본 event_loop
fixture 는 scope 가 Function
인데요, 때문에 매 번 테스트 함수가 실행될 때 마다 기존 루프가 닫히고 새로운 루프가 생깁니다. (눈물)
따라서 다음과 같은 일이 벌어집니다.
처음 테스트에서 loop 가 생성.
AsyncIOMotorClient() 가 해당 loop 를 이용해서 정상적으로 쿼리.
처음 테스트가 성공하고 loop 가 닫힘
loop 가 닫혔다는것을 알 방법이 없는 AsyncIOMotorClient 는 이미 닫힌 루프로 쿼리를 계속 시도
나머지 테스트는 실패
위와 같은 문제를 해결하기 위해서 전체 테스트를 실행하는 동안 loop 를 하나만 쓰도록 session scope 의 event loop fixture 를 새로 정의하는 것입니다!
넵 해당 설정까지 해주었으나 에러가 발생하였습니다.
때문에
이와 같이 db 초기화를 위해 event loop 를 직접 넣어주고
이와 같이 초기화시 AsyncIOMotorClient 의 io_loop named parameter 에 직접 넣어줌으로서 해결하였습니다.
버전정보 등을 픽스해주신 값들을 그대로 사용했음에도 차이가 생기는게 의아하긴 합니다.
관련해서 좀 더 공부해야할 필요는 있을 것 같습니다.
답변 감사드립니다.
https://github.com/Evil-Goblin/delivery_fastapi
코드는 여기 올려두었습니다.
급조한 코드다보니 이쁘지 않아 조금 부끄럽네요.
실행 환경이 사내로컬환경이라서 공유드리기가 어렵습니다.
죄송합니다.
혹시라도 환경정보 확인하고 싶으신 사항 있으시면 말씀 부탁드립니다.
저에게 발생한 문제의 경우 단 한번의 성공없이 처음부터 motor의 이벤트루프가 실행되지 않은 것으로 확인되었습니다.
디버그로 따라가봐도 motor 의 find_one 을 수행하려 할 때 이벤트 루프가 닫혀있는 것을 확인할 수 있었습니다.
언급해주신 최초 한번의 테스트는 성공 이후 다른 모든 테스트가 실패한다고 말씀주신 것과 상황이 달라서 저또한 의아했습니다.
결과적으로 해결하긴 했지만 motor 라이브러리를 써본 경험이 없어서 많이 해맸던 것 같습니다.
그런데 댓글이 달릴때마다 해결 여부가 미해결로 변경되나보군요.