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: adds content create #155

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
218 changes: 154 additions & 64 deletions src/posit/connect/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
class ContentItem(Resource):
"""A piece of content."""

@property
def id(self) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Only looking at this since you moved it: should we actually remove this accessor? Is my understanding correct that id is effectively deprecated and that guid is the real identifier you should always use? @aronatkins?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We originally did not include id in the public v1 API. The id was added afterwards in order to help with cross-referencing against the on-disk layout and server logs (which did not include guid at that time).

At this point, it is probably too strong a statement to declare id as deprecated, but it cannot be used to look-up content items using the v1 API.

return self.get("id") # type: ignore

@property
def guid(self) -> str:
return self.get("guid") # type: ignore
Expand Down Expand Up @@ -185,10 +189,6 @@ def dashboard_url(self) -> str:
def app_role(self) -> str:
return self.get("app_role") # type: ignore

@property
def id(self) -> str:
return self.get("id") # type: ignore

@property
def permissions(self) -> Permissions:
return Permissions(self.config, self.session, content_guid=self.guid)
Expand Down Expand Up @@ -222,82 +222,160 @@ def update(
default_py_environment_management: Optional[bool] = ...,
service_account_name: Optional[str] = ...,
) -> None:
"""
Update the content item.

Args:
name (str): The name of the content.
title (Optional[str]): The title of the content.
description (str): The description of the content.
access_type (str): The access type of the content.
owner_guid (Optional[str]): The owner GUID of the content.
connection_timeout (Optional[int]): The connection timeout in seconds.
read_timeout (Optional[int]): The read timeout in seconds.
init_timeout (Optional[int]): The initialization timeout in seconds.
idle_timeout (Optional[int]): The idle timeout in seconds.
max_processes (Optional[int]): The maximum number of processes.
min_processes (Optional[int]): The minimum number of processes.
max_conns_per_process (Optional[int]): The maximum number of connections per process.
load_factor (Optional[float]): The load factor.
cpu_request (Optional[float]): The CPU request.
cpu_limit (Optional[float]): The CPU limit.
memory_request (Optional[int]): The memory request in bytes.
memory_limit (Optional[int]): The memory limit in bytes.
amd_gpu_limit (Optional[int]): The AMD GPU limit.
nvidia_gpu_limit (Optional[int]): The NVIDIA GPU limit.
run_as (Optional[str]): The user to run as.
run_as_current_user (Optional[bool]): Whether to run as the current user.
default_image_name (Optional[str]): The default image name.
default_r_environment_management (Optional[bool]): Whether to use default R environment management.
default_py_environment_management (Optional[bool]): Whether to use default Python environment management.
service_account_name (Optional[str]): The service account name.

Returns
-------
None
"""Update the content item.

Parameters
----------
name : str, optional
title : Optional[str], optional
description : str, optional
access_type : str, optional
owner_guid : Optional[str], optional
connection_timeout : Optional[int], optional
read_timeout : Optional[int], optional
init_timeout : Optional[int], optional
idle_timeout : Optional[int], optional
max_processes : Optional[int], optional
min_processes : Optional[int], optional
max_conns_per_process : Optional[int], optional
load_factor : Optional[float], optional
cpu_request : Optional[float], optional
cpu_limit : Optional[float], optional
memory_request : Optional[int], optional
memory_limit : Optional[int], optional
amd_gpu_limit : Optional[int], optional
nvidia_gpu_limit : Optional[int], optional
run_as : Optional[str], optional
run_as_current_user : Optional[bool], optional
default_image_name : Optional[str], optional
default_r_environment_management : Optional[bool], optional
default_py_environment_management : Optional[bool], optional
service_account_name : Optional[str], optional
"""
...

@overload
def update(self, *args, **kwargs) -> None:
"""Update the content item."""
...

def update(self, *args, **kwargs) -> None:
"""Update the content item."""
body = dict(*args, **kwargs)
url = urls.append_path(self.config.url, f"v1/content/{self.guid}")
response = self.session.patch(url, json=body)
super().update(**response.json())


class Content(Resources):
def __init__(self, config: Config, session: Session) -> None:
self.url = urls.append_path(config.url, "v1/content")
self.config = config
self.session = session

def count(self) -> int:
"""Count the number of content items.

