X Tutup
Skip to content

Commit be68285

Browse files
amimasJohnVillalovos
authored andcommitted
feat(api): add support for project feature flags and feature flag user lists
Add new API objects for managing project feature flags and feature flag user lists, including CRUD operations, CLI support, and renaming via save(). Introduce JsonAttribute type for handling JSON fields in request bodies (e.g., feature flag strategies). Closes #3350
1 parent 8d76028 commit be68285

18 files changed

+958
-10
lines changed

docs/api-objects.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ API examples
2424
gl_objects/environments
2525
gl_objects/events
2626
gl_objects/epics
27-
gl_objects/features
27+
gl_objects/gitlab_features
2828
gl_objects/geo_nodes
2929
gl_objects/groups
3030
gl_objects/group_access_tokens
@@ -49,6 +49,8 @@ API examples
4949
gl_objects/pipelines_and_jobs
5050
gl_objects/projects
5151
gl_objects/project_access_tokens
52+
gl_objects/project_feature_flags
53+
gl_objects/project_feature_flag_user_lists
5254
gl_objects/protected_branches
5355
gl_objects/protected_container_repositories
5456
gl_objects/protected_environments
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
##############
2-
Features flags
3-
##############
1+
################################
2+
GitLab Development Feature Flags
3+
################################
4+
5+
.. note::
6+
7+
This API is for managing GitLab's internal development feature flags and requires administrator access.
8+
For project-level feature flags, see :doc:`project_feature_flags`.
49

510
Reference
611
---------
@@ -29,4 +34,4 @@ Create or set a feature::
2934

3035
Delete a feature::
3136

32-
feature.delete()
37+
feature.delete()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
###############################
2+
Project Feature Flag User Lists
3+
###############################
4+
5+
Reference
6+
---------
7+
8+
* v4 API:
9+
10+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserList`
11+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserListManager`
12+
+ :attr:`gitlab.v4.objects.Project.feature_flags_user_lists`
13+
14+
* GitLab API: https://docs.gitlab.com/api/feature_flag_user_lists
15+
16+
Examples
17+
--------
18+
19+
List user lists::
20+
21+
user_lists = project.feature_flags_user_lists.list()
22+
23+
Get a user list::
24+
25+
user_list = project.feature_flags_user_lists.get(list_iid)
26+
27+
Create a user list::
28+
29+
user_list = project.feature_flags_user_lists.create({
30+
'name': 'my_user_list',
31+
'user_xids': 'user1,user2,user3'
32+
})
33+
34+
Update a user list::
35+
36+
user_list.name = 'updated_list_name'
37+
user_list.user_xids = 'user1,user2'
38+
user_list.save()
39+
40+
Delete a user list::
41+
42+
user_list.delete()
43+
44+
Search for a user list::
45+
46+
user_lists = project.feature_flags_user_lists.list(search='my_list')
47+
48+
See also
49+
--------
50+
51+
* :doc:`project_feature_flags`
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#####################
2+
Project Feature Flags
3+
#####################
4+
5+
Reference
6+
---------
7+
8+
* v4 API:
9+
10+
+ :class:`gitlab.v4.objects.ProjectFeatureFlag`
11+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagManager`
12+
+ :attr:`gitlab.v4.objects.Project.feature_flags`
13+
14+
* GitLab API: https://docs.gitlab.com/api/feature_flags
15+
16+
Examples
17+
--------
18+
19+
List feature flags::
20+
21+
flags = project.feature_flags.list()
22+
23+
Get a feature flag::
24+
25+
flag = project.feature_flags.get('my_feature_flag')
26+
27+
Create a feature flag::
28+
29+
flag = project.feature_flags.create({'name': 'my_feature_flag', 'version': 'new_version_flag'})
30+
31+
Create a feature flag with strategies::
32+
33+
flag = project.feature_flags.create({
34+
'name': 'my_complex_flag',
35+
'version': 'new_version_flag',
36+
'strategies': [{
37+
'name': 'userWithId',
38+
'parameters': {'userIds': 'user1,user2'}
39+
}]
40+
})
41+
42+
Update a feature flag::
43+
44+
flag.description = 'Updated description'
45+
flag.save()
46+
47+
Rename a feature flag::
48+
49+
# You can rename a flag by changing its name attribute and calling save()
50+
flag.name = 'new_flag_name'
51+
flag.save()
52+
53+
# Alternatively, you can use the manager's update method
54+
project.feature_flags.update('old_flag_name', {'name': 'new_flag_name'})
55+
56+
Delete a feature flag::
57+
58+
flag.delete()
59+
60+
See also
61+
--------
62+
63+
* :doc:`project_feature_flag_user_lists`

docs/gl_objects/projects.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,44 @@ Search projects by custom attribute::
409409
project.customattributes.set('type', 'internal')
410410
gl.projects.list(custom_attributes={'type': 'internal'}, get_all=True)
411411

412+
Project feature flags
413+
=====================
414+
415+
Reference
416+
---------
417+
418+
* v4 API:
419+
420+
+ :class:`gitlab.v4.objects.ProjectFeatureFlag`
421+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagManager`
422+
+ :attr:`gitlab.v4.objects.Project.feature_flags`
423+
424+
* GitLab API: https://docs.gitlab.com/api/feature_flags
425+
426+
Examples
427+
--------
428+
429+
See :doc:`project_feature_flags`.
430+
431+
Project feature flag user lists
432+
===============================
433+
434+
Reference
435+
---------
436+
437+
* v4 API:
438+
439+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserList`
440+
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserListManager`
441+
+ :attr:`gitlab.v4.objects.Project.feature_flags_user_lists`
442+
443+
* GitLab API: https://docs.gitlab.com/api/feature_flag_user_lists
444+
445+
Examples
446+
--------
447+
448+
See :doc:`project_feature_flag_user_lists`.
449+
412450
Project files
413451
=============
414452

