Skip to content

Commit 545c4e2

Browse files
Tim020claude
andauthored
Migrate queries to SQLAlchemy 2.0 select() API (#755)
* refactor(queries): Migrate app_server.py to SQLAlchemy 2.0 select() API - Replace session.query(User).filter() with select().where() - Replace session.query(Model).get(id) with session.get(Model, id) - Replace session.query(Session).delete() with session.execute(delete()) - Remove test_initialization_detects_admin_exists (duplicated coverage) - Add test_configure_jwt_creates_secret_if_missing for JWT initialization - All tests passing, no deprecation warnings Migrated 7 query patterns (lines 110, 119, 131, 133, 150, 307-309, 349) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(queries): Migrate models/script.py to SQLAlchemy 2.0 select() API - Replace session.query().filter().all() with select().where() - Replace with_entities() subqueries with select() for columns - Replace func aggregation queries with session.execute().scalar() - Replace composite key .get(dict) with session.get(Model, tuple) - Add test coverage for StageDirectionStyle.pre_delete() hook - Add test coverage for CompiledScript.compile_script() method - Add test coverage for CompiledScript.load_compiled_script() method - All tests passing, no deprecation warnings Migrated 5 query patterns (lines 204-210, 242-249, 258-263, 285-288) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * style: Fix formatting in test_digi_server.py - Remove extra blank line per ruff formatting rules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(queries): Migrate rbac/rbac_db.py to SQLAlchemy 2.0 select() API - Replace session.query().filter_by() with select().filter_by() - Add test coverage for delete_actor method in RBAC system - Test verifies RBAC assignments are properly deleted - All tests passing (12 total), no deprecation warnings - Code formatting and linting checks passing Migrated 1 query pattern (line 247) in _delete_from_rbac_db method 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(migration): Migrate script controller queries to SQLAlchemy 2.0 Migrated 14 legacy query patterns in controllers/api/show/script/script.py: - 7× session.query(Script).filter().first() → session.scalars(select().where()).first() - 3× session.query(ScriptCuts).filter().all() → session.scalars(select().where()).all() - 2× session.query(ScriptLineRevisionAssociation).filter().all() with .has() → session.scalars(select().where()).all() - 2× session.query().with_entities() subqueries → select() with session.execute().scalar() - 11× session.query().get(dict) → session.get(Model, tuple) for composite keys Created comprehensive test coverage (9 tests) in test/test_script_controller.py: - Tests for all 4 controllers (Script, CompiledScript, ScriptCuts, ScriptMaxPage) - Covers GET endpoints via HTTP requests - Verifies query patterns directly for complex composite key operations - All tests passing ✅ Changes maintain backward compatibility and pass all existing tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(migration): Migrate script revisions controller to SQLAlchemy 2.0 Migrated 5 legacy query patterns in controllers/api/show/script/revisions.py: - 5× session.query(Script).filter().first() → session.scalars(select().where()).first() All patterns were the same simple filter_first pattern across two controllers: - ScriptRevisionsController (GET, POST, DELETE) - ScriptCurrentRevisionController (GET, POST) Created comprehensive test coverage (4 tests) in test/test_script_revisions_controller.py: - Tests for GET /api/v1/show/script/revisions - Tests for GET /api/v1/show/script/revisions/current - Verifies proper 404 handling when script not found - All tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(migration): Migrate stage direction styles controller to SQLAlchemy 2.0 Migrated 4 legacy query patterns in controllers/api/show/script/stage_direction_styles.py: - 4× session.query(Script).filter().first() → session.scalars(select().where()).first() All patterns were the same simple filter_first pattern across StageDirectionStylesController: - GET: Retrieve all stage direction styles for a script - POST: Create new stage direction style - PATCH: Update existing stage direction style - DELETE: Remove stage direction style Created comprehensive test coverage (2 tests) in test/test_stage_direction_styles_controller.py: - Tests for GET /api/v1/show/script/stage_direction_styles - Verifies correct retrieval and empty list handling - All tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(server): Migrate config.py to SQLAlchemy 2.0 syntax Migrate controllers/api/show/script/config.py to use SQLAlchemy 2.0 select() API. Add comprehensive test coverage. Changes: - Migrate 1 filter_all pattern from query() to select() API - Add test_script_config_controller.py with 2 tests - All tests passing Pattern migrated: - session.query(Session).filter().all() → session.scalars(select(Session).where()).all() This completes Priority 3: Script Sub-controllers (10 patterns total). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(test): Reorganize test directory to mirror application structure Restructure test directory to follow the same hierarchy as the application controllers and models, improving organization and maintainability. Changes: - Create test/api/, test/api/show/script/, and test/models/ directories - Move controller tests to test/api/show/script/: * test_config.py (was test_script_config_controller.py) * test_revisions.py (was test_script_revisions_controller.py) * test_script.py (was test_script_controller.py) * test_stage_direction_styles.py (was test_stage_direction_styles_controller.py) - Move API tests to test/api/: * test_auth.py (was test_auth_api.py) * test_rbac.py * test_settings.py - Move model tests to test/models/: * test_script.py (was test_script_models.py) - Update all imports from relative to absolute (test.test_utils) - Add __init__.py files to all new directories All 57 tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(server): Migrate show controllers to SQLAlchemy 2.0 syntax Migrate all show-related controllers (shows, acts, scenes, characters, cast, and cues) to use SQLAlchemy 2.0 select() API. Changes: - Migrate 11 query patterns across 6 files - shows.py: 1 pattern (simple all()) - acts.py: 1 pattern (filter_all) - scenes.py: 1 pattern (filter_all) - characters.py: 1 pattern (filter_first) - cast.py: 1 pattern (filter_first) - cues.py: 6 patterns (5x filter_first Script, 1x filter_all CueAssociation) Pattern migrations: - session.query(Model).all() → session.scalars(select(Model)).all() - session.query(Model).filter().all() → session.scalars(select(Model).where()).all() - session.query(Model).filter().first() → session.scalars(select(Model).where()).first() All 57 tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(migration): Migrate Priority 5 controllers to SQLAlchemy 2.0 Migrate remaining controller query patterns from legacy query() API to SQLAlchemy 2.0 select() API. This phase covers WebSocket controllers, session management, microphone management, and authentication. Files migrated (11 patterns): - controllers/api/websocket.py (1 pattern) - controllers/api/show/sessions.py (1 pattern) - controllers/api/show/microphones.py (5 patterns) - controllers/api/auth.py (1 pattern) - controllers/ws_controller.py (2 patterns) Test coverage: - Added test_create_user_duplicate_username to test/api/test_auth.py to explicitly test username conflict detection query - Created test/api/test_websocket.py (2 tests) - Created test/api/show/test_sessions.py (2 tests) - Created test/api/show/test_microphones.py (4 tests) - All 9 Priority 5 tests pass before and after migration Migration pattern: session.query(Model).filter(condition).first/all() → session.scalars(select(Model).where(condition)).first/all() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(migration): Migrate Priority 6 auth controllers to SQLAlchemy 2.0 Migrate remaining authentication controller query patterns from legacy query() API to SQLAlchemy 2.0 select() API. Files migrated (4 patterns): - controllers/api/auth.py (4 patterns) - Lines 116-117: User session lookup during user deletion - Lines 126-128: User session lookup in retry loop - Lines 162-164: User authentication by username - Line 264: Get all users Test coverage: - Added test_delete_user to test/api/test_auth.py Tests user deletion which queries for active sessions (lines 116-117, 126-128) - Added test_get_users to test/api/test_auth.py Tests get all users endpoint (line 264) - Existing test_login_success and test_login_invalid_username Already test login authentication query (lines 162-164) All 4 tests pass before and after migration, proving behavioral equivalence. Migration pattern: session.query(Model).filter(condition).first/all() → session.scalars(select(Model).where(condition)).first/all() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(sqlalchemy): Migrate script revisions query patterns to SQLAlchemy 2.0 Migrated 2 query patterns in controllers/api/show/script/revisions.py: Pattern 1 (POST /api/v1/show/script/revisions): - OLD: session.query(func.max(...)).filter(...).one()[0] - NEW: session.scalar(select(func.max(...)).where(...)) Pattern 2 (DELETE /api/v1/show/script/revisions): - OLD: session.query(ScriptRevision).filter(...).one() - NEW: session.scalars(select(ScriptRevision).where(...)).one() Added endpoint-based tests: - test_create_revision_uses_max_query: Tests POST endpoint that uses func.max() - test_delete_revision_uses_find_first_query: Tests DELETE endpoint fallback to revision 1 All tests pass with new syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(sqlalchemy): Migrate user overrides query pattern to SQLAlchemy 2.0 Migrated 1 query pattern in models/user.py: UserOverrides.get_by_type() method: - OLD: session.query(UserOverrides).filter_by(...).filter_by(...).all() - NEW: session.scalars(select(UserOverrides).where(...).where(...)).all() Added endpoint-based tests: - test_get_stage_direction_overrides: Tests GET endpoint that retrieves overrides - test_get_stage_direction_overrides_no_overrides: Tests empty result set All tests pass with new syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(sqlalchemy): Migrate API key authentication query to SQLAlchemy 2.0 Migrated 1 query pattern in utils/web/base_controller.py: BaseAPIController.prepare() - API key authentication: - OLD: session.query(User).filter(User.api_token.isnot(None)).all() - NEW: session.scalars(select(User).where(User.api_token.isnot(None))).all() Used existing tests from test/api/test_auth.py: - test_api_token_authentication: Tests successful API key auth - test_api_token_invalid: Tests invalid API key rejection All tests pass with new syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(sqlalchemy): Migrate JWT secret query to SQLAlchemy 2.0 Migrated 1 query pattern in utils/web/jwt_service.py: JWTService.get_secret() - Fetch JWT secret from database: - OLD: session.query(SystemSettings).filter(SystemSettings.key == "jwt_secret").first() - NEW: session.scalars(select(SystemSettings).where(SystemSettings.key == "jwt_secret")).first() Used existing test from test/api/test_auth.py: - test_login_success: Tests login which triggers JWT creation and secret retrieval All tests pass with new syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(sqlalchemy): Migrate RBAC dynamic query to SQLAlchemy 2.0 Migrated 1 complex query pattern in rbac/rbac_db.py: RBAC_DB.get_objects_for_resource() - Dynamic relationship traversal: - OLD: session.query(self._db.get_mapper_for_table(table.fullname)).filter_by(**cols).all() - NEW: session.scalars(select(self._db.get_mapper_for_table(table.fullname)).filter_by(**cols)).all() Added endpoint-based test: - test_get_objects_for_resource: Tests GET /api/v1/rbac/user/objects endpoint which walks the database relationship graph from Show to find related resources All tests pass with new syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix formatting --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9ff440d commit 545c4e2

46 files changed

Lines changed: 2611 additions & 297 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

server/controllers/api/auth.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import datetime, timezone
33

44
import bcrypt
5+
from sqlalchemy import select
56
from tornado import escape, gen
67
from tornado.ioloop import IOLoop
78

@@ -45,9 +46,9 @@ async def post(self):
4546
is_admin = data.get("is_admin", False)
4647

4748
with self.make_session() as session:
48-
conflict_user = (
49-
session.query(User).filter(User.username == username).first()
50-
)
49+
conflict_user = session.scalars(
50+
select(User).where(User.username == username)
51+
).first()
5152
if conflict_user:
5253
self.set_status(400)
5354
await self.finish({"message": "Username already taken"})
@@ -111,11 +112,9 @@ async def post(self):
111112
# Then really make sure we have logged out the user for all sessions (basically,
112113
# wait for the websocket ops to finish)
113114
session_logout_attempts = 0
114-
user_sessions = (
115-
session.query(Session)
116-
.filter(Session.user_id == user_to_delete.id)
117-
.all()
118-
)
115+
user_sessions = session.scalars(
116+
select(Session).where(Session.user_id == user_to_delete.id)
117+
).all()
119118
while user_sessions and session_logout_attempts < 5:
120119
for user_session in user_sessions:
121120
ws_session = self.application.get_ws(user_session.internal_id)
@@ -124,11 +123,9 @@ async def post(self):
124123
)
125124
ws_session.current_user_id = None
126125
await gen.sleep(0.2)
127-
user_sessions = (
128-
session.query(Session)
129-
.filter(Session.user_id == user_to_delete.id)
130-
.all()
131-
)
126+
user_sessions = session.scalars(
127+
select(Session).where(Session.user_id == user_to_delete.id)
128+
).all()
132129
session_logout_attempts += 1
133130

