-
Notifications
You must be signed in to change notification settings - Fork 164
refactor(BA-4082): add DB Source Pattern in Group repository #8502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(BA-4082): add DB Source Pattern in Group repository #8502
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the GroupRepository by extracting all database session-opening logic into a new GroupDBSource class, making GroupRepository a thin delegation layer that only applies resilience decorators. External dependencies are now passed as method parameters to GroupDBSource instead of being stored as fields, improving the separation of concerns. Dead code was removed and tests were updated to directly test GroupDBSource where appropriate.
Changes:
- Created new
GroupDBSourceclass to handle all database operations - Refactored
GroupRepositoryto delegate toGroupDBSourcewith resilience decorators - Updated tests to test
GroupDBSourcedirectly and avoid private field chaining
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/ai/backend/manager/repositories/group/repository.py |
Simplified to a thin delegation layer; removed all DB logic and delegated to GroupDBSource |
src/ai/backend/manager/repositories/group/db_source/db_source.py |
New file containing all DB operations previously in GroupRepository |
src/ai/backend/manager/repositories/group/db_source/__init__.py |
New init file exposing GroupDBSource |
tests/unit/manager/repositories/group/test_group_repository.py |
Updated test fixtures to test GroupDBSource directly; improved fixture composition to avoid private field access |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| import copy | ||
| import logging | ||
| import uuid |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module 'uuid' is imported with both 'import' and 'import from'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same pattern used in the original repository.py.
—import uuid is used for uuid.uuid4() calls, while from uuid import UUID is used for type annotations like Sequence[UUID].
src/ai/backend/manager/repositories/group/db_source/db_source.py
Outdated
Show resolved
Hide resolved
| _db: ExtendedAsyncSAEngine | ||
| _role_manager: RoleManager | ||
|
|
||
| def __init__(self, db: ExtendedAsyncSAEngine) -> None: | ||
| self._db = db | ||
| self._role_manager = RoleManager() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GroupDBSource stores only _db and _role_manager as fields. External dependencies like storage_manager, valkey_stat_client, and config_provider are passed as method parameters instead.
| if user_update_mode not in (None, "add", "remove"): | ||
| raise InvalidUserUpdateMode("invalid user_update_mode") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This input validation intentionally stays in GroupRepository rather than moving to GroupDBSource, as it only checks the parameter value without any DB access.
| async def _get_group_by_id(self, session: SASession, group_id: uuid.UUID) -> GroupRow | None: | ||
| """Private method to get a group by ID using an existing session.""" | ||
| result = await session.execute(sa.select(GroupRow).where(groups.c.id == group_id)) | ||
| return result.scalar_one_or_none() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed _get_group_by_id — confirmed zero references across the entire codebase.
| async def group_db_source( | ||
| self, | ||
| db_with_cleanup: ExtendedAsyncSAEngine, | ||
| storage_manager_mock: StorageSessionManager, | ||
| ) -> GroupRepository: | ||
| """Create GroupRepository instance""" | ||
| return GroupRepository( | ||
| db=db_with_cleanup, | ||
| config_provider=MagicMock(), | ||
| valkey_stat_client=MagicMock(), | ||
| storage_manager=storage_manager_mock, | ||
| ) | ||
| ) -> GroupDBSource: | ||
| """Create GroupDBSource instance""" | ||
| return GroupDBSource(db=db_with_cleanup) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored tests to avoid private field chaining (repo._db_source._method). TestGroupRepositoryDeleteEndpoints now tests GroupDBSource directly.
| @pytest.fixture | ||
| async def group_db_source_with_mock_role_manager( | ||
| self, | ||
| db_with_cleanup: ExtendedAsyncSAEngine, | ||
| ) -> GroupDBSource: | ||
| """GroupDBSource with mocked RoleManager for create tests.""" | ||
| db_source = GroupDBSource(db=db_with_cleanup) | ||
| mock_role_manager = MagicMock() | ||
| mock_role_manager.create_system_role = AsyncMock(return_value=None) | ||
| db_source._role_manager = mock_role_manager | ||
| return db_source | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For create tests that need the full repository flow (with resilience), a group_db_source_with_mock_role_manager fixture is separated so each fixture only accesses one level of private fields.
058198f to
ddf1ca4
Compare
HyeockJinKim
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review the conflicts.
Extract all DB session-opening logic from GroupRepository into GroupDBSource, making GroupRepository a thin delegation layer with resilience decorators only. This follows the same pattern applied to UserRepository in BA-4080. Key changes: - Create GroupDBSource class with all DB access methods - GroupRepository delegates to GroupDBSource for all operations - External dependencies (valkey_stat_client, config_provider, storage_manager) passed as method parameters to db_source - Remove dead code _get_group_by_id (no references found) - Remove unnecessary except-reraise for VFolderOperationFailed - Refactor tests to avoid private field chaining by testing GroupDBSource directly where appropriate Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…older check Replace bare `except: pass` with `log.warning` to aid debugging when mount entry parsing fails during active kernel vfolder mount checks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move TestGroupRepositoryDeleteEndpoints to test_group_db_source.py as TestGroupDBSourceDeleteEndpoints, keeping test_group_repository.py focused on GroupRepository public API tests only. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… fixtures Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3e7a6c2 to
eb7c707
Compare
Summary
GroupRepositoryintoGroupDBSource, makingGroupRepositorya thin delegation layer with resilience decorators onlyvalkey_stat_client,config_provider,storage_manager) are passed as method parameters toGroupDBSourceinstead of being stored as fields_get_group_by_id) and unnecessaryexcept VFolderOperationFailed: raisepatternGroupDBSourcedirectly where appropriateRelated Issue
Test plan
ruff check) passesmypy) passes on changed files🤖 Generated with Claude Code