Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-gitlab for 
openSUSE:Factory checked in at 2026-03-30 18:31:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-gitlab (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-gitlab.new.1999 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-gitlab"

Mon Mar 30 18:31:32 2026 rev:31 rq:1343535 version:8.2.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-gitlab/python-python-gitlab.changes    
    2026-03-02 17:41:30.066265083 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-python-gitlab.new.1999/python-python-gitlab.changes
      2026-03-30 18:34:52.280018471 +0200
@@ -1,0 +2,13 @@
+Sun Mar 29 18:21:31 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- update to 8.2.0:
+  * Documentation
+    - testing: Document passing pytest options during local
+      development (e6669f9)
+  * Features
+    - api: Add support for project feature flags and feature flag
+      user lists (be68285)
+    - projects: Add optional parameter to set approval rule on all
+      protected branches. (8d76028)
+
+-------------------------------------------------------------------

Old:
----
  python_gitlab-8.1.0.tar.gz

New:
----
  python_gitlab-8.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-gitlab.spec ++++++
--- /var/tmp/diff_new_pack.TYkiaX/_old  2026-03-30 18:34:52.940046065 +0200
+++ /var/tmp/diff_new_pack.TYkiaX/_new  2026-03-30 18:34:52.944046232 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-python-gitlab
-Version:        8.1.0
+Version:        8.2.0
 Release:        0
 Summary:        Python module for interacting with the GitLab API
 License:        LGPL-3.0-only

++++++ python_gitlab-8.1.0.tar.gz -> python_gitlab-8.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/CHANGELOG.md 
new/python_gitlab-8.2.0/CHANGELOG.md
--- old/python_gitlab-8.1.0/CHANGELOG.md        2026-02-28 02:26:08.000000000 
+0100
+++ new/python_gitlab-8.2.0/CHANGELOG.md        2026-03-28 02:49:39.000000000 
+0100
@@ -2,6 +2,22 @@
 
 All versions below are listed in reverse chronological order.
 
+## v8.2.0 (2026-03-28)
+
+### Documentation
+
+- **testing**: Document passing pytest options during local development
+  
([`e6669f9`](https://github.com/python-gitlab/python-gitlab/commit/e6669f96d662d310109afa4a61fe8dabbd780a4e))
+
+### Features
+
+- **api**: Add support for project feature flags and feature flag user lists
+  
([`be68285`](https://github.com/python-gitlab/python-gitlab/commit/be68285793f35afc10a72b59da3fb24429631f54))
+
+- **projects**: Add optional parameter to set approval rule on all protected 
branches.
+  
([`8d76028`](https://github.com/python-gitlab/python-gitlab/commit/8d76028a1ae3554527291dc98e6be041ff089ec5))
+
+
 ## v8.1.0 (2026-02-28)
 
 ### Bug Fixes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/PKG-INFO 
new/python_gitlab-8.2.0/PKG-INFO
--- old/python_gitlab-8.1.0/PKG-INFO    2026-02-28 02:26:14.690398200 +0100
+++ new/python_gitlab-8.2.0/PKG-INFO    2026-03-28 02:49:46.099087200 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-gitlab
-Version: 8.1.0
+Version: 8.2.0
 Summary: The python wrapper for the GitLab REST and GraphQL APIs.
 Author-email: Gauvain Pocentek <[email protected]>
 Maintainer-email: John Villalovos <[email protected]>, Max Wittig 
<[email protected]>, Nejc Habjan <[email protected]>, Roger Meier 
<[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/docs/api-objects.rst 
new/python_gitlab-8.2.0/docs/api-objects.rst
--- old/python_gitlab-8.1.0/docs/api-objects.rst        2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/api-objects.rst        2026-03-28 
02:49:29.000000000 +0100
@@ -24,7 +24,7 @@
    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
@@ -49,6 +49,8 @@
    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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/docs/gl_objects/features.rst 
new/python_gitlab-8.2.0/docs/gl_objects/features.rst
--- old/python_gitlab-8.1.0/docs/gl_objects/features.rst        2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/gl_objects/features.rst        1970-01-01 
01:00:00.000000000 +0100
@@ -1,32 +0,0 @@
-##############
-Features flags
-##############
-
-Reference
----------
-
-* v4 API:
-
-  + :class:`gitlab.v4.objects.Feature`
-  + :class:`gitlab.v4.objects.FeatureManager`
-  + :attr:`gitlab.Gitlab.features`
-
-* GitLab API: https://docs.gitlab.com/api/features
-
-Examples
---------
-
-List features::
-
-    features = gl.features.list(get_all=True)
-
-Create or set a feature::
-
-    feature = gl.features.set(feature_name, True)
-    feature = gl.features.set(feature_name, 30)
-    feature = gl.features.set(feature_name, True, user=filipowm)
-    feature = gl.features.set(feature_name, 40, group=mygroup)
-
-Delete a feature::
-
-    feature.delete()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/docs/gl_objects/gitlab_features.rst 
new/python_gitlab-8.2.0/docs/gl_objects/gitlab_features.rst
--- old/python_gitlab-8.1.0/docs/gl_objects/gitlab_features.rst 1970-01-01 
01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/gl_objects/gitlab_features.rst 2026-03-28 
02:49:29.000000000 +0100
@@ -0,0 +1,37 @@
+################################
+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
+---------
+
+* v4 API:
+
+  + :class:`gitlab.v4.objects.Feature`
+  + :class:`gitlab.v4.objects.FeatureManager`
+  + :attr:`gitlab.Gitlab.features`
+
+* GitLab API: https://docs.gitlab.com/api/features
+
+Examples
+--------
+
+List features::
+
+    features = gl.features.list(get_all=True)
+
+Create or set a feature::
+
+    feature = gl.features.set(feature_name, True)
+    feature = gl.features.set(feature_name, 30)
+    feature = gl.features.set(feature_name, True, user=filipowm)
+    feature = gl.features.set(feature_name, 40, group=mygroup)
+
+Delete a feature::
+
+    feature.delete()
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/docs/gl_objects/project_feature_flag_user_lists.rst 
new/python_gitlab-8.2.0/docs/gl_objects/project_feature_flag_user_lists.rst
--- old/python_gitlab-8.1.0/docs/gl_objects/project_feature_flag_user_lists.rst 
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/gl_objects/project_feature_flag_user_lists.rst 
2026-03-28 02:49:29.000000000 +0100
@@ -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`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/docs/gl_objects/project_feature_flags.rst 
new/python_gitlab-8.2.0/docs/gl_objects/project_feature_flags.rst
--- old/python_gitlab-8.1.0/docs/gl_objects/project_feature_flags.rst   
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/gl_objects/project_feature_flags.rst   
2026-03-28 02:49:29.000000000 +0100
@@ -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`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/docs/gl_objects/projects.rst 
new/python_gitlab-8.2.0/docs/gl_objects/projects.rst
--- old/python_gitlab-8.1.0/docs/gl_objects/projects.rst        2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/docs/gl_objects/projects.rst        2026-03-28 
02:49:29.000000000 +0100
@@ -409,6 +409,44 @@
     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
 =============
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/gitlab/_version.py 
new/python_gitlab-8.2.0/gitlab/_version.py
--- old/python_gitlab-8.1.0/gitlab/_version.py  2026-02-28 02:26:08.000000000 
+0100
+++ new/python_gitlab-8.2.0/gitlab/_version.py  2026-03-28 02:49:39.000000000 
+0100
@@ -3,4 +3,4 @@
 __email__ = "[email protected]"
 __license__ = "LGPL3"
 __title__ = "python-gitlab"
-__version__ = "8.1.0"
+__version__ = "8.2.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/gitlab/types.py 
new/python_gitlab-8.2.0/gitlab/types.py
--- old/python_gitlab-8.1.0/gitlab/types.py     2026-02-28 02:25:57.000000000 
+0100
+++ new/python_gitlab-8.2.0/gitlab/types.py     2026-03-28 02:49:29.000000000 
+0100
@@ -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:
@@ -36,6 +39,13 @@
 
 
 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
 
@@ -49,6 +59,16 @@
         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."""
 
@@ -82,9 +102,23 @@
 
 
 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):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/gitlab/utils.py 
new/python_gitlab-8.2.0/gitlab/utils.py
--- old/python_gitlab-8.1.0/gitlab/utils.py     2026-02-28 02:25:57.000000000 
+0100
+++ new/python_gitlab-8.2.0/gitlab/utils.py     2026-03-28 02:49:29.000000000 
+0100
@@ -198,7 +198,15 @@
             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):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/gitlab/v4/objects/__init__.py 
new/python_gitlab-8.2.0/gitlab/v4/objects/__init__.py
--- old/python_gitlab-8.1.0/gitlab/v4/objects/__init__.py       2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/gitlab/v4/objects/__init__.py       2026-03-28 
02:49:29.000000000 +0100
@@ -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 *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/gitlab/v4/objects/feature_flag_user_lists.py 
new/python_gitlab-8.2.0/gitlab/v4/objects/feature_flag_user_lists.py
--- old/python_gitlab-8.1.0/gitlab/v4/objects/feature_flag_user_lists.py        
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/gitlab/v4/objects/feature_flag_user_lists.py        
2026-03-28 02:49:29.000000000 +0100
@@ -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}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/gitlab/v4/objects/feature_flags.py 
new/python_gitlab-8.2.0/gitlab/v4/objects/feature_flags.py
--- old/python_gitlab-8.1.0/gitlab/v4/objects/feature_flags.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/gitlab/v4/objects/feature_flags.py  2026-03-28 
02:49:29.000000000 +0100
@@ -0,0 +1,106 @@
+"""
+GitLab API:
+https://docs.gitlab.com/api/feature_flags
+"""
+
+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.
+
+        This is the standard method to use when updating a feature flag object
+        that you have already retrieved.
+
+        It is overridden here to correctly handle renaming. When `name` is
+        changed, the API requires the *original* name in the URL, and this
+        method provides it.
+
+        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(
+        # new_name is used for renaming via CLI and mapped to 'name' in 
update()
+        optional=("name", "new_name", "description", "active", "strategies")
+    )
+    _list_filters = ("scope",)
+    _types = {"strategies": types.JsonAttribute}
+
+    def update(
+        self,
+        id: str | int | None = None,
+        new_data: dict[str, Any] | None = None,
+        **kwargs: Any,
+    ) -> dict[str, Any]:
+        """Update a Project Feature Flag.
+
+        This is a lower-level method called by `ProjectFeatureFlag.save()` and
+        is also used directly by the CLI.
+
+        The `new_name` parameter is a special case to support renaming via the
+        CLI (`--new-name`). It is converted to the `name` parameter that the
+        GitLab API expects in the request body.
+
+        Args:
+            id: The current name of the feature flag.
+            new_data: The dictionary of attributes to update.
+            **kwargs: Extra options to send to the server (e.g. sudo)
+        """
+        # Avoid mutating the caller-provided new_data dict by working on a 
copy.
+        data = dict(new_data or {})
+        # When used via CLI, we have 'new_name' to distinguish from the ID 
'name'.
+        # When used via .save(), the object passes 'name' directly in new_data.
+        if "new_name" in data:
+            data["name"] = data.pop("new_name")
+        return super().update(id, data, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/gitlab/v4/objects/merge_request_approvals.py 
new/python_gitlab-8.2.0/gitlab/v4/objects/merge_request_approvals.py
--- old/python_gitlab-8.1.0/gitlab/v4/objects/merge_request_approvals.py        
2026-02-28 02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/gitlab/v4/objects/merge_request_approvals.py        
2026-03-28 02:49:29.000000000 +0100
@@ -90,7 +90,13 @@
     _from_parent_attrs = {"project_id": "id"}
     _create_attrs = RequiredOptional(
         required=("name", "approvals_required"),
-        optional=("user_ids", "group_ids", "protected_branch_ids", 
"usernames"),
+        optional=(
+            "user_ids",
+            "group_ids",
+            "protected_branch_ids",
+            "usernames",
+            "applies_to_all_protected_branches",
+        ),
     )
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/gitlab/v4/objects/projects.py 
new/python_gitlab-8.2.0/gitlab/v4/objects/projects.py
--- old/python_gitlab-8.1.0/gitlab/v4/objects/projects.py       2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/gitlab/v4/objects/projects.py       2026-03-28 
02:49:29.000000000 +0100
@@ -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
@@ -201,6 +203,8 @@
     environments: ProjectEnvironmentManager
     events: ProjectEventManager
     exports: ProjectExportManager
+    feature_flags: ProjectFeatureFlagManager
+    feature_flags_user_lists: ProjectFeatureFlagUserListManager
     files: ProjectFileManager
     forks: ProjectForkManager
     generic_packages: GenericPackageManager
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/python_gitlab.egg-info/PKG-INFO 
new/python_gitlab-8.2.0/python_gitlab.egg-info/PKG-INFO
--- old/python_gitlab-8.1.0/python_gitlab.egg-info/PKG-INFO     2026-02-28 
02:26:14.000000000 +0100
+++ new/python_gitlab-8.2.0/python_gitlab.egg-info/PKG-INFO     2026-03-28 
02:49:45.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-gitlab
-Version: 8.1.0
+Version: 8.2.0
 Summary: The python wrapper for the GitLab REST and GraphQL APIs.
 Author-email: Gauvain Pocentek <[email protected]>
 Maintainer-email: John Villalovos <[email protected]>, Max Wittig 
<[email protected]>, Nejc Habjan <[email protected]>, Roger Meier 
<[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/python_gitlab.egg-info/SOURCES.txt 
new/python_gitlab-8.2.0/python_gitlab.egg-info/SOURCES.txt
--- old/python_gitlab-8.1.0/python_gitlab.egg-info/SOURCES.txt  2026-02-28 
02:26:14.000000000 +0100
+++ new/python_gitlab-8.2.0/python_gitlab.egg-info/SOURCES.txt  2026-03-28 
02:49:46.000000000 +0100
@@ -53,8 +53,8 @@
 docs/gl_objects/environments.rst
 docs/gl_objects/epics.rst
 docs/gl_objects/events.rst
-docs/gl_objects/features.rst
 docs/gl_objects/geo_nodes.rst
+docs/gl_objects/gitlab_features.rst
 docs/gl_objects/group_access_tokens.rst
 docs/gl_objects/groups.rst
 docs/gl_objects/invitations.rst
@@ -77,6 +77,8 @@
 docs/gl_objects/personal_access_tokens.rst
 docs/gl_objects/pipelines_and_jobs.rst
 docs/gl_objects/project_access_tokens.rst
+docs/gl_objects/project_feature_flag_user_lists.rst
+docs/gl_objects/project_feature_flags.rst
 docs/gl_objects/projects.rst
 docs/gl_objects/protected_branches.rst
 docs/gl_objects/protected_container_repositories.rst
@@ -149,6 +151,8 @@
 gitlab/v4/objects/epics.py
 gitlab/v4/objects/events.py
 gitlab/v4/objects/export_import.py
+gitlab/v4/objects/feature_flag_user_lists.py
+gitlab/v4/objects/feature_flags.py
 gitlab/v4/objects/features.py
 gitlab/v4/objects/files.py
 gitlab/v4/objects/geo_nodes.py
@@ -233,6 +237,8 @@
 tests/functional/api/test_member_roles.py
 tests/functional/api/test_merge_requests.py
 tests/functional/api/test_packages.py
+tests/functional/api/test_project_feature_flag_user_lists.py
+tests/functional/api/test_project_feature_flags.py
 tests/functional/api/test_project_job_token_scope.py
 tests/functional/api/test_projects.py
 tests/functional/api/test_push_rules.py
@@ -252,6 +258,8 @@
 tests/functional/cli/test_cli_artifacts.py
 tests/functional/cli/test_cli_files.py
 tests/functional/cli/test_cli_packages.py
+tests/functional/cli/test_cli_project_feature_flag_user_lists.py
+tests/functional/cli/test_cli_project_feature_flags.py
 tests/functional/cli/test_cli_projects.py
 tests/functional/cli/test_cli_repository.py
 tests/functional/cli/test_cli_resource_access_tokens.py
@@ -332,6 +340,8 @@
 tests/unit/objects/test_pipeline_schedules.py
 tests/unit/objects/test_pipelines.py
 tests/unit/objects/test_project_access_tokens.py
+tests/unit/objects/test_project_feature_flag_user_lists.py
+tests/unit/objects/test_project_feature_flags.py
 tests/unit/objects/test_project_import_export.py
 tests/unit/objects/test_project_merge_request_approvals.py
 tests/unit/objects/test_project_statistics.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/requirements-lint.txt 
new/python_gitlab-8.2.0/requirements-lint.txt
--- old/python_gitlab-8.1.0/requirements-lint.txt       2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/requirements-lint.txt       2026-03-28 
02:49:29.000000000 +0100
@@ -1,9 +1,9 @@
 -r requirements.txt
 argcomplete==2.0.0
-black==26.1.0
-commitizen==4.13.8
+black==26.3.1
+commitizen==4.13.9
 flake8==7.3.0
-isort==8.0.0
+isort==8.0.1
 mypy==1.19.1
 pylint==4.0.5
 pytest==9.0.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/requirements-test.txt 
new/python_gitlab-8.2.0/requirements-test.txt
--- old/python_gitlab-8.1.0/requirements-test.txt       2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/requirements-test.txt       2026-03-28 
02:49:29.000000000 +0100
@@ -1,10 +1,10 @@
 -r requirements.txt
 anyio==4.12.1
 build==1.4.0
-coverage==7.13.4
+coverage==7.13.5
 pytest-console-scripts==1.4.1
-pytest-cov==7.0.0
-pytest-github-actions-annotate-failures==0.3.0
+pytest-cov==7.1.0
+pytest-github-actions-annotate-failures==0.4.0
 pytest==9.0.2
 PyYaml==6.0.3
 responses==0.26.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/requirements.txt 
new/python_gitlab-8.2.0/requirements.txt
--- old/python_gitlab-8.1.0/requirements.txt    2026-02-28 02:25:57.000000000 
+0100
+++ new/python_gitlab-8.2.0/requirements.txt    2026-03-28 02:49:29.000000000 
+0100
@@ -1,4 +1,4 @@
 gql==4.0.0
 httpx==0.28.1
-requests==2.32.5
+requests==2.33.0
 requests-toolbelt==1.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/functional/api/test_project_feature_flag_user_lists.py
 
new/python_gitlab-8.2.0/tests/functional/api/test_project_feature_flag_user_lists.py
--- 
old/python_gitlab-8.1.0/tests/functional/api/test_project_feature_flag_user_lists.py
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_gitlab-8.2.0/tests/functional/api/test_project_feature_flag_user_lists.py
        2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,56 @@
+import pytest
+
+from gitlab import exceptions
+
+
[email protected]
+def user_list(project, user):
+    user_list = project.feature_flags_user_lists.create(
+        {"name": "test_user_list", "user_xids": str(user.id)}
+    )
+    yield user_list
+    try:
+        user_list.delete()
+    except exceptions.GitlabDeleteError:
+        pass
+
+
+def test_create_user_list(project, user):
+    user_list = project.feature_flags_user_lists.create(
+        {"name": "created_user_list", "user_xids": str(user.id)}
+    )
+    assert user_list.name == "created_user_list"
+    assert str(user.id) in user_list.user_xids
+    user_list.delete()
+
+
+def test_list_user_lists(project, user_list):
+    ff_user_lists = project.feature_flags_user_lists.list()
+    assert len(ff_user_lists) >= 1
+    assert user_list.iid in [ff_user.iid for ff_user in ff_user_lists]
+
+
+def test_get_user_list(project, user_list, user):
+    retrieved_list = project.feature_flags_user_lists.get(user_list.iid)
+    assert retrieved_list.name == user_list.name
+    assert str(user.id) in retrieved_list.user_xids
+
+
+def test_update_user_list(project, user_list):
+    user_list.name = "updated_user_list"
+    user_list.save()
+
+    updated_list = project.feature_flags_user_lists.get(user_list.iid)
+    assert updated_list.name == "updated_user_list"
+
+
+def test_delete_user_list(project, user_list):
+    user_list.delete()
+    with pytest.raises(exceptions.GitlabGetError):
+        project.feature_flags_user_lists.get(user_list.iid)
+
+
+def test_search_user_list(project, user_list):
+    ff_user_lists = 
project.feature_flags_user_lists.list(search=user_list.name)
+    assert len(ff_user_lists) >= 1
+    assert user_list.iid in [ff_user.iid for ff_user in ff_user_lists]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/functional/api/test_project_feature_flags.py 
new/python_gitlab-8.2.0/tests/functional/api/test_project_feature_flags.py
--- old/python_gitlab-8.1.0/tests/functional/api/test_project_feature_flags.py  
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/tests/functional/api/test_project_feature_flags.py  
2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,127 @@
+import pytest
+
+from gitlab import exceptions
+
+
[email protected]
+def feature_flag(project):
+    flag_name = "test_flag_fixture"
+    flag = project.feature_flags.create(
+        {"name": flag_name, "version": "new_version_flag"}
+    )
+    yield flag
+    try:
+        flag.delete()
+    except exceptions.GitlabDeleteError:
+        pass
+
+
+def test_create_feature_flag(project):
+    flag_name = "test_flag_create"
+    flag = project.feature_flags.create(
+        {"name": flag_name, "version": "new_version_flag"}
+    )
+    assert flag.name == flag_name
+    assert flag.active is True
+    flag.delete()
+
+
+def test_create_feature_flag_with_strategies(project):
+    flag_name = "test_flag_strategies"
+    strategies = [{"name": "userWithId", "parameters": {"userIds": "user1"}}]
+    flag = project.feature_flags.create(
+        {"name": flag_name, "version": "new_version_flag", "strategies": 
strategies}
+    )
+    assert len(flag.strategies) == 1
+    assert flag.strategies[0]["name"] == "userWithId"
+    assert flag.strategies[0]["parameters"]["userIds"] == "user1"
+    flag.delete()
+
+
+def test_list_feature_flags(project, feature_flag):
+    flags = project.feature_flags.list()
+    assert len(flags) >= 1
+    assert feature_flag.name in [f.name for f in flags]
+
+
+def test_update_feature_flag(project, feature_flag):
+    feature_flag.active = False
+    feature_flag.save()
+
+    updated_flag = project.feature_flags.get(feature_flag.name)
+    assert updated_flag.active is False
+
+
+def test_rename_feature_flag(project, feature_flag):
+    # Rename via save()
+    new_name = "renamed_flag"
+    feature_flag.name = new_name
+    feature_flag.save()
+
+    updated_flag = project.feature_flags.get(new_name)
+    assert updated_flag.name == new_name
+
+    # Rename via update()
+    newer_name = "renamed_flag_2"
+    project.feature_flags.update(new_name, {"name": newer_name})
+
+    updated_flag_2 = project.feature_flags.get(newer_name)
+    assert updated_flag_2.name == newer_name
+
+    # Update the fixture object so teardown can delete the correct flag
+    feature_flag.name = newer_name
+
+
+def test_delete_feature_flag(project, feature_flag):
+    feature_flag.delete()
+    with pytest.raises(exceptions.GitlabGetError):
+        project.feature_flags.get(feature_flag.name)
+
+
+def test_delete_feature_flag_strategy(project, feature_flag):
+    strategies = [
+        {"name": "default", "parameters": {}},
+        {"name": "userWithId", "parameters": {"userIds": "user1"}},
+    ]
+    feature_flag.strategies = strategies
+    feature_flag.save()
+
+    updated_feature_flag = project.feature_flags.get(feature_flag.name)
+    assert len(updated_feature_flag.strategies) == 2
+
+    # Remove strategy using _destroy
+    updated_strategies = updated_feature_flag.strategies
+    for strategy in updated_strategies:
+        if strategy["name"] == "userWithId":
+            strategy["_destroy"] = True
+    updated_feature_flag.save()
+
+    updated_feature_flag = project.feature_flags.get(feature_flag.name)
+    assert len(updated_feature_flag.strategies) == 1
+    assert updated_feature_flag.strategies[0]["name"] == "default"
+
+
+def test_delete_feature_flag_scope(project, feature_flag):
+    strategies = [
+        {
+            "name": "default",
+            "parameters": {},
+            "scopes": [{"environment_scope": "*"}, {"environment_scope": 
"production"}],
+        }
+    ]
+    feature_flag.strategies = strategies
+    feature_flag.save()
+
+    updated_feature_flag = project.feature_flags.get(feature_flag.name)
+    assert len(updated_feature_flag.strategies[0]["scopes"]) == 2
+
+    # Remove scope using _destroy
+    updated_strategies = updated_feature_flag.strategies
+    for scope in updated_strategies[0]["scopes"]:
+        if scope["environment_scope"] == "production":
+            scope["_destroy"] = True
+    updated_feature_flag.save()
+
+    updated_feature_flag = project.feature_flags.get(feature_flag.name)
+    assert len(updated_feature_flag.strategies[0]["scopes"]) == 1
+    assert 
updated_feature_flag.strategies[0]["scopes"][0]["environment_scope"] == "*"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/functional/cli/test_cli_project_feature_flag_user_lists.py
 
new/python_gitlab-8.2.0/tests/functional/cli/test_cli_project_feature_flag_user_lists.py
--- 
old/python_gitlab-8.1.0/tests/functional/cli/test_cli_project_feature_flag_user_lists.py
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_gitlab-8.2.0/tests/functional/cli/test_cli_project_feature_flag_user_lists.py
    2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,120 @@
+import json
+
+import pytest
+
+
[email protected]
+def user_list_cli(gitlab_cli, project, user):
+    list_name = "cli_test_list_fixture"
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag-user-list",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        list_name,
+        "--user-xids",
+        str(user.id),
+    ]
+    ret = gitlab_cli(cmd)
+    data = json.loads(ret.stdout)
+    iid = str(data["iid"])
+
+    yield iid
+
+    try:
+        cmd = [
+            "project-feature-flag-user-list",
+            "delete",
+            "--project-id",
+            str(project.id),
+            "--iid",
+            iid,
+        ]
+        gitlab_cli(cmd)
+    except Exception:
+        pass
+
+
+def test_project_feature_flag_user_list_cli_create_delete(gitlab_cli, project, 
user):
+    list_name = "cli_test_list_create"
+
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag-user-list",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        list_name,
+        "--user-xids",
+        str(user.id),
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert data["name"] == list_name
+    assert str(user.id) in data["user_xids"]
+    iid = str(data["iid"])
+
+    cmd = [
+        "project-feature-flag-user-list",
+        "delete",
+        "--project-id",
+        str(project.id),
+        "--iid",
+        iid,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+
+
+def test_project_feature_flag_user_list_cli_list(gitlab_cli, project, 
user_list_cli):
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag-user-list",
+        "list",
+        "--project-id",
+        str(project.id),
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert any(item["name"] == "cli_test_list_fixture" for item in data)
+
+
+def test_project_feature_flag_user_list_cli_get(gitlab_cli, project, 
user_list_cli):
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag-user-list",
+        "get",
+        "--project-id",
+        str(project.id),
+        "--iid",
+        user_list_cli,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert data["name"] == "cli_test_list_fixture"
+
+
+def test_project_feature_flag_user_list_cli_update(gitlab_cli, project, 
user_list_cli):
+    new_name = "cli_updated_list"
+    cmd = [
+        "project-feature-flag-user-list",
+        "update",
+        "--project-id",
+        str(project.id),
+        "--iid",
+        user_list_cli,
+        "--name",
+        new_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/functional/cli/test_cli_project_feature_flags.py 
new/python_gitlab-8.2.0/tests/functional/cli/test_cli_project_feature_flags.py
--- 
old/python_gitlab-8.1.0/tests/functional/cli/test_cli_project_feature_flags.py  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_gitlab-8.2.0/tests/functional/cli/test_cli_project_feature_flags.py  
    2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,203 @@
+import json
+
+import pytest
+
+
[email protected]
+def feature_flag_cli(gitlab_cli, project):
+    flag_name = "test_flag_cli_fixture"
+    cmd = [
+        "project-feature-flag",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+    ]
+    gitlab_cli(cmd)
+    yield flag_name
+    try:
+        cmd = [
+            "project-feature-flag",
+            "delete",
+            "--project-id",
+            str(project.id),
+            "--name",
+            flag_name,
+        ]
+        gitlab_cli(cmd)
+    except Exception:
+        pass
+
+
+def test_project_feature_flag_cli_create_delete(gitlab_cli, project):
+    flag_name = "test_flag_cli_create"
+    cmd = [
+        "project-feature-flag",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    assert flag_name in ret.stdout
+
+    cmd = [
+        "project-feature-flag",
+        "delete",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+
+
+def test_project_feature_flag_cli_create_with_strategies(gitlab_cli, project):
+    flag_name = "test_flag_cli_strategies"
+    strategies_json = (
+        '[{"name": "userWithId", "parameters": {"userIds": "user1,user2"}}]'
+    )
+
+    cmd = [
+        "project-feature-flag",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+        "--strategies",
+        strategies_json,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag",
+        "get",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert len(data["strategies"]) == 1
+    assert data["strategies"][0]["name"] == "userWithId"
+
+
+def test_project_feature_flag_cli_list(gitlab_cli, project, feature_flag_cli):
+    cmd = ["project-feature-flag", "list", "--project-id", str(project.id)]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    assert feature_flag_cli in ret.stdout
+
+
+def test_project_feature_flag_cli_get(gitlab_cli, project, feature_flag_cli):
+    cmd = [
+        "project-feature-flag",
+        "get",
+        "--project-id",
+        str(project.id),
+        "--name",
+        feature_flag_cli,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    assert feature_flag_cli in ret.stdout
+
+
+def test_project_feature_flag_cli_update(gitlab_cli, project, 
feature_flag_cli):
+    cmd = [
+        "project-feature-flag",
+        "update",
+        "--project-id",
+        str(project.id),
+        "--name",
+        feature_flag_cli,
+        "--active",
+        "false",
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag",
+        "get",
+        "--project-id",
+        str(project.id),
+        "--name",
+        feature_flag_cli,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert data["active"] is False
+
+
+def test_project_feature_flag_cli_create_with_malformed_strategies(gitlab_cli, 
project):
+    flag_name = "test_flag_cli_malformed_strategies"
+    strategies_json = '[{"name": "userWithId"'  # Malformed JSON
+
+    cmd = [
+        "project-feature-flag",
+        "create",
+        "--project-id",
+        str(project.id),
+        "--name",
+        flag_name,
+        "--strategies",
+        strategies_json,
+    ]
+    ret = gitlab_cli(cmd)
+    assert not ret.success
+    assert "Could not parse JSON data" in ret.stderr
+
+
+def test_project_feature_flag_cli_rename(gitlab_cli, project, 
feature_flag_cli):
+    new_name = "cli_renamed_flag"
+    cmd = [
+        "project-feature-flag",
+        "update",
+        "--project-id",
+        str(project.id),
+        "--name",
+        feature_flag_cli,
+        "--new-name",
+        new_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+
+    cmd = [
+        "-o",
+        "json",
+        "project-feature-flag",
+        "get",
+        "--project-id",
+        str(project.id),
+        "--name",
+        new_name,
+    ]
+    ret = gitlab_cli(cmd)
+    assert ret.success
+    data = json.loads(ret.stdout)
+    assert data["name"] == new_name
+    # Cleanup renamed flag
+    cmd = [
+        "project-feature-flag",
+        "delete",
+        "--project-id",
+        str(project.id),
+        "--name",
+        new_name,
+    ]
+    gitlab_cli(cmd)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/tests/functional/fixtures/.env 
new/python_gitlab-8.2.0/tests/functional/fixtures/.env
--- old/python_gitlab-8.1.0/tests/functional/fixtures/.env      2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/tests/functional/fixtures/.env      2026-03-28 
02:49:29.000000000 +0100
@@ -1,4 +1,4 @@
 GITLAB_IMAGE=gitlab/gitlab-ee
-GITLAB_TAG=18.9.0-ee.0
+GITLAB_TAG=18.9.2-ee.0
 GITLAB_RUNNER_IMAGE=gitlab/gitlab-runner
 GITLAB_RUNNER_TAG=96856197
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/unit/objects/test_project_feature_flag_user_lists.py
 
new/python_gitlab-8.2.0/tests/unit/objects/test_project_feature_flag_user_lists.py
--- 
old/python_gitlab-8.1.0/tests/unit/objects/test_project_feature_flag_user_lists.py
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_gitlab-8.2.0/tests/unit/objects/test_project_feature_flag_user_lists.py
  2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,30 @@
+"""
+Unit tests for Project Feature Flag User Lists.
+"""
+
+import responses
+
+
+def test_create_user_list_with_list_conversion(project):
+    """
+    Verify that passing a list of integers for user_xids is converted
+    to a comma-separated string in the API payload.
+    """
+    with responses.RequestsMock() as rs:
+        rs.add(
+            responses.POST,
+            "http://localhost/api/v4/projects/1/feature_flags_user_lists";,
+            json={"iid": 1, "name": "list", "user_xids": "1,2,3"},
+            status=201,
+            match=[
+                responses.matchers.json_params_matcher(
+                    {"name": "list", "user_xids": "1,2,3"}
+                )
+            ],
+        )
+
+        project.feature_flags_user_lists.create(
+            {"name": "list", "user_xids": [1, 2, 3]}
+        )
+
+        assert len(rs.calls) == 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_gitlab-8.1.0/tests/unit/objects/test_project_feature_flags.py 
new/python_gitlab-8.2.0/tests/unit/objects/test_project_feature_flags.py
--- old/python_gitlab-8.1.0/tests/unit/objects/test_project_feature_flags.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/python_gitlab-8.2.0/tests/unit/objects/test_project_feature_flags.py    
2026-03-28 02:49:29.000000000 +0100
@@ -0,0 +1,35 @@
+"""
+Unit tests for Project Feature Flags.
+"""
+
+import responses
+
+from gitlab.v4.objects import ProjectFeatureFlag
+
+
+def test_feature_flag_rename(project):
+    """
+    Verify that renaming a feature flag uses the old name in the URL
+    and the new name in the payload.
+    """
+    flag_content = {"name": "old_name", "version": "new_version_flag", 
"active": True}
+    flag = ProjectFeatureFlag(project.feature_flags, flag_content)
+
+    # Rename locally
+    flag.name = "new_name"
+
+    with responses.RequestsMock() as rs:
+        rs.add(
+            responses.PUT,
+            "http://localhost/api/v4/projects/1/feature_flags/old_name";,
+            json={"name": "new_name", "version": "new_version_flag", "active": 
True},
+            status=200,
+            match=[responses.matchers.json_params_matcher({"name": 
"new_name"})],
+        )
+
+        flag.save()
+
+        assert len(rs.calls) == 1
+        # URL should use the old name (ID)
+        assert rs.calls[0].request.url.endswith("/feature_flags/old_name")
+        assert flag.name == "new_name"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_gitlab-8.1.0/tests/unit/test_types.py 
new/python_gitlab-8.2.0/tests/unit/test_types.py
--- old/python_gitlab-8.1.0/tests/unit/test_types.py    2026-02-28 
02:25:57.000000000 +0100
+++ new/python_gitlab-8.2.0/tests/unit/test_types.py    2026-03-28 
02:49:29.000000000 +0100
@@ -1,6 +1,6 @@
 import pytest
 
-from gitlab import types
+from gitlab import exceptions, types
 
 
 class TestRequiredOptional:
@@ -122,3 +122,40 @@
 def test_lowercase_string_attribute_get_for_api():
     o = types.LowercaseStringAttribute("FOO")
     assert o.get_for_api(key="spam") == ("spam", "foo")
+
+
+# JsonAttribute tests
+def test_json_attribute() -> None:
+    attr = types.JsonAttribute()
+
+    attr.set_from_cli('{"key": "value"}')
+    assert attr.get() == {"key": "value"}
+
+    with pytest.raises(exceptions.GitlabParsingError):
+        attr.set_from_cli("  ")
+
+
+# CommaSeparatedStringAttribute tests
+def test_comma_separated_string_attribute() -> None:
+    # Test with list of integers
+    attr = types.CommaSeparatedStringAttribute([1, 2, 3])
+    assert attr.get_for_api(key="ids") == ("ids", "1,2,3")
+
+    # Test with list of strings
+    attr = types.CommaSeparatedStringAttribute(["a", "b"])
+    assert attr.get_for_api(key="names") == ("names", "a,b")
+
+    # Test with string value (should be preserved)
+    attr = types.CommaSeparatedStringAttribute("1,2,3")
+    assert attr.get_for_api(key="ids") == ("ids", "1,2,3")
+
+    # Test CLI setting
+    attr = types.CommaSeparatedStringAttribute()
+    attr.set_from_cli("1, 2, 3")
+    assert attr.get() == ["1", "2", "3"]
+
+    attr.set_from_cli("")
+    assert attr.get() == []
+
+    # Verify transform_in_body is True
+    assert types.CommaSeparatedStringAttribute.transform_in_body is True

Reply via email to