Skip to content

Commit 3792917

Browse files
committed
feat(api): enable feature flag renaming via save()
The `ProjectFeatureFlag` object uses `name` as its ID attribute. Previously, modifying the `name` of a flag object and calling `save()` would fail because the library would attempt to send a PUT request to the *new* name, which does not yet exist, resulting in a 404 error. This change overrides the `save()` method in `ProjectFeatureFlag` to correctly handle renaming. It now detects when the `name` attribute has been modified and uses the original name for the API request URL, while sending the new name in the request body. This makes renaming feature flags more intuitive and consistent with the behavior of other objects in the library.
1 parent 2365ef7 commit 3792917

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

gitlab/v4/objects/feature_flags.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
from __future__ import annotations
77

8-
from gitlab import types
8+
from typing import Any
9+
10+
from gitlab import types, utils
911
from gitlab.base import RESTObject
1012
from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin
1113
from gitlab.types import RequiredOptional
@@ -15,6 +17,50 @@
1517

1618
class ProjectFeatureFlag(SaveMixin, ObjectDeleteMixin, RESTObject):
1719
_id_attr = "name"
20+
manager: ProjectFeatureFlagManager
21+
22+
def _get_save_url_id(self) -> str | int | None:
23+
"""Get the ID used to construct the API URL for the save operation.
24+
25+
For renames, this must be the *original* name of the flag. For other
26+
updates, it is the current name.
27+
"""
28+
if self._id_attr in self._updated_attrs:
29+
# If the name is being changed, use the original name for the URL.
30+
obj_id = self._attrs.get(self._id_attr)
31+
if isinstance(obj_id, str):
32+
return utils.EncodedId(obj_id)
33+
return obj_id
34+
return self.encoded_id
35+
36+
def save(self, **kwargs: Any) -> dict[str, Any] | None:
37+
"""Save the changes made to the object to the server.
38+
39+
The object is updated to match what the server returns.
40+
41+
This method overrides the default ``save()`` method to handle renaming
42+
feature flags. When the name is modified, the API requires the original
43+
name in the URL to identify the resource, while the new name is sent
44+
in the request body.
45+
46+
Args:
47+
**kwargs: Extra options to send to the server (e.g. sudo)
48+
49+
Returns:
50+
The new object data (*not* a RESTObject)
51+
52+
Raises:
53+
GitlabAuthenticationError: If authentication is not correct
54+
GitlabUpdateError: If the server cannot perform the request
55+
"""
56+
updated_data = self._get_updated_data()
57+
if not updated_data:
58+
return None
59+
60+
obj_id = self._get_save_url_id()
61+
server_data = self.manager.update(obj_id, updated_data, **kwargs)
62+
self._update_attrs(server_data)
63+
return server_data
1864

1965

2066
class ProjectFeatureFlagManager(CRUDMixin[ProjectFeatureFlag]):
@@ -24,6 +70,8 @@ class ProjectFeatureFlagManager(CRUDMixin[ProjectFeatureFlag]):
2470
_create_attrs = RequiredOptional(
2571
required=("name",), optional=("version", "description", "active", "strategies")
2672
)
27-
_update_attrs = RequiredOptional(optional=("description", "active", "strategies"))
73+
_update_attrs = RequiredOptional(
74+
optional=("name", "description", "active", "strategies")
75+
)
2876
_list_filters = ("scope",)
2977
_types = {"strategies": types.JsonAttribute}

tests/functional/api/test_project_feature_flags.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,26 @@ def test_update_feature_flag(project, feature_flag):
5252
assert updated_flag.active is False
5353

5454

55+
def test_rename_feature_flag(project, feature_flag):
56+
# Rename via save()
57+
new_name = "renamed_flag"
58+
feature_flag.name = new_name
59+
feature_flag.save()
60+
61+
updated_flag = project.feature_flags.get(new_name)
62+
assert updated_flag.name == new_name
63+
64+
# Rename via update()
65+
newer_name = "renamed_flag_2"
66+
project.feature_flags.update(new_name, {"name": newer_name})
67+
68+
updated_flag_2 = project.feature_flags.get(newer_name)
69+
assert updated_flag_2.name == newer_name
70+
71+
# Update the fixture object so teardown can delete the correct flag
72+
feature_flag.name = newer_name
73+
74+
5575
def test_delete_feature_flag(project, feature_flag):
5676
feature_flag.delete()
5777
with pytest.raises(exceptions.GitlabGetError):

0 commit comments

Comments
 (0)