Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3993fe7

Browse files
fix(epics): use actual group_id for save/delete operations on nested epics
When an epic belonging to a subgroup is retrieved through a parentgroup's epic listing, save() and delete() operations would fail becausethey used the parent group's path instead of the epic's actual group_id.This commit overrides save() and delete() methods in GroupEpic to usethe epic's group_id attribute to construct the correct API path, ensuringoperations work correctly regardless of how the epic was retrieved.Alao add the ability to pass a custom path using `_pg_custom_path` tothe `UpdateMixin.update()` and `SaveMixin.save()` methods. This allowedthe override of the `update()` method to re-use the `SaveMixin.save()`method.This commit was created with the assistance of AICloses:#3261
1 parent0f5655c commit3993fe7

File tree

7 files changed

+159
-5
lines changed

7 files changed

+159
-5
lines changed

‎gitlab/mixins.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ def update(
292292
self,
293293
id:str|int|None=None,
294294
new_data:dict[str,Any]|None=None,
295+
_pg_custom_path:str|None=None,
295296
**kwargs:Any,
296297
)->dict[str,Any]:
297298
"""Update an object on the server.
@@ -314,6 +315,8 @@ def update(
314315
path=self.path
315316
else:
316317
path=f"{self.path}/{utils.EncodedId(id)}"
318+
if_pg_custom_pathisnotNone:
319+
path=_pg_custom_path
317320

318321
excludes= []
319322
ifself._obj_clsisnotNoneandself._obj_cls._id_attrisnotNone:
@@ -413,7 +416,9 @@ def _get_updated_data(self) -> dict[str, Any]:
413416

414417
returnupdated_data
415418

416-
defsave(self,**kwargs:Any)->dict[str,Any]|None:
419+
defsave(
420+
self,_pg_custom_path:str|None=None,**kwargs:Any
421+
)->dict[str,Any]|None:
417422
"""Save the changes made to the object to the server.
418423
419424
The object is updated to match what the server returns.
@@ -437,7 +442,9 @@ def save(self, **kwargs: Any) -> dict[str, Any] | None:
437442
obj_id=self.encoded_id
438443
ifTYPE_CHECKING:
439444
assertisinstance(self.manager,UpdateMixin)
440-
server_data=self.manager.update(obj_id,updated_data,**kwargs)
445+
server_data=self.manager.update(
446+
id=obj_id,new_data=updated_data,_pg_custom_path=_pg_custom_path,**kwargs
447+
)
441448
self._update_attrs(server_data)
442449
returnserver_data
443450

‎gitlab/v4/objects/appearance.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def update(
4040
self,
4141
id:str|int|None=None,
4242
new_data:dict[str,Any]|None=None,
43+
_pg_custom_path:str|None=None,
4344
**kwargs:Any,
4445
)->dict[str,Any]:
4546
"""Update an object on the server.

‎gitlab/v4/objects/epics.py‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
fromtypingimportAny,TYPE_CHECKING
44

5+
importgitlab.utils
56
fromgitlabimportexceptionsasexc
67
fromgitlabimporttypes
78
fromgitlab.baseimportRESTObject
@@ -24,11 +25,71 @@
2425

2526
classGroupEpic(ObjectDeleteMixin,SaveMixin,RESTObject):
2627
_id_attr="iid"
28+
manager:GroupEpicManager
2729

2830
issues:GroupEpicIssueManager
2931
resourcelabelevents:GroupEpicResourceLabelEventManager
3032
notes:GroupEpicNoteManager
3133

34+
def_epic_path(self)->str:
35+
"""Return the API path for this epic using its real group."""
36+
ifnothasattr(self,"group_id")orself.group_idisNone:
37+
raiseAttributeError(
38+
"Cannot compute epic path: attribute 'group_id' is missing."
39+
)
40+
encoded_group_id=gitlab.utils.EncodedId(self.group_id)
41+
returnf"/groups/{encoded_group_id}/epics/{self.encoded_id}"
42+
43+
@exc.on_http_error(exc.GitlabUpdateError)
44+
defsave(
45+
self,_pg_custom_path:str|None=None,**kwargs:Any
46+
)->dict[str,Any]|None:
47+
"""Save the changes made to the object to the server.
48+
49+
The object is updated to match what the server returns.
50+
51+
This method uses the epic's group_id attribute to construct the correct
52+
API path. This is important when the epic was retrieved from a parent
53+
group but actually belongs to a sub-group.
54+
55+
Args:
56+
**kwargs: Extra options to send to the server (e.g. sudo)
57+
58+
Returns:
59+
The new object data (*not* a RESTObject)
60+
61+
Raises:
62+
GitlabAuthenticationError: If authentication is not correct
63+
GitlabUpdateError: If the server cannot perform the request
64+
"""
65+
# Use the epic's actual group_id to construct the correct path.
66+
path=self._epic_path()if_pg_custom_pathisNoneelse_pg_custom_path
67+
68+
# Call SaveMixin.save() method
69+
returnsuper().save(_pg_custom_path=path,**kwargs)
70+
71+
@exc.on_http_error(exc.GitlabDeleteError)
72+
defdelete(self,**kwargs:Any)->None:
73+
"""Delete the object from the server.
74+
75+
This method uses the epic's group_id attribute to construct the correct
76+
API path. This is important when the epic was retrieved from a parent
77+
group but actually belongs to a sub-group.
78+
79+
Args:
80+
**kwargs: Extra options to send to the server (e.g. sudo)
81+
82+
Raises:
83+
GitlabAuthenticationError: If authentication is not correct
84+
GitlabDeleteError: If the server cannot perform the request
85+
"""
86+
ifTYPE_CHECKING:
87+
assertself.encoded_idisnotNone
88+
89+
# Use the epic's actual group_id to construct the correct path.
90+
path=self._epic_path()
91+
self.manager.gitlab.http_delete(path,**kwargs)
92+
3293

3394
classGroupEpicManager(CRUDMixin[GroupEpic]):
3495
_path="/groups/{group_id}/epics"
@@ -51,7 +112,7 @@ class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject):
51112
# 'self.manager.update()' call in the 'save' method.
52113
manager:GroupEpicIssueManager
53114

54-
defsave(self,**kwargs:Any)->None:
115+
defsave(self,_pg_custom_path:str|None=None,**kwargs:Any)->None:
55116
"""Save the changes made to the object to the server.
56117
57118
The object is updated to match what the server returns.