gitlab/types.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
22

33
import dataclasses
4+
import json
45
from typing import Any, TYPE_CHECKING
56

7+
from gitlab import exceptions
8+
69

710
@dataclasses.dataclass(frozen=True)
811
class RequiredOptional:
@@ -36,6 +39,13 @@ def validate_attrs(
3639

3740

3841
class GitlabAttribute:
42+
# Used in utils._transform_types() to decide if we should call get_for_api()
43+
# on the attribute when transform_data is False (e.g. for POST/PUT/PATCH).
44+
#
45+
# This allows us to force transformation of data even when sending JSON bodies,
46+
# which is useful for types like CommaSeparatedStringAttribute.
47+
transform_in_body = False
48+
3949
def __init__(self, value: Any = None) -> None:
4050
self._value = value
4151

@@ -49,6 +59,16 @@ def get_for_api(self, *, key: str) -> tuple[str, Any]:
4959
return (key, self._value)
5060

5161

62+
class JsonAttribute(GitlabAttribute):
63+
def set_from_cli(self, cli_value: str) -> None:
64+
try:
65+
self._value = json.loads(cli_value)
66+
except (ValueError, TypeError) as e:
67+
raise exceptions.GitlabParsingError(
68+
f"Could not parse JSON data: {e}"
69+
) from e
70+
71+
5272
class _ListArrayAttribute(GitlabAttribute):
5373
"""Helper class to support `list` / `array` types."""
5474

@@ -82,9 +102,23 @@ def get_for_api(self, *, key: str) -> tuple[str, Any]:
82102

83103

84104
class CommaSeparatedListAttribute(_ListArrayAttribute):
85-
"""For values which are sent to the server as a Comma Separated Values
86-
(CSV) string. We allow them to be specified as a list and we convert it
87-
into a CSV"""
105+
"""
106+
For values which are sent to the server as a Comma Separated Values (CSV) string
107+
in query parameters (GET), but as a list/array in JSON bodies (POST/PUT).
108+
"""
109+
110+
111+
class CommaSeparatedStringAttribute(_ListArrayAttribute):
112+
"""
113+
For values which are sent to the server as a Comma Separated Values (CSV) string.
114+
Unlike CommaSeparatedListAttribute, this type ensures the value is converted
115+
to a string even in JSON bodies (POST/PUT requests).
116+
"""
117+
118+
# Used in utils._transform_types() to ensure the value is converted to a string
119+
# via get_for_api() even when transform_data is False (e.g. for POST/PUT/PATCH).
120+
# This is needed because some APIs require a CSV string instead of a JSON array.
121+
transform_in_body = True
88122

89123

90124
class LowercaseStringAttribute(GitlabAttribute):

gitlab/utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,15 @@ def _transform_types(
198198
files[attr_name] = (key, data.pop(attr_name))
199199
continue
200200

201-
if not transform_data:
201+
# If transform_data is False, it means we are preparing data for a JSON body
202+
# (POST/PUT/PATCH). In this case, we normally skip transformation because
203+
# most types (like ArrayAttribute) only need transformation for query
204+
# parameters (GET).
205+
#
206+
# However, some types (like CommaSeparatedStringAttribute) need to be
207+
# transformed even in JSON bodies (e.g. converting a list to a CSV string).
208+
# The 'transform_in_body' flag on the attribute class controls this behavior.
209+
if not transform_data and not gitlab_attribute.transform_in_body:
202210
continue
203211

204212
if isinstance(gitlab_attribute, types.GitlabAttribute):

gitlab/v4/objects/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from .epics import *
2525
from .events import *
2626
from .export_import import *
27+
from .feature_flag_user_lists import *
28+
from .feature_flags import *
2729
from .features import *
2830
from .files import *
2931
from .geo_nodes import *
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
GitLab API:
3+
https://docs.gitlab.com/api/feature_flag_user_lists
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from gitlab import types
9+
from gitlab.base import RESTObject
10+
from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin
11+
from gitlab.types import RequiredOptional
12+
13+
__all__ = ["ProjectFeatureFlagUserList", "ProjectFeatureFlagUserListManager"]
14+
15+
16+
class ProjectFeatureFlagUserList(SaveMixin, ObjectDeleteMixin, RESTObject):
17+
_id_attr = "iid"
18+
19+
20+
class ProjectFeatureFlagUserListManager(CRUDMixin[ProjectFeatureFlagUserList]):
21+
_path = "/projects/{project_id}/feature_flags_user_lists"
22+
_obj_cls = ProjectFeatureFlagUserList
23+
_from_parent_attrs = {"project_id": "id"}
24+
_create_attrs = RequiredOptional(required=("name", "user_xids"))
25+
_update_attrs = RequiredOptional(optional=("name", "user_xids"))
26+
_list_filters = ("search",)
27+
_types = {"user_xids": types.CommaSeparatedStringAttribute}

0 commit comments

Comments
 (0)
X Tutup