diff --git a/.github/actions/install-dependencies/action.yaml b/.github/actions/install-dependencies/action.yaml new file mode 100644 index 0000000000..a7b539bbc2 --- /dev/null +++ b/.github/actions/install-dependencies/action.yaml @@ -0,0 +1,45 @@ +name: 'Install Dependencies' +description: 'Sets up common tools and dependencies used across workflows' +inputs: + install-just: + description: 'Install Just task runner' + required: false + default: 'true' + install-uv: + description: 'Install uv package manager' + required: false + default: 'true' + just-version: + description: 'Version of Just to install' + required: false + default: '1.21.0' + uv-cache-dependency-glob: + description: 'Glob pattern for uv cache dependency files' + required: false + default: '' + uv-version: + description: 'Version of uv to install' + required: false + default: '0.9.7' + python-version: + description: 'Python version' + required: false + default: '3.13' + +runs: + using: 'composite' + steps: + - name: Install Just + if: ${{ inputs.install-just == 'true' }} + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0 + with: + just-version: ${{ inputs.just-version }} + + - name: Install uv + if: ${{ inputs.install-uv == 'true' }} + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + with: + version: ${{ inputs.uv-version }} + enable-cache: ${{ inputs.uv-cache-dependency-glob != '' && 'true' || 'false' }} + cache-dependency-glob: ${{ inputs.uv-cache-dependency-glob }} + python-version: ${{ inputs.python-version }} diff --git a/.github/workflows/backend-lint-and-test.yaml b/.github/workflows/backend-lint-and-test.yaml index 475fa75e1b..32389a9328 100644 --- a/.github/workflows/backend-lint-and-test.yaml +++ b/.github/workflows/backend-lint-and-test.yaml @@ -66,12 +66,10 @@ jobs: with: persist-credentials: false - - name: Install uv and set the Python version - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - name: Install dependencies + uses: ./.github/actions/install-dependencies with: - version: "0.9.7" - enable-cache: false - python-version: "3.13" + uv-cache-dependency-glob: 'application/backend/uv.lock' - name: Install OpenCV dependencies run: | @@ -80,37 +78,30 @@ jobs: libgl1 \ libglib2.0-0 - - name: Prepare venv and install Python dependencies - working-directory: application/backend - run: | - uv lock --check - uv sync --frozen --extra cpu --extra mqtt - - name: Check formatting with ruff working-directory: application/backend run: | - uv run ruff check --output-format=github . - uv run ruff format --check . + just ruff - name: Check imports with import-linter working-directory: application/backend run: | - uv run lint-imports + just lint-imports - name: Check source code with mypy working-directory: application/backend run: | - uv run mypy . --config-file=pyproject.toml + just mypy - name: Run unit tests working-directory: application/backend run: | - uv run pytest tests/unit + just test-unit - name: Run integration tests working-directory: application/backend run: | - uv run pytest tests/integration + just test-integration required_check: name: Required Check backend-lint-and-test diff --git a/.github/workflows/lib-lint-and-test.yaml b/.github/workflows/lib-lint-and-test.yaml index 53be12559f..f5a8d3b695 100644 --- a/.github/workflows/lib-lint-and-test.yaml +++ b/.github/workflows/lib-lint-and-test.yaml @@ -67,24 +67,16 @@ jobs: with: persist-credentials: false - - name: Install uv and set the Python version - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - name: Install dependencies + uses: ./.github/actions/install-dependencies with: - version: "0.9.7" - enable-cache: false + uv-cache-dependency-glob: 'library/uv.lock' python-version: "3.10" # Use the lowest supported Python version for linting/type-checking - - name: Installing dependencies - working-directory: library - run: | - uv lock --check - uv sync --frozen - - name: Check formatting with ruff working-directory: library run: | - uv run ruff check --output-format=github . - uv run ruff format --check . + just ruff # TODO: remove 'continue-on-error' after addressing all mypy issues # https://github.com/open-edge-platform/training_extensions/issues/5006 @@ -92,7 +84,7 @@ jobs: continue-on-error: true working-directory: library run: | - uv run mypy src/otx + just mypy unit-tests: runs-on: [self-hosted, linux, x64, dev, dmount] @@ -122,24 +114,17 @@ jobs: with: toolchain: stable - - name: Install uv and set the Python version - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + - name: Install dependencies + uses: ./.github/actions/install-dependencies with: - version: "0.9.7" - enable-cache: false + uv-cache-dependency-glob: 'library/uv.lock' python-version: ${{ matrix.python-version }} - - name: Installing dependencies - working-directory: library - run: | - uv lock --check - uv sync --frozen - - name: Unit testing (excluding models) working-directory: library env: OMP_NUM_THREADS: "1" - run: uv run pytest tests/unit --ignore=tests/unit/backend/native/models --cov --cov-report=xml --cov-append + run: just test-unit # Unit test models separately using --forked flag so that each is run in a separate process. # Running these models after each other from the same process can cause segfaults @@ -147,7 +132,7 @@ jobs: working-directory: library env: OMP_NUM_THREADS: "1" - run: uv run pytest tests/unit/backend/native/models --cov --cov-report=xml --cov-append --forked + run: just test-unit-native-models - name: Upload coverage reports to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 @@ -182,19 +167,18 @@ jobs: with: persist-credentials: false + - name: Install dependencies + uses: ./.github/actions/install-dependencies + with: + uv-cache-dependency-glob: 'library/uv.lock' + python-version: ${{ matrix.python-version }} + # TODO remove after switching to an official Datumaro release with pre-built wheels - name: Installing Rust toolchain uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 with: toolchain: stable - - name: Install uv and set the Python version - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - with: - version: "0.9.7" - enable-cache: false - python-version: ${{ matrix.python-version }} - - name: Installing dependencies working-directory: library run: | @@ -206,13 +190,7 @@ jobs: env: CUBLAS_WORKSPACE_CONFIG: ":4096:8" run: | - uv run --frozen --extra cuda pytest tests/integration \ - -ra \ - --showlocals \ - --csv=${{ matrix.task }}_Python-${{ matrix.python-version }}.csv \ - --task ${{ matrix.task }} \ - --open-subprocess \ - --run-category-only + just device='cuda' test-integration '${{ matrix.task }}_Python-${{ matrix.python-version }}.csv' '${{ matrix.task }}' required-check: name: Required Check lib-lint-and-test diff --git a/.github/workflows/ui-lint-and-test.yaml b/.github/workflows/ui-lint-and-test.yaml index 064c52a0e7..c2275a291b 100644 --- a/.github/workflows/ui-lint-and-test.yaml +++ b/.github/workflows/ui-lint-and-test.yaml @@ -69,12 +69,8 @@ jobs: with: persist-credentials: false - - name: Install uv and set the Python version - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - with: - version: "0.9.7" - enable-cache: false - python-version: "3.13" + - name: Install dependencies + uses: ./.github/actions/install-dependencies - name: Install OpenCV dependencies run: | @@ -90,13 +86,12 @@ jobs: - name: Get OpenAPI spec working-directory: application/backend run: | - export PYTHONPATH=. - uv run app/cli.py gen-api --target-path openapi-spec.json + just gen-openapi - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: openapi-spec - path: application/backend/openapi-spec.json + path: application/backend/.build/openapi-spec.json build: name: Build UI diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 931243c4f4..6b6c63a268 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -88,18 +88,10 @@ jobs: persist-credentials: false # Install and setup build tools - - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: "3.13" - - - name: Install uv - uses: astral-sh/setup-uv@a02a550bdd3185dba2ebb6aa98d77047ce54ad21 # v6.2.1 + - name: Install dependencies + uses: ./.github/actions/install-dependencies with: - version: "0.9.7" - enable-cache: true - cache-dependency-glob: "application/backend/uv.lock" + uv-cache-dependency-glob: 'application/backend/uv.lock' - name: Install Rust uses: dtolnay/rust-toolchain@6d9817901c499d6b02debbb57edb38d33daa680b @@ -115,25 +107,17 @@ jobs: # Build backend - - name: Build venv - working-directory: application/backend - env: - DEVICE: ${{ inputs.device || 'xpu' }} - run: | - uv lock --check - uv sync --extra installer --extra $env:DEVICE --extra mqtt --frozen --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org - - name: Build OpenAPI spec working-directory: application/backend env: - PYTHONPATH: "." + DEVICE: ${{ inputs.device || 'xpu' }} run: | - uv run --no-sync app/cli.py gen-api --target-path .build/openapi-spec.json + just --shell "${{ env.GIT_SH_PATH }}" --shell-arg -c device="$env:DEVICE" gen-openapi - name: Build executable working-directory: application/backend run: | - uv run pyinstaller --noconfirm geti-tune.spec + just --shell "${{ env.GIT_SH_PATH }}" --shell-arg -c device="$env:DEVICE" pyinstaller # Copy backend artifacts to UI diff --git a/application/Justfile b/application/Justfile new file mode 100644 index 0000000000..7c17b0ffee --- /dev/null +++ b/application/Justfile @@ -0,0 +1,70 @@ +#!/usr/bin/env just --justfile +import "recipes.justfile" + +# Use bash as the shell for evaluating backtick expressions +set shell := ["bash", "-cu"] + +# ------------------------------------------------------------------------------------------------- +# Runtime variables +# ------------------------------------------------------------------------------------------------- +port := "7860" +ui-port := "3000" +webcam-device := "/dev/video0" +data-dir := "" +host := "0.0.0.0" +webrtc-ports := "50000-51000" +device := "cpu" # cpu, cuda, xpu + +# ------------------------------------------------------------------------------------------------- +# Variables used in the Justfile to build in Docker image +# ------------------------------------------------------------------------------------------------- +docker-build-context := "" +build-target := "cpu" # options: cpu, gpu, xpu +component-name := "geti-tune" +container-registry := 'localhost:5000/open-edge-platform' +version := "latest" +image-name := container-registry / component-name / build-target + ":" + version + +host-ip := `uv run python -c "import socket; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.connect(('8.8.8.8',80)); print(s.getsockname()[0]); s.close()"` + +docker-build-args := "--build-arg http_proxy --build-arg https_proxy --build-arg no_proxy --build-arg HTTP_PROXY --build-arg HTTPS_PROXY --build-arg NO_PROXY" +docker-run-args := "--env http_proxy --env https_proxy --env no_proxy --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY" + +# Run both backend and frontend development servers +dev: + #!/usr/bin/env bash + set -euo pipefail + + export PUBLIC_API_URL="http://{{ host-ip }}:{{ port }}" + + (cd backend && just port={{ port }} host={{ host }} device={{ device }} dev) & + (cd ui && npm install && PORT={{ ui-port }} npm start) & + + wait + +# Build geti tune with optional port mapping +build-image: + @echo "Building docker image for component: {{ component-name }}" + @docker build \ + {{ docker-build-args }} \ + {{ docker-build-context }} \ + --tag "{{ image-name }}" \ + --file docker/Dockerfile . \ + --target geti-tune-{{ build-target }} + +# launch geti tune with optional webcam, volume and port mapping +run-image: build-image + @echo "Running docker image for component: {{ component-name }} with build-target: {{ build-target }}" + @echo "Proxy args: {{ docker-run-args }}" + @docker run --rm \ + {{ docker-run-args }} \ + --sysctl net.ipv4.ip_local_port_range="{{ replace(webrtc-ports, '-', ' ') }}" \ + --publish {{ webrtc-ports }}:{{ webrtc-ports }}/udp \ + --publish {{ port }}:{{ port }} \ + --env PORT={{ port }} \ + --env HOST={{ host }} \ + --env WEBRTC_ADVERTISE_IP='{{ host-ip }}' \ + {{ if data-dir != "" { " --volume " + data-dir + ":/geti_prompt/data" } else { "" } }} \ + {{ if path_exists(webcam-device) == "true" { " --device " + webcam-device + ":" + webcam-device } else { "" } }} \ + --name "{{ component-name }}-{{ version }}" \ + "{{ image-name }}" \ No newline at end of file diff --git a/application/backend/Justfile b/application/backend/Justfile new file mode 100644 index 0000000000..550162e2b3 --- /dev/null +++ b/application/backend/Justfile @@ -0,0 +1,88 @@ +#!/usr/bin/env just --justfile +import "../recipes.justfile" + +port := "7860" +host := "0.0.0.0" + +# Ensure version in GHA workflow matches uv version here +uv_version := "0.9.7" +device := "cpu" # cpu, cuda, xpu + +default: + @just --list + +# Generate OpenAPI specification from FastAPI app +gen-openapi: _venv + PYTHONPATH=. uv run --no-sync app/cli.py gen-api --target-path .build/openapi-spec.json + +# Initialize database +init-db: _venv + PYTHONPATH=. uv run --no-sync app/cli.py init-db + +# Seed database +seed-db: _venv init-db + echo "Seeding database with initial data..." + PYTHONPATH=. uv run --no-sync app/cli.py seed --with-model=True || exit 0 + +# Start FastAPI development server with auto-reload +dev: _venv seed-db download-files + CORS_ORIGINS='*' \ + PYTHONPATH=app uv run uvicorn main:app --reload --host {{ host }} --port {{ port }} + +_pre-venv: + if ! uv self version | grep {{uv_version}} >/dev/null; then \ + curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/astral-sh/uv/releases/download/{{uv_version}}/uv-installer.sh" | sh; \ + fi + +_venv: _pre-venv + uv lock --check + uv sync --extra {{ device }} --extra mqtt --frozen --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org + +# Generate uv lock file +venv-lock: _pre-venv + uv lock + +# Run unit tests +test-unit: _venv + if [ -d "./tests/unit" ]; then \ + uv run pytest ./tests/unit -v; \ + else \ + echo "./tests/unit directory not found - skipping unit tests"; \ + fi + +# Run integration tests +test-integration: _venv + if [ -d "./tests/integration" ]; then \ + uv run pytest ./tests/integration -v; \ + else \ + echo "./tests/integration directory not found - skipping integration tests"; \ + fi + +# Run all tests (unit + integration) +tests: test-unit test-integration + +# Fix code style and format with ruff +style-fix: _venv + uv run ruff check --config pyproject.toml --fix + uv run ruff format --config pyproject.toml + +# Run linters (ruff + mypy) +lint: _venv ruff mypy + +ruff: + uv run ruff check --config pyproject.toml --output-format=github . + uv run ruff format --check --config pyproject.toml . + +lint-imports: + uv run lint-imports + +mypy: + uv run mypy . --config-file=pyproject.toml + +# ------------------------------------------------------------------------------------------------- +# Installer +# ------------------------------------------------------------------------------------------------- + +pyinstaller: _venv + uv sync --extra installer --extra {{device}} --extra mqtt --frozen --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org + uv run pyinstaller --noconfirm geti-tune.spec \ No newline at end of file diff --git a/application/backend/run.sh b/application/backend/run.sh deleted file mode 100755 index 6a558d11a5..0000000000 --- a/application/backend/run.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# ----------------------------------------------------------------------------- -# run.sh - Script to run the Geti Tune FastAPI server -# -# Features: -# - Optionally seed the database before starting the server by setting: -# SEED_DB=true -# - Optionally download test video and model files before starting the server by setting: -# DOWNLOAD_FILES=true -# -# Usage: -# SEED_DB=true DOWNLOAD_FILES=true ./run.sh # Seed database, download data and launch the server -# ./run.sh # Run server without seeding or downloading files -# -# Environment variables: -# SEED_DB If set to "true", runs `uv run app/cli seed` before starting the server. -# DOWNLOAD_FILES If set to "true", downloads test video and model files if not already present. -# APP_MODULE Python module to run (default: app/main.py) -# UV_CMD Command to launch Uvicorn (default: "uv run") -# -# Requirements: -# - 'uv' CLI tool (Uvicorn) installed and available in PATH -# - Python modules and dependencies installed correctly -# ----------------------------------------------------------------------------- - -SEED_DB=${SEED_DB:-false} -DOWNLOAD_FILES=${DOWNLOAD_FILES:-false} -APP_MODULE=${APP_MODULE:-app/main.py} -UV_CMD=${UV_CMD:-uv run} - -export PYTHONUNBUFFERED=1 -export PYTHONPATH=. - -if [[ "$SEED_DB" == "true" ]]; then - echo "Seeding the database..." - rm data/geti_tune.db || true - $UV_CMD app/cli.py init-db - $UV_CMD app/cli.py seed --with-model=True -fi - -# URLs and target paths -DETECTION_VIDEO_URL="https://storage.geti.intel.com/test-data/geti-tune/media/card-video.mp4" -DETECTION_VIDEO_TARGET="data/media/card-video.mp4" -DETECTION_MODEL_XML_URL="https://storage.geti.intel.com/test-data/geti-tune/models/ssd-card-detection.xml" -DETECTION_MODEL_BIN_URL="https://storage.geti.intel.com/test-data/geti-tune/models/ssd-card-detection.bin" -DETECTION_MODEL_TARGET_DIR="data/projects/9d6af8e8-6017-4ebe-9126-33aae739c5fa/models/977eeb18-eaac-449d-bc80-e340fbe052ad" -DETECTION_MODEL_XML_TARGET="$DETECTION_MODEL_TARGET_DIR/model.xml" -DETECTION_MODEL_BIN_TARGET="$DETECTION_MODEL_TARGET_DIR/model.bin" - -SEGMENTATION_VIDEO_URL="https://storage.geti.intel.com/test-data/geti-tune/media/fish-video.mp4" -SEGMENTATION_VIDEO_TARGET="data/media/fish-video.mp4" -SEGMENTATION_MODEL_XML_URL="https://storage.geti.intel.com/test-data/geti-tune/models/rtmdet-tiny-fish-segmentation.xml" -SEGMENTATION_MODEL_BIN_URL="https://storage.geti.intel.com/test-data/geti-tune/models/rtmdet-tiny-fish-segmentation.bin" -SEGMENTATION_MODEL_TARGET_DIR="data/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890/models/c3d4e5f6-a7b8-9012-cdef-123456789012" -SEGMENTATION_MODEL_XML_TARGET="$SEGMENTATION_MODEL_TARGET_DIR/model.xml" -SEGMENTATION_MODEL_BIN_TARGET="$SEGMENTATION_MODEL_TARGET_DIR/model.bin" - -if [[ "$DOWNLOAD_FILES" == "true" ]]; then - echo "Downloading required files if not present..." - # Download detection video - if [ ! -f "$DETECTION_VIDEO_TARGET" ]; then - mkdir -p "$(dirname "$DETECTION_VIDEO_TARGET")" - echo "Downloading test video..." - curl -fL "$DETECTION_VIDEO_URL" -o "$DETECTION_VIDEO_TARGET" - else - echo "Test video already exists at $DETECTION_VIDEO_TARGET" - fi - # Download segmentation video - if [ ! -f "$SEGMENTATION_VIDEO_TARGET" ]; then - mkdir -p "$(dirname "$SEGMENTATION_VIDEO_TARGET")" - echo "Downloading test video..." - curl -fL "$SEGMENTATION_VIDEO_URL" -o "$SEGMENTATION_VIDEO_TARGET" - else - echo "Test video already exists at $SEGMENTATION_VIDEO_TARGET" - fi - # Download detection model XML - if [ ! -f "$DETECTION_MODEL_XML_TARGET" ]; then - mkdir -p "$DETECTION_MODEL_TARGET_DIR" - echo "Downloading model XML..." - curl -fL "$DETECTION_MODEL_XML_URL" -o "$DETECTION_MODEL_XML_TARGET" - else - echo "Model XML already exists at $DETECTION_MODEL_XML_TARGET" - fi - # Download segmentation model XML - if [ ! -f "$SEGMENTATION_MODEL_XML_TARGET" ]; then - mkdir -p "$SEGMENTATION_MODEL_TARGET_DIR" - echo "Downloading model XML..." - curl -fL "$SEGMENTATION_MODEL_XML_URL" -o "$SEGMENTATION_MODEL_XML_TARGET" - else - echo "Model XML already exists at $SEGMENTATION_MODEL_XML_TARGET" - fi - # Download detection model BIN - if [ ! -f "$DETECTION_MODEL_BIN_TARGET" ]; then - mkdir -p "$DETECTION_MODEL_TARGET_DIR" - echo "Downloading model BIN..." - curl -fL "$DETECTION_MODEL_BIN_URL" -o "$DETECTION_MODEL_BIN_TARGET" - else - echo "Model BIN already exists at $DETECTION_MODEL_BIN_TARGET" - fi - # Download segmentation model BIN - if [ ! -f "$SEGMENTATION_MODEL_BIN_TARGET" ]; then - mkdir -p "$SEGMENTATION_MODEL_TARGET_DIR" - echo "Downloading model BIN..." - curl -fL "$SEGMENTATION_MODEL_BIN_URL" -o "$SEGMENTATION_MODEL_BIN_TARGET" - else - echo "Model BIN already exists at $SEGMENTATION_MODEL_BIN_TARGET" - fi -fi - -echo "Starting FastAPI server..." - -exec $UV_CMD "$APP_MODULE" diff --git a/application/recipes.justfile b/application/recipes.justfile new file mode 100644 index 0000000000..bed997eb5d --- /dev/null +++ b/application/recipes.justfile @@ -0,0 +1,77 @@ +detection-model-target-dir := "data/projects/9d6af8e8-6017-4ebe-9126-33aae739c5fa/models/977eeb18-eaac-449d-bc80-e340fbe052ad" +segmentation-model-target-dir := "data/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890/models/c3d4e5f6-a7b8-9012-cdef-123456789012" + +detection-video-url := "https://storage.geti.intel.com/test-data/geti-tune/media/card-video.mp4" +detection-video-target := "data/media/card-video.mp4" +detection-model-xml-url := "https://storage.geti.intel.com/test-data/geti-tune/models/ssd-card-detection.xml" +detection-model-xml-target := detection-model-target-dir + "/model.xml" +detection-model-bin-url := "https://storage.geti.intel.com/test-data/geti-tune/models/ssd-card-detection.bin" +detection-model-bin-target := detection-model-target-dir + "/model.bin" + +segmentation-video-url := "https://storage.geti.intel.com/test-data/geti-tune/media/fish-video.mp4" +segmentation-video-target := "data/media/fish-video.mp4" +segmentation-model-xml-url := "https://storage.geti.intel.com/test-data/geti-tune/models/rtmdet-tiny-fish-segmentation.xml" +segmentation-model-xml-target := segmentation-model-target-dir + "/model.xml" +segmentation-model-bin-url := "https://storage.geti.intel.com/test-data/geti-tune/models/rtmdet-tiny-fish-segmentation.bin" +segmentation-model-bin-target := segmentation-model-target-dir + "/model.bin" + +check-proxy: + #!/usr/bin/env bash + if [ -z ${https_proxy+x} ]; then + echo "Error: https_proxy is unset"; + else + echo "https_proxy is set to '$https_proxy'"; + fi + +download-files: check-proxy + #!/usr/bin/env bash + echo "Downloading required files if not present..." + # Download detection video + if [ ! -f "{{ detection-video-target }}" ]; then + mkdir -p "$(dirname "{{ detection-video-target }}")" + echo "Downloading test video..." + curl -fL "{{ detection-video-url }}" -o "{{ detection-video-target }}" + else + echo "Test video already exists at {{ detection-video-target }}" + fi + # Download segmentation video + if [ ! -f "{{ segmentation-video-target }}" ]; then + mkdir -p "$(dirname "{{ segmentation-video-target }}")" + echo "Downloading test video..." + curl -fL "{{ segmentation-video-url }}" -o "{{ segmentation-video-target }}" + else + echo "Test video already exists at {{ segmentation-video-target }}" + fi + # Download detection model XML + if [ ! -f "{{ detection-model-xml-target }}" ]; then + mkdir -p "$(dirname "{{ detection-model-xml-target }}")" + echo "Downloading model XML..." + curl -fL "{{ detection-model-xml-url }}" -o "{{ detection-model-xml-target }}" + else + echo "Model XML already exists at {{ detection-model-xml-target }}" + fi + # Download detection model BIN + if [ ! -f "{{ detection-model-bin-target }}" ]; then + mkdir -p "$(dirname "{{ detection-model-bin-target }}")" + echo "Downloading model BIN..." + curl -fL "{{ detection-model-bin-url }}" -o "{{ detection-model-bin-target }}" + else + echo "Model BIN already exists at {{ detection-model-bin-target }}" + fi + # Download segmentation model XML + if [ ! -f "{{ segmentation-model-xml-target }}" ]; then + mkdir -p "$(dirname "{{ segmentation-model-xml-target }}")" + echo "Downloading model XML..." + curl -fL "{{ segmentation-model-xml-url }}" -o "{{ segmentation-model-xml-target }}" + else + echo "Model XML already exists at {{ segmentation-model-xml-target }}" + fi + # Download segmentation model BIN + if [ ! -f "{{ segmentation-model-bin-target }}" ]; then + mkdir -p "$(dirname "{{ segmentation-model-bin-target }}")" + echo "Downloading model BIN..." + curl -fL "{{ segmentation-model-bin-url }}" -o "{{ segmentation-model-bin-target }}" + else + echo "Model BIN already exists at {{ segmentation-model-bin-target }}" + fi + diff --git a/library/Justfile b/library/Justfile new file mode 100644 index 0000000000..08d883b2b6 --- /dev/null +++ b/library/Justfile @@ -0,0 +1,72 @@ +#!/usr/bin/env just --justfile + +set shell := ["bash", "-cu"] + +py_path := "tests:src" +# Ensure version in GHA workflow matches uv version here +uv_version := "0.9.7" +device := "cpu" # cpu, cuda, xpu + +default_csv_file_name := 'integration_tests.csv' +default_task := 'all' + +default: + @just --list + +# ------------------------------------------------------------------------------------------------- +# Environment +# ------------------------------------------------------------------------------------------------- +pre-venv: + if ! uv self version | grep {{uv_version}} >/dev/null; then \ + curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/astral-sh/uv/releases/download/{{uv_version}}/uv-installer.sh" | sh; \ + fi + +venv: pre-venv + uv lock --check + uv sync --extra {{ device }} --frozen --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org + +venv-lock: pre-venv + uv lock + +# ------------------------------------------------------------------------------------------------- +# Tests +# ------------------------------------------------------------------------------------------------- +test-unit: venv + if [ -d "./tests/unit" ]; then \ + PYTHONPATH={{py_path}} uv run pytest ./tests/unit --ignore=tests/unit/backend/native/models --cov --cov-report=xml --cov-append -v; \ + else \ + echo "./tests/unit directory not found - skipping unit tests"; \ + fi + +test-unit-native-models: venv + if [ -d "./tests/unit" ]; then \ + PYTHONPATH={{py_path}} uv run pytest tests/unit/backend/native/models --cov --cov-report=xml --cov-append --forked -v; \ + else \ + echo "./tests/unit directory not found - skipping unit tests"; \ + fi + +test-integration csv-file-name=default_csv_file_name task=default_task: venv + if [ -d "./tests/integration" ]; then \ + PYTHONPATH={{py_path}} uv run pytest ./tests/integration -ra --showlocals --csv={{ csv-file-name }} --task {{ task }} --open-subprocess --run-category-only -v; \ + else \ + echo "./tests/integration directory not found - skipping integration tests"; \ + fi + +tests: test-unit test-integration + +# ------------------------------------------------------------------------------------------------- +# Static analysis / style +# ------------------------------------------------------------------------------------------------- + +style-fix: venv + uv run ruff check --config pyproject.toml --fix + uv run ruff format --config pyproject.toml + +lint: venv ruff mypy + +ruff: + uv run ruff check --config pyproject.toml --output-format=github . + uv run ruff format --check --config pyproject.toml . + +mypy: + uv run mypy src/otx --config-file=pyproject.toml \ No newline at end of file