Returns
-------
int
"""
Update the content item.
results = self.session.get(self.url).json()
return len(results)

Args:
*args
**kwargs
@overload
def create(
self,
name: str = ...,
title: Optional[str] = ...,
description: str = ...,
access_type: str = ...,
connection_timeout: Optional[int] = ...,
read_timeout: Optional[int] = ...,
init_timeout: Optional[int] = ...,
idle_timeout: Optional[int] = ...,
max_processes: Optional[int] = ...,
min_processes: Optional[int] = ...,
max_conns_per_process: Optional[int] = ...,
load_factor: Optional[float] = ...,
cpu_request: Optional[float] = ...,
cpu_limit: Optional[float] = ...,
memory_request: Optional[int] = ...,
memory_limit: Optional[int] = ...,
amd_gpu_limit: Optional[int] = ...,
nvidia_gpu_limit: Optional[int] = ...,
run_as: Optional[str] = ...,
run_as_current_user: Optional[bool] = ...,
default_image_name: Optional[str] = ...,
default_r_environment_management: Optional[bool] = ...,
default_py_environment_management: Optional[bool] = ...,
service_account_name: Optional[str] = ...,
) -> ContentItem:
"""Create a content item.

Parameters
----------
name : str, optional
title : Optional[str], optional
description : str, optional
access_type : str, optional
connection_timeout : Optional[int], optional
read_timeout : Optional[int], optional
init_timeout : Optional[int], optional
idle_timeout : Optional[int], optional
max_processes : Optional[int], optional
min_processes : Optional[int], optional
max_conns_per_process : Optional[int], optional
load_factor : Optional[float], optional
cpu_request : Optional[float], optional
cpu_limit : Optional[float], optional
memory_request : Optional[int], optional
memory_limit : Optional[int], optional
amd_gpu_limit : Optional[int], optional
nvidia_gpu_limit : Optional[int], optional
run_as : Optional[str], optional
run_as_current_user : Optional[bool], optional
default_image_name : Optional[str], optional
default_r_environment_management : Optional[bool], optional
default_py_environment_management : Optional[bool], optional
service_account_name : Optional[str], optional

Returns
-------
None
ContentItem
"""
...

def update(self, *args, **kwargs) -> None:
@overload
def create(self, *args, **kwargs) -> ContentItem:
"""Create a content item.

Returns
-------
ContentItem
"""
Update the content item.
...

Args:
*args
**kwargs
def create(self, *args, **kwargs) -> ContentItem:
"""Create a content item.

Returns
-------
None
ContentItem
"""
body = dict(*args, **kwargs)
url = urls.append_path(self.config.url, f"v1/content/{self.guid}")
response = self.session.patch(url, json=body)
super().update(**response.json())


class Content(Resources):
def __init__(self, config: Config, session: Session) -> None:
self.url = urls.append_path(config.url, "v1/content")
self.config = config
self.session = session
path = "v1/content"
url = urls.append_path(self.config.url, path)
response = self.session.post(url, json=body)
return ContentItem(self.config, self.session, **response.json())

def find(self) -> List[ContentItem]:
"""Find content items.

Returns
-------
List[ContentItem]
"""
results = self.session.get(self.url).json()
items = (
ContentItem(
Expand All @@ -310,6 +388,12 @@ def find(self) -> List[ContentItem]:
return [item for item in items]

def find_one(self) -> ContentItem | None:
"""Find a content item.

Returns
-------
ContentItem | None
"""
results = self.session.get(self.url).json()
items = (
ContentItem(
Expand All @@ -321,11 +405,17 @@ def find_one(self) -> ContentItem | None:
)
return next(items, None)

def get(self, id: str) -> ContentItem:
url = urls.append_path(self.url, id)
def get(self, guid: str) -> ContentItem:
"""Get a content item.

Parameters
----------
guid : str

Returns
-------
ContentItem
"""
url = urls.append_path(self.url, guid)
response = self.session.get(url)
return ContentItem(self.config, self.session, **response.json())

def count(self) -> int:
results = self.session.get(self.url).json()
return len(results)
26 changes: 26 additions & 0 deletions tests/posit/connect/test_content.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import responses

from responses import matchers

from posit.connect.client import Client
from posit.connect.content import ContentItem

Expand Down Expand Up @@ -30,6 +32,30 @@ def test_update(self):
assert content.name == new_name


class TestContentCreate:
@responses.activate
def test(self):
# data
guid = "f2f37341-e21d-3d80-c698-a935ad614066"
fake_content_item = load_mock(f"v1/content/{guid}.json")

# behavior
responses.post(
f"https://connect.example/__api__/v1/content",
json=load_mock(f"v1/content/{guid}.json"),
match=[matchers.json_params_matcher({"name": fake_content_item["name"]})],
)

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

# invoke
content_item = client.content.create(name=fake_content_item["name"])

# assert
assert content_item.name == fake_content_item["name"]


class TestContents:
@responses.activate
def test_get_all_content(self):
Expand Down
Loading