| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -214,6 +214,38 @@ the value on the object is accepted: | |||
| 214 | 214 | issue.my_super_awesome_feature_flag = "random_value" | |
| 215 | 215 | issue.save() | |
| 216 | 216 | ||
| 217 | + You can get a dictionary representation copy of the Gitlab Object. Modifications made to | ||
| 218 | + the dictionary will have no impact on the GitLab Object. | ||
| 219 | + | ||
| 220 | + * `asdict()` method. Returns a dictionary representation of the Gitlab object. | ||
| 221 | + * `attributes` property. Returns a dictionary representation of the Gitlab | ||
| 222 | + object. Also returns any relevant parent object attributes. | ||
| 223 | + | ||
| 224 | + .. note:: | ||
| 225 | + | ||
| 226 | + `attributes` returns the parent object attributes that are defined in | ||
| 227 | + `object._from_parent_attrs`. What this can mean is that for example a `ProjectIssue` | ||
| 228 | + object will have a `project_id` key in the dictionary returned from `attributes` but | ||
| 229 | + `asdict()` will not. | ||
| 230 | + | ||
| 231 | + | ||
| 232 | + .. code-block:: python | ||
| 233 | + | ||
| 234 | + project = gl.projects.get(1) | ||
| 235 | + project_dict = project.asdict() | ||
| 236 | + | ||
| 237 | + # Or a dictionary representation also containing some of the parent attributes | ||
| 238 | + issue = project.issues.get(1) | ||
| 239 | + attribute_dict = issue.attributes | ||
| 240 | + | ||
| 241 | + You can get a JSON string represenation of the Gitlab Object. For example: | ||
| 242 | + | ||
| 243 | + .. code-block:: python | ||
| 244 | + | ||
| 245 | + project = gl.projects.get(1) | ||
| 246 | + print(project.to_json()) | ||
| 247 | + # Use arguments supported by `json.dump()` | ||
| 248 | + print(project.to_json(sort_keys=True, indent=4)) | ||
| 217 | 249 | ||
| 218 | 250 | Base types | |
| 219 | 251 | ========== | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,7 +15,9 @@ | |||
| 15 | 15 | # You should have received a copy of the GNU Lesser General Public License | |
| 16 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 17 | 17 | ||
| 18 | + import copy | ||
| 18 | 19 | import importlib | |
| 20 | + import json | ||
| 19 | 21 | import pprint | |
| 20 | 22 | import textwrap | |
| 21 | 23 | from types import ModuleType | |
@@ -151,15 +153,26 @@ def __getattr__(self, name: str) -> Any: | |||
| 151 | 153 | def __setattr__(self, name: str, value: Any) -> None: | |
| 152 | 154 | self.__dict__["_updated_attrs"][name] = value | |
| 153 | 155 | ||
| 156 | + def asdict(self, *, with_parent_attrs: bool = False) -> Dict[str, Any]: | ||
| 157 | + data = {} | ||
| 158 | + if with_parent_attrs: | ||
| 159 | + data.update(copy.deepcopy(self._parent_attrs)) | ||
| 160 | + data.update(copy.deepcopy(self._attrs)) | ||
| 161 | + data.update(copy.deepcopy(self._updated_attrs)) | ||
| 162 | + return data | ||
| 163 | + | ||
| 164 | + @property | ||
| 165 | + def attributes(self) -> Dict[str, Any]: | ||
| 166 | + return self.asdict(with_parent_attrs=True) | ||
| 167 | + | ||
| 168 | + def to_json(self, *, with_parent_attrs: bool = False, **kwargs: Any) -> str: | ||
| 169 | + return json.dumps(self.asdict(with_parent_attrs=with_parent_attrs), **kwargs) | ||
| 170 | + | ||
| 154 | 171 | def __str__(self) -> str: | |
| 155 | - data = self._attrs.copy() | ||
| 156 | - data.update(self._updated_attrs) | ||
| 157 | - return f"{type(self)} => {data}" | ||
| 172 | + return f"{type(self)} => {self.asdict()}" | ||
| 158 | 173 | ||
| 159 | 174 | def pformat(self) -> str: | |
| 160 | - data = self._attrs.copy() | ||
| 161 | - data.update(self._updated_attrs) | ||
| 162 | - return f"{type(self)} => \n{pprint.pformat(data)}" | ||
| 175 | + return f"{type(self)} => \n{pprint.pformat(self.asdict())}" | ||
| 163 | 176 | ||
| 164 | 177 | def pprint(self) -> None: | |
| 165 | 178 | print(self.pformat()) | |
@@ -250,13 +263,6 @@ def encoded_id(self) -> Optional[Union[int, str]]: | |||
| 250 | 263 | obj_id = gitlab.utils.EncodedId(obj_id) | |
| 251 | 264 | return obj_id | |
| 252 | 265 | ||
| 253 | - @property | ||
| 254 | - def attributes(self) -> Dict[str, Any]: | ||
| 255 | - d = self.__dict__["_updated_attrs"].copy() | ||
| 256 | - d.update(self.__dict__["_attrs"]) | ||
| 257 | - d.update(self.__dict__["_parent_attrs"]) | ||
| 258 | - return d | ||
| 259 | - | ||
| 260 | 266 | ||
| 261 | 267 | class RESTObjectList: | |
| 262 | 268 | """Generator object representing a list of RESTObject's. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,16 @@ class FakeManager(base.RESTManager): | |||
| 36 | 36 | _path = "/tests" | |
| 37 | 37 | ||
| 38 | 38 | ||
| 39 | + class FakeParent: | ||
| 40 | + id = 42 | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + class FakeManagerWithParent(base.RESTManager): | ||
| 44 | + _path = "/tests/{test_id}/cases" | ||
| 45 | + _obj_cls = FakeObject | ||
| 46 | + _from_parent_attrs = {"test_id": "id"} | ||
| 47 | + | ||
| 48 | + | ||
| 39 | 49 | @pytest.fixture | |
| 40 | 50 | def fake_gitlab(): | |
| 41 | 51 | return FakeGitlab() | |
@@ -46,6 +56,21 @@ def fake_manager(fake_gitlab): | |||
| 46 | 56 | return FakeManager(fake_gitlab) | |
| 47 | 57 | ||
| 48 | 58 | ||
| 59 | + @pytest.fixture | ||
| 60 | + def fake_manager_with_parent(fake_gitlab): | ||
| 61 | + return FakeManagerWithParent(fake_gitlab, parent=FakeParent) | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + @pytest.fixture | ||
| 65 | + def fake_object(fake_manager): | ||
| 66 | + return FakeObject(fake_manager, {"attr1": "foo", "alist": [1, 2, 3]}) | ||
| 67 | + | ||
| 68 | + | ||
| 69 | + @pytest.fixture | ||
| 70 | + def fake_object_with_parent(fake_manager_with_parent): | ||
| 71 | + return FakeObject(fake_manager_with_parent, {"attr1": "foo", "alist": [1, 2, 3]}) | ||
| 72 | + | ||
| 73 | + | ||
| 49 | 74 | class TestRESTManager: | |
| 50 | 75 | def test_computed_path_simple(self): | |
| 51 | 76 | class MGR(base.RESTManager): | |
@@ -306,3 +331,75 @@ def test_repr(self, fake_manager): | |||
| 306 | 331 | ||
| 307 | 332 | FakeObject._id_attr = None | |
| 308 | 333 | assert repr(obj) == "<FakeObject>" | |
| 334 | + | ||
| 335 | + def test_attributes_get(self, fake_object): | ||
| 336 | + assert fake_object.attr1 == "foo" | ||
| 337 | + result = fake_object.attributes | ||
| 338 | + assert result == {"attr1": "foo", "alist": [1, 2, 3]} | ||
| 339 | + | ||
| 340 | + def test_attributes_shows_updates(self, fake_object): | ||
| 341 | + # Updated attribute value is reflected in `attributes` | ||
| 342 | + fake_object.attr1 = "hello" | ||
| 343 | + assert fake_object.attributes == {"attr1": "hello", "alist": [1, 2, 3]} | ||
| 344 | + assert fake_object.attr1 == "hello" | ||
| 345 | + # New attribute is in `attributes` | ||
| 346 | + fake_object.new_attrib = "spam" | ||
| 347 | + assert fake_object.attributes == { | ||
| 348 | + "attr1": "hello", | ||
| 349 | + "new_attrib": "spam", | ||
| 350 | + "alist": [1, 2, 3], | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + def test_attributes_is_copy(self, fake_object): | ||
| 354 | + # Modifying the dictionary does not cause modifications to the object | ||
| 355 | + result = fake_object.attributes | ||
| 356 | + result["alist"].append(10) | ||
| 357 | + assert result == {"attr1": "foo", "alist": [1, 2, 3, 10]} | ||
| 358 | + assert fake_object.attributes == {"attr1": "foo", "alist": [1, 2, 3]} | ||
| 359 | + | ||
| 360 | + def test_attributes_has_parent_attrs(self, fake_object_with_parent): | ||
| 361 | + assert fake_object_with_parent.attr1 == "foo" | ||
| 362 | + result = fake_object_with_parent.attributes | ||
| 363 | + assert result == {"attr1": "foo", "alist": [1, 2, 3], "test_id": "42"} | ||
| 364 | + | ||
| 365 | + def test_asdict(self, fake_object): | ||
| 366 | + assert fake_object.attr1 == "foo" | ||
| 367 | + result = fake_object.asdict() | ||
| 368 | + assert result == {"attr1": "foo", "alist": [1, 2, 3]} | ||
| 369 | + | ||
| 370 | + def test_asdict_no_parent_attrs(self, fake_object_with_parent): | ||
| 371 | + assert fake_object_with_parent.attr1 == "foo" | ||
| 372 | + result = fake_object_with_parent.asdict() | ||
| 373 | + assert result == {"attr1": "foo", "alist": [1, 2, 3]} | ||
| 374 | + assert "test_id" not in fake_object_with_parent.asdict() | ||
| 375 | + assert "test_id" not in fake_object_with_parent.asdict(with_parent_attrs=False) | ||
| 376 | + assert "test_id" in fake_object_with_parent.asdict(with_parent_attrs=True) | ||
| 377 | + | ||
| 378 | + def test_asdict_modify_dict_does_not_change_object(self, fake_object): | ||
| 379 | + result = fake_object.asdict() | ||
| 380 | + # Demonstrate modifying the dictionary does not modify the object | ||
| 381 | + result["attr1"] = "testing" | ||
| 382 | + result["alist"].append(4) | ||
| 383 | + assert result == {"attr1": "testing", "alist": [1, 2, 3, 4]} | ||
| 384 | + assert fake_object.attr1 == "foo" | ||
| 385 | + assert fake_object.alist == [1, 2, 3] | ||
| 386 | + | ||
| 387 | + def test_asdict_modify_dict_does_not_change_object2(self, fake_object): | ||
| 388 | + # Modify attribute and then ensure modifying a list in the returned dict won't | ||
| 389 | + # modify the list in the object. | ||
| 390 | + fake_object.attr1 = [9, 7, 8] | ||
| 391 | + assert fake_object.asdict() == { | ||
| 392 | + "attr1": [9, 7, 8], | ||
| 393 | + "alist": [1, 2, 3], | ||
| 394 | + } | ||
| 395 | + result = fake_object.asdict() | ||
| 396 | + result["attr1"].append(1) | ||
| 397 | + assert fake_object.asdict() == { | ||
| 398 | + "attr1": [9, 7, 8], | ||
| 399 | + "alist": [1, 2, 3], | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + def test_asdict_modify_object(self, fake_object): | ||
| 403 | + # asdict() returns the updated value | ||
| 404 | + fake_object.attr1 = "spam" | ||
| 405 | + assert fake_object.asdict() == {"attr1": "spam", "alist": [1, 2, 3]} | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments