Skip to content

Commit 486354e

Browse files
committed
Add Tag.update() -> Tag method
1 parent 6b82ba3 commit 486354e

File tree

4 files changed

+140
-51
lines changed

4 files changed

+140
-51
lines changed

integration/tests/posit/connect/test_tags.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ def test_tag_content_items(self):
150150
self.contentC["guid"],
151151
}
152152

153+
# Update tag
154+
tagDName = tagD.update(name="tagD_updated")
155+
assert tagDName["name"] == "tagD_updated"
156+
assert self.client.tags.get(tagD["id"])["name"] == "tagD_updated"
157+
158+
tagDParent = tagDName.update(parent=tagB)
159+
assert tagDParent["parent_id"] == tagB["id"]
160+
assert self.client.tags.get(tagD["id"])["parent_id"] == tagB["id"]
161+
162+
# Cleanup
153163
self.contentA.tags.delete(tagRoot)
154164
self.contentB.tags.delete(tagRoot)
155165
self.contentC.tags.delete(tagRoot)
@@ -159,6 +169,5 @@ def test_tag_content_items(self):
159169
assert len(tagC.content_items.find()) == 0
160170
assert len(tagD.content_items.find()) == 0
161171

162-
# cleanup
163172
tagRoot.destroy()
164173
assert len(self.client.tags.find()) == 0

src/posit/connect/tags.py

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,36 @@ def find(self) -> list[Tag]:
1818
pass
1919

2020

21+
def _update_parent_kwargs(kwargs: dict) -> dict:
22+
"""
23+
Sets the `parent_id` key in the kwargs if `parent` is provided.
24+
25+
Asserts that the `parent=` and `parent_id=` keys are not both provided.
26+
"""
27+
parent = kwargs.get("parent", None)
28+
if parent is None:
29+
# No parent to upgrade, return the kwargs as is
30+
return kwargs
31+
32+
if not isinstance(parent, Tag):
33+
raise TypeError(
34+
"`parent=` must be a Tag instance. If using a string, please use `parent_id=`"
35+
)
36+
37+
parent_id = kwargs.get("parent_id", None)
38+
if parent_id:
39+
raise ValueError("Cannot provide both `parent=` and `parent_id=`")
40+
41+
ret_kwargs = {**kwargs}
42+
43+
# Remove `parent` from ret_kwargs
44+
# and store the `parent_id` in the ret_kwargs below
45+
del ret_kwargs["parent"]
46+
47+
ret_kwargs["parent_id"] = parent["id"]
48+
return ret_kwargs
49+
50+
2151
class Tag(Active):
2252
"""Tag resource."""
2353

@@ -135,6 +165,63 @@ def destroy(self) -> None:
135165
url = self._ctx.url + self._path
136166
self._ctx.session.delete(url)
137167

168+
# Allow for every combination of `name` and (`parent` or `parent_id`)
169+
@overload
170+
def update(self, /, *, name: str = ..., parent: Tag | None = ...) -> Tag: ...
171+
@overload
172+
def update(self, /, *, name: str = ..., parent_id: str | None = ...) -> Tag: ...
173+
174+
def update( # pyright: ignore[reportIncompatibleMethodOverride] ; This method returns `Tag`. Parent method returns `None`
175+
self,
176+
**kwargs,
177+
) -> Tag:
178+
"""
179+
Update the tag.
180+
181+
Parameters
182+
----------
183+
name : str
184+
The name of the tag.
185+
parent : Tag | None, optional
186+
The parent `Tag` object. If there is no parent, the tag is a top-level tag. To remove
187+
the parent tag, set the value to `None`. Only one of `parent` or `parent_id` can be
188+
provided.
189+
parent_id : str | None, optional
190+
The identifier for the parent tag. If there is no parent, the tag is a top-level tag.
191+
To remove the parent tag, set the value to `None`.
192+
193+
Returns
194+
-------
195+
Tag
196+
Updated tag object.
197+
198+
Examples
199+
--------
200+
```python
201+
import posit
202+
203+
client = posit.connect.Client()
204+
last_tag = client.tags.find()[-1]
205+
206+
# Update the tag's name
207+
updated_tag = last_tag.update(name="new_name")
208+
209+
# Remove the tag's parent
210+
updated_tag = last_tag.update(parent=None)
211+
updated_tag = last_tag.update(parent_id=None)
212+
213+
# Update the tag's parent
214+
parent_tag = client.tags.find()[0]
215+
updated_tag = last_tag.update(parent=parent_tag)
216+
updated_tag = last_tag.update(parent_id=parent_tag["id"])
217+
```
218+
"""
219+
updated_kwargs = _update_parent_kwargs(kwargs)
220+
url = self._ctx.url + self._path
221+
response = self._ctx.session.patch(url, json=updated_kwargs)
222+
result = response.json()
223+
return Tag(self._ctx, self._path, **result)
224+
138225

139226
class TagContentItems(ContextManager):
140227
def __init__(self, ctx: Context, path: str) -> None:
@@ -303,35 +390,6 @@ def get(self, tag_id: str) -> Tag:
303390
response = self._ctx.session.get(url)
304391
return Tag(self._ctx, path, **response.json())
305392

306-
def _update_parent_kwargs(self, kwargs: dict) -> dict:
307-
"""
308-
Sets the `parent_id` key in the kwargs if `parent` is provided.
309-
310-
Asserts that the `parent=` and `parent_id=` keys are not both provided.
311-
"""
312-
parent = kwargs.get("parent", None)
313-
if parent is None:
314-
# No parent to upgrade, return the kwargs as is
315-
return kwargs
316-
317-
if not isinstance(parent, Tag):
318-
raise TypeError(
319-
"`parent=` must be a Tag instance. If using a string, please use `parent_id=`"
320-
)
321-
322-
parent_id = kwargs.get("parent_id", None)
323-
if parent_id:
324-
raise ValueError("Cannot provide both `parent=` and `parent_id=`")
325-
326-
ret_kwargs = {**kwargs}
327-
328-
# Remove `parent` from ret_kwargs
329-
# and store the `parent_id` in the ret_kwargs below
330-
del ret_kwargs["parent"]
331-
332-
ret_kwargs["parent_id"] = parent["id"]
333-
return ret_kwargs
334-
335393
# Allow for every combination of `name` and (`parent` or `parent_id`)
336394
@overload
337395
def find(self, /, *, name: str = ..., parent: Tag = ...) -> list[Tag]: ...
@@ -379,7 +437,7 @@ def find(self, /, **kwargs) -> list[Tag]:
379437
subtags = client.tags.find(name="sub_name", parent=mytag["id"])
380438
```
381439
"""
382-
updated_kwargs = self._update_parent_kwargs(
440+
updated_kwargs = _update_parent_kwargs(
383441
kwargs, # pyright: ignore[reportArgumentType]
384442
)
385443
url = self._ctx.url + self._path
@@ -425,7 +483,7 @@ def create(self, /, **kwargs) -> Tag:
425483
tag = client.tags.create(name="tag_name", parent=category_tag)
426484
```
427485
"""
428-
updated_kwargs = self._update_parent_kwargs(
486+
updated_kwargs = _update_parent_kwargs(
429487
kwargs, # pyright: ignore[reportArgumentType]
430488
)
431489

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "33",
3+
"name": "academy-updated",
4+
"parent_id": null,
5+
"created_time": "2021-10-18T18:37:56Z",
6+
"updated_time": "2021-10-18T18:37:56Z"
7+
}

tests/posit/connect/test_tags.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,6 @@ def test_content_with_tag(self):
252252
@responses.activate
253253
def test_destroy(self):
254254
# behavior
255-
# mock_all_tags = responses.get(
256-
# "https://connect.example/__api__/v1/tags",
257-
# json=load_mock_list("v1/tags.json"),
258-
# )
259255
mock_29_tag = responses.get(
260256
"https://connect.example/__api__/v1/tags/29",
261257
json=load_mock_dict("v1/tags/29.json"),
@@ -264,32 +260,51 @@ def test_destroy(self):
264260
"https://connect.example/__api__/v1/tags/29",
265261
json={}, # empty response
266262
)
267-
# post_destroy_json = load_mock_list("v1/tags.json")
268-
# for tag in post_destroy_json:
269-
# if tag["id"] in {"29", "30"}:
270-
# post_destroy_json.remove(tag)
271-
# mock_all_tags_after_destroy = responses.get(
272-
# "https://connect.example/__api__/v1/tags",
273-
# json=post_destroy_json,
274-
# )
275263

276264
# setup
277265
client = Client(api_key="12345", url="https://connect.example")
278266

279267
# invoke
280-
# tags = client.tags.find()
281-
# assert len(tags) == 28
282268
tag29 = client.tags.get("29")
283269
tag29.destroy()
284-
# tags = client.tags.find()
285-
# # All children tags are removed
286-
# assert len(tags) == 26
287270

288271
# assert
289-
# assert mock_all_tags.call_count == 1
290272
assert mock_29_tag.call_count == 1
291273
assert mock_29_destroy.call_count == 1
292-
# assert mock_all_tags_after_destroy.call_count == 1
274+
275+
@responses.activate
276+
def test_update(self):
277+
# behavior
278+
mock_get_33_tag = responses.get(
279+
"https://connect.example/__api__/v1/tags/33",
280+
json=load_mock_dict("v1/tags/33.json"),
281+
)
282+
mock_update_33_tag = responses.patch(
283+
"https://connect.example/__api__/v1/tags/33",
284+
json=load_mock_dict("v1/tags/33-patched.json"),
285+
)
286+
287+
# setup
288+
client = Client(api_key="12345", url="https://connect.example")
289+
tag33 = client.tags.get("33")
290+
291+
# invoke
292+
updated_tag33_0 = tag33.update(name="academy-updated", parent_id=None)
293+
updated_tag33_1 = tag33.update(name="academy-updated", parent=None)
294+
295+
parent_tag = Tag(client._ctx, "/v1/tags/1", id="42", name="Parent")
296+
updated_tag33_2 = tag33.update(name="academy-updated", parent=parent_tag)
297+
updated_tag33_3 = tag33.update(name="academy-updated", parent_id=parent_tag["id"])
298+
299+
# assert
300+
assert mock_get_33_tag.call_count == 1
301+
assert mock_update_33_tag.call_count == 4
302+
303+
for tag in [updated_tag33_0, updated_tag33_1, updated_tag33_2, updated_tag33_3]:
304+
assert isinstance(tag, Tag)
305+
306+
# Asserting updated values are deferred to integration testing
307+
# to avoid agreening with the mocked data
293308

294309

295310
class TestContentItemTags:

0 commit comments

Comments
 (0)