mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
fix: make boolean fields resilient to NULL values
Problem: Features with NULL values in passes/in_progress fields caused Pydantic validation errors in the API. Solution - defense in depth: 1. Database model: Add nullable=False to passes and in_progress columns 2. Migration: Auto-fix existing NULL values to False on database connect 3. API layer: Handle NULL gracefully in feature_to_response (treat as False) 4. MCP server: Explicitly set in_progress=False when creating features This ensures: - New databases cannot have NULL boolean fields - Existing databases are auto-migrated on connect - Even if NULL values exist, they're handled gracefully at runtime Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,8 +27,8 @@ class Feature(Base):
|
||||
name = Column(String(255), nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
steps = Column(JSON, nullable=False) # Stored as JSON array
|
||||
passes = Column(Boolean, default=False, index=True)
|
||||
in_progress = Column(Boolean, default=False, index=True)
|
||||
passes = Column(Boolean, nullable=False, default=False, index=True)
|
||||
in_progress = Column(Boolean, nullable=False, default=False, index=True)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert feature to dictionary for JSON serialization."""
|
||||
@@ -39,8 +39,9 @@ class Feature(Base):
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"steps": self.steps,
|
||||
"passes": self.passes,
|
||||
"in_progress": self.in_progress,
|
||||
# Handle legacy NULL values gracefully - treat as False
|
||||
"passes": self.passes if self.passes is not None else False,
|
||||
"in_progress": self.in_progress if self.in_progress is not None else False,
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +74,18 @@ def _migrate_add_in_progress_column(engine) -> None:
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_fix_null_boolean_fields(engine) -> None:
|
||||
"""Fix NULL values in passes and in_progress columns."""
|
||||
from sqlalchemy import text
|
||||
|
||||
with engine.connect() as conn:
|
||||
# Fix NULL passes values
|
||||
conn.execute(text("UPDATE features SET passes = 0 WHERE passes IS NULL"))
|
||||
# Fix NULL in_progress values
|
||||
conn.execute(text("UPDATE features SET in_progress = 0 WHERE in_progress IS NULL"))
|
||||
conn.commit()
|
||||
|
||||
|
||||
def create_database(project_dir: Path) -> tuple:
|
||||
"""
|
||||
Create database and return engine + session maker.
|
||||
@@ -87,8 +100,9 @@ def create_database(project_dir: Path) -> tuple:
|
||||
engine = create_engine(db_url, connect_args={"check_same_thread": False})
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Migrate existing databases to add in_progress column
|
||||
# Migrate existing databases
|
||||
_migrate_add_in_progress_column(engine)
|
||||
_migrate_fix_null_boolean_fields(engine)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
return engine, SessionLocal
|
||||
|
||||
@@ -409,6 +409,7 @@ def feature_create_bulk(
|
||||
description=feature_data["description"],
|
||||
steps=feature_data["steps"],
|
||||
passes=False,
|
||||
in_progress=False,
|
||||
)
|
||||
session.add(db_feature)
|
||||
created_count += 1
|
||||
@@ -459,6 +460,7 @@ def feature_create(
|
||||
description=description,
|
||||
steps=steps,
|
||||
passes=False,
|
||||
in_progress=False,
|
||||
)
|
||||
session.add(db_feature)
|
||||
session.commit()
|
||||
|
||||
@@ -72,7 +72,10 @@ def get_db_session(project_dir: Path):
|
||||
|
||||
|
||||
def feature_to_response(f) -> FeatureResponse:
|
||||
"""Convert a Feature model to a FeatureResponse."""
|
||||
"""Convert a Feature model to a FeatureResponse.
|
||||
|
||||
Handles legacy NULL values in boolean fields by treating them as False.
|
||||
"""
|
||||
return FeatureResponse(
|
||||
id=f.id,
|
||||
priority=f.priority,
|
||||
@@ -80,8 +83,9 @@ def feature_to_response(f) -> FeatureResponse:
|
||||
name=f.name,
|
||||
description=f.description,
|
||||
steps=f.steps if isinstance(f.steps, list) else [],
|
||||
passes=f.passes,
|
||||
in_progress=f.in_progress,
|
||||
# Handle legacy NULL values gracefully - treat as False
|
||||
passes=f.passes if f.passes is not None else False,
|
||||
in_progress=f.in_progress if f.in_progress is not None else False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user