Python Api Tutorial

Python API Project Tutorial

Learn OpenPAUL by building a real project: a Python client that fetches gene information from the HGNC and NCBI Gene APIs using test-driven development.

What you'll build:

Prerequisites:


Step 1: Initialize the Project

# Create and initialize the project
mkdir gene-api-client
cd gene-api-client
uv init

# Add dependencies
uv add pytest pytest-mock requests

# Scaffold OpenPAUL
npx openpaul

npx openpaul creates two things:

It does not create loop state files — those are created in the next step.


Step 2: Verify Plugin Installation

npx openpaul automatically added "plugin": ["openpaul"] to opencode.json. Restart OpenCode to load the plugin. The plugin registers slash commands on load, so /openpaul:help should autocomplete in the TUI.


Step 3: Initialize the Project in OpenCode

Open the project in OpenCode, then run:

/openpaul:init

This initializes the loop state and starts a brief conversation. OpenPAUL asks two questions:

"What's the core value this project delivers?"

"What are you building? (1-2 sentences)"

Example answers:

After the conversation, OpenPAUL creates:

If you maintain a roadmap, create or edit .openpaul/ROADMAP.md now, or leave it for later.


Step 4: PLAN — Create First Plan

Use the TDD wizard in OpenCode. Enter /openpaul:plan with no flags and answer the step-by-step prompts for phase, plan ID, criteria, boundaries, and tasks.

OpenPAUL stores the plan at .openpaul/phases/1-01-PLAN.json and outputs a summary:

# 📋 Plan: 1-01

Type: execute | Wave: 1 | Tasks: 3
Structure: simple

## Criteria
- fetch_by_hgnc_id returns gene data dict with symbol, name, entrez_id
- network errors raise RequestException with context
- all tests pass with 100% coverage of client code

## Tasks
1. Write failing test for HGNC fetch: Test file exists and test fails
2. Implement HGNCClient: AC-1 and AC-2 satisfied
3. Add edge case tests: AC-3 satisfied — 100% coverage

Status: ✅ Plan created successfully
Location: .openpaul/phases/1-01-PLAN.json
Markdown: .openpaul/phases/1-01-PLAN.md
Next action: Run /openpaul:apply to execute the plan

Step 5: APPLY — Execute the Plan

/openpaul:apply

OpenPAUL displays each task in sequence with its action, verification step, and done criteria. Execute them one by one.

Task 1: Write Failing Test

# tests/test_hgnc_client.py
import pytest
import requests
from hgnc_client import HGNCClient


class TestHGNCClient:
    def test_fetch_gene_by_id_success(self, mocker):
        """Test successful gene fetch by HGNC ID."""
        mock_response = {
            "responseHeader": {"status": 0},
            "response": {
                "docs": [{
                    "hgnc_id": "HGNC:5",
                    "symbol": "A1BG",
                    "name": "alpha-1-B glycoprotein",
                    "entrez_id": "1"
                }]
            }
        }
        mock_get = mocker.patch("requests.get")
        mock_get.return_value = mocker.Mock(
            status_code=200,
            json=lambda: mock_response,
        )

        client = HGNCClient()
        result = client.fetch_by_hgnc_id("HGNC:5")

        assert result["symbol"] == "A1BG"
        assert result["entrez_id"] == "1"
        mock_get.assert_called_once_with(
            "https://rest.genenames.org/fetch/hgnc_id/HGNC:5",
            headers={"Accept": "application/json"},
            timeout=30,
        )

Verify: pytest tests/test_hgnc_client.py -v — test should fail (no implementation yet). ✓

Task 2: Implement HGNCClient

# src/hgnc_client.py
import requests
from typing import Optional, Dict, Any


