Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Down Expand Up @@ -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/api/feature_flag_user_lists

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/api/feature_flags

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/api/feature_flags

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/api/feature_flag_user_lists

Examples
--------

See :doc:`project_feature_flag_user_lists`.

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

Expand Down
40 changes: 37 additions & 3 deletions gitlab/types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

import dataclasses
import json
from typing import Any, TYPE_CHECKING

from gitlab import exceptions


@dataclasses.dataclass(frozen=True)
class RequiredOptional:
Expand Down Expand Up @@ -36,6 +39,13 @@ def validate_attrs(


class GitlabAttribute:
# Used in utils._transform_types() to decide if we should call get_for_api()
# on the attribute when transform_data is False (e.g. for POST/PUT/PATCH).
#
# This allows us to force transformation of data even when sending JSON bodies,
# which is useful for types like CommaSeparatedStringAttribute.
transform_in_body = False

def __init__(self, value: Any = None) -> None:
self._value = value

Expand All @@ -49,6 +59,16 @@ 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:
try:
self._value = json.loads(cli_value)
except (ValueError, TypeError) as e:
raise exceptions.GitlabParsingError(
f"Could not parse JSON data: {e}"
) from e


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

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


class CommaSeparatedListAttribute(_ListArrayAttribute):
"""For values which are sent to the server as a Comma Separated Values
(CSV) string. We allow them to be specified as a list and we convert it
into a CSV"""
"""
For values which are sent to the server as a Comma Separated Values (CSV) string
in query parameters (GET), but as a list/array in JSON bodies (POST/PUT).
"""


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).
"""

# Used in utils._transform_types() to ensure the value is converted to a string
# via get_for_api() even when transform_data is False (e.g. for POST/PUT/PATCH).
# This is needed because some APIs require a CSV string instead of a JSON array.
transform_in_body = True


class LowercaseStringAttribute(GitlabAttribute):
Expand Down
10 changes: 9 additions & 1 deletion gitlab/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,15 @@ def _transform_types(
files[attr_name] = (key, data.pop(attr_name))
continue

if not transform_data:
# If transform_data is False, it means we are preparing data for a JSON body
# (POST/PUT/PATCH). In this case, we normally skip transformation because
# most types (like ArrayAttribute) only need transformation for query
# parameters (GET).
#
# However, some types (like CommaSeparatedStringAttribute) need to be
# transformed even in JSON bodies (e.g. converting a list to a CSV string).
# The 'transform_in_body' flag on the attribute class controls this behavior.
if not transform_data and not gitlab_attribute.transform_in_body:
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/api/feature_flag_user_lists
"""

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}
Loading
Loading