Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

# Run on every push and every PR against main.
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
checks:
runs-on: ubuntu-latest
strategy:
# Test the range of Python versions we claim to support.
matrix:
python-version: ["3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip

- name: Install (editable, with dev deps)
run: python -m pip install -e ".[dev]"

- name: Lint + type-check
run: |
ruff check autonomy_stack tests
mypy

- name: Test
run: pytest
16 changes: 16 additions & 0 deletions autonomy_stack/hardware/camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from abc import ABC, abstractmethod

import numpy as np
import numpy.typing as npt


class Camera(ABC):
@abstractmethod
def read(self) -> npt.NDArray[np.uint8]:
"""Read one frame: an (H, W, 3) unit8 BGR image."""
...

@abstractmethod
def close(self) -> None:
"""Release the camera resource."""
...
13 changes: 13 additions & 0 deletions autonomy_stack/hardware/motor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from abc import ABC, abstractmethod


class MotorDriver(ABC):
@abstractmethod
def set_throttle(self, value: float) -> None:
"""Set drive throttle in [-1.0, 1.0]. -1 full reverse, +1 full forward."""
...

@abstractmethod
def stop(self) -> None:
"""Stop the motor. Either successful or raise an exception."""
...
16 changes: 16 additions & 0 deletions tests/test_interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""The interface contracts are abstract - they must reject direct instantiation.

This is the Phase 0 guarantee: every concrete driver/planner is forced to
implement the full contract, because an incomplete one can't be constructed.
"""

import pytest

from autonomy_stack.hardware.camera import Camera
from autonomy_stack.hardware.motor import MotorDriver


@pytest.mark.parametrize("interface", [MotorDriver, Camera])
def test_interfaces_cannot_be_instantiated(interface: type) -> None:
with pytest.raises(TypeError):
interface()
Loading