Skip to content

Commit

Permalink
Merge pull request #2042 from python-gitlab/jlvillal/exclusive
Browse files Browse the repository at this point in the history
Clean-up the `validate_attrs` method/function
  • Loading branch information
nejch committed Jun 1, 2022
2 parents dea9435 + 9d629bb commit 28cf3c3
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 44 deletions.
6 changes: 2 additions & 4 deletions gitlab/mixins.py
Expand Up @@ -278,7 +278,7 @@ def create(
if data is None:
data = {}

utils._validate_attrs(data=data, attributes=self._create_attrs)
self._create_attrs.validate_attrs(data=data)
data, files = utils._transform_types(data, self._types)

# Handle specific URL for creation
Expand Down Expand Up @@ -345,9 +345,7 @@ def update(
excludes = []
if self._obj_cls is not None and self._obj_cls._id_attr is not None:
excludes = [self._obj_cls._id_attr]
utils._validate_attrs(
data=new_data, attributes=self._update_attrs, excludes=excludes
)
self._update_attrs.validate_attrs(data=new_data, excludes=excludes)
new_data, files = utils._transform_types(new_data, self._types)

http_method = self._get_update_method()
Expand Down
29 changes: 28 additions & 1 deletion gitlab/types.py
Expand Up @@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import dataclasses
from typing import Any, Optional, Tuple, TYPE_CHECKING
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING


@dataclasses.dataclass(frozen=True)
Expand All @@ -25,6 +25,33 @@ class RequiredOptional:
optional: Tuple[str, ...] = ()
exclusive: Tuple[str, ...] = ()

def validate_attrs(
self,
*,
data: Dict[str, Any],
excludes: Optional[List[str]] = None,
) -> None:
if excludes is None:
excludes = []

if self.required:
required = [k for k in self.required if k not in excludes]
missing = [attr for attr in required if attr not in data]
if missing:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")

if self.exclusive:
exclusives = [attr for attr in data if attr in self.exclusive]
if len(exclusives) > 1:
raise AttributeError(
f"Provide only one of these attributes: {', '.join(exclusives)}"
)
if not exclusives:
raise AttributeError(
f"Must provide one of these attributes: "
f"{', '.join(self.exclusive)}"
)


class GitlabAttribute:
def __init__(self, value: Any = None) -> None:
Expand Down
29 changes: 1 addition & 28 deletions gitlab/utils.py
Expand Up @@ -19,7 +19,7 @@
import traceback
import urllib.parse
import warnings
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
from typing import Any, Callable, Dict, Optional, Tuple, Type, Union

import requests

Expand Down Expand Up @@ -159,30 +159,3 @@ def warn(
stacklevel=stacklevel,
source=source,
)


def _validate_attrs(
data: Dict[str, Any],
attributes: types.RequiredOptional,
excludes: Optional[List[str]] = None,
) -> None:
if excludes is None:
excludes = []

if attributes.required:
required = [k for k in attributes.required if k not in excludes]
missing = [attr for attr in required if attr not in data]
if missing:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")

if attributes.exclusive:
exclusives = [attr for attr in data if attr in attributes.exclusive]
if len(exclusives) > 1:
raise AttributeError(
f"Provide only one of these attributes: {', '.join(exclusives)}"
)
if not exclusives:
raise AttributeError(
"Must provide one of these attributes: %(attrs)s"
% {"attrs": ", ".join(attributes.exclusive)}
)
4 changes: 2 additions & 2 deletions gitlab/v4/objects/epics.py
@@ -1,7 +1,7 @@
from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union

from gitlab import exceptions as exc
from gitlab import types, utils
from gitlab import types
from gitlab.base import RESTManager, RESTObject
from gitlab.mixins import (
CreateMixin,
Expand Down Expand Up @@ -107,7 +107,7 @@ def create(
"""
if TYPE_CHECKING:
assert data is not None
utils._validate_attrs(data=data, attributes=self._create_attrs)
self._create_attrs.validate_attrs(data=data)
path = f"{self.path}/{data.pop('issue_id')}"
server_data = self.gitlab.http_post(path, **kwargs)
if TYPE_CHECKING:
Expand Down
4 changes: 2 additions & 2 deletions gitlab/v4/objects/files.py
Expand Up @@ -145,7 +145,7 @@ def create(

if TYPE_CHECKING:
assert data is not None
utils._validate_attrs(data=data, attributes=self._create_attrs)
self._create_attrs.validate_attrs(data=data)
new_data = data.copy()
file_path = utils.EncodedId(new_data.pop("file_path"))
path = f"{self.path}/{file_path}"
Expand Down Expand Up @@ -179,7 +179,7 @@ def update( # type: ignore
file_path = utils.EncodedId(file_path)
data["file_path"] = file_path
path = f"{self.path}/{file_path}"
utils._validate_attrs(data=data, attributes=self._update_attrs)
self._update_attrs.validate_attrs(data=data)
result = self.gitlab.http_put(path, post_data=data, **kwargs)
if TYPE_CHECKING:
assert isinstance(result, dict)
Expand Down
4 changes: 2 additions & 2 deletions gitlab/v4/objects/issues.py
Expand Up @@ -2,7 +2,7 @@

from gitlab import cli
from gitlab import exceptions as exc
from gitlab import types, utils
from gitlab import types
from gitlab.base import RESTManager, RESTObject
from gitlab.mixins import (
CreateMixin,
Expand Down Expand Up @@ -272,7 +272,7 @@ def create( # type: ignore
GitlabAuthenticationError: If authentication is not correct
GitlabCreateError: If the server cannot perform the request
"""
utils._validate_attrs(data=data, attributes=self._create_attrs)
self._create_attrs.validate_attrs(data=data)
if TYPE_CHECKING:
assert self.path is not None
server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs)
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/mixins/test_mixin_methods.py
Expand Up @@ -3,7 +3,6 @@

from gitlab import base
from gitlab import types as gl_types
from gitlab import utils
from gitlab.mixins import (
CreateMixin,
DeleteMixin,
Expand Down Expand Up @@ -174,11 +173,11 @@ class M(CreateMixin, FakeManager):

mgr = M(gl)
data = {"foo": "bar", "baz": "blah"}
utils._validate_attrs(data=data, attributes=mgr._create_attrs)
mgr._create_attrs.validate_attrs(data=data)

data = {"baz": "blah"}
with pytest.raises(AttributeError) as error:
utils._validate_attrs(data=data, attributes=mgr._create_attrs)
mgr._create_attrs.validate_attrs(data=data)
assert "foo" in str(error.value)


Expand Down Expand Up @@ -240,11 +239,11 @@ class M(UpdateMixin, FakeManager):

mgr = M(gl)
data = {"foo": "bar", "baz": "blah"}
utils._validate_attrs(data=data, attributes=mgr._update_attrs)
mgr._update_attrs.validate_attrs(data=data)

data = {"baz": "blah"}
with pytest.raises(AttributeError) as error:
utils._validate_attrs(data=data, attributes=mgr._update_attrs)
mgr._update_attrs.validate_attrs(data=data)
assert "foo" in str(error.value)


Expand Down

0 comments on commit 28cf3c3

Please sign in to comment.