Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: get content for user #226

Merged
merged 2 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +14 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm new to the architecture of this package — for my own learning, what does this do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cls.item is the content item created in the setup_class method. L16 performs a delete action, and L17 asserts that the total number of content items on the server is 0. The user used for testing has administrator privileges.

The general idea here is to try and ensure a "clean" server state between test classes. That way, other test classes would not need to be aware of each other. I'm not sure this is viable in the long term, but it's okay for now.


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
Content resource for user. Sets the content item owner identifier (`owner_guid`) to `self.guid`.
tdstein marked this conversation as resolved.
Show resolved Hide resolved
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
Loading