1818import hashlib
1919import tempfile
2020from urllib .parse import quote_plus , urlparse
21- from typing import ClassVar , Dict , Optional , Tuple , Any
21+ from typing import ClassVar , Dict , Optional , Tuple , Any , cast
2222from types import SimpleNamespace
2323from pathlib import Path
2424from flask import Flask , render_template , Response , request , jsonify , send_file , redirect , url_for , session , abort
8181 "open_access" : "Open access" ,
8282}
8383
84+
85+ def _new_model (model_cls : Any , ** kwargs : Any ) -> Any :
86+ """Instantiate ORM models through Any to accommodate dynamic Flask-SQLAlchemy typing."""
87+ return cast (Any , model_cls )(** kwargs )
88+
8489def build_postgres_uri_from_env (env : Dict [str , str ]) -> Tuple [str , Dict [str , str ]]:
8590 """Construct a SQLAlchemy URI for Postgres using environment variables."""
8691 host = env .get ("PLANEXE_FRONTEND_MULTIUSER_DB_HOST" ) or env .get ("PLANEXE_POSTGRES_HOST" ) or "database_postgres"
@@ -99,7 +104,7 @@ def __init__(self, user_id: str, is_admin: bool = False):
99104
100105class MyAdminIndexView (AdminIndexView ):
101106 @expose ('/' )
102- def index (self ):
107+ def index (self ) -> Any :
103108 if not current_user .is_authenticated :
104109 return redirect (url_for ('login' ))
105110 if not current_user .is_admin :
@@ -533,7 +538,7 @@ def _create_tables_with_retry(attempts: int = 5, delay_seconds: float = 2.0) ->
533538 # Setup Flask-Login
534539 self .login_manager = LoginManager ()
535540 self .login_manager .init_app (self .app )
536- self .login_manager .login_view = 'login'
541+ self .login_manager .login_view = cast ( Any , 'login' )
537542
538543 @self .login_manager .user_loader
539544 def load_user (user_id ):
@@ -588,10 +593,11 @@ def _track_flask_app_started(self):
588593 }
589594
590595 with self .app .app_context ():
591- event = EventItem (
596+ event = _new_model (
597+ EventItem ,
592598 event_type = EventType .GENERIC_EVENT ,
593599 message = "Flask app started" ,
594- context = event_context
600+ context = event_context ,
595601 )
596602 self .db .session .add (event )
597603 self .db .session .commit ()
@@ -822,7 +828,8 @@ def _upsert_user_from_oauth(self, provider: str, profile: dict[str, Any]) -> Use
822828 self .db .session .commit ()
823829 return user
824830
825- user = UserAccount (
831+ user = _new_model (
832+ UserAccount ,
826833 email = profile .get ("email" ),
827834 name = profile .get ("name" ) or profile .get ("username" ) or profile .get ("login" ),
828835 given_name = profile .get ("given_name" ),
@@ -834,7 +841,8 @@ def _upsert_user_from_oauth(self, provider: str, profile: dict[str, Any]) -> Use
834841 self .db .session .add (user )
835842 self .db .session .commit ()
836843
837- provider_row = UserProvider (
844+ provider_row = _new_model (
845+ UserProvider ,
838846 user_id = user .id ,
839847 provider = provider ,
840848 provider_user_id = provider_user_id ,
@@ -866,7 +874,8 @@ def _get_or_create_api_key(self, user: UserAccount) -> str:
866874 raw_key = f"pex_{ secrets .token_urlsafe (24 )} "
867875 key_hash = hashlib .sha256 (f"{ api_key_secret } :{ raw_key } " .encode ("utf-8" )).hexdigest ()
868876 key_prefix = raw_key [:10 ]
869- api_key = UserApiKey (
877+ api_key = _new_model (
878+ UserApiKey ,
870879 user_id = user .id ,
871880 key_hash = key_hash ,
872881 key_prefix = key_prefix ,
@@ -879,7 +888,8 @@ def _apply_credit_delta(self, user: UserAccount, delta: Decimal, reason: str, so
879888 current_balance = self ._to_credit_decimal (user .credits_balance )
880889 next_balance = current_balance + self ._to_credit_decimal (delta )
881890 user .credits_balance = max (Decimal ("0" ), next_balance ).quantize (CREDIT_SCALE )
882- ledger = CreditHistory (
891+ ledger = _new_model (
892+ CreditHistory ,
883893 user_id = user .id ,
884894 delta = self ._to_credit_decimal (delta ),
885895 reason = reason ,
@@ -916,7 +926,8 @@ def _apply_payment_credits(
916926 if existing :
917927 return "duplicate_payment"
918928 credit_amount = self ._to_credit_decimal (credits )
919- payment = PaymentRecord (
929+ payment = _new_model (
930+ PaymentRecord ,
920931 user_id = user .id ,
921932 provider = provider ,
922933 provider_payment_id = provider_payment_id ,
@@ -940,7 +951,8 @@ def _apply_payment_credits(
940951 def _record_event (self , event_type : EventType , message : str , context : Optional [dict [str , Any ]] = None ) -> None :
941952 """Best-effort event logging for operational visibility."""
942953 try :
943- event = EventItem (
954+ event = _new_model (
955+ EventItem ,
944956 event_type = event_type ,
945957 message = message ,
946958 context = context ,
@@ -1150,7 +1162,8 @@ def _get_current_user_account(self) -> Optional[UserAccount]:
11501162 admin_pref_id = uuid .uuid5 (uuid .NAMESPACE_URL , f"planexe-admin-pref:{ self .admin_username } " )
11511163 user = self .db .session .get (UserAccount , admin_pref_id )
11521164 if user is None :
1153- user = UserAccount (
1165+ user = _new_model (
1166+ UserAccount ,
11541167 id = admin_pref_id ,
11551168 is_admin = True ,
11561169 name = "Admin" ,
@@ -2353,7 +2366,10 @@ def stripe_checkout():
23532366 "checkout_payment_status" : session_obj .get ("payment_status" ),
23542367 },
23552368 )
2356- return redirect (session_obj .url )
2369+ session_url = session_obj .url
2370+ if not isinstance (session_url , str ) or not session_url :
2371+ return jsonify ({"error" : "stripe checkout missing redirect url" }), 502
2372+ return redirect (session_url )
23572373
23582374 @self .app .route ('/billing/stripe/webhook' , methods = ['POST' ])
23592375 def stripe_webhook ():
@@ -2608,7 +2624,7 @@ def run():
26082624 prompt_param = request_form_or_args .get ('prompt' , '' )
26092625 user_id_param = request_form_or_args .get ('user_id' , '' )
26102626 nonce_param = request_form_or_args .get ('nonce' , '' )
2611- parameters = {key : value for key , value in request_form_or_args .items ()}
2627+ parameters : dict [ str , Any ] | None = {key : value for key , value in request_form_or_args .items ()}
26122628
26132629 # Remove the parameters that have already been extracted from the parameters dictionary
26142630 parameters .pop ('prompt' , None )
@@ -2673,7 +2689,8 @@ def run():
26732689 if (user .credits_balance or 0 ) <= 0 :
26742690 return jsonify ({"error" : "No credits available" }), 402
26752691
2676- task = TaskItem (
2692+ task = _new_model (
2693+ TaskItem ,
26772694 state = TaskState .pending ,
26782695 prompt = prompt_param ,
26792696 progress_percentage = 0.0 ,
@@ -2696,7 +2713,8 @@ def run():
26962713 "parameters" : parameters ,
26972714 "method" : request .method
26982715 }
2699- event = EventItem (
2716+ event = _new_model (
2717+ EventItem ,
27002718 event_type = EventType .TASK_PENDING ,
27012719 message = f"Enqueued task via /run endpoint" ,
27022720 context = event_context
@@ -2718,7 +2736,7 @@ def create_plan():
27182736 request_content_type : str = request .headers .get ('Content-Type' , '' )
27192737
27202738 prompt_param = request .form .get ('prompt' , '' )
2721- parameters = {key : value for key , value in request .form .items ()}
2739+ parameters : dict [ str , Any ] = {key : value for key , value in request .form .items ()}
27222740 parameters .pop ('csrf_token' , None )
27232741 parameters .pop ('prompt' , None )
27242742 parameters .pop ('user_id' , None )
@@ -2763,7 +2781,8 @@ def create_plan():
27632781 if self ._to_credit_decimal (user .credits_balance ) < Decimal ("2" ):
27642782 return jsonify ({"error" : "Insufficient credits (minimum 2 required)" }), 402
27652783
2766- task = TaskItem (
2784+ task = _new_model (
2785+ TaskItem ,
27672786 state = TaskState .pending ,
27682787 prompt = prompt_param ,
27692788 progress_percentage = 0.0 ,
@@ -2787,7 +2806,8 @@ def create_plan():
27872806 "parameters" : parameters ,
27882807 "method" : request .method
27892808 }
2790- event = EventItem (
2809+ event = _new_model (
2810+ EventItem ,
27912811 event_type = EventType .TASK_PENDING ,
27922812 message = "Enqueued task via /create_plan endpoint" ,
27932813 context = event_context
0 commit comments