Skip to content

Commit

Permalink
feat: get content for user (#226)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdstein authored Jul 1, 2024
1 parent 6ce4066 commit c927af0
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 14 deletions.
6 changes: 6 additions & 0 deletions integration/tests/posit/connect/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


class TestContent:
@classmethod
def setup_class(cls):
cls.client = connect.Client()
cls.item = cls.client.content.create(
Expand All @@ -10,6 +11,11 @@ def setup_class(cls):
access_type="acl",
)

@classmethod
def teardown_class(cls):
cls.item.delete()
assert cls.client.content.count() == 0

def test_count(self):
assert self.client.content.count() == 1

Expand Down
29 changes: 29 additions & 0 deletions integration/tests/posit/connect/test_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from posit import connect


class TestAttributeContent:
"""Checks behavior of the content attribute."""

@classmethod
def setup_class(cls):
cls.client = connect.Client()
cls.user = cls.client.me
cls.user.content.create(
name="Sample",
description="Simple sample content for testing",
access_type="acl",
)

@classmethod
def teardown_class(cls):
assert cls.user.content.find_one().delete() is None
assert cls.user.content.count() == 0

def test_count(self):
assert self.user.content.count() == 1

def test_find(self):
assert self.user.content.find()

def test_find_one(self):
assert self.user.content.find_one()
57 changes: 44 additions & 13 deletions src/posit/connect/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from typing import List, Optional, overload
from collections import defaultdict
from typing import TYPE_CHECKING, List, Optional, overload


from requests import Session
Expand All @@ -13,11 +14,10 @@
from .bundles import Bundles
from .permissions import Permissions
from .resources import Resources, Resource
from .users import Users


class ContentItemOwner(Resource):
"""Owner information."""
"""Content item owner resource."""

@property
def guid(self) -> str:
Expand Down Expand Up @@ -153,6 +153,8 @@ def owner(self) -> ContentItemOwner:
# It is possible to get a content item that does not contain owner.
# "owner" is an optional additional request param.
# If it's not included, we can retrieve the information by `owner_guid`
from .users import Users

self["owner"] = Users(self.config, self.session).get(
self.owner_guid
)
Expand Down Expand Up @@ -442,12 +444,41 @@ def update(self, *args, **kwargs) -> None:


class Content(Resources):
"""Content resource."""
"""Content resource.
Parameters
----------
config : Config
Configuration object.
session : Session
Requests session object.
owner_guid : str, optional
Content item owner identifier. Filters results to those owned by a specific user (the default is None, which implies not filtering results on owner identifier).
"""

def __init__(self, config: Config, session: Session) -> None:
def __init__(
self,
config: Config,
session: Session,
*,
owner_guid: str | None = None,
) -> None:
self.url = urls.append(config.url, "v1/content")
self.config = config
self.session = session
self.owner_guid = owner_guid

def _get_default_params(self) -> dict:
"""Build default parameters for GET requests.
Returns
-------
dict
"""
params = {}
if self.owner_guid:
params["owner_guid"] = self.owner_guid
return params

def count(self) -> int:
"""Count the number of content items.
Expand All @@ -456,8 +487,7 @@ def count(self) -> int:
-------
int
"""
results = self.session.get(self.url).json()
return len(results)
return len(self.find())

@overload
def create(
Expand Down Expand Up @@ -600,18 +630,19 @@ def find(
-------
List[ContentItem]
"""
params = dict(*args, include=include, **kwargs)
params = self._get_default_params()
params.update(args)
params.update(kwargs)
params["include"] = include
response = self.session.get(self.url, params=params)
results = response.json()
items = (
return [
ContentItem(
config=self.config,
session=self.session,
**result,
)
for result in results
)
return [item for item in items]
for result in response.json()
]

@overload
def find_one(
Expand Down
7 changes: 7 additions & 0 deletions src/posit/connect/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from . import me, urls

from .config import Config
from .content import Content
from .paginator import Paginator
from .resources import Resource, Resources

Expand All @@ -17,6 +18,8 @@ class User(Resource):
Attributes
----------
content: Content
A content resource scoped to this user.
guid : str
email : str
username : str
Expand All @@ -32,6 +35,10 @@ class User(Resource):
Whether the user is locked.
"""

@property
def content(self) -> Content:
return Content(self.config, self.session, owner_guid=self.guid)

@property
def guid(self) -> str:
return self.get("guid") # type: ignore
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
{
"guid": "93a3cd6d-5a1b-236c-9808-6045f2a73fb5",
"name": "My-Streamlit-app",
"title": "My Streamlit app",
"description": "",
"access_type": "logged_in",
"connection_timeout": null,
"read_timeout": null,
"init_timeout": null,
"idle_timeout": null,
"max_processes": null,
"min_processes": null,
"max_conns_per_process": null,
"load_factor": null,
"memory_request": null,
"memory_limit": null,
"cpu_request": null,
"cpu_limit": null,
"amd_gpu_limit": null,
"nvidia_gpu_limit": null,
"service_account_name": null,
"default_image_name": null,
"created_time": "2023-02-28T14:00:17Z",
"last_deployed_time": "2023-03-01T14:12:21Z",
"bundle_id": "217640",
"app_mode": "python-streamlit",
"content_category": "",
"parameterized": false,
"cluster_name": "Local",
"image_name": null,
"r_version": null,
"py_version": "3.9.17",
"quarto_version": null,
"r_environment_management": null,
"default_r_environment_management": null,
"py_environment_management": true,
"default_py_environment_management": null,
"run_as": null,
"run_as_current_user": false,
"owner_guid": "20a79ce3-6e87-4522-9faf-be24228800a4",
"content_url": "https://connect.example/content/93a3cd6d-5a1b-236c-9808-6045f2a73fb5/",
"dashboard_url": "https://connect.example/connect/#/apps/93a3cd6d-5a1b-236c-9808-6045f2a73fb5",
"app_role": "viewer",
"id": "8462",
"owner": {
"guid": "20a79ce3-6e87-4522-9faf-be24228800a4",
"username": "carlos12",
"first_name": "Carlos",
"last_name": "User"
}
}
]
2 changes: 1 addition & 1 deletion tests/posit/connect/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def test(self):
# invoke
content = client.content.find()

# assert
# assert
assert mock_get.call_count == 1
assert len(content) == 3
assert content[0].name == "team-admin-dashboard"
Expand Down
42 changes: 42 additions & 0 deletions tests/posit/connect/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import requests
import responses

from responses import matchers

from posit.connect.client import Client
from posit.connect.users import User

Expand Down Expand Up @@ -92,6 +94,46 @@ def test_locked(self):
assert user.locked is False


class TestUserContent:
"""Check behavior of content attribute."""

@responses.activate
def test_find(self):
"""Check GET /v1/content call includes owner_guid query parameter."""
# behavior
mock_get_user = responses.get(
"https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4",
json=load_mock(
"v1/users/20a79ce3-6e87-4522-9faf-be24228800a4.json"
),
)

mock_get_content = responses.get(
"https://connect.example/__api__/v1/content",
json=load_mock(
"v1/content?owner_guid=20a79ce3-6e87-4522-9faf-be24228800a4.json"
),
match=[
matchers.query_param_matcher(
{"owner_guid": "20a79ce3-6e87-4522-9faf-be24228800a4"},
strict_match=False,
)
],
)

# setup
c = Client(api_key="12345", url="https://connect.example/")
user = c.users.get("20a79ce3-6e87-4522-9faf-be24228800a4")

# invoke
content = user.content.find()

# assert
assert mock_get_user.call_count == 1
assert mock_get_content.call_count == 1
assert len(content) == 1


class TestUserLock:
@responses.activate
def test_lock(self):
Expand Down

0 comments on commit c927af0

Please sign in to comment.