11import enum
22import json
33from datetime import datetime
4- from typing import Optional , cast
4+ from typing import Optional
55
66import sqlalchemy as sa
77from flask_login import UserMixin
88from sqlalchemy import DateTime , String , func , select
9- from sqlalchemy .orm import Mapped , mapped_column , reconstructor
9+ from sqlalchemy .orm import Mapped , Session , mapped_column , reconstructor
1010
1111from models .base import Base
1212
@@ -118,10 +118,24 @@ def current_tenant(self):
118118
119119 @current_tenant .setter
120120 def current_tenant (self , tenant : "Tenant" ):
121- ta = db .session .scalar (select (TenantAccountJoin ).filter_by (tenant_id = tenant .id , account_id = self .id ).limit (1 ))
122- if ta :
123- self .role = TenantAccountRole (ta .role )
124- self ._current_tenant = tenant
121+ with Session (db .engine , expire_on_commit = False ) as session :
122+ tenant_join_query = select (TenantAccountJoin ).where (
123+ TenantAccountJoin .tenant_id == tenant .id , TenantAccountJoin .account_id == self .id
124+ )
125+ tenant_join = session .scalars (tenant_join_query ).first ()
126+ tenant_query = select (Tenant ).where (Tenant .id == tenant .id )
127+ # A workaround to reload the tenant with `expire_on_commit=False`, allowing
128+ # access to it after the session has been closed.
129+ # This prevents `DetachedInstanceError` when accessing the tenant outside
130+ # the session's lifecycle.
131+ # (The `tenant` argument is typically loaded by `db.session` without the
132+ # `expire_on_commit=False` flag, meaning its lifetime is tied to the web
133+ # request's lifecycle.)
134+ tenant_reloaded = session .scalars (tenant_query ).one ()
135+
136+ if tenant_join :
137+ self .role = TenantAccountRole (tenant_join .role )
138+ self ._current_tenant = tenant_reloaded
125139 return
126140 self ._current_tenant = None
127141
@@ -130,23 +144,19 @@ def current_tenant_id(self) -> str | None:
130144 return self ._current_tenant .id if self ._current_tenant else None
131145
132146 def set_tenant_id (self , tenant_id : str ):
133- tenant_account_join = cast (
134- tuple [Tenant , TenantAccountJoin ],
135- (
136- db .session .query (Tenant , TenantAccountJoin )
137- .where (Tenant .id == tenant_id )
138- .where (TenantAccountJoin .tenant_id == Tenant .id )
139- .where (TenantAccountJoin .account_id == self .id )
140- .one_or_none ()
141- ),
147+ query = (
148+ select (Tenant , TenantAccountJoin )
149+ .where (Tenant .id == tenant_id )
150+ .where (TenantAccountJoin .tenant_id == Tenant .id )
151+ .where (TenantAccountJoin .account_id == self .id )
142152 )
143-
144- if not tenant_account_join :
145- return
146-
147- tenant , join = tenant_account_join
148- self .role = TenantAccountRole (join .role )
149- self ._current_tenant = tenant
153+ with Session ( db . engine , expire_on_commit = False ) as session :
154+ tenant_account_join = session . execute ( query ). one_or_none ()
155+ if not tenant_account_join :
156+ return
157+ tenant , join = tenant_account_join
158+ self .role = TenantAccountRole (join .role )
159+ self ._current_tenant = tenant
150160
151161 @property
152162 def current_role (self ):
0 commit comments