perf(files): batch tenant summaries

This commit is contained in:
2026-07-30 01:15:53 +02:00
parent 86a905a3a7
commit 632cc6cf7d
2 changed files with 146 additions and 0 deletions

View File

@@ -140,6 +140,43 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
} }
def _tenant_summary_batch(session, tenant_ids) -> dict[str, dict[str, int]]:
from sqlalchemy import func
from govoplan_files.backend.db.models import FileAsset, FileConnectorCredential, FileConnectorPolicy, FileConnectorProfile, FileConnectorSpace
ids = tuple(dict.fromkeys(str(tenant_id) for tenant_id in tenant_ids if tenant_id))
if not ids:
return {}
counts: dict[str, dict[str, int]] = {
tenant_id: {
"files": 0,
"connector_credentials": 0,
"connector_policies": 0,
"connector_profiles": 0,
"connector_spaces": 0,
}
for tenant_id in ids
}
models = (
("files", FileAsset),
("connector_credentials", FileConnectorCredential),
("connector_policies", FileConnectorPolicy),
("connector_profiles", FileConnectorProfile),
("connector_spaces", FileConnectorSpace),
)
for count_key, model in models:
rows = (
session.query(model.tenant_id, func.count(model.id))
.filter(model.tenant_id.in_(ids))
.group_by(model.tenant_id)
.all()
)
for tenant_id, count in rows:
counts[tenant_id][count_key] = int(count)
return counts
def _veto_group_delete(session, tenant_id: str, group_id: str) -> None: def _veto_group_delete(session, tenant_id: str, group_id: str) -> None:
from govoplan_files.backend.db.models import FileAsset, FileConnectorSpace, FileFolder, FileShare from govoplan_files.backend.db.models import FileAsset, FileConnectorSpace, FileFolder, FileShare
@@ -189,6 +226,7 @@ manifest = ModuleManifest(
route_factory=_files_router, route_factory=_files_router,
role_templates=ROLE_TEMPLATES, role_templates=ROLE_TEMPLATES,
tenant_summary_providers=(_tenant_summary,), tenant_summary_providers=(_tenant_summary,),
tenant_summary_batch_providers=(_tenant_summary_batch,),
delete_veto_providers={"group": (_veto_group_delete,)}, delete_veto_providers={"group": (_veto_group_delete,)},
nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),), nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),),
frontend=FrontendModule( frontend=FrontendModule(

View File

@@ -0,0 +1,108 @@
from __future__ import annotations
import unittest
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session
from govoplan_access.backend.db.models import Account, Group, User
from govoplan_core.core.change_sequence import ChangeSequenceEntry
from govoplan_core.db.base import Base
from govoplan_files.backend.db.models import (
FileAsset,
FileConnectorCredential,
FileConnectorPolicy,
FileConnectorProfile,
FileConnectorSpace,
)
from govoplan_files.backend.manifest import _tenant_summary_batch
class FilesTenantSummaryBatchTests(unittest.TestCase):
def test_batch_summary_uses_one_grouped_query_per_owned_table(self) -> None:
engine = create_engine("sqlite+pysqlite:///:memory:")
Base.metadata.create_all(
engine,
tables=[
Account.__table__,
User.__table__,
Group.__table__,
FileAsset.__table__,
FileConnectorCredential.__table__,
FileConnectorPolicy.__table__,
FileConnectorProfile.__table__,
FileConnectorSpace.__table__,
ChangeSequenceEntry.__table__,
],
)
try:
with Session(engine) as session:
session.add_all(
[
FileAsset(
id="file-1",
tenant_id="tenant-1",
owner_type="tenant",
display_path="/one.txt",
filename="one.txt",
),
FileConnectorCredential(
id="credential-1",
tenant_id="tenant-1",
label="Credential",
),
FileConnectorPolicy(
id="policy-1",
tenant_id="tenant-1",
scope_type="tenant",
),
FileConnectorProfile(
id="profile-1",
tenant_id="tenant-1",
label="Profile",
provider="webdav",
),
FileConnectorSpace(
id="space-1",
tenant_id="tenant-1",
owner_type="tenant",
label="Space",
connector_profile_id="profile-1",
provider="webdav",
),
]
)
session.commit()
query_count = 0
def count_query(*_args: object) -> None:
nonlocal query_count
query_count += 1
event.listen(engine, "before_cursor_execute", count_query)
try:
counts = _tenant_summary_batch(
session,
["tenant-1", "tenant-empty"],
)
finally:
event.remove(engine, "before_cursor_execute", count_query)
self.assertEqual(5, query_count)
self.assertEqual(
{
"files": 1,
"connector_credentials": 1,
"connector_policies": 1,
"connector_profiles": 1,
"connector_spaces": 1,
},
counts["tenant-1"],
)
self.assertEqual(0, counts["tenant-empty"]["files"])
finally:
engine.dispose()
if __name__ == "__main__":
unittest.main()