134131
# Delete all RBAC associations for this user
@@ -162,7 +159,9 @@ async def post(self):
162159

163160
with self.make_session() as session:
164161
async with NamedLockRegistry.acquire(f"UserLock::{username}"):
165-
user = session.query(User).filter(User.username == username).first()
162+
user = session.scalars(
163+
select(User).where(User.username == username)
164+
).first()
166165
if not user:
167166
self.set_status(401)
168167
await self.finish({"message": "Invalid username/password"})
@@ -262,7 +261,7 @@ class UsersHandler(BaseAPIController):
262261
def get(self):
263262
user_schema = UserSchema()
264263
with self.make_session() as session:
265-
users = session.query(User).all()
264+
users = session.scalars(select(User)).all()
266265
self.set_status(200)
267266
self.finish({"users": [user_schema.dump(u) for u in users]})
268267

server/controllers/api/show/acts.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import List
22

3+
from sqlalchemy import select
34
from tornado import escape
45

56
from models.show import Act, Scene, Show
@@ -21,9 +22,9 @@ def get(self):
2122
with self.make_session() as session:
2223
show = session.get(Show, show_id)
2324
if show:
24-
acts: List[Act] = (
25-
session.query(Act).filter(Act.show_id == show.id).all()
26-
)
25+
acts: List[Act] = session.scalars(
26+
select(Act).where(Act.show_id == show.id)
27+
).all()
2728
acts = [act_schema.dump(c) for c in acts]
2829
self.set_status(200)
2930
self.finish({"acts": acts})