class HGNCClient:
    BASE_URL = "https://rest.genenames.org"

    def __init__(self, timeout: int = 30):
        self.timeout = timeout
        self.headers = {"Accept": "application/json"}

    def fetch_by_hgnc_id(self, hgnc_id: str) -> Optional[Dict[str, Any]]:
        """Fetch gene data by HGNC ID.

        Args:
            hgnc_id: HGNC identifier (e.g., "HGNC:5")

        Returns:
            Gene data dictionary or None if not found

        Raises:
            requests.RequestException: On network errors
        """
        url = f"{self.BASE_URL}/fetch/hgnc_id/{hgnc_id}"

        try:
            response = requests.get(url, headers=self.headers, timeout=self.timeout)
            response.raise_for_status()
            docs = response.json().get("response", {}).get("docs", [])
            return docs[0] if docs else None

        except requests.RequestException as e:
            raise requests.RequestException(f"Failed to fetch {hgnc_id}: {e}")

Verify: pytest tests/test_hgnc_client.py -v — AC-1 and AC-2 satisfied. ✓

Task 3: Edge Case Tests

# tests/test_hgnc_client.py (continued)

    def test_fetch_gene_not_found(self, mocker):
        """Test gene not found returns None."""
        mock_get = mocker.patch("requests.get")
        mock_get.return_value = mocker.Mock(
            status_code=200,
            json=lambda: {"responseHeader": {"status": 0}, "response": {"docs": []}},
        )
        client = HGNCClient()
        assert client.fetch_by_hgnc_id("HGNC:999999") is None

    def test_fetch_network_error(self, mocker):
        """Test network error raises exception."""
        mock_get = mocker.patch("requests.get")
        mock_get.side_effect = requests.ConnectionError("Network error")
        client = HGNCClient()
        with pytest.raises(requests.RequestException):
            client.fetch_by_hgnc_id("HGNC:5")

Verify: pytest tests/test_hgnc_client.py --cov=src -v — AC-3 satisfied. ✓


Step 6: UNIFY — Close the Loop

/openpaul:unify --status success \
  --actuals '[
    {"name": "Write failing test for HGNC fetch", "status": "completed"},
    {"name": "Implement HGNCClient", "status": "completed"},
    {"name": "Add edge case tests", "status": "completed"}
  ]'

OpenPAUL closes the loop, writes .openpaul/phases/1-01-SUMMARY.json, and advances state ready for the next plan. Output:

# 🔗 Loop Closed: 1-01

## Summary
Status: ✅ success
Tasks: ████████████ completed

### Tasks Completed
1. ✅ Write failing test for HGNC fetch
2. ✅ Implement HGNCClient
3. ✅ Add edge case tests

## Next Steps
✅ Loop successfully closed
Ready for: Next loop iteration
Run: /openpaul:plan to start planning the next phase

Step 7: Continue Development

Phase 2: NCBI Integration

Run /openpaul:plan and follow the TDD wizard prompts:

Execute and close:

/openpaul:apply
/openpaul:unify

Phase 3: CLI Tool

Run /openpaul:plan and follow the TDD wizard prompts:


Session Continuity

After a break:

/openpaul:resume

OpenPAUL loads your saved session, shows any file changes since you paused, and tells you exactly what to do next:

## 📋 Session Resume

Session ID: sess-...
Paused: 2024-01-15T14:30:00.000Z
Current Phase: 2 - APPLY

Work in Progress:
- Implementing NCBIClient.fetch_ncbi_gene

Next Steps:
- Resume task 2/3: Implement NCBIClient

📍 Loop: ✓ PLAN → ◉ APPLY → ○ UNIFY

Next Action: Run /openpaul:apply to execute the plan

Summary

You've learned:

  1. Scaffoldnpx openpaul creates .openpaul/state.json and .opencode/ config
  2. Plugin installednpx openpaul automatically adds "plugin": ["openpaul"] to opencode.json; restart OpenCode to load
  3. Initialize/openpaul:init sets up loop state files
  4. Plan/openpaul:plan runs the TDD wizard and creates structured JSON plans with criteria and tasks
  5. Apply/openpaul:apply displays tasks for sequential execution with verification
  6. Unify/openpaul:unify closes the loop and writes a JSON summary
  7. Resume/openpaul:resume restores context after breaks

The loop ensures: