X Tutup
Skip to content
Open
4 changes: 3 additions & 1 deletion docs/api-objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ API examples
gl_objects/environments
gl_objects/events
gl_objects/epics
gl_objects/features
gl_objects/gitlab_features
gl_objects/geo_nodes
gl_objects/groups
gl_objects/group_access_tokens
Expand All @@ -49,6 +49,8 @@ API examples
gl_objects/pipelines_and_jobs
gl_objects/projects
gl_objects/project_access_tokens
gl_objects/project_feature_flags
gl_objects/project_feature_flag_user_lists
gl_objects/protected_branches
gl_objects/protected_container_repositories
gl_objects/protected_environments
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
##############
Features flags
##############
################################
GitLab Development Feature Flags
################################

.. note::

This API is for managing GitLab's internal development feature flags and requires administrator access.
For project-level feature flags, see :doc:`project_feature_flags`.

Reference
---------
Expand All @@ -11,7 +16,7 @@ Reference
+ :class:`gitlab.v4.objects.FeatureManager`
+ :attr:`gitlab.Gitlab.features`

* GitLab API: https://docs.gitlab.com/api/features
* GitLab API: https://docs.gitlab.com/ee/api/features.html

Examples
--------
Expand All @@ -29,4 +34,4 @@ Create or set a feature::

Delete a feature::

feature.delete()
feature.delete()
51 changes: 51 additions & 0 deletions docs/gl_objects/project_feature_flag_user_lists.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
###############################
Project Feature Flag User Lists
###############################

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserList`
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserListManager`
+ :attr:`gitlab.v4.objects.Project.feature_flags_user_lists`

* GitLab API: https://docs.gitlab.com/ee/api/feature_flag_user_lists.html

Examples
--------

List user lists::

user_lists = project.feature_flags_user_lists.list()

Get a user list::

user_list = project.feature_flags_user_lists.get(list_iid)

Create a user list::

user_list = project.feature_flags_user_lists.create({
'name': 'my_user_list',
'user_xids': 'user1,user2,user3'
})

Update a user list::

user_list.name = 'updated_list_name'
user_list.user_xids = 'user1,user2'
user_list.save()

Delete a user list::

user_list.delete()

Search for a user list::

user_lists = project.feature_flags_user_lists.list(search='my_list')

See also
--------

* :doc:`project_feature_flags`
63 changes: 63 additions & 0 deletions docs/gl_objects/project_feature_flags.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#####################
Project Feature Flags
#####################

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectFeatureFlag`
+ :class:`gitlab.v4.objects.ProjectFeatureFlagManager`
+ :attr:`gitlab.v4.objects.Project.feature_flags`

* GitLab API: https://docs.gitlab.com/ee/api/feature_flags.html

Examples
--------

List feature flags::

flags = project.feature_flags.list()

Get a feature flag::

flag = project.feature_flags.get('my_feature_flag')

Create a feature flag::

flag = project.feature_flags.create({'name': 'my_feature_flag', 'version': 'new_version_flag'})

Create a feature flag with strategies::

flag = project.feature_flags.create({
'name': 'my_complex_flag',
'version': 'new_version_flag',
'strategies': [{
'name': 'userWithId',
'parameters': {'userIds': 'user1,user2'}
}]
})

Update a feature flag::

flag.description = 'Updated description'
flag.save()

Rename a feature flag::

# You can rename a flag by changing its name attribute and calling save()
flag.name = 'new_flag_name'
flag.save()

# Alternatively, you can use the manager's update method
project.feature_flags.update('old_flag_name', {'name': 'new_flag_name'})

Delete a feature flag::

flag.delete()

See also
--------

* :doc:`project_feature_flag_user_lists`
38 changes: 38 additions & 0 deletions docs/gl_objects/projects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,44 @@ Search projects by custom attribute::
project.customattributes.set('type', 'internal')
gl.projects.list(custom_attributes={'type': 'internal'}, get_all=True)

Project feature flags
=====================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectFeatureFlag`
+ :class:`gitlab.v4.objects.ProjectFeatureFlagManager`
+ :attr:`gitlab.v4.objects.Project.feature_flags`

* GitLab API: https://docs.gitlab.com/ee/api/feature_flags.html

Examples
--------

See :doc:`project_feature_flags`.

