Skip to content

Commit 1c6d1a6

Browse files
QuantumGhostCluas
authored andcommitted
fix(api): fix DetachedInstanceError for Account.current_tenant_id (langgenius#24789)
The `Account._current_tenant` object is loaded by a database session (typically `db.session`) whose lifetime is not aligned with the Account model instance. This misalignment causes a `DetachedInstanceError` to be raised when accessing attributes of `Account._current_tenant` after the original session has been closed. To resolve this issue, we now reload the tenant object with `expire_on_commit=False`, ensuring the tenant remains accessible even after the session is closed.
1 parent d20e2bc commit 1c6d1a6

File tree

1 file changed

+32
-22
lines changed

1 file changed

+32
-22
lines changed

api/models/account.py

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import enum
22
import json
33
from datetime import datetime
4-
from typing import Optional, cast
4+
from typing import Optional
55

66
import sqlalchemy as sa
77
from flask_login import UserMixin
88
from 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

1111
from 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.scalar(tenant_join_query)
126+
tenant_query = select(Tenant).where(Tenant.id == tenant.id)
127+
# TODO: 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).first()
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

Comments
 (0)