Skip to content
Closed
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
21 changes: 21 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

[*.py]
max_line_length = 120

[*.{yml,yaml,toml,json}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
72 changes: 72 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Tests & Lint

on:
push:
branches: ["**"]
tags-ignore: ["**"]
pull_request:

permissions:
contents: read

jobs:
# ---------------------------------------------------------------------------
# Lint and format check (fast feedback)
# ---------------------------------------------------------------------------
lint:
name: Ruff lint & format
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install ruff
run: pip install ruff

- name: Ruff lint
run: ruff check src/

- name: Ruff format check
run: ruff format src/ --check

# ---------------------------------------------------------------------------
# Unit tests
# ---------------------------------------------------------------------------
test:
name: pytest / Python ${{ matrix.python-version }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: lint
permissions:
contents: read

strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package with test dependencies
run: pip install -e ".[test]"

- name: Run tests with coverage
run: pytest unit-test/ -v --cov=src --cov-report=term-missing --cov-report=xml

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest'
with:
name: coverage-report
path: coverage.xml
32 changes: 32 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.2
hooks:
# Run ruff linter
- id: ruff
args: [--fix]
# Run ruff formatter
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=5120]
- id: debug-statements
- id: no-commit-to-branch
args: [--branch, main]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies: []
args: [--ignore-missing-imports, --non-interactive]
pass_filenames: false
args: [src/openlifu_sdk, --ignore-missing-imports]
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Changelog

All notable changes to `openlifu-sdk` are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [Unreleased]

### Added
- `CONTRIBUTING.md` with development setup, coding conventions, and release process.
- `CHANGELOG.md` (this file).
- `.editorconfig` for consistent editor settings across contributors.
- `.pre-commit-config.yaml` with ruff (lint + format) and general file checks.
- `ruff`, `mypy`, and `pytest` configuration in `pyproject.toml`.
- CI workflow (`.github/workflows/test.yml`) that runs lint and tests on every push
and pull request across Ubuntu, Windows, and macOS.

### Fixed
- **`LIFUUart`** — removed library-level `log.propagate = False`, hard-coded
`log.setLevel(logging.ERROR)`, and manual `StreamHandler` setup. Libraries must not
configure the root logging hierarchy. Logger name changed from `"UART"` to
`__name__`.
- **`LIFUTXDevice`** — same logging configuration fix; logger name changed from
`"TXDevice"` to `__name__`.
- **`LIFUInterface`** — `signal_connect`, `signal_disconnect`, `signal_data_received`,
`hvcontroller`, and `txdevice` were declared as class-level attributes, causing all
instances to share the same signal objects. These are now proper instance attributes
created in `__init__`. Instance attribute types are also correctly annotated with
`Optional[...]`.
- **`pyproject.toml`** — the `[test]` and `[dev]` optional-dependency groups were
identical. `[dev]` now adds `pre-commit`, `ruff`, and `mypy` on top of the test
dependencies.
- **All modules** — replaced f-string logging calls (`logger.error(f"msg {x}")`) with
lazy `%`-style formatting (`logger.error("msg %s", x)`) throughout
`LIFUTXDevice.py`, `LIFUHVController.py`, `LIFUUart.py`, `LIFUInterface.py`, and
`LIFUUserConfig.py`.
- **`LIFUTXDevice.py`** — fixed a bare `logging.warn` statement (useless expression /
root-logger call) in `calc_pulse_pattern`; replaced with `logger.warning(...)`.
- **`LIFUTXDevice.py`** — moved the mid-file `LIFUConfig` import block to the top of
the file (alongside all other imports) to comply with PEP 8 and ruff E402.
- **`LIFUTXDevice.write_config_json`** — `raise ValueError(...)` inside an `except`
block now chains the original exception (`raise ... from e`) per PEP 3134 / ruff B904.

---

## [1.0.1] — 2025-XX-XX

_(Previous release — see git history for details.)_

## [1.0.0] — 2025-XX-XX

_(Initial public release.)_

[Unreleased]: https://github.com/OpenwaterHealth/openlifu-sdk/compare/1.0.1...HEAD
[1.0.1]: https://github.com/OpenwaterHealth/openlifu-sdk/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/OpenwaterHealth/openlifu-sdk/releases/tag/1.0.0
167 changes: 167 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Contributing to openlifu-sdk

Thank you for your interest in contributing! This guide covers environment setup, coding conventions, testing, and the pull request process.

## Table of Contents

- [Development setup](#development-setup)
- [Project structure](#project-structure)
- [Coding conventions](#coding-conventions)
- [Running tests](#running-tests)
- [Linting and formatting](#linting-and-formatting)
- [Pre-commit hooks](#pre-commit-hooks)
- [Pull request process](#pull-request-process)
- [Release process](#release-process)

---

## Development setup

1. **Fork and clone** the repository:

```bash
git clone https://github.com/<your-fork>/openlifu-sdk.git
cd openlifu-sdk
```

2. **Create a virtual environment** (Python 3.12+):

```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
```

3. **Install in editable mode** with dev dependencies:

```bash
pip install -e ".[dev]"
```

4. **Install pre-commit hooks** (optional but recommended):

```bash
pre-commit install
```

---

## Project structure

```
src/openlifu_sdk/ # Library source (src layout)
unit-test/ # Automated unit tests (pytest)
examples/ # Hardware-targeting interactive scripts (not automated)
.github/workflows/ # CI workflows
```

The package uses a `src/` layout (PEP 517). All library code lives under `src/openlifu_sdk/`.

---

## Coding conventions

- **Python version:** 3.12+. Use modern syntax (`X | Y` unions, `match`, etc.).
- **Imports:** Use `from __future__ import annotations` at the top of every module.
- **Line length:** 120 characters (enforced by ruff).
- **Logging:**
- Use `logging.getLogger(__name__)` — never hard-code logger names.
- Never set `logger.propagate = False` or add handlers in library code.
- Use `%s` lazy formatting: `logger.info("msg %s", value)` — never f-strings.
- **Type annotations:** Add return type annotations to all public methods.
- **Docstrings:** Google style with `Args:` / `Returns:` / `Raises:` sections.
- **Exceptions:** Raise with chaining inside `except` blocks (`raise ... from err`).
- **Signals:** Use `LIFUSignal` for event callbacks; never share signal objects at class level — always create them in `__init__`.

---

## Running tests

```bash
# All tests, verbose
pytest unit-test/ -v

# With coverage
pytest unit-test/ -v --cov=src --cov-report=term-missing

# Single test file
pytest unit-test/test_tx_device.py -v
```

Tests in `unit-test/` use `unittest.mock.MagicMock(spec=LIFUUart)` to mock hardware — **no physical device is required**.

The `examples/` scripts require real hardware and are not run in CI.

---

## Linting and formatting

This project uses [ruff](https://docs.astral.sh/ruff/) for both linting and formatting.

```bash
# Check for lint errors
ruff check src/

# Auto-fix lint errors
ruff check src/ --fix

# Format code
ruff format src/

# Check formatting without modifying files
ruff format src/ --check
```

All lint and format checks run automatically in CI on every push and pull request.

---

## Pre-commit hooks

[pre-commit](https://pre-commit.com/) runs ruff, mypy, and general file checks before every commit.

```bash
# Install hooks (one-time, after cloning)
pre-commit install

# Run manually on all files
pre-commit run --all-files
```

---

## Pull request process

1. Create a branch from `main`:

```bash
git checkout -b feat/my-feature
```

2. Make your changes, write or update tests, and ensure all checks pass:

```bash
pytest unit-test/ -v
ruff check src/ && ruff format src/ --check
```

3. Open a pull request against `main`. CI will run automatically.
4. Address reviewer feedback.
5. A maintainer will merge once CI is green and the PR is approved.

---

## Release process

Releases are driven by git tags:

| Tag pattern | Outcome |
|---|---|
| `1.2.3` | Full release — wheel built, GitHub Release created, published to PyPI |
| `pre-1.2.3` | Pre-release — wheel built, GitHub pre-release created, **not** published to PyPI |

Steps for maintainers:

1. Ensure `CHANGELOG.md` is up to date.
2. Push a tag: `git tag 1.2.3 && git push origin 1.2.3`
3. The `release-build.yml` workflow builds the wheel and creates the GitHub Release automatically.
4. The `publish-pypi.yml` workflow publishes to PyPI when a non-pre-release GitHub Release is published.
Loading
Loading