-
Notifications
You must be signed in to change notification settings - Fork 676
Expand file tree
/
Copy pathtypes.py
More file actions
138 lines (103 loc) · 4.48 KB
/
types.py
File metadata and controls
138 lines (103 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
import dataclasses
import json
from typing import Any, TYPE_CHECKING
from gitlab import exceptions
@dataclasses.dataclass(frozen=True)
class RequiredOptional:
required: tuple[str, ...] = ()
optional: tuple[str, ...] = ()
exclusive: tuple[str, ...] = ()
def validate_attrs(
self, *, data: dict[str, Any], excludes: list[str] | None = 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:
# 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
def get(self) -> Any:
return self._value
def set_from_cli(self, cli_value: Any) -> None:
self._value = cli_value
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."""
def set_from_cli(self, cli_value: str) -> None:
if not cli_value.strip():
self._value = []
else:
self._value = [item.strip() for item in cli_value.split(",")]
def get_for_api(self, *, key: str) -> tuple[str, str]:
# Do not comma-split single value passed as string
if isinstance(self._value, str):
return (key, self._value)
if TYPE_CHECKING:
assert isinstance(self._value, list)
return (key, ",".join([str(x) for x in self._value]))
class ArrayAttribute(_ListArrayAttribute):
"""To support `array` types as documented in
https://docs.gitlab.com/ee/api/#array"""
def get_for_api(self, *, key: str) -> tuple[str, Any]:
if isinstance(self._value, str):
return (f"{key}[]", self._value)
if TYPE_CHECKING:
assert isinstance(self._value, list)
return (f"{key}[]", self._value)
class CommaSeparatedListAttribute(_ListArrayAttribute):
"""
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):
def get_for_api(self, *, key: str) -> tuple[str, str]:
return (key, str(self._value).lower())
class FileAttribute(GitlabAttribute):
@staticmethod
def get_file_name(attr_name: str | None = None) -> str | None:
return attr_name
class ImageAttribute(FileAttribute):
@staticmethod
def get_file_name(attr_name: str | None = None) -> str:
return f"{attr_name}.png" if attr_name else "image.png"