Skip to content

Commit 42cec5b

Browse files
committed
CI typecheck
1 parent ce74c3e commit 42cec5b

4 files changed

Lines changed: 50 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,43 @@ jobs:
6666
name: typecheck
6767
runs-on: ubuntu-latest
6868
continue-on-error: true
69-
timeout-minutes: 20
69+
timeout-minutes: 35
7070

7171
steps:
7272
- name: Checkout
7373
uses: actions/checkout@v4
7474

75+
- name: Set up Python 3.13
76+
uses: actions/setup-python@v5
77+
with:
78+
python-version: "3.13"
79+
cache: "pip"
80+
cache-dependency-path: |
81+
worker_plan/pyproject.toml
82+
frontend_multi_user/pyproject.toml
83+
frontend_single_user/requirements.txt
84+
worker_plan_database/requirements.txt
85+
open_dir_server/requirements.txt
86+
7587
- name: Set up Node
7688
uses: actions/setup-node@v4
7789
with:
7890
node-version: "22"
7991

92+
- name: Prepare Python environment for pyright
93+
run: |
94+
python -m venv .venv
95+
.venv/bin/python -m pip install --upgrade pip
96+
.venv/bin/pip install -e worker_plan
97+
.venv/bin/pip install -e frontend_multi_user
98+
.venv/bin/pip install -r frontend_single_user/requirements.txt
99+
.venv/bin/pip install -r worker_plan_database/requirements.txt
100+
.venv/bin/pip install -r open_dir_server/requirements.txt
101+
ln -sfn ../.venv frontend_multi_user/.venv
102+
ln -sfn ../.venv frontend_single_user/.venv
103+
ln -sfn ../.venv worker_plan_database/.venv
104+
ln -sfn ../.venv open_dir_server/.venv
105+
80106
- name: Install pyright
81107
run: npm install -g pyright
82108

frontend_multi_user/src/app.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,12 +1168,18 @@ def _normalize_plan_view_mode(value: Any) -> str:
11681168
return mode
11691169
return "view"
11701170

1171+
@staticmethod
1172+
def _coerce_json_dict(value: Any) -> dict[str, Any]:
1173+
if not isinstance(value, dict):
1174+
return {}
1175+
return {str(key): item for key, item in value.items()}
1176+
11711177
def _get_plan_view_mode_preference(self) -> str:
11721178
user = self._get_current_user_account()
11731179
if user is None:
11741180
return "view"
1175-
config = user.frontend_multi_user_config if isinstance(user.frontend_multi_user_config, dict) else {}
1176-
plan_page = config.get("plan_page") if isinstance(config.get("plan_page"), dict) else {}
1181+
config = self._coerce_json_dict(user.frontend_multi_user_config)
1182+
plan_page = self._coerce_json_dict(config.get("plan_page"))
11771183
return self._normalize_plan_view_mode(plan_page.get("selected_segment"))
11781184

11791185
def _set_plan_view_mode_preference(self, mode: str) -> None:
@@ -1183,9 +1189,9 @@ def _set_plan_view_mode_preference(self, mode: str) -> None:
11831189
normalized_mode = self._normalize_plan_view_mode(mode)
11841190
# JSON columns are not mutation-tracked by default; build fresh dicts so
11851191
# SQLAlchemy sees a new value and persists the update reliably.
1186-
existing_config = user.frontend_multi_user_config if isinstance(user.frontend_multi_user_config, dict) else {}
1192+
existing_config = self._coerce_json_dict(user.frontend_multi_user_config)
11871193
config = dict(existing_config)
1188-
existing_plan_page = config.get("plan_page") if isinstance(config.get("plan_page"), dict) else {}
1194+
existing_plan_page = self._coerce_json_dict(config.get("plan_page"))
11891195
plan_page = dict(existing_plan_page)
11901196
plan_page["selected_segment"] = normalized_mode
11911197
config["plan_page"] = plan_page
@@ -1547,7 +1553,7 @@ def _build_plan_failure_trace(self, task: TaskItem) -> dict[str, Any]:
15471553

15481554
return failure_trace
15491555

1550-
def _build_plan_telemetry_cache_key(self, task: TaskItem, include_raw: bool) -> Optional[tuple[str, str, bool]]:
1556+
def _build_plan_telemetry_cache_key(self, task: TaskItem, include_raw: bool) -> Optional[tuple[str, str, bool, bool]]:
15511557
state = task.state if isinstance(task.state, TaskState) else None
15521558
if include_raw or state not in (TaskState.completed, TaskState.failed):
15531559
return None
@@ -1969,7 +1975,7 @@ def inject_current_user_name():
19691975
@self.app.route('/')
19701976
def index():
19711977
user = None
1972-
recent_tasks: list[TaskItem] = []
1978+
recent_tasks: list[SimpleNamespace] = []
19731979
total_tasks_count = 0
19741980
is_admin = False
19751981
nonce = None

frontend_multi_user/tests/test_plan_failure_trace_helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import unittest
66
import zipfile
77
from pathlib import Path
8+
from typing import Any
89

910

1011
PROJECT_ROOT = Path(__file__).resolve().parents[2]
@@ -23,19 +24,21 @@
2324

2425
APP_MODULE_PATH = FRONTEND_SRC / "app.py"
2526
APP_IMPORT_ERROR = None
26-
MyFlaskApp = None
27+
APP_AVAILABLE = False
28+
MyFlaskApp: type[Any] = object
2729
try:
2830
APP_SPEC = importlib.util.spec_from_file_location("frontend_multi_user_app", APP_MODULE_PATH)
2931
if APP_SPEC is None or APP_SPEC.loader is None:
3032
raise RuntimeError(f"Unable to load app module at {APP_MODULE_PATH}")
3133
frontend_app_module = importlib.util.module_from_spec(APP_SPEC)
3234
APP_SPEC.loader.exec_module(frontend_app_module)
3335
MyFlaskApp = frontend_app_module.MyFlaskApp
36+
APP_AVAILABLE = True
3437
except ModuleNotFoundError as exc:
3538
APP_IMPORT_ERROR = exc
3639

3740

38-
@unittest.skipIf(MyFlaskApp is None, f"frontend_multi_user app dependencies unavailable: {APP_IMPORT_ERROR}")
41+
@unittest.skipIf(not APP_AVAILABLE, f"frontend_multi_user app dependencies unavailable: {APP_IMPORT_ERROR}")
3942
class TestPlanFailureTraceHelpers(unittest.TestCase):
4043
def setUp(self) -> None:
4144
self.app_obj = MyFlaskApp.__new__(MyFlaskApp)

frontend_multi_user/tests/test_plan_telemetry_helpers.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datetime import UTC, datetime
88
from pathlib import Path
99
from types import SimpleNamespace
10+
from typing import Any
1011
from unittest import mock
1112

1213

@@ -26,8 +27,9 @@
2627

2728
APP_MODULE_PATH = FRONTEND_SRC / "app.py"
2829
APP_IMPORT_ERROR = None
29-
MyFlaskApp = None
30-
TaskState = None
30+
APP_AVAILABLE = False
31+
MyFlaskApp: type[Any] = object
32+
TaskState: Any = SimpleNamespace(processing="processing", completed="completed")
3133
frontend_app_module = None
3234
try:
3335
APP_SPEC = importlib.util.spec_from_file_location("frontend_multi_user_app", APP_MODULE_PATH)
@@ -37,11 +39,12 @@
3739
APP_SPEC.loader.exec_module(frontend_app_module)
3840
MyFlaskApp = frontend_app_module.MyFlaskApp
3941
TaskState = frontend_app_module.TaskState
42+
APP_AVAILABLE = True
4043
except ModuleNotFoundError as exc:
4144
APP_IMPORT_ERROR = exc
4245

4346

44-
@unittest.skipIf(MyFlaskApp is None, f"frontend_multi_user app dependencies unavailable: {APP_IMPORT_ERROR}")
47+
@unittest.skipIf(not APP_AVAILABLE, f"frontend_multi_user app dependencies unavailable: {APP_IMPORT_ERROR}")
4548
class TestPlanTelemetryHelpers(unittest.TestCase):
4649
class _DummyColumn:
4750
def asc(self):

0 commit comments

Comments
 (0)