-
Notifications
You must be signed in to change notification settings - Fork 164
refactor(BA-4004): migrate direct access pattern of group_config to directory pattern (phase 1) #7956
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
base: main
Are you sure you want to change the base?
refactor(BA-4004): migrate direct access pattern of group_config to directory pattern (phase 1) #7956
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 group configuration (dotfiles) API from direct database access to a repository pattern with layered architecture. The change introduces a GroupConfigRepository with a separate GroupConfigDBSource layer and adds comprehensive unit tests for the repository layer.
Changes:
- Introduced repository pattern with resilience policies (metrics and retry) for group configuration operations
- Separated database access logic into a dedicated db_source layer
- Added unit tests for the repository layer covering core functionality
Reviewed changes
Copilot reviewed 5 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/ai/backend/manager/repositories/group_config/repository.py |
New repository layer with resilience policies for group configuration operations |
src/ai/backend/manager/repositories/group_config/db_source/db_source.py |
New database source layer handling direct SQL queries for dotfiles operations |
src/ai/backend/manager/api/groupconfig.py |
Refactored to use GroupConfigRepository instead of direct database queries |
tests/unit/manager/repositories/group_config/test_group_config_repository.py |
New unit tests for repository layer |
tests/unit/manager/repositories/group_config/BUILD |
Build configuration for tests |
tests/unit/manager/repositories/group_config/__init__.py |
Package initialization |
src/ai/backend/manager/repositories/group_config/__init__.py |
Package initialization |
src/ai/backend/manager/repositories/group_config/db_source/__init__.py |
Package initialization |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/unit/manager/repositories/group_config/test_group_config_repository.py
Outdated
Show resolved
Hide resolved
tests/unit/manager/repositories/group_config/test_group_config_repository.py
Outdated
Show resolved
Hide resolved
fregataa
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.
If you have a plan to add service layer, then please do not change any API handlers in this branch
tests/unit/manager/repositories/group_config/test_group_config_repository.py
Outdated
Show resolved
Hide resolved
0d95297 to
1c75810
Compare
1c75810 to
2625aed
Compare
|
This pr contains following functions. |
fregataa
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.
test_processors seems to be duplicate of test_service
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
2625aed to
27b3a56
Compare
27b3a56 to
c007293
Compare
|
new commits include:
thanks for review! |
| async def resolve_group_id_and_domain( | ||
| self, group_id_or_name: uuid.UUID | str, domain_name: Optional[str] |
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.
Could you please set the Domain input first, as the concept of 'group' is nested under the domain?
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
src/ai/backend/manager/repositories/group_config/db_source/db_source.py
Outdated
Show resolved
Hide resolved
c007293 to
429f2db
Compare
|
This commits include
Thanks for reveiw! |
26a8ea8 to
b642ec9
Compare
fregataa
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.
Looks good. I left a comment to another reviewer.
| @dataclass | ||
| class GroupConfigRepositories: | ||
| repository: GroupConfigRepository | ||
|
|
||
| @classmethod | ||
| def create(cls, args: RepositoryArgs) -> Self: | ||
| repository = GroupConfigRepository(args.db) | ||
|
|
||
| return cls( | ||
| repository=repository, | ||
| ) |
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.
Do we need such repositories pattern rather than just repository? @HyeockJinKim
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.
I'll handle this part differently once I find a suitable way to separate it.
|
Please check the CI test |
|
thanks for the review. |
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.
The code style and the fact that public methods in the repository layer are called multiple times seem to be a bit problematic. When these parts are implemented, it creates too many new database sessions, which doesn't look good.
| async def resolve_group_id( | ||
| self, domain_name: Optional[str], group_id_or_name: uuid.UUID | str | ||
| ) -> uuid.UUID: | ||
| """ |
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 Union doesn't look good coming in, but I'll fix this later.
| @dataclass | ||
| class GroupConfigRepositories: | ||
| repository: GroupConfigRepository | ||
|
|
||
| @classmethod | ||
| def create(cls, args: RepositoryArgs) -> Self: | ||
| repository = GroupConfigRepository(args.db) | ||
|
|
||
| return cls( | ||
| repository=repository, | ||
| ) |
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.
I'll handle this part differently once I find a suitable way to separate it.
|
I'll think for the point where I can make verification logic(which originally included lot of branching) close to the service, |
b642ec9 to
aad2a0b
Compare
|
I've put some modifications to minimize db_connection per services,
For now, most of the service logics have 2~3 database connections. But if there's any other way to squize connections or any other ideas please let me know! thank you. |
| return await self._db_source.resolve_group(domain_name, group_id_or_name) | ||
|
|
||
| @group_config_repository_resilience.apply() | ||
| async def get_dotfiles(self, group_id: uuid.UUID) -> GroupDotfilesResult: |
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.
We are renaming the group to project, so please adjust to project_id.
| result = await self._db_source.get_dotfiles(group_id) | ||
|
|
||
| if result.leftover_space == 0: | ||
| raise DotfileCreationFailed("No leftover space for dotfile storage") | ||
| if len(result.dotfiles) >= 100: | ||
| raise DotfileCreationFailed("Dotfile creation limit reached") | ||
|
|
||
| duplicate = [x for x in result.dotfiles if x["path"] == dotfile.path] | ||
| if len(duplicate) > 0: | ||
| raise DotfileAlreadyExists | ||
|
|
||
| new_dotfiles = list(result.dotfiles) | ||
| new_dotfiles.append({ | ||
| "path": dotfile.path, | ||
| "perm": dotfile.permission, | ||
| "data": dotfile.data, | ||
| }) | ||
| dotfile_packed = msgpack.packb(new_dotfiles) | ||
| if len(dotfile_packed) > MAXIMUM_DOTFILE_SIZE: | ||
| raise DotfileCreationFailed("No leftover space for dotfile storage") | ||
|
|
||
| await self._db_source.update_dotfiles(group_id, dotfile_packed) | ||
|
|
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.
Is it really necessary to handle this at the repository layer? The issue seems to be that the database session gets separated.
| async def _resolve_group_for_admin( | ||
| self, | ||
| domain_name: str | None, | ||
| group_id_or_name: uuid.UUID | str, | ||
| user_domain: str, | ||
| is_superadmin: bool, | ||
| ) -> uuid.UUID: |
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.
Reading current_user internally would make it more concise.
| # Regular user: check if they are a member of the group | ||
| is_member = await self._group_config_repository.check_user_in_group(user_id, group.id) | ||
| if not is_member: | ||
| raise GenericForbidden("Users cannot access group dotfiles of other groups") |
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 find an exception that better reflects the situation instead of GenericForbidden.
| except ProjectNotFound: | ||
| # Original API raises DotfileNotFound when group is not found in delete | ||
| raise DotfileNotFound |
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.
Project Not Found doesn't seem to mean DotFileNotFound, does it?
9c34f42 to
ebde60e
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 rebase the main branch and run the type checker and linter once.
ebde60e to
222c002
Compare
5672748 to
f461f9c
Compare
…uery Introduce GroupConfigRepository with ResolvedGroup type that fetches group id and domain_name in a single query, reducing redundant DB round-trips from resolve_group_id + get_group_domain. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add service layer, action types, processors, and tests for group configuration dotfile operations. Service uses consolidated resolve_group for efficient permission validation. Co-Authored-By: Claude Opus 4.5 <[email protected]>
f461f9c to
0f6082f
Compare
resolves #8061 (BA-4004) (issue will consists of multilple PRs)
Summary
api/groupconfig.pyto repository patternGroupConfigRepositorywithdb_sourcelayer separationChanges
New files
repositories/group_config/repository.py- Repository with resilience policiesrepositories/group_config/db_source/db_source.py- Pure DB query layertests/unit/manager/repositories/group_config/test_group_config_repository.pyModified files
api/groupconfig.py- UseGroupConfigRepositoryinstead of direct DB queriesArchitecture
Concerns
Model/dotfiles.pyto repository dir? (no need to query in modelrepo = GroupConfigRepository(root_ctx.db)this way. but we only have few handlers if could over engineering.Checklist: (if applicable)
ai.backend.testdocsdirectory