diff --git a/backend/poetry.lock b/backend/poetry.lock index b92c77bb9e..3d5476dd13 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1935,6 +1935,31 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "httpx" +version = "0.27.2" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "idna" version = "3.10" diff --git a/backend/tests/api-e2e/test_api_allocations.py b/backend/tests/api-e2e/test_api_allocations.py index ceba1d6ba0..df9ec41cea 100644 --- a/backend/tests/api-e2e/test_api_allocations.py +++ b/backend/tests/api-e2e/test_api_allocations.py @@ -1,3 +1,4 @@ +from fastapi.testclient import TestClient import pytest from flask import current_app as app @@ -9,6 +10,7 @@ @pytest.mark.api def test_allocations( client: Client, + fastapi_client: TestClient, deployer: UserAccount, ua_alice: UserAccount, ua_bob: UserAccount, @@ -31,7 +33,28 @@ def test_allocations( res = client.pending_snapshot() assert res["epoch"] > 0 - ua_alice.allocate(1000, alice_proposals) + ua_alice_nonce, _ = ua_alice._client.get_allocation_nonce(ua_alice.address) + signature = ua_alice._client.sign_operation( + ua_alice._account, 1000, alice_proposals, ua_alice_nonce + ) + rv = fastapi_client.post( + "/allocations/allocate", + json={ + "payload": { + "allocations": [ + {"proposalAddress": address, "amount": 1000} + for address in alice_proposals + ], + "nonce": ua_alice_nonce, + }, + "userAddress": ua_alice.address, + "signature": signature, + "isManuallyEdited": False, + }, + ) + assert rv.status_code == 201 + + # ua_alice.allocate(1000, alice_proposals) ua_bob.allocate(1000, alice_proposals[:1]) allocations, _ = client.get_epoch_allocations(STARTING_EPOCH) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 517cc9c544..9c2e086c9a 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -9,6 +9,7 @@ from http import HTTPStatus from unittest.mock import MagicMock, Mock +from fastapi.testclient import TestClient import gql import pytest from flask import current_app @@ -19,6 +20,7 @@ from web3 import Web3 import logging +from v2.main import app as fastapi_app from app import create_app from app.engine.user.effective_deposit import DepositEvent, EventType, UserDeposit from app.exceptions import ExternalApiException @@ -416,6 +418,21 @@ def random_string() -> str: return "".join(random.choices(characters, k=length_of_string)) +@pytest.fixture +def fastapi_client(deployment) -> TestClient: + # take SQLALCHEMY_DATABASE_URI and use as DB_URI + os.environ["DB_URI"] = deployment.SQLALCHEMY_DATABASE_URI + os.environ["PROPOSALS_CONTRACT_ADDRESS"] = deployment.PROJECTS_CONTRACT_ADDRESS + + for key in dir(deployment): + if key.isupper(): + value = getattr(deployment, key) + if value is not None: + os.environ[key] = str(value) + + return TestClient(fastapi_app) + + @pytest.fixture def flask_client(deployment) -> FlaskClient: """An application for the integration / API tests.""" @@ -656,12 +673,12 @@ def get_rewards_budget(self, address: str, epoch: int): def get_user_rewards_in_upcoming_epoch(self, address: str): rv = self._flask_client.get(f"/rewards/budget/{address}/upcoming") - current_app.logger.debug("get_user_rewards_in_upcoming_epoch :", rv.text) + current_app.logger.debug(f"get_user_rewards_in_upcoming_epoch :{rv.text}") return json.loads(rv.text) def get_user_rewards_in_epoch(self, address: str, epoch: int): rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}") - current_app.logger.debug("get_rewards_budget :", rv.text) + current_app.logger.debug(f"get_rewards_budget :{rv.text}") return json.loads(rv.text) def get_total_users_rewards_in_epoch(self, epoch): diff --git a/backend/v2/core/dependencies.py b/backend/v2/core/dependencies.py index 3fbd366444..163da14099 100644 --- a/backend/v2/core/dependencies.py +++ b/backend/v2/core/dependencies.py @@ -40,7 +40,13 @@ class DatabaseSettings(OctantSettings): @property def sqlalchemy_database_uri(self) -> str: - return self.db_uri.replace("postgresql://", "postgresql+asyncpg://") + if "postgresql://" in self.db_uri: + return self.db_uri.replace("postgresql://", "postgresql+asyncpg://") + + if "sqlite://" in self.db_uri: + return self.db_uri.replace("sqlite://", "sqlite+aiosqlite://") + + raise ValueError("Unsupported database URI") def get_database_settings() -> DatabaseSettings: @@ -51,16 +57,21 @@ def get_database_settings() -> DatabaseSettings: def get_sessionmaker( settings: Annotated[DatabaseSettings, Depends(get_database_settings)] ) -> async_sessionmaker[AsyncSession]: + kw = {} + if "postgresql" in settings.sqlalchemy_database_uri: + kw = { + "pool_size": 100, # Initial pool size (default is 5) + "max_overflow": 10, # Extra connections if pool is exhausted + "pool_timeout": 30, # Timeout before giving up on a connection + "pool_recycle": 3600, # Recycle connections after 1 hour (for long-lived connections) + "pool_pre_ping": True, # Check if the connection is alive before using it + } + engine = create_async_engine( settings.sqlalchemy_database_uri, echo=False, # Disable SQL query logging (for performance) - pool_size=100, # Initial pool size (default is 5) - max_overflow=10, # Extra connections if pool is exhausted - pool_timeout=30, # Timeout before giving up on a connection - pool_recycle=3600, # Recycle connections after 1 hour (for long-lived connections) - pool_pre_ping=True, # Check if the connection is alive before using it future=True, # Use the future-facing SQLAlchemy 2.0 style - # connect_args={"options": "-c timezone=utc"} # Ensures timezone is UTC + **kw, ) sessionmaker = async_sessionmaker(