From dd61a5868bb555b3e07ca6f7267b95d8f81b8d2b Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Thu, 26 Feb 2026 22:47:16 +0100 Subject: [PATCH 1/2] fix: define column_property checks after class body to avoid NotImplemented error deferred() descriptors do not support .isnot() at class-definition time, producing NotImplemented instead of SQL expressions. This crashed the frontend_multi_user deployment on startup (psycopg2 can't adapt NotImplementedType). Moving the has_* column_property definitions after the class body and referencing __table__.c gives us the raw Column objects that correctly generate IS NOT NULL expressions. Co-Authored-By: Claude Opus 4.6 --- database_api/model_planitem.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/database_api/model_planitem.py b/database_api/model_planitem.py index 5684a552e..93603aec4 100644 --- a/database_api/model_planitem.py +++ b/database_api/model_planitem.py @@ -104,11 +104,6 @@ class PlanItem(db.Model): # Artifact schema/version marker (legacy snapshots are NULL/1, split-storage snapshots are 2+). run_artifact_layout_version = db.Column(db.Integer, nullable=True, default=None) - # Lightweight admin/UI helpers; avoids loading large payload columns just to render links. - has_generated_report_html = column_property(generated_report_html.isnot(None)) - has_run_zip_snapshot = column_property(run_zip_snapshot.isnot(None)) - has_run_track_activity_jsonl = column_property(run_track_activity_jsonl.isnot(None)) - def __repr__(self): return f"{self.id}: {self.timestamp_created}, {self.state}, {self.prompt!r}, parameters: {self.parameters!r}" @@ -148,6 +143,17 @@ def demo_items(cls) -> list['PlanItem']: return [task1, task2, task3] +# Lightweight IS NOT NULL checks — defined outside the class body because +# deferred() descriptors do not support .isnot() at class-definition time +# (produces NotImplemented). Using __table__.c gives us the raw Column +# objects which support SQL operators correctly. +_t = PlanItem.__table__.c +PlanItem.has_generated_report_html = column_property(_t.generated_report_html.isnot(None)) +PlanItem.has_run_zip_snapshot = column_property(_t.run_zip_snapshot.isnot(None)) +PlanItem.has_run_track_activity_jsonl = column_property(_t.run_track_activity_jsonl.isnot(None)) +del _t + + @event.listens_for(PlanItem, "before_insert") @event.listens_for(PlanItem, "before_update") def _sanitize_planitem_fields(_mapper, _connection, target): From 006fc9332e1bb7ff99c062fc852cb48ccd01d374 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Thu, 26 Feb 2026 23:00:46 +0100 Subject: [PATCH 2/2] fix: add TYPE_CHECKING annotations for has_* column properties Pyright cannot see attributes assigned outside the class body. Add TYPE_CHECKING-guarded bool annotations inside the class so pyright knows about the has_* attributes without confusing SQLAlchemy at runtime. Co-Authored-By: Claude Opus 4.6 --- database_api/model_planitem.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/database_api/model_planitem.py b/database_api/model_planitem.py index 93603aec4..5b1812aa8 100644 --- a/database_api/model_planitem.py +++ b/database_api/model_planitem.py @@ -1,6 +1,7 @@ import enum import uuid from datetime import datetime, UTC +from typing import TYPE_CHECKING from database_api.planexe_db_singleton import db from sqlalchemy_utils import UUIDType from sqlalchemy import JSON @@ -104,6 +105,14 @@ class PlanItem(db.Model): # Artifact schema/version marker (legacy snapshots are NULL/1, split-storage snapshots are 2+). run_artifact_layout_version = db.Column(db.Integer, nullable=True, default=None) + # Lightweight IS NOT NULL checks (actual column_property assigned after class body). + # TYPE_CHECKING-only annotations so pyright knows the types without confusing + # SQLAlchemy's declarative metaclass at runtime. + if TYPE_CHECKING: + has_generated_report_html: bool + has_run_zip_snapshot: bool + has_run_track_activity_jsonl: bool + def __repr__(self): return f"{self.id}: {self.timestamp_created}, {self.state}, {self.prompt!r}, parameters: {self.parameters!r}"