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

Commit27bd80c

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.Also 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.Closes:#3261
1 parent0f5655c commit27bd80c

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

‎gitlab/mixins.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ def update(
314314
path=self.path
315315
else:
316316
path=f"{self.path}/{utils.EncodedId(id)}"
317+
if"_pg_custom_path"inkwargs:
318+
path=kwargs.pop("_pg_custom_path")
317319

318320
excludes= []
319321
ifself._obj_clsisnotNoneandself._obj_cls._id_attrisnotNone:

‎gitlab/v4/objects/epics.py‎

Lines changed: 59 additions & 0 deletions
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,69 @@
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(self,**kwargs:Any)->dict[str,Any]|None:
45+
"""Save the changes made to the object to the server.
46+
47+
The object is updated to match what the server returns.
48+
49+
This method uses the epic's group_id attribute to construct the correct
50+
API path. This is important when the epic was retrieved from a parent
51+
group but actually belongs to a sub-group.
52+
53+
Args:
54+
**kwargs: Extra options to send to the server (e.g. sudo)
55+
56+
Returns:
57+
The new object data (*not* a RESTObject)
58+
59+
Raises:
60+
GitlabAuthenticationError: If authentication is not correct
61+
GitlabUpdateError: If the server cannot perform the request
62+
"""
63+
# Use the epic's actual group_id to construct the correct path.
64+
path=self._epic_path()
65+
66+
# Call SaveMixin.save() method
67+
returnsuper().save(_pg_custom_path=path,**kwargs)
68+
69+
@exc.on_http_error(exc.GitlabDeleteError)
70+
defdelete(self,**kwargs:Any)->None:
71+
"""Delete the object from the server.
72+
73+
This method uses the epic's group_id attribute to construct the correct
74+
API path. This is important when the epic was retrieved from a parent
75+
group but actually belongs to a sub-group.
76+
77+
Args:
78+
**kwargs: Extra options to send to the server (e.g. sudo)
79+
80+
Raises:
81+
GitlabAuthenticationError: If authentication is not correct
82+
GitlabDeleteError: If the server cannot perform the request
83+
"""
84+
ifTYPE_CHECKING:
85+
assertself.encoded_idisnotNone
86+
87+
# Use the epic's actual group_id to construct the correct path.
88+
path=self._epic_path()
89+
self.manager.gitlab.http_delete(path,**kwargs)
90+
3291

3392
classGroupEpicManager(CRUDMixin[GroupEpic]):
3493
_path="/groups/{group_id}/epics"

‎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