55import os
66import zipfile
77from datetime import datetime
8+ from typing import Any
89
9- from flask import Blueprint , current_app , jsonify , request , send_file
10+ from flask import Blueprint , current_app , request , send_file
11+
12+ from api ._flask_types import FlaskReturn , json_ok
13+ from models .export import ExportStateDict
1014
1115from utils .export_state_store import (
1216 EXPORT_STATE_FILE ,
3337_STATE_FILE = EXPORT_STATE_FILE
3438
3539
36- def _state_lock ():
40+ def _state_lock () -> Any :
3741 return export_state_lock (_STATE_FILE )
3842
3943
40- def _load_state_from_disk () -> dict :
44+ def _load_state_from_disk () -> ExportStateDict :
4145 return load_export_state_from_disk (_STATE_FILE )
4246
4347
44- def _atomic_write_state (state : dict ) -> None :
48+ def _atomic_write_state (state : ExportStateDict ) -> None :
4549 atomic_write_export_state (state , _STATE_FILE )
4650
4751
48- def _read_state () -> dict :
52+ def _read_state () -> ExportStateDict :
4953 with _state_lock ():
5054 return _load_state_from_disk ()
5155
5256
53- def _write_state (sessions_map : dict , count : int ) -> None :
57+ def _write_state (sessions_map : dict [ str , float ] , count : int ) -> None :
5458 """Persist merge of *sessions_map* and update last-export metadata (*count* = this run only)."""
5559 with _state_lock ():
5660 state = _load_state_from_disk ()
@@ -61,10 +65,10 @@ def _write_state(sessions_map: dict, count: int) -> None:
6165
6266
6367@export_bp .route ("/api/export/state" )
64- def get_export_state ():
68+ def get_export_state () -> FlaskReturn :
6569 state = _read_state ()
6670 n = state .get ("exportedCount" , 0 )
67- return jsonify (
71+ return json_ok (
6872 {
6973 "last_export_time" : state .get ("lastExportTime" ),
7074 # Sessions exported in the last completed bulk export (not a lifetime total).
@@ -75,16 +79,16 @@ def get_export_state():
7579
7680
7781@export_bp .route ("/api/export" , methods = ["POST" ])
78- def bulk_export ():
82+ def bulk_export () -> FlaskReturn :
7983 body = request .get_json (silent = True )
8084 if body is None :
8185 body = {}
8286 if not isinstance (body , dict ):
83- return jsonify ({"error" : "Invalid request body" }), 400
87+ return json_ok ({"error" : "Invalid request body" }), 400
8488
8589 since = body .get ("since" , "all" )
8690 if since not in ("all" , "last" , "incremental" ):
87- return jsonify ({"error" : "Invalid since mode" , "since" : since }), 400
91+ return json_ok ({"error" : "Invalid since mode" , "since" : since }), 400
8892
8993 base = (
9094 current_app .config .get ("CLAUDE_PROJECTS_DIR" )
@@ -94,14 +98,14 @@ def bulk_export():
9498 rules = current_app .config .get ("EXCLUSION_RULES" ) or []
9599
96100 state = _read_state ()
97- last_export_sessions : dict = (
101+ last_export_sessions : dict [ str , float ] = (
98102 state .get ("sessions" , {}) if since == "incremental" else {}
99103 )
100104
101105 buf = io .BytesIO ()
102106 count = 0
103- manifest = []
104- new_sessions_map : dict = {}
107+ manifest : list [ dict [ str , Any ]] = []
108+ new_sessions_map : dict [ str , float ] = {}
105109 latest_day = None
106110
107111 with zipfile .ZipFile (buf , "w" , zipfile .ZIP_DEFLATED ) as zf :
@@ -227,7 +231,7 @@ def bulk_export():
227231
228232 if count == 0 :
229233 return (
230- jsonify (
234+ json_ok (
231235 {
232236 "error" : "Nothing to export" ,
233237 "since" : since ,
@@ -251,12 +255,12 @@ def bulk_export():
251255 buf ,
252256 mimetype = "application/zip" ,
253257 as_attachment = True ,
254- download_name = f"claude-code-export{ suffix } -{ date_tag } .zip" ,
258+ download_name = f"claude-code-export{ suffix } -{ date_tag } .zip" , # type: ignore[call-arg]
255259 )
256260
257261
258262@export_bp .route ("/api/export/session/<path:project_name>/<session_id>" )
259- def export_session (project_name , session_id ) :
263+ def export_session (project_name : str , session_id : str ) -> FlaskReturn :
260264 import os
261265 from utils .session_path import safe_join
262266
@@ -267,16 +271,16 @@ def export_session(project_name, session_id):
267271 try :
268272 filepath = safe_join (base , project_name , f"{ session_id } .jsonl" )
269273 except ValueError :
270- return jsonify ({"error" : "Invalid path" }), 400
274+ return json_ok ({"error" : "Invalid path" }), 400
271275
272276 if not os .path .isfile (filepath ):
273- return jsonify ({"error" : "Session not found" }), 404
277+ return json_ok ({"error" : "Session not found" }), 404
274278
275279 fmt = request .args .get ("format" , "md" )
276280 session = parse_session (filepath )
277281 rules = current_app .config .get ("EXCLUSION_RULES" ) or []
278282 if is_session_excluded (rules , session , project_name ):
279- return jsonify ({"error" : "Session not found" }), 404
283+ return json_ok ({"error" : "Session not found" }), 404
280284 stats = compute_stats (session )
281285 title_slug = slugify (session ["title" ], default = "session" )
282286
@@ -288,7 +292,7 @@ def export_session(project_name, session_id):
288292 buf ,
289293 mimetype = "application/json" ,
290294 as_attachment = True ,
291- download_name = f"{ title_slug } .json" ,
295+ download_name = f"{ title_slug } .json" , # type: ignore[call-arg]
292296 )
293297
294298 md = session_to_markdown (session , stats )
@@ -298,5 +302,5 @@ def export_session(project_name, session_id):
298302 buf ,
299303 mimetype = "text/markdown" ,
300304 as_attachment = True ,
301- download_name = f"{ title_slug } .md" ,
305+ download_name = f"{ title_slug } .md" , # type: ignore[call-arg]
302306 )
0 commit comments