Skip to content

Commit

Permalink
refactor Load vars from env (#61)
Browse files Browse the repository at this point in the history
* refactor: Load vars from env

* fix: Activate uvicorn reload
  • Loading branch information
Ramimashkouk authored Jul 10, 2024
1 parent f7f5fd7 commit cd847ec
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 44 deletions.
17 changes: 12 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@ run_dev_backend: check_project_arg install_backend_env ## Runs backend in dev mo

# backend tests
.PHONY: unit_tests
unit_tests: install_backend_env ## Runs all backend unit tests
cd ${BACKEND_DIR} && poetry run pytest ./app/tests/api
cd ${BACKEND_DIR} && poetry run pytest ./app/tests/services
unit_tests: ## Runs all backend unit tests
if [ ! -d "./df_designer_project" ]; then \
cd "${BACKEND_DIR}" && \
poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \
fi

cd df_designer_project && \
poetry install && \
poetry run pytest ../${BACKEND_DIR}/app/tests/api ../${BACKEND_DIR}/app/tests/services


.PHONY: integration_tests
integration_tests: build_backend ## Runs all backend integration tests
integration_tests: ## Runs all backend integration tests
if [ ! -d "./df_designer_project" ]; then \
cd "${BACKEND_DIR}" && \
poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \
Expand All @@ -86,7 +93,7 @@ integration_tests: build_backend ## Runs all backend integration tests


.PHONY: backend_e2e_test
backend_e2e_test: build_backend ## Runs e2e backend test
backend_e2e_test: ## Runs e2e backend test
if [ ! -d "./df_designer_project" ]; then \
cd "${BACKEND_DIR}" && \
poetry run dflowd init --destination ../../ --no-input --overwrite-if-exists; \
Expand Down
29 changes: 9 additions & 20 deletions backend/df_designer/app/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import sys
from pathlib import Path

import nest_asyncio
import typer
import uvicorn
from cookiecutter.main import cookiecutter

from app.core.config import settings
from app.core.logger_config import get_logger
# Patch nest_asyncio before importing DFF
nest_asyncio.apply = lambda: None

from app.core.config import app_runner, settings # noqa: E402
from app.core.logger_config import get_logger # noqa: E402

cli = typer.Typer()

Expand Down Expand Up @@ -83,30 +85,17 @@ def run_scenario(build_id: int, project_dir: str = "."):
@cli.command("run_app")
def run_app(
ip_address: str = settings.host,
port: int = settings.backend_port,
port: int = settings.port,
conf_reload: str = str(settings.conf_reload),
project_dir: str = settings.work_directory,
) -> None:
"""Run the backend."""

# Patch nest_asyncio before import DFF
import nest_asyncio

nest_asyncio.apply = lambda: None

settings.host = ip_address
settings.backend_port = port
settings.port = port
settings.conf_reload = conf_reload.lower() in ["true", "yes", "t", "y", "1"]
settings.work_directory = project_dir
settings.uvicorn_config = uvicorn.Config(
settings.APP,
settings.host,
settings.backend_port,
reload=settings.conf_reload,
reload_dirs=str(settings.work_directory),
)
settings.server = uvicorn.Server(settings.uvicorn_config)
settings.server.run()

app_runner.run()


@cli.command("init")
Expand Down
39 changes: 30 additions & 9 deletions backend/df_designer/app/core/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import os
from pathlib import Path

import uvicorn
from dotenv import load_dotenv
from pydantic_settings import BaseSettings

load_dotenv()


class Settings(BaseSettings):
API_V1_STR: str = "/api/v1"
Expand All @@ -14,11 +18,10 @@ class Settings(BaseSettings):
start_page: Path = static_files.joinpath("index.html")
package_dir: Path = config_file_path.parents[3]

host: str = "0.0.0.0"
backend_port: int = 8000
ui_port: int = 3000
log_level: str = "debug"
conf_reload: bool = True # Enable auto-reload for development mode
host: str = os.getenv("HOST", "0.0.0.0")
port: int = int(os.getenv("PORT", 8000))
log_level: str = os.getenv("LOG_LEVEL", "info")
conf_reload: bool = os.getenv("CONF_RELOAD", "true").lower() in ["true", "1", "t", "y", "yes"]

builds_path: Path = Path(f"{work_directory}/df_designer/builds.yaml")
runs_path: Path = Path(f"{work_directory}/df_designer/runs.yaml")
Expand All @@ -27,10 +30,28 @@ class Settings(BaseSettings):
index_path: Path = Path(f"{work_directory}/bot/custom/.services_index.yaml")
snippet2lint_path: Path = Path(f"{work_directory}/bot/custom/.snippet2lint.py")

uvicorn_config: uvicorn.Config = uvicorn.Config(
APP, host, backend_port, log_level=log_level, reload=conf_reload, reload_dirs=[work_directory, str(package_dir)]
)
server: uvicorn.Server = uvicorn.Server(uvicorn_config)

class AppRunner:
def __init__(self, settings: Settings):
self.settings = settings

def run(self):
if reload := self.settings.conf_reload:
reload_conf = {
"reload": reload,
"reload_dirs": [self.settings.work_directory, str(self.settings.package_dir)],
}
else:
reload_conf = {"reload": reload}

uvicorn.run(
self.settings.APP,
host=self.settings.host,
port=self.settings.port,
log_level=self.settings.log_level,
**reload_conf,
)


settings = Settings()
app_runner = AppRunner(settings)
13 changes: 10 additions & 3 deletions backend/df_designer/app/services/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@
"""
import asyncio
import logging
import os
from abc import ABC, abstractmethod
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from dotenv import load_dotenv

from app.core.config import settings
from app.core.logger_config import get_logger, setup_logging
from app.db.base import read_conf, write_conf
from app.schemas.process_status import Status

load_dotenv()

GRACEFUL_TERMINATION_TIMEOUT = float(os.getenv("GRACEFUL_TERMINATION_TIMEOUT", 2))
PING_PONG_TIMEOUT = float(os.getenv("PING_PONG_TIMEOUT", 0.5))


def _map_to_str(params: Dict[str, Any]):
for k, v in params.items():
Expand Down Expand Up @@ -138,7 +146,7 @@ async def stop(self) -> None:
self.logger.debug("Terminating process '%s'", self.id)
self.process.terminate()
try:
await asyncio.wait_for(self.process.wait(), timeout=10.0)
await asyncio.wait_for(self.process.wait(), timeout=GRACEFUL_TERMINATION_TIMEOUT)
self.logger.debug("Process '%s' was gracefully terminated.", self.id)
except asyncio.TimeoutError:
self.process.kill()
Expand Down Expand Up @@ -172,12 +180,11 @@ async def write_stdin(self, message: bytes) -> None:

async def is_alive(self) -> bool:
"""Checks if the process is alive by writing to stdin andreading its stdout."""
timeout = 0.5
message = b"Hi\n"
try:
# Attempt to write and read from the process with a timeout.
await self.write_stdin(message)
output = await asyncio.wait_for(self.read_stdout(), timeout=timeout)
output = await asyncio.wait_for(self.read_stdout(), timeout=PING_PONG_TIMEOUT)
if not output:
return False
self.logger.debug("Process is alive and output afer communication is: %s", output.decode())
Expand Down
21 changes: 14 additions & 7 deletions backend/df_designer/app/tests/integration/test_api_integration.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import asyncio
import os

import httpx
import pytest
from dotenv import load_dotenv
from fastapi import status
from httpx import ASGITransport, AsyncClient
from httpx_ws import aconnect_ws
from httpx_ws.transport import ASGIWebSocketTransport
from fastapi import status

from app.api.deps import get_build_manager, get_run_manager
from app.core.logger_config import get_logger
from app.main import app
from app.schemas.process_status import Status
from app.tests.conftest import override_dependency, start_process

load_dotenv()

BUILD_COMPLETION_TIMEOUT = float(os.getenv("BUILD_COMPLETION_TIMEOUT", 10))
RUN_RUNNING_TIMEOUT = float(os.getenv("RUN_RUNNING_TIMEOUT", 5))

logger = get_logger(__name__)


async def _assert_process_status(response, process_manager, expected_end_status):
async def _assert_process_status(response, process_manager, expected_end_status, timeout):
assert response.json().get("status") == "ok", "Start process response status is not 'ok'"
process_manager.check_status.assert_awaited_once()

try:
await asyncio.wait_for(
process_manager.processes[process_manager.last_id].process.wait(), timeout=10
) # TODO: Consider making this timeout configurable
await asyncio.wait_for(process_manager.processes[process_manager.last_id].process.wait(), timeout=timeout)
except asyncio.exceptions.TimeoutError as exc:
if expected_end_status in [Status.ALIVE, Status.RUNNING]:
logger.debug("Loop process timed out. Expected behavior.")
Expand All @@ -42,11 +47,11 @@ async def _assert_process_status(response, process_manager, expected_end_status)
return current_status


async def _test_start_process(mocker_obj, get_manager_func, endpoint, preset_end_status, expected_end_status):
async def _test_start_process(mocker_obj, get_manager_func, endpoint, preset_end_status, expected_end_status, timeout):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as async_client:
async with override_dependency(mocker_obj, get_manager_func) as process_manager:
response = await start_process(async_client, endpoint, preset_end_status)
current_status = await _assert_process_status(response, process_manager, expected_end_status)
current_status = await _assert_process_status(response, process_manager, expected_end_status, timeout)

if current_status == Status.RUNNING:
process_manager.processes[process_manager.last_id].process.terminate()
Expand Down Expand Up @@ -111,6 +116,7 @@ async def test_start_build(mocker, end_status, process_status):
endpoint="/api/v1/bot/build/start",
preset_end_status=end_status,
expected_end_status=process_status,
timeout=BUILD_COMPLETION_TIMEOUT,
)


Expand Down Expand Up @@ -145,6 +151,7 @@ async def test_start_run(mocker, end_status, process_status):
endpoint=f"/api/v1/bot/run/start/{build_id}",
preset_end_status=end_status,
expected_end_status=process_status,
timeout=RUN_RUNNING_TIMEOUT,
)


Expand Down

0 comments on commit cd847ec

Please sign in to comment.