Project feature flag user lists
===============================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserList`
+ :class:`gitlab.v4.objects.ProjectFeatureFlagUserListManager`
+ :attr:`gitlab.v4.objects.Project.feature_flags_user_lists`

* GitLab API: https://docs.gitlab.com/ee/api/feature_flag_user_lists.html

Examples
--------

See :doc:`project_feature_flag_user_lists`.

Project files
=============

Expand Down
19 changes: 19 additions & 0 deletions gitlab/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import dataclasses
import json
from typing import Any, TYPE_CHECKING


Expand Down Expand Up @@ -49,6 +50,14 @@ def get_for_api(self, *, key: str) -> tuple[str, Any]:
return (key, self._value)


class JsonAttribute(GitlabAttribute):
def set_from_cli(self, cli_value: str) -> None:
if not cli_value.strip():
self._value = None
else:
self._value = json.loads(cli_value)


class _ListArrayAttribute(GitlabAttribute):
"""Helper class to support `list` / `array` types."""

Expand Down Expand Up @@ -87,6 +96,16 @@ class CommaSeparatedListAttribute(_ListArrayAttribute):
into a CSV"""


class CommaSeparatedStringAttribute(_ListArrayAttribute):
"""
For values which are sent to the server as a Comma Separated Values (CSV) string.
Unlike CommaSeparatedListAttribute, this type ensures the value is converted
to a string even in JSON bodies (POST/PUT requests).
"""

transform_in_post = True


class LowercaseStringAttribute(GitlabAttribute):
def get_for_api(self, *, key: str) -> tuple[str, str]:
return (key, str(self._value).lower())
Expand Down
4 changes: 3 additions & 1 deletion gitlab/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ def _transform_types(
files[attr_name] = (key, data.pop(attr_name))
continue

if not transform_data:
if not transform_data and not getattr(
gitlab_attribute, "transform_in_post", False
):
continue

if isinstance(gitlab_attribute, types.GitlabAttribute):
Expand Down
2 changes: 2 additions & 0 deletions gitlab/v4/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from .epics import *
from .events import *
from .export_import import *
from .feature_flag_user_lists import *
from .feature_flags import *
from .features import *
from .files import *
from .geo_nodes import *
Expand Down
27 changes: 27 additions & 0 deletions gitlab/v4/objects/feature_flag_user_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
GitLab API:
https://docs.gitlab.com/ee/api/feature_flag_user_lists.html
"""

from __future__ import annotations

from gitlab import types
from gitlab.base import RESTObject
from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin
from gitlab.types import RequiredOptional

__all__ = ["ProjectFeatureFlagUserList", "ProjectFeatureFlagUserListManager"]


class ProjectFeatureFlagUserList(SaveMixin, ObjectDeleteMixin, RESTObject):
_id_attr = "iid"


class ProjectFeatureFlagUserListManager(CRUDMixin[ProjectFeatureFlagUserList]):
_path = "/projects/{project_id}/feature_flags_user_lists"
_obj_cls = ProjectFeatureFlagUserList
_from_parent_attrs = {"project_id": "id"}
_create_attrs = RequiredOptional(required=("name", "user_xids"))
_update_attrs = RequiredOptional(optional=("name", "user_xids"))
_list_filters = ("search",)
_types = {"user_xids": types.CommaSeparatedStringAttribute}
77 changes: 77 additions & 0 deletions gitlab/v4/objects/feature_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
GitLab API:
https://docs.gitlab.com/ee/api/feature_flags.html
"""

from __future__ import annotations

from typing import Any

from gitlab import types, utils
from gitlab.base import RESTObject
from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin
from gitlab.types import RequiredOptional

__all__ = ["ProjectFeatureFlag", "ProjectFeatureFlagManager"]


class ProjectFeatureFlag(SaveMixin, ObjectDeleteMixin, RESTObject):
_id_attr = "name"
manager: ProjectFeatureFlagManager

def _get_save_url_id(self) -> str | int | None:
"""Get the ID used to construct the API URL for the save operation.

For renames, this must be the *original* name of the flag. For other
updates, it is the current name.
"""
if self._id_attr in self._updated_attrs:
# If the name is being changed, use the original name for the URL.
obj_id = self._attrs.get(self._id_attr)
if isinstance(obj_id, str):
return utils.EncodedId(obj_id)
return obj_id
return self.encoded_id

def save(self, **kwargs: Any) -> dict[str, Any] | None:
"""Save the changes made to the object to the server.

The object is updated to match what the server returns.

This method overrides the default ``save()`` method to handle renaming
feature flags. When the name is modified, the API requires the original
name in the URL to identify the resource, while the new name is sent
in the request body.

Args:
**kwargs: Extra options to send to the server (e.g. sudo)

Returns:
The new object data (*not* a RESTObject)

Raises:
GitlabAuthenticationError: If authentication is not correct
GitlabUpdateError: If the server cannot perform the request
"""
updated_data = self._get_updated_data()
if not updated_data:
return None

obj_id = self._get_save_url_id()
server_data = self.manager.update(obj_id, updated_data, **kwargs)
self._update_attrs(server_data)
return server_data


class ProjectFeatureFlagManager(CRUDMixin[ProjectFeatureFlag]):
_path = "/projects/{project_id}/feature_flags"
_obj_cls = ProjectFeatureFlag
_from_parent_attrs = {"project_id": "id"}
_create_attrs = RequiredOptional(
required=("name",), optional=("version", "description", "active", "strategies")
)
_update_attrs = RequiredOptional(
optional=("name", "description", "active", "strategies")
)
_list_filters = ("scope",)
_types = {"strategies": types.JsonAttribute}
4 changes: 4 additions & 0 deletions gitlab/v4/objects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
)
from .events import ProjectEventManager # noqa: F401
from .export_import import ProjectExportManager, ProjectImportManager # noqa: F401
from .feature_flag_user_lists import ProjectFeatureFlagUserListManager # noqa: F401
from .feature_flags import ProjectFeatureFlagManager # noqa: F401
from .files import ProjectFileManager # noqa: F401
from .hooks import ProjectHookManager # noqa: F401
from .integrations import ProjectIntegrationManager, ProjectServiceManager # noqa: F401
Expand Down Expand Up @@ -201,6 +203,8 @@ class Project(
environments: ProjectEnvironmentManager
events: ProjectEventManager
exports: ProjectExportManager
feature_flags: ProjectFeatureFlagManager
feature_flags_user_lists: ProjectFeatureFlagUserListManager
files: ProjectFileManager
forks: ProjectForkManager
generic_packages: GenericPackageManager
Expand Down
Loading
X Tutup