Skip to content

Commit 1e9e3b3

Browse files
authored
Modernize tooling: ruff + pytest + pyproject.toml, bump to 1.0.0 (#12)
- Replace pylint/flake8/requirements.txt with ruff and pyproject.toml (ruff lint + format, pytest, coverage, setuptools dynamic version, dev extras). - Move __version__ into serializable/version.py and bump to 1.0.0. - Add AGENTS.md + CLAUDE.md with workflow rules and script conventions aligned with other openvax repos. - Add format.sh and develop.sh; update lint.sh to run ruff, deploy.sh to gate on lint + test before building and uploading via twine. - Split tests.yml into lint + test matrix (Python 3.9-3.12) and clean up .gitignore. - Clean up existing source to pass ruff (f-strings, raise ... from, ClassVar, named exception on bare except, module-level lookup cache).
1 parent fb4c01c commit 1e9e3b3

30 files changed

Lines changed: 501 additions & 221 deletions

.github/workflows/tests.yml

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,67 @@
11
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
22
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
33

4-
# TODO:
5-
# - cache this directory $HOME/.cache/pyensembl/
6-
# - update coveralls
7-
# - get a badge for tests passing
8-
# - download binary dependencies from conda
94
name: Tests
10-
on: [push, pull_request]
5+
on:
6+
push:
7+
branches: [master]
8+
pull_request:
119

1210
jobs:
13-
build:
11+
lint:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.11"
21+
cache: "pip"
22+
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install -e ".[dev]"
27+
28+
- name: Lint with ruff
29+
run: |
30+
ruff check serializable tests
31+
32+
- name: Check formatting with ruff
33+
run: |
34+
ruff format --check serializable tests
35+
36+
test:
1437
runs-on: ubuntu-latest
1538
strategy:
1639
fail-fast: true
1740
matrix:
18-
python-version: ["3.9", "3.10", "3.11"]
41+
python-version: ["3.9", "3.10", "3.11", "3.12"]
1942

2043
steps:
2144
- name: Checkout repository
22-
uses: actions/checkout@v3
45+
uses: actions/checkout@v4
46+
2347
- name: Set up Python ${{ matrix.python-version }}
24-
uses: actions/setup-python@v3
48+
uses: actions/setup-python@v5
2549
with:
2650
python-version: ${{ matrix.python-version }}
2751
cache: "pip"
52+
2853
- name: Install dependencies
2954
run: |
3055
python -m pip install --upgrade pip
31-
python -m pip install flake8 pytest pytest-cov coveralls pylint
32-
pip install -r requirements.txt
33-
pip install .
34-
- name: Lint with flake8
35-
run: |
36-
# stop the build if there are Python syntax errors or undefined names
37-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
38-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
39-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
40-
- name: Run default linting script
41-
run: |
42-
./lint.sh
56+
pip install -e ".[dev]"
57+
4358
- name: Run unit tests
4459
run: |
45-
./test.sh
46-
- name: Publish coverage to Coveralls
47-
uses: coverallsapp/github-action@v2.2.3
60+
pytest tests/ -v --cov=serializable --cov-report=xml
61+
62+
- name: Upload coverage to Coveralls
63+
if: matrix.python-version == '3.11'
64+
uses: coverallsapp/github-action@v2
65+
with:
66+
github-token: ${{ secrets.GITHUB_TOKEN }}
67+
file: coverage.xml

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ celerybeat-schedule
7979
.env
8080

8181
# virtualenv
82+
.venv/
8283
venv/
8384
ENV/
8485

AGENTS.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
## Repo Info
2+
3+
- **Package**: `serializable` — base class with serialization helpers for user-defined Python objects.
4+
- **GitHub**: https://github.com/openvax/serializable
5+
- **PyPI**: https://pypi.org/project/serializable/
6+
- **Author**: Alex Rubinsteyn <alex.rubinsteyn@unc.edu>
7+
- **Primary branch**: `master`
8+
9+
---
10+
11+
## Golden Rules
12+
13+
1. **Never commit to `master`.** Always `git checkout -b <feature-branch>` before editing. Land via PR.
14+
2. **Every PR bumps the version.** Even doc-only PRs — at minimum a patch bump. Edit `serializable/version.py`.
15+
3. **"Done" means merged AND deployed to PyPI** — never stop at merge. After a PR merges, run `./deploy.sh` from a clean `master`. Skipping deploy = task not done.
16+
4. **File problems as issues, don't silently work around them.** If you hit a bug here or in a sibling openvax repo, open a GitHub issue on the correct repo and link it from the PR.
17+
5. **After a PR ships, look for the next block of work.** Read open issues across relevant openvax repos, group by dependency + urgency. Prefer *foundational* changes that unblock multiple downstream improvements; otherwise chain the smallest independent improvements.
18+
19+
---
20+
21+
## Before Completing Any Task
22+
23+
Before considering any code change complete, you MUST:
24+
25+
1. **Run `./format.sh`** — Auto-format all code
26+
2. **Run `./lint.sh`** — Verify linting passes (runs both `ruff check` and `ruff format --check`)
27+
3. **Run `./test.sh`** — Verify all tests pass
28+
29+
Do not tell the user you are "done" or that changes are "complete" until all three of these pass.
30+
31+
## Scripts
32+
33+
- `./format.sh` — Formats code with ruff (run this first)
34+
- `./lint.sh` — Checks linting and formatting (must pass). **Always use this for linting if it exists.**
35+
- `./test.sh` — Runs pytest with coverage (must pass)
36+
- `./deploy.sh` — Builds the distribution and uploads to PyPI via `twine` (gates on `lint.sh` and `test.sh`). **Always use this for deploying if it exists.**
37+
- `./develop.sh` — Installs package in development mode into `.venv`
38+
39+
## Code Style
40+
41+
- Use ruff for formatting and linting
42+
- Configuration is in `pyproject.toml` under `[tool.ruff]`
43+
- Line length: 100 characters
44+
- Target Python version: 3.9+
45+
46+
---
47+
48+
## Workflow Orchestration
49+
50+
### 1. Upfront Planning
51+
- For ANY non-trivial task (3+ steps or architectural decisions): write a detailed spec before touching code
52+
- If something goes sideways, STOP and re-plan immediately — don't keep pushing
53+
- Use planning/verification steps, not just building
54+
- Write detailed specs upfront to reduce ambiguity
55+
56+
### 2. Self-Improvement Loop
57+
- After ANY correction from the user: update `tasks/lessons.md` with the pattern
58+
- Write rules for yourself that prevent the same mistake
59+
- Ruthlessly iterate on these lessons until mistake rate drops
60+
- Review lessons at session start for relevant project
61+
62+
### 3. Verification Before Done
63+
- Never mark a task complete without proving it works
64+
- Diff behavior between the latest code and your changes when relevant
65+
- Ask yourself: "Would a staff engineer approve this?"
66+
- Run tests, check logs, demonstrate correctness
67+
68+
### 4. Demand Elegance (Balanced)
69+
- For non-trivial changes: pause and ask "is there a more elegant way?"
70+
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
71+
- Skip this for simple, obvious fixes — don't over-engineer
72+
- Challenge your own work before presenting it
73+
74+
### 5. Autonomous Bug Fixing
75+
- When given a bug report: just fix it. Don't ask for hand-holding
76+
- Point at logs, errors, failing tests — then resolve them
77+
- Zero context switching required from the user
78+
- Fix failing unit tests without being told how
79+
80+
---
81+
82+
## Task Management
83+
84+
1. **Plan First**: Write plan to `tasks/todo.md` with checkable items
85+
2. **Verify Plan**: Check in before starting implementation
86+
3. **Track Progress**: Mark items complete as you go
87+
4. **Explain Changes**: High-level summary at each step
88+
5. **Document Results**: Add review section to `tasks/todo.md`
89+
6. **Capture Lessons**: Update `tasks/lessons.md` after corrections
90+
91+
---
92+
93+
## Core Principles
94+
95+
- **Simplicity First**: Make every change as simple as possible. Impact minimal code.
96+
- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
97+
- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs.

CLAUDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Project Instructions
2+
3+
See @AGENTS.md for verification steps, workflow principles, and repo info.
4+
5+
---
6+
7+
## Claude Code-Specific
8+
9+
### Plan Mode
10+
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
11+
- Use plan mode for verification steps, not just building
12+
13+
### Subagent Strategy
14+
- Use subagents liberally to keep main context window clean
15+
- Offload research, exploration, and parallel analysis to subagents
16+
- For complex problems, throw more compute at it via subagents
17+
- One task per subagent for focused execution

deploy.sh

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
./lint.sh && \
2-
./test.sh && \
3-
python3 -m pip install --upgrade build && \
4-
python3 -m pip install --upgrade twine && \
5-
rm -rf dist && \
6-
python3 -m build && \
7-
python3 -m twine upload dist/*
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
cd "$ROOT"
6+
7+
./lint.sh
8+
./test.sh
9+
10+
PYTHON_BIN="${DEPLOY_PYTHON:-python3}"
11+
"$PYTHON_BIN" -m pip install --upgrade build twine
12+
rm -rf dist build ./*.egg-info
13+
"$PYTHON_BIN" -m build
14+
"$PYTHON_BIN" -m twine check dist/*
15+
"$PYTHON_BIN" -m twine upload dist/*

develop.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
VENV_DIR=".venv"
6+
7+
if [ ! -d "$VENV_DIR" ]; then
8+
echo "Creating virtual environment at $VENV_DIR..."
9+
python -m venv "$VENV_DIR"
10+
fi
11+
12+
# shellcheck disable=SC1091
13+
source "$VENV_DIR/bin/activate"
14+
15+
# Check if UV is installed and available in the PATH
16+
if command -v uv &> /dev/null; then
17+
echo "Using uv to install package with development dependencies..."
18+
uv pip install -e ".[dev]"
19+
else
20+
echo "uv not found, falling back to regular pip..."
21+
pip install -e ".[dev]"
22+
fi

format.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
SOURCES="serializable tests"
6+
7+
echo "Running ruff format..."
8+
ruff format $SOURCES
9+
10+
echo "Formatting complete!"

lint-and-test.sh

Lines changed: 0 additions & 1 deletion
This file was deleted.

lint.sh

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
#!/bin/bash
2-
set -o errexit
1+
#!/usr/bin/env bash
32

4-
# getting false positives due to this issue with pylint:
5-
# https://bitbucket.org/logilab/pylint/issues/701/false-positives-with-not-an-iterable-and
3+
set -e
64

7-
find serializable -name '*.py' \
8-
| xargs pylint \
9-
--errors-only \
10-
--disable=unsubscriptable-object,not-an-iterable
5+
SOURCES="serializable tests"
116

12-
echo 'Passes pylint check'
7+
echo "Running ruff check..."
8+
ruff check $SOURCES
9+
10+
echo "Running ruff format check..."
11+
ruff format --check $SOURCES
12+
13+
echo "All checks passed!"

pylintrc

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)