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
94 changes: 94 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# GitHub Actions CI/CD

This directory contains GitHub Actions workflows for automated testing, quality checks, and deployment.

## Workflows

### CI Pipeline (`ci.yml`)
**Triggered on:** Push to main/trunk/develop branches and all pull requests

**What it does:**
- Tests across Python 3.10, 3.11, and 3.12
- Runs linting with ruff (code style and formatting)
- Performs type checking with mypy
- Executes the complete test suite
- Tests basic CLI functionality
- Builds the package to ensure it's distributable

**Status:** Required for merging PRs

### Code Quality (`quality.yml`)
**Triggered on:** All pushes and pull requests

**What it does:**
- Fast quality checks on Python 3.11
- Code formatting verification
- Linting checks
- Type annotation validation
- Quick test run with early exit on failure

**Status:** Required for merging PRs

## Local Development

Before pushing changes, run the same checks locally:

```bash
# Run all CI checks
just ci

# Or run individual checks
just lint # Linting and formatting
just type-check # Type checking
just test # Test suite
```

## Troubleshooting CI Failures

### Linting Failures
```bash
# Fix formatting issues
just lint-fix

# Check remaining issues
just lint
```

### Type Check Failures
```bash
# Run type checking locally
just type-check

# Common fixes:
# - Add missing type annotations
# - Fix return type mismatches
# - Handle Optional types properly
```

### Test Failures
```bash
# Run tests with verbose output
just test

# Run specific test file
uv run python -m pytest tests/test_models.py -v

# Run tests with debugging
uv run python -m pytest tests/ -v --tb=long
```

### Build Failures
```bash
# Test local build
just build

# Check dependencies
uv sync --dev
```

## CI Performance

- **Code Quality workflow:** ~30-45 seconds
- **Full CI pipeline:** ~2-3 minutes per Python version

The workflows are optimized for speed while maintaining comprehensive coverage.
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: CI

on:
push:
branches: [ main, trunk, develop ]
pull_request:
branches: [ main, trunk, develop ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: |
uv sync --dev
uv run pip list

- name: Run linting
run: |
echo "Running ruff linting..."
uv run python -m ruff check src/libro/
echo "Running ruff format check..."
uv run python -m ruff format --check src/libro/

- name: Run type checking
run: |
echo "Running mypy type checking..."
uv run python -m mypy --package libro

- name: Run tests
run: |
echo "Running pytest..."
uv run python -m pytest tests/ -v --tb=short

- name: Test CLI basics
run: |
echo "Testing basic CLI functionality..."
# Test version command
uv run libro --version
# Test help command
uv run libro --help

build:
runs-on: ubuntu-latest
needs: test

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"

- name: Set up Python
run: uv python install 3.11

- name: Install dependencies
run: uv sync --dev

- name: Build package
run: |
echo "Building package..."
uv run python -m build

- name: Check build artifacts
run: |
ls -la dist/
# Verify wheel and sdist were created
test -f dist/*.whl
test -f dist/*.tar.gz
65 changes: 65 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Code Quality

on:
push:
branches: [ main, trunk, develop ]
pull_request:
branches: [ main, trunk, develop ]

jobs:
quality:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"

- name: Set up Python 3.11
run: uv python install 3.11

- name: Install dependencies
run: uv sync --dev

- name: Check code formatting
run: |
echo "Checking code formatting with ruff..."
uv run python -m ruff format --check src/libro/
if [ $? -ne 0 ]; then
echo "Code formatting issues found. Run 'just lint-fix' to fix them."
exit 1
fi
echo "Code formatting is correct"

- name: Check linting
run: |
echo "Running linting checks with ruff..."
uv run python -m ruff check src/libro/
if [ $? -ne 0 ]; then
echo "Linting issues found. Run 'just lint' to see details."
exit 1
fi
echo "No linting issues found"

- name: Check type annotations
run: |
echo "Running type checking with mypy..."
uv run python -m mypy --package libro
if [ $? -ne 0 ]; then
echo "Type checking failed. Run 'just type-check' locally to debug."
exit 1
fi
echo "Type checking passed"

- name: Run fast tests
run: |
echo "Running quick test suite..."
uv run python -m pytest tests/ -x -q
if [ $? -ne 0 ]; then
echo "Tests failed. Run 'just test' locally to debug."
exit 1
fi
echo "All tests passed"
47 changes: 35 additions & 12 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,64 @@ set quiet
default:
@just --list

# Run pre-commit checks
# Run lint and format checks
lint:
echo "Linting 🔬"
ruff check src/libro/
echo "Running ruff to check..."
uv run python -m ruff check src/libro/
uv run python -m ruff format --check src/libro/
echo "."

# Fix lint and format checks
lint-fix:
echo "Fixing lint issues..."
uv run python -m ruff format src/libro/
echo "."

# Run mypy typecheck
type-check:
echo "Running mypy to type check..."
uv run python -m mypy --package libro

# Run tests
test:
echo "Running tests..."
uv run python -m pytest tests/ -v

# Run all CI checks locally
ci: lint type-check test
echo "All CI checks passed!"

# Clean Python artifacts
clean:
echo "Scrub a dub dub 🧼"
echo "Cleaning..."
rm -rf build/
rm -rf dist/
rm -rf *.egg-info
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
echo "."

## uv
# Uv runs the project out of the local .venv
# Create venv by running `uv venv`

# Install dependencies
install:
echo "Installing dependencies 📦"
echo "Installing dependencies"
uv sync
echo "."

# Install developer dependencies
dev-install:
echo "Installing developer dependencies"
uv sync --dev
echo "."

# Build the project
build: clean lint install
echo "Building 📦"
build: clean lint dev-install
echo "Building"
uv run -m build
echo "."

# Publish the project to PyPI
publish: build
echo "Publishing to PyPI 🚀"
echo "Publishing to PyPI"
uv run -m twine upload dist/*
echo "."

Expand Down
25 changes: 25 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,33 @@ packages = ["src/libro"]
[tool.ruff]
target-version = "py310"

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
disallow_incomplete_defs = false
check_untyped_defs = true
disallow_untyped_decorators = false
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
disallow_untyped_calls = false
namespace_packages = true
explicit_package_bases = true

[[tool.mypy.overrides]]
module = "appdirs.*"
ignore_missing_imports = true

[dependency-groups]
dev = [
"build>=1.3.0",
"mypy>=1.17.1",
"pytest>=8.4.1",
"ruff>=0.12.10",
"twine>=6.1.0",
]
12 changes: 8 additions & 4 deletions src/libro/actions/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ def init_db(dbfile):
def migrate_db(conn):
"""Add reading lists tables to existing databases if they don't exist."""
cursor = conn.cursor()

# Check if reading_lists table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='reading_lists'")
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='reading_lists'"
)
if not cursor.fetchone():
cursor.execute("""CREATE TABLE reading_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand All @@ -66,9 +68,11 @@ def migrate_db(conn):
)
""")
conn.commit()

# Check if reading_list_books table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='reading_list_books'")
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='reading_list_books'"
)
if not cursor.fetchone():
cursor.execute("""CREATE TABLE reading_list_books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand Down
Loading