Chapter 19.1: Python Bridge with EXTREME TDD
This chapter demonstrates building a Python-based MCP handler using EXTREME TDD methodology: 5-minute RED-GREEN-REFACTOR cycles with quality gates.
Overview
We’ll build a text analysis handler in Python that leverages NLP libraries, demonstrating:
- RED (2 min): Write failing test
- GREEN (2 min): Minimal code to pass
- REFACTOR (1 min): Clean up + quality gates
- COMMIT: If gates pass
Prerequisites
# Install Python bridge dependencies
pip install pforge-python textblob nltk
# Download NLTK data
python -c "import nltk; nltk.download('punkt'); nltk.download('averaged_perceptron_tagger')"
Example: Text Analysis Handler
Cycle 1: RED - Basic Structure (2 min)
GOAL: Create failing test for text word count
# tests/test_text_analyzer.py
import pytest
from handlers import TextAnalyzer
def test_word_count():
"""Test basic word counting."""
analyzer = TextAnalyzer()
result = analyzer.handle(text="Hello world")
assert result["word_count"] == 2
Run test:
pytest tests/test_text_analyzer.py::test_word_count
# ❌ FAIL: ModuleNotFoundError: No module named 'handlers'
Time check: ✅ Under 2 minutes
Cycle 1: GREEN - Minimal Implementation (2 min)
# handlers.py
from pforge_python import handler
@handler("analyze_text")
class TextAnalyzer:
def handle(self, text: str) -> dict:
word_count = len(text.split())
return {"word_count": word_count}
Run test:
pytest tests/test_text_analyzer.py::test_word_count
# ✅ PASS
Time check: ✅ Under 2 minutes
Cycle 1: REFACTOR + Quality Gates (1 min)
# Run quality gates
black handlers.py tests/
mypy handlers.py
pylint handlers.py --max-line-length=100
pytest --cov=handlers --cov-report=term-missing
# Coverage: 100% ✅
# Pylint: 10/10 ✅
# Type check: ✅ Pass
COMMIT:
git add handlers.py tests/test_text_analyzer.py
git commit -m "feat: add word count to text analyzer
- Implements basic word counting
- 100% test coverage
- All quality gates pass
🤖 Generated with EXTREME TDD"
Total time: ✅ 5 minutes
Cycle 2: RED - Sentiment Analysis (2 min)
GOAL: Add sentiment analysis
# tests/test_text_analyzer.py
def test_sentiment_analysis():
"""Test sentiment analysis."""
analyzer = TextAnalyzer()
result = analyzer.handle(text="I love this amazing product!")
assert "sentiment" in result
assert result["sentiment"]["polarity"] > 0 # Positive sentiment
assert 0 <= result["sentiment"]["subjectivity"] <= 1
Run test:
pytest tests/test_text_analyzer.py::test_sentiment_analysis
# ❌ FAIL: KeyError: 'sentiment'
Time check: ✅ Under 2 minutes
Cycle 2: GREEN - Add Sentiment (2 min)
# handlers.py
from pforge_python import handler
from textblob import TextBlob
@handler("analyze_text")
class TextAnalyzer:
def handle(self, text: str) -> dict:
word_count = len(text.split())
# Add sentiment analysis
blob = TextBlob(text)
return {
"word_count": word_count,
"sentiment": {
"polarity": blob.sentiment.polarity,
"subjectivity": blob.sentiment.subjectivity,
},
}
Run test:
pytest tests/test_text_analyzer.py::test_sentiment_analysis
# ✅ PASS
Time check: ✅ Under 2 minutes
Cycle 2: REFACTOR + Quality Gates (1 min)
# Quality gates
black handlers.py tests/
pytest --cov=handlers --cov-report=term-missing
# Coverage: 100% ✅
# All tests: 2/2 passing ✅
COMMIT:
git add handlers.py tests/test_text_analyzer.py
git commit -m "feat: add sentiment analysis
- TextBlob integration for polarity/subjectivity
- 100% test coverage maintained
- All tests passing (2/2)
🤖 Generated with EXTREME TDD"
Total time: ✅ 5 minutes
Cycle 3: RED - Noun Phrase Extraction (2 min)
# tests/test_text_analyzer.py
def test_noun_phrases():
"""Test noun phrase extraction."""
analyzer = TextAnalyzer()
result = analyzer.handle(text="The quick brown fox jumps over the lazy dog")
assert "noun_phrases" in result
assert isinstance(result["noun_phrases"], list)
assert len(result["noun_phrases"]) > 0
Run test:
pytest tests/test_text_analyzer.py::test_noun_phrases
# ❌ FAIL: KeyError: 'noun_phrases'
Time check: ✅ Under 2 minutes
Cycle 3: GREEN - Extract Noun Phrases (2 min)
# handlers.py
from pforge_python import handler
from textblob import TextBlob
@handler("analyze_text")
class TextAnalyzer:
def handle(self, text: str) -> dict:
word_count = len(text.split())
blob = TextBlob(text)
return {
"word_count": word_count,
"sentiment": {
"polarity": blob.sentiment.polarity,
"subjectivity": blob.sentiment.subjectivity,
},
"noun_phrases": list(blob.noun_phrases),
}
Run test:
pytest tests/test_text_analyzer.py::test_noun_phrases
# ✅ PASS (3/3)
Time check: ✅ Under 2 minutes
Cycle 3: REFACTOR + Quality Gates (1 min)
Refactor: Extract blob creation to avoid repetition
# handlers.py
from pforge_python import handler
from textblob import TextBlob
@handler("analyze_text")
class TextAnalyzer:
def handle(self, text: str) -> dict:
blob = self._create_blob(text)
return {
"word_count": len(text.split()),
"sentiment": {
"polarity": blob.sentiment.polarity,
"subjectivity": blob.sentiment.subjectivity,
},
"noun_phrases": list(blob.noun_phrases),
}
def _create_blob(self, text: str) -> TextBlob:
"""Create TextBlob instance for analysis."""
return TextBlob(text)
Quality gates:
black handlers.py
pylint handlers.py --max-line-length=100
pytest --cov=handlers --cov-report=term-missing
# Coverage: 100% ✅
# Pylint: 10/10 ✅
# All tests: 3/3 ✅
COMMIT:
git add handlers.py tests/test_text_analyzer.py
git commit -m "feat: add noun phrase extraction
- Extract noun phrases using TextBlob
- Refactor: extract blob creation helper
- Maintain 100% coverage (3/3 tests)
🤖 Generated with EXTREME TDD"
Total time: ✅ 5 minutes
Integration with pforge
Configuration (forge.yaml)
forge:
name: python-nlp-server
version: 0.1.0
transport: stdio
tools:
- type: native
name: analyze_text
description: "Analyze text with NLP: word count, sentiment, noun phrases"
handler:
path: python:handlers.TextAnalyzer
params:
text:
type: string
required: true
description: "Text to analyze"
Running the Server
# Build server
pforge build --release
# Run server
pforge serve
# Test via MCP client
echo '{"text": "I love this amazing product!"}' | pforge test analyze_text
Output:
{
"word_count": 5,
"sentiment": {
"polarity": 0.65,
"subjectivity": 0.85
},
"noun_phrases": [
"amazing product"
]
}
Quality Metrics
Final Coverage Report
Name Stmts Miss Cover Missing
-----------------------------------------------
handlers.py 12 0 100%
tests/__init__.py 0 0 100%
tests/test_text_analyzer.py 15 0 100%
-----------------------------------------------
TOTAL 27 0 100%
Complexity Analysis
radon cc handlers.py -a
# handlers.py
# C 1:0 TextAnalyzer._create_blob - A (1)
# C 1:0 TextAnalyzer.handle - A (2)
# Average complexity: A (1.5) ✅
Type Coverage
mypy handlers.py --strict
# Success: no issues found in 1 source file ✅
Development Workflow Summary
Total development time: 15 minutes (3 cycles × 5 min)
Commits: 3 clean commits, all tests passing
Quality maintained:
- ✅ 100% test coverage throughout
- ✅ All quality gates passing
- ✅ Complexity: A grade
- ✅ Type safety: 100%
Key Principles Applied:
- Jidoka (“stop the line”): Quality gates prevent bad commits
- Kaizen (continuous improvement): Each cycle adds value
- Respect for People: Clear, readable code
- Built-in Quality: TDD ensures correctness
Troubleshooting
Common Issues
Import errors:
# Ensure pforge-python is in PYTHONPATH
export PYTHONPATH=$PWD/bridges/python:$PYTHONPATH
NLTK data missing:
python -c "import nltk; nltk.download('all')"
Coverage not at 100%:
# Check what's missing
pytest --cov=handlers --cov-report=html
open htmlcov/index.html
Summary
This chapter demonstrated:
- ✅ EXTREME TDD with 5-minute cycles
- ✅ Python bridge integration
- ✅ NLP library usage (TextBlob)
- ✅ 100% test coverage maintained
- ✅ Quality gates enforced
- ✅ Clean commit history
Next: Chapter 19.2 - Go Bridge with EXTREME TDD