Skip to content

Clean-up the validate_attrs method/function #2042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 1, 2022
Merged
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
6 changes: 2 additions & 4 deletions gitlab/mixins.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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