Skip to content

Commit

Permalink
Merge pull request #57 from deeppavlov/dev
Browse files Browse the repository at this point in the history
release: v0.1.0b1
  • Loading branch information
Ramimashkouk authored Jul 2, 2024
2 parents 3dfdbf8 + 45d370c commit c898b41
Show file tree
Hide file tree
Showing 77 changed files with 2,252 additions and 400 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@

# - name: Run back & front
# run: |
# python -m poetry run dflowd run_backend &
# python -m poetry run dflowd run_app &
# sleep 10
# working-directory: df_designer_project

Expand Down
34 changes: 34 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Introduction
We have almost finished the main functionality. Nevertheless, we will be glad to receive your pull requests and issues for adding new features if you are missing something. We know that we have weaknesses in the documentation and basic examples.
We will be glad if you contribute to Dialog Flow Designer.

## Rules for submitting a PR
All PRs are reviewed by DflowD developers team. In order to make the reviewer job easier and increase the chance that your PR will be accepted, please add a short description with information about why this PR is needed and what changes will be made.

## Development
We use poetry as a handy dependency management and packaging tool, which reads pyproject.toml to get specification for commands. poetry is a tool for command running automatization. If your environment does not support poetry, it can be installed as a python package with `pipx install poetry`. However, It's recommended to install isolated from the global Python environment, which prevents potential conflicts with other packages ([Installation on the official site](https://python-poetry.org/docs/#installing-with-the-official-installer:~:text=its%20own%20environment.-,Install%20Poetry,-The%20installer%20script)).


### Prepare the Enviroment

```bash
python3 -m venv poetry-venv \ # create virtual env and install poetry
&& poetry-venv/bin/pip install poetry==1.8.2
cd backend/df_designer \ # using poetry, install DflowD package
&& poetry install \
&& poetry shell \
&& cd ../../
```

### Documentation
Build the documentation:
```bash
make build_doc
```
`docs/_build` dir will be created and you can open the index file `./docs/_build/html/index.html` with your browser.

### Test
To run unit, integration, and end-to-end tests:
```bash
make backend_tests
```
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ WORKDIR /src2/project_dir
RUN poetry lock --no-update \
&& poetry install

CMD ["poetry", "run", "dflowd", "run_backend"]
CMD ["poetry", "run", "dflowd", "run_app"]


# #TODO: change scr to app (maybe)
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ check_project_arg:

.PHONY: run_backend
run_backend: check_project_arg ## Runs backend using the built dist. NEEDS arg: PROJECT_NAME
cd ${PROJECT_NAME} && poetry install && poetry run dflowd run_backend --conf-reload="False"
cd ${PROJECT_NAME} && poetry install && poetry run dflowd run_app --conf-reload="False"

.PHONY: run_dev_backend
run_dev_backend: check_project_arg install_backend_env ## Runs backend in dev mode. NEEDS arg: PROJECT_NAME
cd ${BACKEND_DIR} && poetry run dflowd run_backend --project-dir ../../${PROJECT_NAME}
cd ${BACKEND_DIR} && poetry run dflowd run_app --project-dir ../../${PROJECT_NAME}

# backend tests
.PHONY: unit_tests
Expand Down Expand Up @@ -136,3 +136,8 @@ run_dev: check_project_arg install_env ## Runs both backend and frontend in dev
.PHONY: init_proj
init_proj: install_backend_env ## Initiates a new project using dflowd
cd ${BACKEND_DIR} && poetry run dflowd init --destination ../../


.PHONY: build_docs
build_docs: install_backend_env ## Builds the docs
cd docs && make html && cd ../
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ WORKDIR /${PROJECT_DIR}
RUN poetry lock --no-update \
&& poetry install

CMD ["poetry", "run", "dflowd", "run_backend"]
CMD ["poetry", "run", "dflowd", "run_app"]
149 changes: 131 additions & 18 deletions backend/df_designer/app/api/api_v1/endpoints/bot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import asyncio
from typing import Optional, Union
from typing import Any, Optional

from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, WebSocket, WebSocketException, status

from app.api import deps
from app.core.logger_config import get_logger
from app.schemas.pagination import Pagination
from app.schemas.preset import Preset
from app.services.index import Index
from app.services.process_manager import BuildManager, ProcessManager, RunManager
from app.services.websocket_manager import WebSocketManager