server/controllers/api/show/cast.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import defaultdict
22

3+
from sqlalchemy import select
34
from tornado import escape
45

56
from models.script import Script, ScriptLine, ScriptRevision
@@ -167,9 +168,9 @@ async def get(self):
167168
with self.make_session() as session:
168169
show: Show = session.get(Show, show_id)
169170
if show:
170-
script: Script = (
171-
session.query(Script).filter(Script.show_id == show.id).first()
172-
)
171+
script: Script = session.scalars(
172+
select(Script).where(Script.show_id == show.id)
173+
).first()
173174

174175
if script.current_revision:
175176
revision: ScriptRevision = session.get(

server/controllers/api/show/characters.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import defaultdict
22

3+
from sqlalchemy import select
34
from tornado import escape
45

56
from models.script import Script, ScriptLine, ScriptRevision
@@ -179,9 +180,9 @@ async def get(self):
179180
with self.make_session() as session:
180181
show: Show = session.get(Show, show_id)
181182
if show:
182-
script: Script = (
183-
session.query(Script).filter(Script.show_id == show.id).first()
184-
)
183+
script: Script = session.scalars(
184+
select(Script).where(Script.show_id == show.id)
185+
).first()
185186

186187
if script.current_revision:
187188
revision: ScriptRevision = session.get(

server/controllers/api/show/cues.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections
22
from typing import List
33

4+
from sqlalchemy import select
45
from tornado import escape
56

67
from models.cue import Cue, CueAssociation, CueType
@@ -173,9 +174,9 @@ def get(self):
173174
with self.make_session() as session:
174175
show = session.get(Show, show_id)
175176
if show:
176-
script: Script = (
177-
session.query(Script).filter(Script.show_id == show.id).first()
178-
)
177+
script: Script = session.scalars(
178+
select(Script).where(Script.show_id == show.id)
179+
).first()
179180

180181
if script.current_revision:
181182
revision: ScriptRevision = session.get(
@@ -186,11 +187,11 @@ def get(self):
186187
self.finish({"message": "Script does not have a current revision"})
187188
return
188189

189-
revision_cues: List[CueAssociation] = (
190-
session.query(CueAssociation)
191-
.filter(CueAssociation.revision_id == revision.id)
192-
.all()
193-
)
190+
revision_cues: List[CueAssociation] = session.scalars(
191+
select(CueAssociation).where(
192+
CueAssociation.revision_id == revision.id
193+
)
194+
).all()
194195

195196
cues = collections.defaultdict(list)
196197
for association in revision_cues:
@@ -210,9 +211,9 @@ async def post(self):
210211
with self.make_session() as session:
211212
show = session.get(Show, show_id)
212213
if show:
213-
script: Script = (
214-
session.query(Script).filter(Script.show_id == show.id).first()
215-
)
214+
script: Script = session.scalars(
215+
select(Script).where(Script.show_id == show.id)
216+
).first()
216217

217218
if script.current_revision:
218219
revision: ScriptRevision = session.get(
@@ -283,9 +284,9 @@ async def patch(self):
283284
with self.make_session() as session:
284285
show = session.get(Show, show_id)
285286
if show:
286-
script: Script = (
287-
session.query(Script).filter(Script.show_id == show.id).first()
288-
)
287+
script: Script = session.scalars(
288+
select(Script).where(Script.show_id == show.id)
289+
).first()
289290

290291
if script.current_revision:
291292
revision: ScriptRevision = session.get(
@@ -387,9 +388,9 @@ async def delete(self):
387388
with self.make_session() as session:
388389
show = session.get(Show, show_id)
389390
if show:
390-
script: Script = (
391-
session.query(Script).filter(Script.show_id == show.id).first()
392-
)
391+
script: Script = session.scalars(
392+
select(Script).where(Script.show_id == show.id)
393+
).first()
393394

394395
if script.current_revision:
395396
revision: ScriptRevision = session.get(
@@ -452,9 +453,9 @@ async def get(self):
452453
with self.make_session() as session:
453454
show: Show = session.get(Show, show_id)
454455
if show:
455-
script: Script = (
456-
session.query(Script).filter(Script.show_id == show.id).first()
457-
)
456+
script: Script = session.scalars(
457+
select(Script).where(Script.show_id == show.id)
458+
).first()
458459

459460
if script.current_revision:
460461
revision: ScriptRevision = session.get(

server/controllers/api/show/microphones.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import List
22

3+
from sqlalchemy import select
34
from tornado import escape
45

56
from models.mics import Microphone, MicrophoneAllocation
@@ -22,11 +23,9 @@ def get(self):
2223
with self.make_session() as session:
2324
show = session.get(Show, show_id)
2425
if show:
25-
mics: List[Microphone] = (
26-
session.query(Microphone)
27-
.filter(Microphone.show_id == show.id)
28-
.all()
29-
)
26+
mics: List[Microphone] = session.scalars(
27+
select(Microphone).where(Microphone.show_id == show.id)
28+
).all()
3029
mics = [mic_schema.dump(c) for c in mics]
3130
self.set_status(200)
3231
self.finish({"microphones": mics})
@@ -52,11 +51,11 @@ async def post(self):
5251
await self.finish({"message": "Name missing"})
5352
return
5453

55-
other_named = (
56-
session.query(Microphone)
57-
.filter(Microphone.show_id == show_id, Microphone.name == name)
58-
.first()
59-
)
54+
other_named = session.scalars(
55+
select(Microphone).where(
56+
Microphone.show_id == show_id, Microphone.name == name
57+
)
58+
).first()
6059
if other_named:
6160
self.set_status(400)
6261
await self.finish({"message": "Name already taken"})
@@ -113,15 +112,13 @@ async def patch(self):
113112
await self.finish({"message": "Name missing"})
114113
return
115114

116-
other_named = (
117-
session.query(Microphone)
118-
.filter(
115+
other_named = session.scalars(
116+
select(Microphone).where(
119117
Microphone.show_id == show_id,
120118
Microphone.name == name,
121119
Microphone.id != microphone_id,
122120
)
123-
.first()
124-
)
121+
).first()
125122
if other_named:
126123
self.set_status(400)
127124
await self.finish({"message": "Name already taken"})
@@ -189,11 +186,9 @@ def get(self):
189186
with self.make_session() as session:
190187
show = session.get(Show, show_id)
191188
if show:
192-
mics: List[Microphone] = (
193-
session.query(Microphone)
194-
.filter(Microphone.show_id == show.id)
195-
.all()
196-
)
189+
mics: List[Microphone] = session.scalars(
190+
select(Microphone).where(Microphone.show_id == show.id)
191+
).all()
197192

198193
allocations = {}
199194
for mic in mics:
@@ -233,14 +228,12 @@ async def patch(self):
233228
await self.finish({"message": "404 scene not found"})
234229
return
235230

236-
existing_allocation: MicrophoneAllocation = (
237-
session.query(MicrophoneAllocation)
238-
.filter(
231+
existing_allocation: MicrophoneAllocation = session.scalars(
232+
select(MicrophoneAllocation).where(
239233
MicrophoneAllocation.scene_id == scene.id,
240234
MicrophoneAllocation.mic_id == mic.id,
241235
)
242-
.first()
243-
)
236+
).first()
244237

245238
character_id = data[microphone_id][scene_id]
246239
if character_id:

server/controllers/api/show/scenes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import List
22

3+
from sqlalchemy import select
34
from tornado import escape
45

56
from models.show import Scene, Show
@@ -21,9 +22,9 @@ def get(self):
2122
with self.make_session() as session:
2223
show = session.get(Show, show_id)
2324
if show:
24-
scenes: List[Scene] = (
25-
session.query(Scene).filter(Scene.show_id == show.id).all()
26-
)
25+
scenes: List[Scene] = session.scalars(
26+
select(Scene).where(Scene.show_id == show.id)
27+
).all()
2728
scenes = [scene_schema.dump(c) for c in scenes]
2829
self.set_status(200)
2930
self.finish({"scenes": scenes})

server/controllers/api/show/script/config.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import List
22

3+
from sqlalchemy import select
4+
35
from models.session import Session
46
from utils.web.base_controller import BaseAPIController
57
from utils.web.route import ApiRoute, ApiVersion
@@ -9,9 +11,9 @@
911
class ScriptStatusController(BaseAPIController):
1012
def get(self):
1113
with self.make_session() as session:
12-
editors: List[Session] = (
13-
session.query(Session).filter(Session.is_editor).all()
14-
)
14+
editors: List[Session] = session.scalars(
15+
select(Session).where(Session.is_editor)
16+
).all()
1517
if editors:
1618
current_editor = editors[0].internal_id
1719
else:

0 commit comments

Comments
 (0)