diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index bcc6674..0000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Docker Build Only - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - push: - branches: [ "!main" ] - pull_request: - branches: [ "*" ] - - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - - -jobs: - docker_build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=tag - type=ref,event=pr - type=raw,value=latest,enable={{is_default_branch}} - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build Docker image - id: build-and-push - uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 - with: - context: . - push: false - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 - #cache-from: type=gha - #cache-to: type=gha,mode=max diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 4d44c40..20b88c1 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -7,9 +7,11 @@ name: Docker Build and Publish on: push: - branches: [ "main" ] + branches: [ "*" ] # Publish semver tags as releases. tags: [ '*' ] + schedule: # Run on Tuesday's at noon + - cron: '0 12 * * 2' env: # Use docker.io for Docker Hub if empty @@ -21,7 +23,6 @@ env: jobs: build_and_publish: - # environment: production runs-on: ubuntu-latest permissions: contents: read @@ -61,7 +62,7 @@ jobs: type=ref,event=pr type=raw,value=latest,enable={{is_default_branch}} - # Build and push Docker image with Buildx (don't push on PR) + # Build and push Docker image with Buildx # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e70236..faceefd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: check-json @@ -12,12 +12,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/kynan/nbstripout - rev: 0.6.1 + rev: 0.7.1 hooks: - id: nbstripout - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.10 + rev: v0.4.9 hooks: # Run the linter. - id: ruff diff --git a/Makefile b/Makefile index 372f38b..07f26a7 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,6 @@ init: # Do the initial configuration of the project @test -e .env || cp example.env .env -ifeq ($(shell uname),Darwin) - @sed -i '' 's|^PROJECT_PATH=.*|PROJECT_PATH=$(shell pwd | sed 's/\//\\\//g')|' .env -else - @sed -i 's/^PROJECT_PATH=.*/PROJECT_PATH=$(shell pwd | sed 's/\//\\\//g')/' .env -endif .env: init diff --git a/compose.yaml b/compose.yaml index e28660f..17f7658 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,7 +1,4 @@ name: sleep_module -include: - - path: tests/wei.compose.yaml - env_file: .env services: sleep_module: container_name: sleep_module @@ -14,7 +11,45 @@ services: volumes: - ./src:/home/app/sleep_module/src - ./tests:/home/app/sleep_module/tests - env_file: .env command: python -m sleep_rest_node --port 2000 ports: - 2000:2000 +##################### +# WEI Core Services # +##################### + wei_server: + image: ghcr.io/ad-sdl/wei + container_name: wei_server + ports: + - 8000:8000 + environment: + - PYTHONUNBUFFERED=1 # Fix weird bug with empty logging + - USER_ID=${USER_ID:-1000} + - GROUP_ID=${GROUP_ID:-1000} + volumes: + - ${WORKCELLS_DIR}:/workcell_defs + command: python3 -m wei.server --workcell /workcell_defs/${WORKCELL_FILENAME} + depends_on: + - wei_redis + profiles: [wei] + wei_engine: + image: ghcr.io/ad-sdl/wei + container_name: wei_engine + volumes: + - ${WORKCELLS_DIR}:/workcell_defs + environment: + - PYTHONUNBUFFERED=1 # Fix weird bug with empty logging + - USER_ID=${USER_ID:-1000} + - GROUP_ID=${GROUP_ID:-1000} + command: python3 -m wei.engine --workcell /workcell_defs/${WORKCELL_FILENAME} + depends_on: + - wei_redis + - wei_server + profiles: [wei] + wei_redis: + image: redis + container_name: wei_redis + ports: + - 6379:6379 + command: redis-server --save 60 1 --loglevel warning + profiles: [wei] diff --git a/example.env b/example.env index d202f98..a3e708b 100644 --- a/example.env +++ b/example.env @@ -1,5 +1,3 @@ -# The PROJECT_PATH should be the absolute path to the root of the repo -PROJECT_PATH= WORKCELL_FILENAME=test_workcell.yaml -WORKCELLS_DIR=${PROJECT_PATH}/tests/workcell_defs +WORKCELLS_DIR=./tests/workcell_defs IMAGE=ghcr.io/ad-sdl/sleep_module diff --git a/pyproject.toml b/pyproject.toml index 31d1c5c..b0c2549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sleep_module" -version = "1.3.0" +version = "1.4.0" description = "A simple example module that implements a sleep(t) function" authors = [{name = "Ryan D. Lewis", email="ryan.lewis@anl.gov"}] dependencies = [ diff --git a/src/sleep_rest_node.py b/src/sleep_rest_node.py index 8848ff2..cda20fb 100644 --- a/src/sleep_rest_node.py +++ b/src/sleep_rest_node.py @@ -1,172 +1,41 @@ """ REST-based node that interfaces with WEI and provides a simple Sleep(t) function """ -import json + import time -from argparse import ArgumentParser -from contextlib import asynccontextmanager from pathlib import Path -from typing import Any - -from fastapi import FastAPI -from fastapi.responses import JSONResponse -from wei.core.data_classes import ( - ModuleAbout, - ModuleAction, - ModuleActionArg, - ModuleStatus, - StepResponse, - StepStatus, -) -from wei.helpers import extract_version - -state: ModuleStatus -module_resources: Any - - -@asynccontextmanager # type: ignore -async def lifespan(app: FastAPI) -> None: - """Initial run function for the app, initializes the state - Parameters - ---------- - app : FastApi - The REST API app being initialized - Returns - ------- - None""" - global state, module_resources - try: - state = ModuleStatus.IDLE - module_resources = [] - except Exception as err: - print(err) - state = ModuleStatus.ERROR - - # Yield control to the application - yield - - # Do any cleanup here - pass - - -app = FastAPI( - lifespan=lifespan, +from fastapi.datastructures import State +from typing_extensions import Annotated +from wei.modules.rest_module import RESTModule +from wei.types.step_types import ActionRequest, StepResponse, StepStatus +from wei.utils import extract_version + +rest_module = RESTModule( + name="sleep_node", + version=extract_version(Path(__file__).parent.parent / "pyproject.toml"), + description="An example node that sleeps for a time ", + model="sleeper", ) -@app.get("/state") -def get_state() -> JSONResponse: - """Returns the current state of the module""" - global state - return JSONResponse(content={"State": state}) - - -@app.get("/about") -async def about() -> JSONResponse: - """Returns a description of the actions and resources the module supports""" - global state - about = ModuleAbout( - name="Sleep Module", - description="A module that sleeps for a specified amount of time", - interface="wei_rest_node", - version=extract_version(Path(__file__).parent.parent / "pyproject.toml"), - actions=[ - ModuleAction( - name="sleep", - description="Sleep for a specified amount of time", - args=[ - ModuleActionArg( - name="t", - description="The amount of time to sleep for, as an integer in seconds", - type="int", - required=True, - ) - ], - ) - ], - resource_pools=[], - ) - return JSONResponse(content=about.model_dump(mode="json")) - - -@app.get("/resources") -async def resources() -> JSONResponse: - """Returns the current resources available to the module""" - global state, module_resources - return JSONResponse(content={"Resources": module_resources}) - - -@app.post("/action") -def do_action( - action_handle: str, # The action to be performed - action_vars: str, # Any arguments necessary to run that action +@rest_module.action(name="sleep", description="An action that sleeps for t seconds") +def sleep( + state: State, + action: ActionRequest, + t: Annotated[int, "time in seconds to sleep for"], ) -> StepResponse: """ - Runs an action on the module - - Parameters - ---------- - action_handle : str - The name of the action to be performed - action_vars : str - Any arguments necessary to run that action. - This should be a JSON object encoded as a string. - - Returns - ------- - response: StepResponse - A response object containing the result of the action + Sleeps the module for t seconds """ - global state - if state == ModuleStatus.BUSY: - return StepResponse( - action_response=StepStatus.FAILED, - action_msg="", - action_log="Module is busy", - ) - state = ModuleStatus.BUSY - - try: - if action_handle == "sleep": - time.sleep(int(json.loads(action_vars)["t"])) - state = ModuleStatus.IDLE - return StepResponse( - action_response=StepStatus.SUCCEEDED, - action_msg="", - action_log="", - ) - else: - # Handle Unsupported actions - state = ModuleStatus.IDLE - return StepResponse( - action_response=StepStatus.FAILED, - action_msg="", - action_log="Unsupported action", - ) - except Exception as e: - print(str(e)) - state = ModuleStatus.IDLE - return StepResponse( - action_response=StepStatus.FAILED, - action_msg="", - action_log=str(e), - ) + time.sleep(int(t)) + return StepResponse( + action_response=StepStatus.SUCCEEDED, + action_msg="", + action_log="", + ) if __name__ == "__main__": - import uvicorn - - parser = ArgumentParser() - parser.add_argument("--host", type=str, default="0.0.0.0", help="Host IP") - parser.add_argument("--port", type=str, default="2000", help="Port to serve on") - # Add any additional arguments here - args = parser.parse_args() - - uvicorn.run( - "sleep_rest_node:app", - host=args.host, - port=int(args.port), - reload=True, - ) + rest_module.start() diff --git a/tests/test_module.py b/tests/test_module.py index e315a2e..b153149 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -6,7 +6,7 @@ import requests from wei import ExperimentClient -from wei.core.data_classes import ModuleAbout, Workcell, WorkflowStatus +from wei.types import ModuleAbout, Workcell, WorkflowStatus class TestWEI_Base(unittest.TestCase): diff --git a/tests/wei.compose.yaml b/tests/wei.compose.yaml deleted file mode 100644 index 7d0673f..0000000 --- a/tests/wei.compose.yaml +++ /dev/null @@ -1,38 +0,0 @@ -##################### -# WEI Core Services # -##################### -services: - wei_server: - image: ghcr.io/ad-sdl/wei - container_name: wei_server - ports: - - 8000:8000 - env_file: ../.env - environment: - - PYTHONUNBUFFERED=1 # Fix weird bug with empty logging - volumes: - - ${WORKCELLS_DIR}:/workcell_defs - command: python3 -m wei.server --workcell /workcell_defs/${WORKCELL_FILENAME} - depends_on: - - wei_redis - profiles: [wei] - wei_engine: - image: ghcr.io/ad-sdl/wei - container_name: wei_engine - volumes: - - ${WORKCELLS_DIR}:/workcell_defs - env_file: ../.env - environment: - - PYTHONUNBUFFERED=1 # Fix weird bug with empty logging - command: python3 -m wei.engine --workcell /workcell_defs/${WORKCELL_FILENAME} - depends_on: - - wei_redis - - wei_server - profiles: [wei] - wei_redis: - image: redis - container_name: wei_redis - ports: - - 6379:6379 - command: redis-server --save 60 1 --loglevel warning - profiles: [wei]