diff --git a/api/db.py b/api/db.py index d04cc070..df11469d 100644 --- a/api/db.py +++ b/api/db.py @@ -8,7 +8,7 @@ from beanie import init_beanie from bson import ObjectId -from fastapi_pagination.ext.motor import paginate +from fastapi_pagination.ext.pymongo import apaginate as paginate from kernelci.api.models import ( EventHistory, Hierarchy, @@ -16,7 +16,7 @@ TelemetryEvent, parse_node_obj, ) -from motor import motor_asyncio +from pymongo import AsyncMongoClient from redis import asyncio as aioredis from .models import User, UserGroup @@ -52,10 +52,10 @@ class Database: BOOL_VALUE_MAP = {"true": True, "false": False} def __init__(self, service="mongodb://db:27017", db_name="kernelci"): - self._motor = motor_asyncio.AsyncIOMotorClient(service) + self._mongo = AsyncMongoClient(service) # TBD: Make redis host configurable self._redis = aioredis.from_url("redis://redis:6379") - self._db = self._motor[db_name] + self._db = self._mongo[db_name] async def initialize_beanie(self): """Initialize Beanie ODM to use `fastapi-users` tools for MongoDB""" diff --git a/api/pubsub_mongo.py b/api/pubsub_mongo.py index dafc0954..c1a53fc5 100644 --- a/api/pubsub_mongo.py +++ b/api/pubsub_mongo.py @@ -24,8 +24,7 @@ from typing import Any, Dict, List, Optional from cloudevents.http import CloudEvent, to_json -from motor import motor_asyncio -from pymongo import ASCENDING, WriteConcern +from pymongo import ASCENDING, AsyncMongoClient, WriteConcern from redis import asyncio as aioredis from .config import PubSubSettings @@ -87,7 +86,7 @@ def __init__( # MongoDB setup if mongo_client is None: mongo_service = os.getenv("MONGO_SERVICE") or "mongodb://db:27017" - self._mongo_client = motor_asyncio.AsyncIOMotorClient(mongo_service) + self._mongo_client = AsyncMongoClient(mongo_service) else: self._mongo_client = mongo_client self._mongo_db = self._mongo_client[mongo_db_name] diff --git a/docker/api/requirements-tests.txt b/docker/api/requirements-tests.txt index f95a1022..538684e3 100644 --- a/docker/api/requirements-tests.txt +++ b/docker/api/requirements-tests.txt @@ -6,4 +6,3 @@ pytest-dependency==0.5.1 pytest-mock==3.6.1 pytest-order==1.0.1 httpx>=0.27.0,<1.0.0 -mongomock_motor==0.0.21 diff --git a/docker/api/requirements.txt b/docker/api/requirements.txt index cca8d520..d2a81106 100644 --- a/docker/api/requirements.txt +++ b/docker/api/requirements.txt @@ -1,11 +1,11 @@ cloudevents==2.0.0 -beanie==1.29.0 +beanie==2.1.0 fastapi[all]==0.135.3 -fastapi-pagination==0.12.30 +fastapi-pagination==0.14.3 fastapi-users[beanie, oauth]==15.0.5 +fastapi-users-db-beanie==5.0.0 MarkupSafe==2.0.1 -motor==3.6.0 -pymongo==4.9.0 +pymongo==4.16.0 passlib==1.7.4 pydantic==2.9.2 pymongo-migrate==0.11.0 diff --git a/pyproject.toml b/pyproject.toml index 4c09724d..9ac468ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,10 +19,10 @@ dependencies = [ "cloudevents == 2.0.0", "beanie == 2.1.0", "fastapi[all] == 0.135.3", - "fastapi-pagination == 0.12.30", + "fastapi-pagination == 0.14.3", "fastapi-users[beanie, oauth] == 15.0.5", + "fastapi-users-db-beanie == 5.0.0", "MarkupSafe == 2.0.1", - "motor == 3.6.0", "pymongo == 4.16.0", "passlib == 1.7.4", "pydantic == 2.9.2", @@ -41,7 +41,6 @@ tests = [ "pytest-mock == 3.6.1", "pytest-order == 1.0.1", "httpx >= 0.27.0, < 1.0.0", - "mongomock_motor == 0.0.36", ] dev = [ "kernelci-api[tests]", diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index b5c1a666..9e4e8172 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -6,10 +6,12 @@ """pytest fixtures for KernelCI API end-to-end tests""" +import asyncio + import pytest from httpx import ASGITransport, AsyncClient from kernelci.api.models import Node, Regression -from motor.motor_asyncio import AsyncIOMotorClient +from pymongo import AsyncMongoClient from api.main import versioned_app @@ -17,7 +19,7 @@ DB_URL = "mongodb://db:27017" DB_NAME = "kernelci" -db_client = AsyncIOMotorClient(DB_URL) +db_client = AsyncMongoClient(DB_URL) db = db_client[DB_NAME] node_model_fields = set(Node.model_fields.keys()) regression_model_fields = set(Regression.model_fields.keys()) @@ -51,9 +53,7 @@ async def db_create(collection, obj): @pytest.fixture(scope="session") def event_loop(): - """Get an instance of the default event loop using database client. - The event loop will be used for all async tests. - """ - loop = db_client.get_io_loop() + """Session-scoped event loop shared by all async tests.""" + loop = asyncio.new_event_loop() yield loop loop.close() diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 9b168128..e73e6d90 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -12,11 +12,9 @@ import fakeredis.aioredis import pytest -from beanie import init_beanie from fastapi import HTTPException, Request, status from fastapi.testclient import TestClient from httpx import ASGITransport, AsyncClient -from mongomock_motor import AsyncMongoMockClient from api.main import ( app, @@ -245,16 +243,17 @@ def mock_unsubscribe(mocker): @pytest.fixture(autouse=True) async def mock_init_beanie(mocker): - """Mocks async call to Database method to initialize Beanie""" + """Mocks async call to Database method to initialize Beanie. + + All tests mock api.db.Database.* directly, so beanie never issues real + queries. Patching initialize_beanie to a no-op avoids pulling in an async + mongo mock, which beanie 2.x (pymongo AsyncMongoClient) has no replacement + for. + """ async_mock = AsyncMock() - client = AsyncMongoMockClient() - init = await init_beanie( - document_models=[User], database=client.get_database(name="db") - ) mocker.patch( "api.db.Database.initialize_beanie", side_effect=async_mock, - return_value=init, ) return async_mock