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

Commitdcc9847

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 commitdcc9847

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-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: 58 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
@@ -29,6 +30,63 @@ class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject):
2930
resourcelabelevents:GroupEpicResourceLabelEventManager
3031
notes:GroupEpicNoteManager
3132

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

3391
classGroupEpicManager(CRUDMixin[GroupEpic]):
3492
_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