Expand All @@ -15,7 +16,9 @@
logger = get_logger(__name__)


async def _stop_process(id_: int, process_manager: ProcessManager, process="run"):
async def _stop_process(id_: int, process_manager: ProcessManager, process="run") -> dict[str, str]:
"""Stops a `build` or `run` process with the given id."""

try:
await process_manager.stop(id_)
except (RuntimeError, ProcessLookupError) as e:
Expand All @@ -29,6 +32,7 @@ async def _stop_process(id_: int, process_manager: ProcessManager, process="run"


async def _check_process_status(id_: int, process_manager: ProcessManager) -> dict[str, str]:
"""Checks the status of a `build` or `run` process with the given id."""
if id_ not in process_manager.processes:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
Expand All @@ -40,32 +44,83 @@ async def _check_process_status(id_: int, process_manager: ProcessManager) -> di

@router.post("/build/start", status_code=201)
async def start_build(
preset: Preset, background_tasks: BackgroundTasks, build_manager: BuildManager = Depends(deps.get_build_manager)
):
preset: Preset,
background_tasks: BackgroundTasks,
build_manager: BuildManager = Depends(deps.get_build_manager),
index: Index = Depends(deps.get_index),
) -> dict[str, str | int]:

"""Starts a `build` process with the given preset.
This runs a background task to check the status of the process every 2 seconds.
Args:
preset (Preset): The preset to set the build process for. Must be among ("success", "failure", "loop")
Returns:
{"status": "ok", "build_id": build_id}: in case of **starting** the build process successfully.
"""

await asyncio.sleep(preset.wait_time)
build_id = await build_manager.start(preset)
background_tasks.add_task(build_manager.check_status, build_id)
background_tasks.add_task(build_manager.check_status, build_id, index)
logger.info("Build process '%s' has started", build_id)
return {"status": "ok", "build_id": build_id}


@router.get("/build/stop/{build_id}", status_code=200)
async def stop_build(*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)):
async def stop_build(*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)) -> dict[str, str]:
"""Stops a `build` process with the given id.
Args:
build_id (int): The id of the process to stop.
build_id (BuildManager): The process manager dependency to stop the process with.
Raises:
HTTPException: With status code 404 if the process is not found.
Returns:
{"status": "ok"}: in case of stopping a process successfully.
"""
return await _stop_process(build_id, build_manager, process="build")


@router.get("/build/status/{build_id}", status_code=200)
async def check_build_status(*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)):
async def check_build_status(
*, build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager)
) -> dict[str, str]:
"""Checks the status of a `build` process with the given id.
Args:
build_id (int): The id of the process to check.
build_manager (BuildManager): The process manager dependency to check the process with.
Raises:
HTTPException: With status code 404 if the process is not found.
Returns:
{"status": "completed"}: in case of a successfully completed process.
{"status": "running"}: in case of a still running process.
{"status": "stopped"}: in case of a stopped process.
{"status": "failed"}: in case of a failed-to-run process.
"""
return await _check_process_status(build_id, build_manager)


@router.get("/builds", response_model=Optional[Union[list, dict]], status_code=200)
@router.get("/builds", response_model=Optional[list | dict], status_code=200)
async def check_build_processes(
build_id: Optional[int] = None,
build_manager: BuildManager = Depends(deps.get_build_manager),
run_manager: RunManager = Depends(deps.get_run_manager),
pagination: Pagination = Depends(),
):
) -> Optional[dict[str, Any]] | list[dict[str, Any]]:
"""Checks the status of all `build` processes and returns them along with their runs info.
The offset and limit parameters can be used to paginate the results.
Args:
build_id (Optional[int]): The id of the process to check. If not specified, all processes will be returned.
"""
if build_id is not None:
return await build_manager.get_build_info(build_id, run_manager)
else:
Expand All @@ -77,7 +132,11 @@ async def check_build_processes(
@router.get("/builds/logs/{build_id}", response_model=Optional[list], status_code=200)
async def get_build_logs(
build_id: int, build_manager: BuildManager = Depends(deps.get_build_manager), pagination: Pagination = Depends()
):
) -> Optional[list[str]]:
"""Gets the logs of a specific `build` process.
The offset and limit parameters can be used to paginate the results.
"""
if build_id is not None:
return await build_manager.fetch_build_logs(build_id, pagination.offset(), pagination.limit)