‎gitlab/v4/objects/labels.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class GroupLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
2525

2626
# Update without ID, but we need an ID to get from list.
2727
@exc.on_http_error(exc.GitlabUpdateError)
28-
defsave(self,**kwargs:Any)->None:
28+
defsave(self,_pg_custom_path:str|None=None,**kwargs:Any)->None:
2929
"""Saves the changes made to the object to the server.
3030
3131
The object is updated to match what the server returns.
@@ -86,7 +86,7 @@ class ProjectLabel(
8686

8787
# Update without ID, but we need an ID to get from list.
8888
@exc.on_http_error(exc.GitlabUpdateError)
89-
defsave(self,**kwargs:Any)->None:
89+
defsave(self,_pg_custom_path:str|None=None,**kwargs:Any)->None:
9090
"""Saves the changes made to the object to the server.
9191
9292
The object is updated to match what the server returns.

‎gitlab/v4/objects/settings.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def update(
9696
self,
9797
id:str|int|None=None,
9898
new_data:dict[str,Any]|None=None,
99+
_pg_custom_path:str|None=None,
99100
**kwargs:Any,
100101
)->dict[str,Any]:
101102
"""Update an object on the server.

‎tests/functional/api/test_epics.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
importuuid
2+
13
importpytest
24

5+
fromtests.functionalimporthelpers
6+
37
pytestmark=pytest.mark.gitlab_premium
48

59

@@ -30,3 +34,39 @@ def test_epic_notes(epic):
3034

3135
epic.notes.create({"body":"Test note"})
3236
assertepic.notes.list()
37+
38+
39+
deftest_epic_save_from_parent_group_updates_subgroup_epic(gl,group):
40+
subgroup_id=uuid.uuid4().hex
41+
subgroup=gl.groups.create(
42+
{
43+
"name":f"subgroup-{subgroup_id}",
44+
"path":f"sg-{subgroup_id}",
45+
"parent_id":group.id,
46+
}
47+
)
48+
49+
nested_epic=subgroup.epics.create(
50+
{"title":f"Nested epic{subgroup_id}","description":"Nested epic"}
51+
)
52+
53+
try:
54+
fetched_epics=group.epics.list(search=nested_epic.title)
55+
assertfetched_epics,"Expected to discover nested epic via parent group list"
56+
57+
fetched_epic=next(
58+
(epicforepicinfetched_epicsifepic.id==nested_epic.id),None
59+
)
60+
assert (
61+
fetched_epicisnotNone
62+
),"Parent group listing did not include nested epic"
63+
64+
new_label=f"nested-{subgroup_id}"
65+
fetched_epic.labels= [new_label]
66+
fetched_epic.save()
67+
68+
refreshed_epic=subgroup.epics.get(nested_epic.iid)
69+
assertnew_labelinrefreshed_epic.labels
70+
finally:
71+
helpers.safe_delete(nested_epic)
72+
helpers.safe_delete(subgroup)

‎tests/unit/objects/test_epics.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
importresponses
2+
3+
fromgitlab.v4.objects.epicsimportGroupEpic
4+
5+
6+
def_build_epic(manager,iid=3,group_id=2,title="Epic"):
7+
data= {"iid":iid,"group_id":group_id,"title":title}
8+
returnGroupEpic(manager,data)
9+
10+
11+
deftest_group_epic_save_uses_actual_group_path(group):
12+
epic_manager=group.epics
13+
epic=_build_epic(epic_manager,title="Original")
14+
epic.title="Updated"
15+
16+
withresponses.RequestsMock()asrsps:
17+
rsps.add(
18+
method=responses.PUT,
19+
url="http://localhost/api/v4/groups/2/epics/3",
20+
json={"iid":3,"group_id":2,"title":"Updated"},
21+
content_type="application/json",
22+
status=200,
23+
match=[responses.matchers.json_params_matcher({"title":"Updated"})],
24+
)
25+
26+
epic.save()
27+
28+
assertepic.title=="Updated"
29+
30+
31+
deftest_group_epic_delete_uses_actual_group_path(group):
32+
epic_manager=group.epics
33+
epic=_build_epic(epic_manager)
34+
35+
withresponses.RequestsMock()asrsps:
36+
rsps.add(
37+
method=responses.DELETE,
38+
url="http://localhost/api/v4/groups/2/epics/3",
39+
status=204,
40+
)
41+
42+
epic.delete()
43+
44+
assertlen(epic._updated_attrs)==0

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp