Skip to content

Commit 6ce4066

Browse files
authored
feat: adds groups (#227)
1 parent 9a660b5 commit 6ce4066

File tree

6 files changed

+276
-0
lines changed

6 files changed

+276
-0
lines changed

docs/_quarto.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ quartodoc:
100100
contents:
101101
- connect.bundles
102102
- connect.content
103+
- connect.groups
103104
- connect.permissions
104105
- connect.tasks
105106
- connect.users
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from posit import connect
2+
3+
4+
class TestGroups:
5+
def setup_class(cls):
6+
cls.client = connect.Client()
7+
cls.item = cls.client.groups.create(name="Friends")
8+
9+
def teardown_class(cls):
10+
cls.item.delete()
11+
assert cls.client.groups.count() == 0
12+
13+
def test_count(self):
14+
assert self.client.groups.count() == 1
15+
16+
def test_get(self):
17+
assert self.client.groups.get(self.item.guid)
18+
19+
def test_find(self):
20+
assert self.client.groups.find() == [self.item]
21+
22+
def test_find_one(self):
23+
assert self.client.groups.find_one() == self.item

src/posit/connect/client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .metrics import Metrics
1515
from .tasks import Tasks
1616
from .users import User, Users
17+
from .groups import Groups
1718

1819

1920
class Client:
@@ -94,6 +95,17 @@ def oauth(self) -> OAuthIntegration:
9495
"""
9596
return OAuthIntegration(config=self.config, session=self.session)
9697

98+
@property
99+
def groups(self) -> Groups:
100+
"""The groups resource interface.
101+
102+
Returns
103+
-------
104+
Groups
105+
The groups resource interface.
106+
"""
107+
return Groups(self.config, self.session)
108+
97109
@property
98110
def tasks(self) -> Tasks:
99111
"""

src/posit/connect/groups.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"""Group resources."""
2+
3+
from __future__ import annotations
4+
from typing import List, overload
5+
6+
import requests
7+
8+
from . import me, urls
9+
10+
from .config import Config
11+
from .paginator import Paginator
12+
from .resources import Resource, Resources
13+
14+
15+
class Group(Resource):
16+
"""Group resource.
17+
18+
Attributes
19+
----------
20+
guid : str
21+
name: str
22+
owner_guid: str
23+
"""
24+
25+
@property
26+
def guid(self) -> str:
27+
return self.get("guid") # type: ignore
28+
29+
@property
30+
def name(self) -> str:
31+
return self.get("name") # type: ignore
32+
33+
@property
34+
def owner_guid(self) -> str:
35+
return self.get("owner_guid") # type: ignore
36+
37+
# CRUD Methods
38+
39+
def delete(self) -> None:
40+
"""Delete the group."""
41+
path = f"v1/groups/{self.guid}"
42+
url = urls.append(self.config.url, path)
43+
self.session.delete(url)
44+
45+
46+
class Groups(Resources):
47+
"""Groups resource."""
48+
49+
def __init__(self, config: Config, session: requests.Session) -> None:
50+
self.config = config
51+
self.session = session
52+
53+
@overload
54+
def create(self, name: str, unique_id: str | None) -> Group:
55+
"""Create a group.
56+
57+
Parameters
58+
----------
59+
name: str
60+
unique_id: str | None
61+
62+
Returns
63+
-------
64+
Group
65+
"""
66+
...
67+
68+
@overload
69+
def create(self, *args, **kwargs) -> Group:
70+
"""Create a group.
71+
72+
Returns
73+
-------
74+
Group
75+
"""
76+
...
77+
78+
def create(self, *args, **kwargs) -> Group:
79+
"""Create a group.
80+
81+
Parameters
82+
----------
83+
name: str
84+
unique_id: str | None
85+
86+
Returns
87+
-------
88+
Group
89+
"""
90+
...
91+
body = dict(*args, **kwargs)
92+
path = "v1/groups"
93+
url = urls.append(self.config.url, path)
94+
response = self.session.post(url, json=body)
95+
return Group(self.config, self.session, **response.json())
96+
97+
@overload
98+
def find(
99+
self,
100+
prefix: str = ...,
101+
) -> List[Group]: ...
102+
103+
@overload
104+
def find(self, *args, **kwargs) -> List[Group]: ...
105+
106+
def find(self, *args, **kwargs):
107+
"""Find groups.
108+
109+
Parameters
110+
----------
111+
prefix: str
112+
Filter by group name prefix. Casing is ignored.
113+
114+
Returns
115+
-------
116+
List[Group]
117+
"""
118+
params = dict(*args, **kwargs)
119+
path = "v1/groups"
120+
url = urls.append(self.config.url, path)
121+
paginator = Paginator(self.session, url, params=params)
122+
results = paginator.fetch_results()
123+
return [
124+
Group(
125+
config=self.config,
126+
session=self.session,
127+
**result,
128+
)
129+
for result in results
130+
]
131+
132+
@overload
133+
def find_one(
134+
self,
135+
prefix: str = ...,
136+
) -> Group | None: ...
137+
138+
@overload
139+
def find_one(self, *args, **kwargs) -> Group | None: ...
140+
141+
def find_one(self, *args, **kwargs) -> Group | None:
142+
"""Find one group.
143+
144+
Parameters
145+
----------
146+
prefix: str
147+
Filter by group name prefix. Casing is ignored.
148+
149+
Returns
150+
-------
151+
Group | None
152+
"""
153+
params = dict(*args, **kwargs)
154+
path = "v1/groups"
155+
url = urls.append(self.config.url, path)
156+
paginator = Paginator(self.session, url, params=params)
157+
pages = paginator.fetch_pages()
158+
results = (result for page in pages for result in page.results)
159+
groups = (
160+
Group(
161+
config=self.config,
162+
session=self.session,
163+
**result,
164+
)
165+
for result in results
166+
)
167+
return next(groups, None)
168+
169+
def get(self, guid: str) -> Group:
170+
"""Get group.
171+
172+
Parameters
173+
----------
174+
guid : str
175+
176+
Returns
177+
-------
178+
Group
179+
"""
180+
url = urls.append(self.config.url, f"v1/groups/{guid}")
181+
response = self.session.get(url)
182+
return Group(
183+
config=self.config,
184+
session=self.session,
185+
**response.json(),
186+
)
187+
188+
def count(self) -> int:
189+
"""Count the number of groups.
190+
191+
Returns
192+
-------
193+
int
194+
"""
195+
path = "v1/groups"
196+
url = urls.append(self.config.url, path)
197+
response: requests.Response = self.session.get(
198+
url, params={"page_size": 1}
199+
)
200+
result: dict = response.json()
201+
return result["total"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"guid": "6f300623-1e0c-48e6-a473-ddf630c0c0c3",
3+
"name": "Friends",
4+
"owner_guid": "20a79ce3-6e87-4522-9faf-be24228800a4"
5+
}

tests/posit/connect/test_groups.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
import requests
5+
import responses
6+
7+
8+
from posit.connect.client import Client
9+
from posit.connect.config import Config
10+
from posit.connect.groups import Group
11+
12+
from .api import load_mock # type: ignore
13+
14+
session = Mock()
15+
url = Mock()
16+
17+
18+
class TestGroupAttributes:
19+
@classmethod
20+
def setup_class(cls):
21+
guid = "6f300623-1e0c-48e6-a473-ddf630c0c0c3"
22+
config = Config(api_key="12345", url="https://connect.example.com/")
23+
session = requests.Session()
24+
fake_item = load_mock(f"v1/groups/{guid}.json")
25+
cls.item = Group(config, session, **fake_item)
26+
27+
def test_guid(self):
28+
assert self.item.guid == "6f300623-1e0c-48e6-a473-ddf630c0c0c3"
29+
30+
def test_name(self):
31+
assert self.item.name == "Friends"
32+
33+
def test_owner_guid(self):
34+
assert self.item.owner_guid == "20a79ce3-6e87-4522-9faf-be24228800a4"

0 commit comments

Comments
 (0)