From fb3994f2761a872d633e1ab29bd83c9f29d27a11 Mon Sep 17 00:00:00 2001 From: hubertpysklo Date: Wed, 11 Feb 2026 17:45:43 +0530 Subject: [PATCH 1/2] DB Fix --- backend/src/platform/api/middleware.py | 3 +++ backend/src/platform/api/routes.py | 6 ++++- .../platform/isolationEngine/environment.py | 25 ++++++++++++++++++- .../services/calendar/database/operations.py | 8 +++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/backend/src/platform/api/middleware.py b/backend/src/platform/api/middleware.py index a19e61e..d364e6f 100644 --- a/backend/src/platform/api/middleware.py +++ b/backend/src/platform/api/middleware.py @@ -3,6 +3,7 @@ import logging import time +from starlette.exceptions import HTTPException from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response, JSONResponse @@ -61,6 +62,8 @@ async def dispatch(self, request: Request, call_next) -> Response: {"detail": str(exc)}, status_code=status.HTTP_503_SERVICE_UNAVAILABLE, ) + except HTTPException: + raise # Let Starlette handle route-level HTTP errors (e.g. 404) except Exception: logger.exception("Unhandled exception in PlatformMiddleware") return JSONResponse( diff --git a/backend/src/platform/api/routes.py b/backend/src/platform/api/routes.py index a98fe03..7ba1011 100644 --- a/backend/src/platform/api/routes.py +++ b/backend/src/platform/api/routes.py @@ -407,7 +407,11 @@ async def init_environment(request: Request) -> JSONResponse: logger.warning("Unauthorized template access in init_environment") return unauthorized() except ValueError as e: - logger.warning(f"Template resolution failed in init_environment: {e}") + logger.warning( + f"Template resolution failed in init_environment: {e} " + f"(service={body.templateService!r}, name={body.templateName!r}, " + f"testId={body.testId!r}, schema={body.templateSchema!r})" + ) return bad_request(str(e)) if not body.testId and not body.impersonateUserId and not body.impersonateEmail: diff --git a/backend/src/platform/isolationEngine/environment.py b/backend/src/platform/isolationEngine/environment.py index 010cd27..a7326ad 100644 --- a/backend/src/platform/isolationEngine/environment.py +++ b/backend/src/platform/isolationEngine/environment.py @@ -55,14 +55,35 @@ def migrate_schema(self, template_schema: str, target_schema: str) -> None: self._set_replica_identity(target_schema) def _ensure_box_columns(self, schema: str) -> None: - """Add columns that may be missing from older template snapshots.""" + """Add columns that may be missing from older template snapshots. + + Only runs against schemas that actually contain Box tables. Each + ALTER TABLE is wrapped in a SAVEPOINT so that a single failure does + not poison the surrounding transaction (the same pattern used by + ``_copy_custom_indexes``). + """ _columns = [ # (table, column, SQL type, default) ("box_folders", "path", "VARCHAR(500)", "'/'"), ("box_files", "path", "VARCHAR(500)", "'/0/'"), ] with self.session_manager.base_engine.begin() as conn: + # Quick check: skip entirely when the schema has no Box tables. + has_box = conn.execute( + text( + "SELECT EXISTS(" + " SELECT 1 FROM information_schema.tables" + " WHERE table_schema = :schema" + " AND table_name = 'box_folders'" + ")" + ), + {"schema": schema}, + ).scalar() + if not has_box: + return + for table, col, sql_type, default in _columns: + nested = conn.begin_nested() try: conn.execute( text( @@ -71,7 +92,9 @@ def _ensure_box_columns(self, schema: str) -> None: f"DEFAULT {default}" ) ) + nested.commit() except Exception as exc: + nested.rollback() logger.warning( f"Could not ensure column {schema}.{table}.{col}: {exc}" ) diff --git a/backend/src/services/calendar/database/operations.py b/backend/src/services/calendar/database/operations.py index 426ec3b..3c207ac 100644 --- a/backend/src/services/calendar/database/operations.py +++ b/backend/src/services/calendar/database/operations.py @@ -2003,7 +2003,7 @@ def _extract_time(text_value: str) -> tuple[int, int] | None: else: parsed_dt = parsed_dt.astimezone(tzinfo) start_dt = parsed_dt - except (ValueError, TypeError): + except ValueError, TypeError: start_dt = now_local.replace(minute=0, second=0, microsecond=0) + timedelta( hours=1 ) @@ -2570,7 +2570,7 @@ def query_free_busy( if time_zone: try: target_tz = ZoneInfo(time_zone) - except (KeyError, ValueError): + except KeyError, ValueError: # Invalid timezone - fall back to UTC pass @@ -2726,7 +2726,7 @@ def query_free_busy( try: event_tz = ZoneInfo(event_tz_name) start_dt = start_dt.replace(tzinfo=event_tz) - except (KeyError, ValueError): + except KeyError, ValueError: start_dt = start_dt.replace(tzinfo=dt_timezone.utc) elif start_dt.tzinfo is None: start_dt = start_dt.replace(tzinfo=dt_timezone.utc) @@ -2739,7 +2739,7 @@ def query_free_busy( try: end_tz = ZoneInfo(end_tz_name) end_dt = end_dt.replace(tzinfo=end_tz) - except (KeyError, ValueError): + except KeyError, ValueError: end_dt = end_dt.replace(tzinfo=dt_timezone.utc) elif end_dt.tzinfo is None: end_dt = end_dt.replace(tzinfo=dt_timezone.utc) From cfa50479cb9f6aa9229771d541c038a629e15354 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 12:22:54 +0000 Subject: [PATCH 2/2] fix: correct Python 3 exception syntax in operations.py Add missing parentheses around exception tuples for Python 3 compatibility. Fixed 4 occurrences at lines 2006, 2573, 2729, and 2742. Co-authored-by: Hubert --- backend/src/services/calendar/database/operations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/services/calendar/database/operations.py b/backend/src/services/calendar/database/operations.py index 3c207ac..426ec3b 100644 --- a/backend/src/services/calendar/database/operations.py +++ b/backend/src/services/calendar/database/operations.py @@ -2003,7 +2003,7 @@ def _extract_time(text_value: str) -> tuple[int, int] | None: else: parsed_dt = parsed_dt.astimezone(tzinfo) start_dt = parsed_dt - except ValueError, TypeError: + except (ValueError, TypeError): start_dt = now_local.replace(minute=0, second=0, microsecond=0) + timedelta( hours=1 ) @@ -2570,7 +2570,7 @@ def query_free_busy( if time_zone: try: target_tz = ZoneInfo(time_zone) - except KeyError, ValueError: + except (KeyError, ValueError): # Invalid timezone - fall back to UTC pass @@ -2726,7 +2726,7 @@ def query_free_busy( try: event_tz = ZoneInfo(event_tz_name) start_dt = start_dt.replace(tzinfo=event_tz) - except KeyError, ValueError: + except (KeyError, ValueError): start_dt = start_dt.replace(tzinfo=dt_timezone.utc) elif start_dt.tzinfo is None: start_dt = start_dt.replace(tzinfo=dt_timezone.utc) @@ -2739,7 +2739,7 @@ def query_free_busy( try: end_tz = ZoneInfo(end_tz_name) end_dt = end_dt.replace(tzinfo=end_tz) - except KeyError, ValueError: + except (KeyError, ValueError): end_dt = end_dt.replace(tzinfo=dt_timezone.utc) elif end_dt.tzinfo is None: end_dt = end_dt.replace(tzinfo=dt_timezone.utc)