Expand All @@ -89,7 +148,19 @@ async def start_run(
preset: Preset,
background_tasks: BackgroundTasks,
run_manager: RunManager = Depends(deps.get_run_manager)
):
) -> dict[str, str | int]:
"""Starts a `run` process with the given preset.
This runs a background task to check the status of the process every 2 seconds.
Args:
build_id (int): The id of the build process to start running.
preset (Preset): The preset to set the build process for. Must be among ("success", "failure", "loop")
Returns:
{"status": "ok", "build_id": run_id}: in case of **starting** the run process successfully.
"""

await asyncio.sleep(preset.wait_time)
run_id = await run_manager.start(build_id, preset)
background_tasks.add_task(run_manager.check_status, run_id)
Expand All @@ -98,21 +169,57 @@ async def start_run(


@router.get("/run/stop/{run_id}", status_code=200)
async def stop_run(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)):
async def stop_run(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> dict[str, str]:
"""Stops a `run` process with the given id.
Args:
run_id (int): The id of the process to stop.
run_manager (RunManager): The process manager dependency to stop the process with.
Raises:
HTTPException: With status code 404 if the process is not found.
Returns:
{"status": "ok"}: in case of stopping a process successfully.
"""

return await _stop_process(run_id, run_manager, process="run")


@router.get("/run/status/{run_id}", status_code=200)
async def check_run_status(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)):
async def check_run_status(*, run_id: int, run_manager: RunManager = Depends(deps.get_run_manager)) -> dict[str, Any]:
"""Checks the status of a `run` process with the given id.
Args:
build_id (int): The id of the process to check.
run_manager (RunManager): The process manager dependency to check the process with.
Raises:
HTTPException: With status code 404 if the process is not found.
Returns:
{"status": "alive"}: in case of a successfully run process. Now it is able to communicate.
{"status": "running"}: in case of a still running process.
{"status": "stopped"}: in case of a stopped process.
{"status": "failed"}: in case of a failed-to-run process.
"""
return await _check_process_status(run_id, run_manager)


@router.get("/runs", response_model=Optional[Union[list, dict]], status_code=200)
@router.get("/runs", response_model=Optional[list | dict], status_code=200)
async def check_run_processes(
run_id: Optional[int] = None,
run_manager: RunManager = Depends(deps.get_run_manager),
pagination: Pagination = Depends(),
):
) -> Optional[dict[str, Any]] | list[dict[str, Any]]:
"""Checks the status of all `run` processes and returns them.
The offset and limit parameters can be used to paginate the results.
Args:
run_id (Optional[int]): The id of the process to check. If not specified, all processes will be returned.
"""

if run_id is not None:
return await run_manager.get_run_info(run_id)
else:
Expand All @@ -122,7 +229,11 @@ async def check_run_processes(
@router.get("/runs/logs/{run_id}", response_model=Optional[list], status_code=200)
async def get_run_logs(
run_id: int, run_manager: RunManager = Depends(deps.get_run_manager), pagination: Pagination = Depends()
):
) -> Optional[list[str]]:
"""Gets the logs of a specific `run` process.
The offset and limit parameters can be used to paginate the results.
"""
if run_id is not None:
return await run_manager.fetch_run_logs(run_id, pagination.offset(), pagination.limit)

Expand All @@ -132,10 +243,12 @@ async def connect(
websocket: WebSocket,
websocket_manager: WebSocketManager = Depends(deps.get_websocket_manager),
run_manager: RunManager = Depends(deps.get_run_manager),
):
"""Establish a WebSocket connection to communicate with an active bot identified by its 'run_id'.
) -> None:
"""Establishes a WebSocket connection to communicate with an alive run process identified by its 'run_id'.
The WebSocket URL should adhere to the format: /bot/run/connect?run_id=<run_id>.
"""

logger.debug("Connecting to websocket")
run_id = websocket.query_params.get("run_id")

Expand Down
Loading

0 comments on commit c898b41

Please sign in to comment.