diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index 0e520c1d..bfbab6e1 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -23,7 +23,7 @@ def test_count(self): assert self.client.content.count() == 1 def test_get(self): - assert self.client.content.get(self.content.guid) == self.content + assert self.client.content.get(self.content["guid"]) == self.content def test_find(self): assert self.client.content.find() @@ -34,12 +34,12 @@ def test_find_one(self): def test_content_item_owner(self): item = self.client.content.find_one(include=None) owner = item.owner - assert owner.guid == self.client.me.guid + assert owner["guid"] == self.client.me["guid"] def test_content_item_owner_from_include(self): item = self.client.content.find_one(include="owner") owner = item.owner - assert owner.guid == self.client.me.guid + assert owner["guid"] == self.client.me["guid"] @pytest.mark.skipif( CONNECT_VERSION <= version.parse("2024.04.1"), diff --git a/integration/tests/posit/connect/test_groups.py b/integration/tests/posit/connect/test_groups.py index ba2c0e94..87f139c6 100644 --- a/integration/tests/posit/connect/test_groups.py +++ b/integration/tests/posit/connect/test_groups.py @@ -14,7 +14,7 @@ def test_count(self): assert self.client.groups.count() == 1 def test_get(self): - assert self.client.groups.get(self.item.guid) + assert self.client.groups.get(self.item["guid"]) def test_find(self): assert self.client.groups.find() == [self.item] diff --git a/src/posit/connect/bundles.py b/src/posit/connect/bundles.py index b38ea7dd..5145536c 100644 --- a/src/posit/connect/bundles.py +++ b/src/posit/connect/bundles.py @@ -9,139 +9,19 @@ class BundleMetadata(resources.Resource): - """Bundle metadata resource. - - Attributes - ---------- - source : str | None - Source of the bundle. - source_repo : str | None - Source repository of the bundle. - source_branch : str | None - Source branch of the bundle. - source_commit : str | None - Source commit of the bundle. - archive_md5 : str | None - MD5 checksum of the bundle archive. - archive_sha1 : str | None - SHA-1 checksum of the bundle archive. - """ - - @property - def source(self) -> str | None: - return self.get("source") - - @property - def source_repo(self) -> str | None: - return self.get("source_repo") - - @property - def source_branch(self) -> str | None: - return self.get("source_branch") - - @property - def source_commit(self) -> str | None: - return self.get("source_commit") - - @property - def archive_md5(self) -> str | None: - return self.get("archive_md5") - - @property - def archive_sha1(self) -> str | None: - return self.get("archive_sha1") + pass class Bundle(resources.Resource): - """Bundle resource. - - Attributes - ---------- - id : str - Identifier of the bundle. - content_guid : str - Content GUID of the bundle. - created_time : str - Creation time of the bundle. - cluster_name : str | None - Cluster name associated with the bundle. - image_name : str | None - Image name of the bundle. - r_version : str | None - R version used in the bundle. - r_environment_management : bool | None - Indicates if R environment management is enabled. - py_version : str | None - Python version used in the bundle. - py_environment_management : bool | None - Indicates if Python environment management is enabled. - quarto_version : str | None - Quarto version used in the bundle. - active : bool | None - Indicates if the bundle is active. - size : int | None - Size of the bundle. - metadata : BundleMetadata - Metadata of the bundle. - """ - - @property - def id(self) -> str: - return self["id"] - - @property - def content_guid(self) -> str: - return self["content_guid"] - - @property - def created_time(self) -> str: - return self["created_time"] - - @property - def cluster_name(self) -> str | None: - return self.get("cluster_name") - - @property - def image_name(self) -> str | None: - return self.get("image_name") - - @property - def r_version(self) -> str | None: - return self.get("r_version") - - @property - def r_environment_management(self) -> bool | None: - return self.get("r_environment_management") - - @property - def py_version(self) -> str | None: - return self.get("py_version") - - @property - def py_environment_management(self) -> bool | None: - return self.get("py_environment_management") - - @property - def quarto_version(self) -> str | None: - return self.get("quarto_version") - - @property - def active(self) -> bool | None: - return self["active"] - - @property - def size(self) -> int | None: - return self["size"] - @property def metadata(self) -> BundleMetadata: return BundleMetadata(self.params, **self.get("metadata", {})) def delete(self) -> None: """Delete the bundle.""" - path = f"v1/content/{self.content_guid}/bundles/{self.id}" - url = self.url + path - self.session.delete(url) + path = f"v1/content/{self['content_guid']}/bundles/{self['id']}" + url = self.params.url + path + self.params.session.delete(url) def deploy(self) -> tasks.Task: """Deploy the bundle. @@ -159,9 +39,11 @@ def deploy(self) -> tasks.Task: >>> task.wait_for() None """ - path = f"v1/content/{self.content_guid}/deploy" - url = self.url + path - response = self.session.post(url, json={"bundle_id": self.id}) + path = f"v1/content/{self['content_guid']}/deploy" + url = self.params.url + path + response = self.params.session.post( + url, json={"bundle_id": self["id"]} + ) result = response.json() ts = tasks.Tasks(self.params) return ts.get(result["task_id"]) @@ -197,9 +79,11 @@ def download(self, output: io.BufferedWriter | str) -> None: f"download() expected argument type 'io.BufferedWriter` or 'str', but got '{type(output).__name__}'" ) - path = f"v1/content/{self.content_guid}/bundles/{self.id}/download" - url = self.url + path - response = self.session.get(url, stream=True) + path = ( + f"v1/content/{self['content_guid']}/bundles/{self['id']}/download" + ) + url = self.params.url + path + response = self.params.session.get(url, stream=True) if isinstance(output, io.BufferedWriter): for chunk in response.iter_content(): output.write(chunk) @@ -284,8 +168,8 @@ def create(self, archive: io.BufferedReader | bytes | str) -> Bundle: ) path = f"v1/content/{self.content_guid}/bundles" - url = self.url + path - response = self.session.post(url, data=data) + url = self.params.url + path + response = self.params.session.post(url, data=data) result = response.json() return Bundle(self.params, **result) @@ -298,8 +182,8 @@ def find(self) -> List[Bundle]: List of all found bundles. """ path = f"v1/content/{self.content_guid}/bundles" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) results = response.json() return [Bundle(self.params, **result) for result in results] @@ -328,7 +212,7 @@ def get(self, uid: str) -> Bundle: The bundle with the specified ID. """ path = f"v1/content/{self.content_guid}/bundles/{uid}" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) result = response.json() return Bundle(self.params, **result) diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index 9f1df481..539decbb 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -5,7 +5,7 @@ import posixpath import time from posixpath import dirname -from typing import List, Optional, overload +from typing import Any, List, Optional, overload from . import tasks from .bundles import Bundles @@ -17,133 +17,21 @@ class ContentItemOwner(Resource): - """Content item owner resource.""" - - @property - def guid(self) -> str: - return self.get("guid") # type: ignore - - @property - def username(self) -> str: - return self.get("username") # type: ignore - - @property - def first_name(self) -> Optional[str]: - return self.get("first_name") # type: ignore - - @property - def last_name(self) -> Optional[str]: - return self.get("last_name") # type: ignore + pass class ContentItem(Resource): - """Content item resource. - - Attributes - ---------- - bundles : Bundles - Bundles resource for the content item. - permissions : Permissions - Permissions resource for the content item. - id : str - Unique identifier of the content item. - guid : str - Globally unique identifier of the content item. - name : str - Name of the content item. - title : Optional[str] - Title of the content item. - description : str - Description of the content item. - access_type : str - Access type of the content item. - connection_timeout : Optional[int] - Connection timeout setting for the content item. - read_timeout : Optional[int] - Read timeout setting for the content item. - init_timeout : Optional[int] - Initialization timeout setting for the content item. - idle_timeout : Optional[int] - Idle timeout setting for the content item. - max_processes : Optional[int] - Maximum number of processes allowed for the content item. - min_processes : Optional[int] - Minimum number of processes required for the content item. - max_conns_per_process : Optional[int] - Maximum number of connections per process for the content item. - load_factor : Optional[float] - Load factor for the content item. - cpu_request : Optional[float] - CPU request for the content item. - cpu_limit : Optional[float] - CPU limit for the content item. - memory_request : Optional[int] - Memory request for the content item. - memory_limit : Optional[int] - Memory limit for the content item. - amd_gpu_limit : Optional[int] - AMD GPU limit for the content item. - nvidia_gpu_limit : Optional[int] - NVIDIA GPU limit for the content item. - created_time : str - Creation time of the content item. - last_deployed_time : str - Last deployment time of the content item. - bundle_id : Optional[str] - Bundle ID associated with the content item. - app_mode : str - Application mode of the content item. - content_category : Optional[str] - Content category of the content item. - parameterized : bool - Indicates if the content item is parameterized. - cluster_name : Optional[str] - Name of the cluster associated with the content item. - image_name : Optional[str] - Name of the image associated with the content item. - default_image_name : Optional[str] - Default image name for the content item. - default_r_environment_management : Optional[bool] - Indicates if R environment management is enabled by default. - default_py_environment_management : Optional[bool] - Indicates if Python environment management is enabled by default. - service_account_name : Optional[str] - Name of the service account associated with the content item. - r_version : Optional[str] - R version used by the content item. - r_environment_management : Optional[bool] - Indicates if R environment management is enabled. - py_version : Optional[str] - Python version used by the content item. - py_environment_management : Optional[bool] - Indicates if Python environment management is enabled. - quarto_version : Optional[str] - Quarto version used by the content item. - run_as : Optional[str] - User to run the content item as. - run_as_current_user : bool - Indicates if the content item runs as the current user. - owner_guid : str - GUID of the owner of the content item. - owner : ContentItemOwner - Owner information of the content item. - content_url : str - URL of the content item. - dashboard_url : str - Dashboard URL of the content item. - app_role : str - Application role of the content item. - tags : List[dict] - Tags associated with the content item. - """ - - # CRUD Methods + def __getitem__(self, key: Any) -> Any: + v = super().__getitem__(key) + if key == "owner" and isinstance(v, dict): + return ContentItemOwner(params=self.params, **v) + return v def delete(self) -> None: """Delete the content item.""" - path = f"v1/content/{self.guid}" - url = self.url + path - self.session.delete(url) + path = f"v1/content/{self['guid']}" + url = self.params.url + path + self.params.session.delete(url) def deploy(self) -> tasks.Task: """Deploy the content. @@ -161,9 +49,9 @@ def deploy(self) -> tasks.Task: >>> task.wait_for() None """ - path = f"v1/content/{self.guid}/deploy" - url = self.url + path - response = self.session.post(url, json={"bundle_id": None}) + path = f"v1/content/{self['guid']}/deploy" + url = self.params.url + path + response = self.params.session.post(url, json={"bundle_id": None}) result = response.json() ts = tasks.Tasks(self.params) return ts.get(result["task_id"]) @@ -185,7 +73,9 @@ def render(self) -> Task: if self.is_rendered: variants = self._variants.find() - variants = [variant for variant in variants if variant.is_default] + variants = [ + variant for variant in variants if variant["is_default"] + ] if len(variants) != 1: raise RuntimeError( f"Found {len(variants)} default variants. Expected 1. Without a single default variant, the content cannot be refreshed. This is indicative of a corrupted state." @@ -194,7 +84,7 @@ def render(self) -> Task: return variant.render() else: raise ValueError( - f"Render not supported for this application mode: {self.app_mode}. Did you need to use the 'restart()' method instead? Note that some application modes do not support 'render()' or 'restart()'." + f"Render not supported for this application mode: {{self['app_mode']}}. Did you need to use the 'restart()' method instead? Note that some application modes do not support 'render()' or 'restart()'." ) def restart(self) -> None: @@ -218,12 +108,14 @@ def restart(self) -> None: self.environment_variables.create(key, unix_epoch_in_seconds) self.environment_variables.delete(key) # GET via the base Connect URL to force create a new worker thread. - url = posixpath.join(dirname(self.url), f"content/{self.guid}") - self.session.get(url) + url = posixpath.join( + dirname(self.params.url), f"content/{self['guid']}" + ) + self.params.session.get(url) return None else: raise ValueError( - f"Restart not supported for this application mode: {self.app_mode}. Did you need to use the 'render()' method instead? Note that some application modes do not support 'render()' or 'restart()'." + f"Restart not supported for this application mode: {self['app_mode']}. Did you need to use the 'render()' method instead? Note that some application modes do not support 'render()' or 'restart()'." ) @overload @@ -297,220 +189,42 @@ def update(self, *args, **kwargs) -> None: def update(self, *args, **kwargs) -> None: """Update the content item.""" body = dict(*args, **kwargs) - url = self.url + f"v1/content/{self.guid}" - response = self.session.patch(url, json=body) + url = self.params.url + f"v1/content/{self['guid']}" + response = self.params.session.patch(url, json=body) super().update(**response.json()) # Relationships @property def bundles(self) -> Bundles: - return Bundles(self.params, self.guid) + return Bundles(self.params, self["guid"]) @property def environment_variables(self) -> EnvVars: - return EnvVars(self.params, self.guid) + return EnvVars(self.params, self["guid"]) @property def permissions(self) -> Permissions: - return Permissions(self.params, self.guid) + return Permissions(self.params, self["guid"]) @property - def owner(self) -> ContentItemOwner: + def owner(self) -> dict: if "owner" not in self: # 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.params).get(self.owner_guid) - return ContentItemOwner(self.params, **self["owner"]) + self["owner"] = Users(self.params).get(self["owner_guid"]) + return self["owner"] @property def _variants(self) -> Variants: - return Variants(self.params, self.guid) - - # Properties - - @property - def id(self) -> str: - return self.get("id") # type: ignore - - @property - def guid(self) -> str: - return self.get("guid") # type: ignore - - @property - def name(self) -> str: - return self.get("name") # type: ignore - - @property - def title(self) -> Optional[str]: - return self.get("title") # type: ignore - - @property - def description(self) -> str: - return self.get("description") # type: ignore - - @property - def access_type(self) -> str: - return self.get("access_type") # type: ignore - - @property - def connection_timeout(self) -> Optional[int]: - return self.get("connection_timeout") # type: ignore - - @property - def read_timeout(self) -> Optional[int]: - return self.get("read_timeout") # type: ignore - - @property - def init_timeout(self) -> Optional[int]: - return self.get("init_timeout") # type: ignore - - @property - def idle_timeout(self) -> Optional[int]: - return self.get("idle_timeout") # type: ignore - - @property - def max_processes(self) -> Optional[int]: - return self.get("max_processes") # type: ignore - - @property - def min_processes(self) -> Optional[int]: - return self.get("min_processes") # type: ignore - - @property - def max_conns_per_process(self) -> Optional[int]: - return self.get("max_conns_per_process") # type: ignore - - @property - def load_factor(self) -> Optional[float]: - return self.get("load_factor") # type: ignore - - @property - def cpu_request(self) -> Optional[float]: - return self.get("cpu_request") # type: ignore - - @property - def cpu_limit(self) -> Optional[float]: - return self.get("cpu_limit") # type: ignore - - @property - def memory_request(self) -> Optional[int]: - return self.get("memory_request") # type: ignore - - @property - def memory_limit(self) -> Optional[int]: - return self.get("memory_limit") # type: ignore - - @property - def amd_gpu_limit(self) -> Optional[int]: - return self.get("amd_gpu_limit") # type: ignore - - @property - def nvidia_gpu_limit(self) -> Optional[int]: - return self.get("nvidia_gpu_limit") # type: ignore - - @property - def created_time(self) -> str: - return self.get("created_time") # type: ignore - - @property - def last_deployed_time(self) -> str: - return self.get("last_deployed_time") # type: ignore - - @property - def bundle_id(self) -> Optional[str]: - return self.get("bundle_id") # type: ignore - - @property - def app_mode(self) -> str: - return self.get("app_mode") # type: ignore - - @property - def content_category(self) -> Optional[str]: - return self.get("content_category") # type: ignore - - @property - def parameterized(self) -> bool: - return self.get("parameterized") # type: ignore - - @property - def cluster_name(self) -> Optional[str]: - return self.get("cluster_name") # type: ignore - - @property - def image_name(self) -> Optional[str]: - return self.get("image_name") # type: ignore - - @property - def default_image_name(self) -> Optional[str]: - return self.get("default_image_name") # type: ignore - - @property - def default_r_environment_management(self) -> Optional[bool]: - return self.get("default_r_environment_management") # type: ignore - - @property - def default_py_environment_management(self) -> Optional[bool]: - return self.get("default_py_environment_management") # type: ignore - - @property - def service_account_name(self) -> Optional[str]: - return self.get("service_account_name") # type: ignore - - @property - def r_version(self) -> Optional[str]: - return self.get("r_version") # type: ignore - - @property - def r_environment_management(self) -> Optional[bool]: - return self.get("r_environment_management") # type: ignore - - @property - def py_version(self) -> Optional[str]: - return self.get("py_version") # type: ignore - - @property - def py_environment_management(self) -> Optional[bool]: - return self.get("py_environment_management") # type: ignore - - @property - def quarto_version(self) -> Optional[str]: - return self.get("quarto_version") # type: ignore - - @property - def run_as(self) -> Optional[str]: - return self.get("run_as") # type: ignore - - @property - def run_as_current_user(self) -> bool: - return self.get("run_as_current_user") # type: ignore - - @property - def owner_guid(self) -> str: - return self.get("owner_guid") # type: ignore - - @property - def content_url(self) -> str: - return self.get("content_url") # type: ignore - - @property - def dashboard_url(self) -> str: - return self.get("dashboard_url") # type: ignore - - @property - def app_role(self) -> str: - return self.get("app_role") # type: ignore - - @property - def tags(self) -> List[dict]: - return self.get("tags", []) + return Variants(self.params, self["guid"]) @property def is_interactive(self) -> bool: - return self.app_mode in { + return self["app_mode"] in { "api", "jupyter-voila", "python-api", @@ -527,7 +241,7 @@ def is_interactive(self) -> bool: @property def is_rendered(self) -> bool: - return self.app_mode in { + return self["app_mode"] in { "rmd-static", "jupyter-static", "quarto-static", @@ -659,8 +373,8 @@ def create(self, **kwargs) -> ContentItem: ContentItem """ path = "v1/content" - url = self.url + path - response = self.session.post(url, json=kwargs) + url = self.params.url + path + response = self.params.session.post(url, json=kwargs) return ContentItem(self.params, **response.json()) @overload @@ -723,8 +437,8 @@ def find( params.update(kwargs) params["include"] = include path = "v1/content" - url = self.url + path - response = self.session.get(url, params=params) + url = self.params.url + path + response = self.params.session.get(url, params=params) return [ ContentItem( self.params, @@ -803,6 +517,6 @@ def get(self, guid: str) -> ContentItem: ContentItem """ path = f"v1/content/{guid}" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) return ContentItem(self.params, **response.json()) diff --git a/src/posit/connect/env.py b/src/posit/connect/env.py index 073915e3..85640931 100644 --- a/src/posit/connect/env.py +++ b/src/posit/connect/env.py @@ -65,8 +65,8 @@ def clear(self) -> None: >>> clear() """ path = f"v1/content/{self.content_guid}/environment" - url = self.url + path - self.session.put(url, json=[]) + url = self.params.url + path + self.params.session.put(url, json=[]) def create(self, key: str, value: str, /) -> None: """Create an environment variable. @@ -123,8 +123,8 @@ def find(self) -> List[str]: ['DATABASE_URL'] """ path = f"v1/content/{self.content_guid}/environment" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) return response.json() def items(self): @@ -198,5 +198,5 @@ def update(self, other=(), /, **kwargs: Optional[str]): body = [{"name": key, "value": value} for key, value in d.items()] path = f"v1/content/{self.content_guid}/environment" - url = self.url + path - self.session.patch(url, json=body) + url = self.params.url + path + self.params.session.patch(url, json=body) diff --git a/src/posit/connect/groups.py b/src/posit/connect/groups.py index 459b8546..6781edcf 100644 --- a/src/posit/connect/groups.py +++ b/src/posit/connect/groups.py @@ -11,34 +11,11 @@ class Group(Resource): - """Group resource. - - Attributes - ---------- - guid : str - name: str - owner_guid: str - """ - - @property - def guid(self) -> str: - return self.get("guid") # type: ignore - - @property - def name(self) -> str: - return self.get("name") # type: ignore - - @property - def owner_guid(self) -> str: - return self.get("owner_guid") # type: ignore - - # CRUD Methods - def delete(self) -> None: """Delete the group.""" - path = f"v1/groups/{self.guid}" - url = self.url + path - self.session.delete(url) + path = f"v1/groups/{self['guid']}" + url = self.params.url + path + self.params.session.delete(url) class Groups(Resources): @@ -83,8 +60,8 @@ def create(self, **kwargs) -> Group: """ ... path = "v1/groups" - url = self.url + path - response = self.session.post(url, json=kwargs) + url = self.params.url + path + response = self.params.session.post(url, json=kwargs) return Group(self.params, **response.json()) @overload @@ -110,8 +87,8 @@ def find(self, **kwargs): List[Group] """ path = "v1/groups" - url = self.url + path - paginator = Paginator(self.session, url, params=kwargs) + url = self.params.url + path + paginator = Paginator(self.params.session, url, params=kwargs) results = paginator.fetch_results() return [ Group( @@ -144,8 +121,8 @@ def find_one(self, **kwargs) -> Group | None: Group | None """ path = "v1/groups" - url = self.url + path - paginator = Paginator(self.session, url, params=kwargs) + url = self.params.url + path + paginator = Paginator(self.params.session, url, params=kwargs) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) groups = ( @@ -168,8 +145,8 @@ def get(self, guid: str) -> Group: ------- Group """ - url = self.url + f"v1/groups/{guid}" - response = self.session.get(url) + url = self.params.url + f"v1/groups/{guid}" + response = self.params.session.get(url) return Group( self.params, **response.json(), @@ -183,8 +160,8 @@ def count(self) -> int: int """ path = "v1/groups" - url = self.url + path - response: requests.Response = self.session.get( + url = self.params.url + path + response: requests.Response = self.params.session.get( url, params={"page_size": 1} ) result: dict = response.json() diff --git a/src/posit/connect/metrics/shiny_usage.py b/src/posit/connect/metrics/shiny_usage.py index 02c870be..ace89274 100644 --- a/src/posit/connect/metrics/shiny_usage.py +++ b/src/posit/connect/metrics/shiny_usage.py @@ -107,8 +107,8 @@ def find(self, **kwargs) -> List[ShinyUsageEvent]: params = rename_params(kwargs) path = "/v1/instrumentation/shiny/usage" - url = self.url + path - paginator = CursorPaginator(self.session, url, params=params) + url = self.params.url + path + paginator = CursorPaginator(self.params.session, url, params=params) results = paginator.fetch_results() return [ ShinyUsageEvent( @@ -165,8 +165,8 @@ def find_one(self, **kwargs) -> ShinyUsageEvent | None: """ params = rename_params(kwargs) path = "/v1/instrumentation/shiny/usage" - url = self.url + path - paginator = CursorPaginator(self.session, url, params=params) + url = self.params.url + path + paginator = CursorPaginator(self.params.session, url, params=params) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) visits = ( diff --git a/src/posit/connect/metrics/visits.py b/src/posit/connect/metrics/visits.py index 684e7e11..c349459c 100644 --- a/src/posit/connect/metrics/visits.py +++ b/src/posit/connect/metrics/visits.py @@ -139,8 +139,8 @@ def find(self, **kwargs) -> List[VisitEvent]: params = rename_params(kwargs) path = "/v1/instrumentation/content/visits" - url = self.url + path - paginator = CursorPaginator(self.session, url, params=params) + url = self.params.url + path + paginator = CursorPaginator(self.params.session, url, params=params) results = paginator.fetch_results() return [ VisitEvent( @@ -197,8 +197,8 @@ def find_one(self, **kwargs) -> VisitEvent | None: """ params = rename_params(kwargs) path = "/v1/instrumentation/content/visits" - url = self.url + path - paginator = CursorPaginator(self.session, url, params=params) + url = self.params.url + path + paginator = CursorPaginator(self.params.session, url, params=params) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) visits = ( diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index 56c28460..76ed6082 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -10,31 +10,11 @@ class Permission(Resource): - @property - def id(self) -> str: - return self.get("id") # type: ignore - - @property - def content_guid(self) -> str: - return self.get("content_guid") # type: ignore - - @property - def principal_guid(self) -> str: - return self.get("principal_guid") # type: ignore - - @property - def principal_type(self) -> str: - return self.get("principal_type") # type: ignore - - @property - def role(self) -> str: - return self.get("role") # type: ignore - def delete(self) -> None: """Delete the permission.""" - path = f"v1/content/{self.content_guid}/permissions/{self.id}" - url = self.url + path - self.session.delete(url) + path = f"v1/content/{self['content_guid']}/permissions/{self['id']}" + url = self.params.url + path + self.params.session.delete(url) @overload def update(self, *args, role: str, **kwargs) -> None: @@ -55,15 +35,15 @@ def update(self, *args, **kwargs) -> None: def update(self, *args, **kwargs) -> None: """Update the permission.""" body = { - "principal_guid": self.principal_guid, - "principal_type": self.principal_type, - "role": self.role, + "principal_guid": self.get("principal_guid"), + "principal_type": self.get("principal_type"), + "role": self.get("role"), } body.update(dict(*args)) body.update(**kwargs) - path = f"v1/content/{self.content_guid}/permissions/{self.id}" - url = self.url + path - response = self.session.put( + path = f"v1/content/{self['content_guid']}/permissions/{self['id']}" + url = self.params.url + path + response = self.params.session.put( url, json=body, ) @@ -121,9 +101,9 @@ def create(self, **kwargs) -> Permission: """ ... path = f"v1/content/{self.content_guid}/permissions" - url = self.url + path - response = self.session.post(url, json=kwargs) - return Permission(self.params, **response.json()) + url = self.params.url + path + response = self.params.session.post(url, json=kwargs) + return Permission(params=self.params, **response.json()) def find(self, **kwargs) -> List[Permission]: """Find permissions. @@ -133,8 +113,8 @@ def find(self, **kwargs) -> List[Permission]: List[Permission] """ path = f"v1/content/{self.content_guid}/permissions" - url = self.url + path - response = self.session.get(url, json=kwargs) + url = self.params.url + path + response = self.params.session.get(url, json=kwargs) results = response.json() return [Permission(self.params, **result) for result in results] @@ -161,6 +141,6 @@ def get(self, uid: str) -> Permission: Permission """ path = f"v1/content/{self.content_guid}/permissions/{uid}" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) return Permission(self.params, **response.json()) diff --git a/src/posit/connect/resources.py b/src/posit/connect/resources.py index 60e8cbea..32ade716 100644 --- a/src/posit/connect/resources.py +++ b/src/posit/connect/resources.py @@ -1,6 +1,4 @@ -from abc import ABC from dataclasses import dataclass -from typing import Any import requests @@ -22,24 +20,16 @@ class ResourceParameters: url: Url -class Resource(ABC, dict): - def __init__(self, params: ResourceParameters, **kwargs): +class Resource(dict): + def __init__(self, /, params: ResourceParameters, **kwargs): + self.params = params super().__init__(**kwargs) - self.params: ResourceParameters - super().__setattr__("params", params) - self.session: requests.Session - super().__setattr__("session", params.session) - self.url: Url - super().__setattr__("url", params.url) - - def __setattr__(self, name: str, value: Any) -> None: - raise AttributeError("cannot set attributes: use update() instead") def update(self, *args, **kwargs): super().update(*args, **kwargs) -class Resources(ABC): +class Resources: def __init__(self, params: ResourceParameters) -> None: self.params = params self.session = params.session diff --git a/src/posit/connect/tasks.py b/src/posit/connect/tasks.py index 374a4906..2e59d91c 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -2,22 +2,12 @@ from __future__ import annotations -from typing import List, overload +from typing import overload from . import resources class Task(resources.Resource): - @property - def id(self) -> str: - """The task identifier. - - Returns - ------- - str - """ - return self["id"] - @property def is_finished(self) -> bool: """The task state. @@ -32,18 +22,6 @@ def is_finished(self) -> bool: """ return self.get("finished", False) - @property - def output(self) -> List[str]: - """Process output. - - The process output produced by the task. - - Returns - ------- - List[str] - """ - return self["output"] - @property def error_code(self) -> int | None: """The error code. @@ -69,16 +47,6 @@ def error_message(self) -> str | None: """ return self.get("error") if self.is_finished else None - @property - def result(self) -> dict | None: - """The task result. - - Returns - ------- - dict | None - """ - return self.get("result") - # CRUD Methods @overload @@ -124,9 +92,9 @@ def update(self, *args, **kwargs) -> None: ] """ params = dict(*args, **kwargs) - path = f"v1/tasks/{self.id}" - url = self.url + path - response = self.session.get(url, params=kwargs) + path = f"v1/tasks/{self['id']}" + url = self.params.url + path + response = self.params.session.get(url, params=kwargs) result = response.json() super().update(**result) @@ -190,7 +158,7 @@ def get(self, uid: str, **kwargs) -> Task: Task """ path = f"v1/tasks/{uid}" - url = self.url + path - response = self.session.get(url, params=kwargs) + url = self.params.url + path + response = self.params.session.get(url, params=kwargs) result = response.json() return Task(self.params, **result) diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py index 7ab1e5db..761e6ea4 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -11,74 +11,9 @@ class User(Resource): - """User resource. - - Attributes - ---------- - content: Content - A content resource scoped to this user. - guid : str - email : str - username : str - first_name : str - last_name : str - user_role : str - created_time : str - updated_time : str - active_time : str - confirmed : bool - Whether the user has confirmed their email address. - locked : bool - Whether the user is locked. - """ - @property def content(self) -> Content: - return Content(self.params, owner_guid=self.guid) - - @property - def guid(self) -> str: - return self.get("guid") # type: ignore - - @property - def email(self) -> str: - return self.get("email") # type: ignore - - @property - def username(self) -> str: - return self.get("username") # type: ignore - - @property - def first_name(self) -> str: - return self.get("first_name") # type: ignore - - @property - def last_name(self) -> str: - return self.get("last_name") # type: ignore - - @property - def user_role(self) -> str: - return self.get("user_role") # type: ignore - - @property - def created_time(self) -> str: - return self.get("created_time") # type: ignore - - @property - def updated_time(self) -> str: - return self.get("updated_time") # type: ignore - - @property - def active_time(self) -> str: - return self.get("active_time") # type: ignore - - @property - def confirmed(self) -> bool: - return self.get("confirmed") # type: ignore - - @property - def locked(self) -> bool: - return self.get("locked") # type: ignore + return Content(self.params, owner_guid=self["guid"]) def lock(self, *, force: bool = False): """ @@ -94,13 +29,13 @@ def lock(self, *, force: bool = False): None """ _me = me.get(self.params) - if _me.guid == self.guid and not force: + if _me["guid"] == self["guid"] and not force: raise RuntimeError( "You cannot lock your own account. Set force=True to override this behavior." ) - url = self.url + f"v1/users/{self.guid}/lock" + url = self.params.url + f"v1/users/{self['guid']}/lock" body = {"locked": True} - self.session.post(url, json=body) + self.params.session.post(url, json=body) super().update(locked=True) def unlock(self): @@ -111,9 +46,9 @@ def unlock(self): ------- None """ - url = self.url + f"v1/users/{self.guid}/lock" + url = self.params.url + f"v1/users/{self['guid']}/lock" body = {"locked": False} - self.session.post(url, json=body) + self.params.session.post(url, json=body) super().update(locked=False) @overload @@ -171,8 +106,8 @@ def update(self, *args, **kwargs) -> None: None """ body = dict(*args, **kwargs) - url = self.url + f"v1/users/{self.guid}" - response = self.session.put(url, json=body) + url = self.params.url + f"v1/users/{self['guid']}" + response = self.params.session.put(url, json=body) super().update(**response.json()) @@ -196,7 +131,7 @@ def find(self, **kwargs) -> List[User]: ... def find(self, **kwargs) -> List[User]: url = self.params.url + "v1/users" - paginator = Paginator(self.session, url, params=kwargs) + paginator = Paginator(self.params.session, url, params=kwargs) results = paginator.fetch_results() return [ User( @@ -220,7 +155,7 @@ def find_one(self, **kwargs) -> User | None: ... def find_one(self, **kwargs) -> User | None: url = self.params.url + "v1/users" - paginator = Paginator(self.session, url, params=kwargs) + paginator = Paginator(self.params.session, url, params=kwargs) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) users = ( @@ -233,8 +168,8 @@ def find_one(self, **kwargs) -> User | None: return next(users, None) def get(self, uid: str) -> User: - url = self.url + f"v1/users/{uid}" - response = self.session.get(url) + url = self.params.url + f"v1/users/{uid}" + response = self.params.session.get(url) return User( self.params, **response.json(), @@ -242,6 +177,6 @@ def get(self, uid: str) -> User: def count(self) -> int: url = self.params.url + "v1/users" - response = self.session.get(url, params={"page_size": 1}) + response = self.params.session.get(url, params={"page_size": 1}) result: dict = response.json() return result["total"] diff --git a/src/posit/connect/variants.py b/src/posit/connect/variants.py index fcc78d3f..eb6a28c0 100644 --- a/src/posit/connect/variants.py +++ b/src/posit/connect/variants.py @@ -5,18 +5,10 @@ class Variant(Resource): - @property - def id(self) -> str: - return self["id"] - - @property - def is_default(self) -> bool: - return self.get("is_default", False) - def render(self) -> Task: - path = f"variants/{self.id}/render" - url = self.url + path - response = self.session.post(url) + path = f"variants/{self['id']}/render" + url = self.params.url + path + response = self.params.session.post(url) return Task(self.params, **response.json()) @@ -27,7 +19,7 @@ def __init__(self, params: ResourceParameters, content_guid: str) -> None: def find(self) -> List[Variant]: path = f"applications/{self.content_guid}/variants" - url = self.url + path - response = self.session.get(url) + url = self.params.url + path + response = self.params.session.get(url) results = response.json() or [] return [Variant(self.params, **result) for result in results] diff --git a/tests/posit/connect/test_bundles.py b/tests/posit/connect/test_bundles.py index dcd2b681..a4b4a2be 100644 --- a/tests/posit/connect/test_bundles.py +++ b/tests/posit/connect/test_bundles.py @@ -6,83 +6,10 @@ from responses import matchers from posit.connect import Client -from posit.connect.bundles import Bundle from .api import get_path, load_mock # type: ignore -class TestBundleProperties: - def setup_class(cls): - cls.bundle = Bundle( - mock.Mock(), - **load_mock( - f"v1/content/f2f37341-e21d-3d80-c698-a935ad614066/bundles/101.json" - ), - ) - - def test_id(self): - assert self.bundle.id == "101" - - def test_content_guid(self): - assert ( - self.bundle.content_guid == "f2f37341-e21d-3d80-c698-a935ad614066" - ) - - def test_created_time(self): - assert self.bundle.created_time == "2006-01-02T15:04:05Z07:00" - - def test_cluster_name(self): - assert self.bundle.cluster_name == "Local" - - def test_image_name(self): - assert self.bundle.image_name == "Local" - - def test_r_version(self): - assert self.bundle.r_version == "3.5.1" - - def test_r_environment_management(self): - assert self.bundle.r_environment_management == True - - def test_py_version(self): - assert self.bundle.py_version == "3.8.2" - - def test_py_environment_management(self): - assert self.bundle.py_environment_management == True - - def test_quarto_version(self): - assert self.bundle.quarto_version == "0.2.22" - - def test_active(self): - assert self.bundle.active == False - - def test_size(self): - assert self.bundle.size == 1000000 - - def test_metadata_source(self): - assert self.bundle.metadata.source == "string" - - def test_metadata_source_repo(self): - assert self.bundle.metadata.source_repo == "string" - - def test_metadata_source_branch(self): - assert self.bundle.metadata.source_branch == "string" - - def test_metadata_source_commit(self): - assert self.bundle.metadata.source_commit == "string" - - def test_metadata_archive_md5(self): - assert ( - self.bundle.metadata.archive_md5 - == "37324238a80595c453c706b22adb83d3" - ) - - def test_metadata_archive_sha1(self): - assert ( - self.bundle.metadata.archive_sha1 - == "a2f7d13d87657df599aeeabdb70194d508cfa92f" - ) - - class TestBundleDelete: @responses.activate def test(self): @@ -158,7 +85,7 @@ def test(self): task = bundle.deploy() # assert - task.id == task_id + task["id"] == task_id assert mock_content_get.call_count == 1 assert mock_bundle_get.call_count == 1 assert mock_bundle_deploy.call_count == 1 @@ -318,7 +245,7 @@ def test(self): bundle = content.bundles.create(data) # # assert - assert bundle.id == "101" + assert bundle["id"] == "101" assert mock_content_get.call_count == 1 assert mock_bundle_post.call_count == 1 @@ -352,7 +279,7 @@ def test_kwargs_pathname(self): bundle = content.bundles.create(pathname) # # assert - assert bundle.id == "101" + assert bundle["id"] == "101" assert mock_content_get.call_count == 1 assert mock_bundle_post.call_count == 1 @@ -401,7 +328,7 @@ def test(self): assert mock_content_get.call_count == 1 assert mock_bundles_get.call_count == 1 assert len(bundles) == 1 - assert bundles[0].id == "101" + assert bundles[0]["id"] == "101" class TestBundlesFindOne: @@ -429,7 +356,7 @@ def test(self): # assert assert mock_content_get.call_count == 1 assert mock_bundles_get.call_count == 1 - assert bundle.id == "101" + assert bundle["id"] == "101" class TestBundlesGet: @@ -460,4 +387,4 @@ def test(self): # assert assert mock_content_get.call_count == 1 assert mock_bundle_get.call_count == 1 - assert bundle.id == bundle_id + assert bundle["id"] == bundle_id diff --git a/tests/posit/connect/test_client.py b/tests/posit/connect/test_client.py index 11942995..74f92d9d 100644 --- a/tests/posit/connect/test_client.py +++ b/tests/posit/connect/test_client.py @@ -127,7 +127,7 @@ def test_me_request(self): ) con = Client(api_key="12345", url="https://connect.example/") - assert con.me.username == "carlos12" + assert con.me["username"] == "carlos12" def test_request(self, MockSession): api_key = "12345" diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 609fa92f..e3912ab2 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -1,188 +1,12 @@ -from unittest import mock - import pytest import responses from responses import matchers from posit.connect.client import Client -from posit.connect.content import ContentItem, ContentItemOwner -from posit.connect.permissions import Permissions from .api import load_mock # type: ignore -class TestContentOwnerAttributes: - @classmethod - def setup_class(cls): - guid = "20a79ce3-6e87-4522-9faf-be24228800a4" - fake_item = load_mock(f"v1/users/{guid}.json") - cls.item = ContentItemOwner(mock.Mock(), **fake_item) - - def test_guid(self): - assert self.item.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" - - def test_username(self): - assert self.item.username == "carlos12" - - def test_first_name(self): - assert self.item.first_name == "Carlos" - - def test_last_name(self): - assert self.item.last_name == "User" - - -class TestContentItemAttributes: - @classmethod - def setup_class(cls): - guid = "f2f37341-e21d-3d80-c698-a935ad614066" - fake_item = load_mock(f"v1/content/{guid}.json") - cls.item = ContentItem(mock.Mock(), **fake_item) - - def test_id(self): - assert self.item.id == "8274" - - def test_guid(self): - assert self.item.guid == "f2f37341-e21d-3d80-c698-a935ad614066" - - def test_name(self): - assert self.item.name == "Performance-Data-1671216053560" - - def test_title(self): - assert self.item.title == "Performance Data" - - def test_description(self): - assert self.item.description == "" - - def test_access_type(self): - assert self.item.access_type == "logged_in" - - def test_connection_timeout(self): - assert self.item.connection_timeout is None - - def test_read_timeout(self): - assert self.item.read_timeout is None - - def test_init_timeout(self): - assert self.item.init_timeout is None - - def test_idle_timeout(self): - assert self.item.idle_timeout is None - - def test_max_processes(self): - assert self.item.max_processes is None - - def test_min_processes(self): - assert self.item.min_processes is None - - def test_max_conns_per_process(self): - assert self.item.max_conns_per_process is None - - def test_load_factor(self): - assert self.item.load_factor is None - - def test_cpu_request(self): - assert self.item.cpu_request is None - - def test_cpu_limit(self): - assert self.item.cpu_limit is None - - def test_memory_request(self): - assert self.item.memory_request is None - - def test_memory_limit(self): - assert self.item.memory_limit is None - - def test_amd_gpu_limit(self): - assert self.item.amd_gpu_limit is None - - def test_nvidia_gpu_limit(self): - assert self.item.nvidia_gpu_limit is None - - def test_created_time(self): - assert self.item.created_time == "2022-12-16T18:40:53Z" - - def test_last_deployed_time(self): - assert self.item.last_deployed_time == "2024-02-24T09:56:30Z" - - def test_bundle_id(self): - assert self.item.bundle_id == "401171" - - def test_app_mode(self): - assert self.item.app_mode == "quarto-static" - - def test_content_category(self): - assert self.item.content_category == "" - - def test_parameterized(self): - assert self.item.parameterized is False - - def test_cluster_name(self): - assert self.item.cluster_name == "Local" - - def test_image_name(self): - assert self.item.image_name is None - - def test_default_image_name(self): - assert self.item.default_image_name is None - - def test_default_r_environment_management(self): - assert self.item.default_r_environment_management is None - - def test_default_py_environment_management(self): - assert self.item.default_py_environment_management is None - - def test_service_account_name(self): - assert self.item.service_account_name is None - - def test_r_version(self): - assert self.item.r_version is None - - def test_r_environment_management(self): - assert self.item.r_environment_management is None - - def test_py_version(self): - assert self.item.py_version == "3.9.17" - - def test_py_environment_management(self): - assert self.item.py_environment_management is True - - def test_quarto_version(self): - assert self.item.quarto_version == "1.3.340" - - def test_run_as(self): - assert self.item.run_as is None - - def test_run_as_current_user(self): - assert self.item.run_as_current_user is False - - def test_owner_guid(self): - assert self.item.owner_guid == "20a79ce3-6e87-4522-9faf-be24228800a4" - - def test_content_url(self): - assert ( - self.item.content_url - == "https://connect.example/content/f2f37341-e21d-3d80-c698-a935ad614066/" - ) - - def test_dashboard_url(self): - assert ( - self.item.dashboard_url - == "https://connect.example/connect/#/apps/f2f37341-e21d-3d80-c698-a935ad614066" - ) - - def test_app_role(self): - assert self.item.app_role == "viewer" - - def test_owner(self): - assert "owner" not in self.item - - def test_permissions(self): - assert isinstance(self.item.permissions, Permissions) - - def test_tags(self): - assert self.item.tags == [] - - class TestContentItemGetContentOwner: @responses.activate def test_owner(self): @@ -204,11 +28,11 @@ def test_owner(self): c = Client("https://connect.example", "12345") item = c.content.get("f2f37341-e21d-3d80-c698-a935ad614066") owner = item.owner - assert owner.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" + assert owner["guid"] == "20a79ce3-6e87-4522-9faf-be24228800a4" # load a second time, assert tha owner is loaded from cached result owner = item.owner - assert owner.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" + assert owner["guid"] == "20a79ce3-6e87-4522-9faf-be24228800a4" assert mock_user_get.call_count == 1 @@ -270,7 +94,7 @@ def test(self): task = content.deploy() # assert - task.id == task_id + task["id"] == task_id assert mock_content_get.call_count == 1 assert mock_content_deploy.call_count == 1 assert mock_tasks_get.call_count == 1 @@ -286,7 +110,7 @@ def test_update(self): ) con = Client("https://connect.example", "12345") content = con.content.get(guid) - assert content.guid == guid + assert content["guid"] == guid new_name = "New Name" fake_content = load_mock(f"v1/content/{guid}.json") @@ -297,7 +121,7 @@ def test_update(self): ) content.update(name=new_name) - assert content.name == new_name + assert content["name"] == new_name class TestContentCreate: @@ -325,7 +149,7 @@ def test(self): content_item = client.content.create(name=fake_content_item["name"]) # assert - assert content_item.name == fake_content_item["name"] + assert content_item["name"] == fake_content_item["name"] class TestContentsFind: @@ -347,9 +171,9 @@ def test(self): # assert assert mock_get.call_count == 1 assert len(content) == 3 - assert content[0].name == "team-admin-dashboard" - assert content[1].name == "Performance-Data-1671216053560" - assert content[2].name == "My-Streamlit-app" + assert content[0]["name"] == "team-admin-dashboard" + assert content[1]["name"] == "Performance-Data-1671216053560" + assert content[2]["name"] == "My-Streamlit-app" @responses.activate def test_params_include(self): @@ -433,7 +257,7 @@ def test_owner_guid(self): # assert assert mock_get.call_count == 1 assert content_item - assert content_item.owner_guid == owner_guid + assert content_item["owner_guid"] == owner_guid @responses.activate def test_name(self): @@ -460,7 +284,7 @@ def test_name(self): # assert assert mock_get.call_count == 1 assert content_item - assert content_item.name == name + assert content_item["name"] == name @responses.activate def test_params_include(self): @@ -510,7 +334,7 @@ def test(self): ) con = Client("https://connect.example", "12345") get_one = con.content.get("f2f37341-e21d-3d80-c698-a935ad614066") - assert get_one.name == "Performance-Data-1671216053560" + assert get_one["name"] == "Performance-Data-1671216053560" class TestContentsCount: diff --git a/tests/posit/connect/test_groups.py b/tests/posit/connect/test_groups.py index d63beed8..74fbaf96 100644 --- a/tests/posit/connect/test_groups.py +++ b/tests/posit/connect/test_groups.py @@ -1,26 +1,8 @@ -from unittest import mock from unittest.mock import Mock -from posit.connect.groups import Group - -from .api import load_mock # type: ignore - session = Mock() url = Mock() -class TestGroupAttributes: - @classmethod - def setup_class(cls): - guid = "6f300623-1e0c-48e6-a473-ddf630c0c0c3" - fake_item = load_mock(f"v1/groups/{guid}.json") - cls.item = Group(mock.Mock(), **fake_item) - - def test_guid(self): - assert self.item.guid == "6f300623-1e0c-48e6-a473-ddf630c0c0c3" - - def test_name(self): - assert self.item.name == "Friends" - - def test_owner_guid(self): - assert self.item.owner_guid == "20a79ce3-6e87-4522-9faf-be24228800a4" +class TestGroup: + pass diff --git a/tests/posit/connect/test_permissions.py b/tests/posit/connect/test_permissions.py index 864169ec..c97b5a13 100644 --- a/tests/posit/connect/test_permissions.py +++ b/tests/posit/connect/test_permissions.py @@ -128,9 +128,9 @@ def test_role_update(self): ) # assert role change with respect to api response - assert permission.role == old_role + assert permission["role"] == old_role permission.update(role=new_role) - assert permission.role == new_role + assert permission["role"] == new_role class TestPermissionsCount: diff --git a/tests/posit/connect/test_tasks.py b/tests/posit/connect/test_tasks.py index 28cdd718..88c35783 100644 --- a/tests/posit/connect/test_tasks.py +++ b/tests/posit/connect/test_tasks.py @@ -16,18 +16,9 @@ def setup_class(cls): **load_mock("v1/tasks/jXhOhdm5OOSkGhJw.json"), ) - def test_id(self): - assert self.task.id == "jXhOhdm5OOSkGhJw" - def test_is_finished(self): assert self.task.is_finished - def test_output(self): - assert self.task.output == [ - "Building static content...", - "Launching static content...", - ] - def test_error_code(self): assert self.task.error_code == 1 @@ -37,9 +28,6 @@ def test_error_message(self): == "Unable to render: Rendering exited abnormally: exit status 1" ) - def test_result(self): - assert self.task.result is None - class TestTaskUpdate: @responses.activate @@ -152,5 +140,5 @@ def test(self): task = c.tasks.get(uid) # assert - assert task.id == uid + assert task["id"] == uid assert mock_tasks_get.call_count == 1 diff --git a/tests/posit/connect/test_users.py b/tests/posit/connect/test_users.py index 568a5f20..d158b240 100644 --- a/tests/posit/connect/test_users.py +++ b/tests/posit/connect/test_users.py @@ -1,4 +1,3 @@ -from unittest import mock from unittest.mock import Mock import pytest @@ -7,7 +6,6 @@ from responses import matchers from posit.connect.client import Client -from posit.connect.users import User from .api import load_mock # type: ignore @@ -15,85 +13,6 @@ url = Mock() -class TestUserAttributes: - def test_guid(self): - user = User(mock.Mock()) - assert hasattr(user, "guid") - assert user.guid is None - user = User(mock.Mock(), guid="test_guid") - assert user.guid == "test_guid" - - def test_email(self): - user = User(mock.Mock()) - assert hasattr(user, "email") - assert user.email is None - user = User(mock.Mock(), email="test@example.com") - assert user.email == "test@example.com" - - def test_username(self): - user = User(mock.Mock()) - assert hasattr(user, "username") - assert user.username is None - user = User(mock.Mock(), username="test_user") - assert user.username == "test_user" - - def test_first_name(self): - user = User(mock.Mock()) - assert hasattr(user, "first_name") - assert user.first_name is None - user = User(mock.Mock(), first_name="John") - assert user.first_name == "John" - - def test_last_name(self): - user = User(mock.Mock()) - assert hasattr(user, "last_name") - assert user.last_name is None - user = User(mock.Mock(), last_name="Doe") - assert user.last_name == "Doe" - - def test_user_role(self): - user = User(mock.Mock()) - assert hasattr(user, "user_role") - assert user.user_role is None - user = User(mock.Mock(), user_role="admin") - assert user.user_role == "admin" - - def test_created_time(self): - user = User(mock.Mock()) - assert hasattr(user, "created_time") - assert user.created_time is None - user = User(mock.Mock(), created_time="2022-01-01T00:00:00") - assert user.created_time == "2022-01-01T00:00:00" - - def test_updated_time(self): - user = User(mock.Mock()) - assert hasattr(user, "updated_time") - assert user.updated_time is None - user = User(mock.Mock(), updated_time="2022-01-01T00:00:00") - assert user.updated_time == "2022-01-01T00:00:00" - - def test_active_time(self): - user = User(mock.Mock()) - assert hasattr(user, "active_time") - assert user.active_time is None - user = User(mock.Mock(), active_time="2022-01-01T00:00:00") - assert user.active_time == "2022-01-01T00:00:00" - - def test_confirmed(self): - user = User(mock.Mock()) - assert hasattr(user, "confirmed") - assert user.confirmed is None - user = User(mock.Mock(), confirmed=True) - assert user.confirmed is True - - def test_locked(self): - user = User(mock.Mock()) - assert hasattr(user, "locked") - assert user.locked is None - user = User(mock.Mock(), locked=False) - assert user.locked is False - - class TestUserContent: """Check behavior of content attribute.""" @@ -145,7 +64,7 @@ def test_lock(self): ) c = Client(api_key="12345", url="https://connect.example/") user = c.users.get("a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6") - assert user.guid == "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6" + assert user["guid"] == "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6" responses.get( "https://connect.example/__api__/v1/user", @@ -158,7 +77,7 @@ def test_lock(self): match=[responses.matchers.json_params_matcher({"locked": True})], ) user.lock() - assert user.locked + assert user["locked"] @responses.activate def test_lock_self_true(self): @@ -170,7 +89,7 @@ def test_lock_self_true(self): ) c = Client(api_key="12345", url="https://connect.example/") user = c.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - assert user.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" + assert user["guid"] == "20a79ce3-6e87-4522-9faf-be24228800a4" responses.get( "https://connect.example/__api__/v1/user", @@ -183,7 +102,7 @@ def test_lock_self_true(self): match=[responses.matchers.json_params_matcher({"locked": True})], ) user.lock(force=True) - assert user.locked + assert user["locked"] @responses.activate def test_lock_self_false(self): @@ -195,7 +114,7 @@ def test_lock_self_false(self): ) c = Client(api_key="12345", url="https://connect.example/") user = c.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - assert user.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" + assert user["guid"] == "20a79ce3-6e87-4522-9faf-be24228800a4" responses.get( "https://connect.example/__api__/v1/user", @@ -209,7 +128,7 @@ def test_lock_self_false(self): ) with pytest.raises(RuntimeError): user.lock(force=False) - assert not user.locked + assert not user["locked"] class TestUserUnlock: @@ -223,14 +142,14 @@ def test_unlock(self): ) c = Client(api_key="12345", url="https://connect.example/") user = c.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - assert user.guid == "20a79ce3-6e87-4522-9faf-be24228800a4" + assert user["guid"] == "20a79ce3-6e87-4522-9faf-be24228800a4" responses.post( "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock", match=[responses.matchers.json_params_matcher({"locked": False})], ) user.unlock() - assert not user.locked + assert not user["locked"] class TestUsers: @@ -245,9 +164,9 @@ def test_users_get(self): con = Client(api_key="12345", url="https://connect.example/") carlos = con.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - assert carlos.username == "carlos12" - assert carlos.first_name == "Carlos" - assert carlos.created_time == "2019-09-09T15:24:32Z" + assert carlos["username"] == "carlos12" + assert carlos["first_name"] == "Carlos" + assert carlos["created_time"] == "2019-09-09T15:24:32Z" @responses.activate def test_users_get_extra_fields(self): @@ -262,7 +181,7 @@ def test_users_get_extra_fields(self): con = Client(api_key="12345", url="https://connect.example/") carlos = con.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - assert carlos.username == "carlos12" + assert carlos["username"] == "carlos12" assert carlos["some_new_field"] == "some_new_value" @responses.activate @@ -287,12 +206,12 @@ def test_user_update(self): carlos = con.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") assert patch_request.call_count == 0 - assert carlos.first_name == "Carlos" + assert carlos["first_name"] == "Carlos" carlos.update(first_name="Carlitos") assert patch_request.call_count == 1 - assert carlos.first_name == "Carlitos" + assert carlos["first_name"] == "Carlitos" @responses.activate def test_user_update_server_error(self): @@ -312,24 +231,6 @@ def test_user_update_server_error(self): with pytest.raises(requests.HTTPError, match="500 Server Error"): carlos.update(first_name="Carlitos") - @responses.activate - def test_user_cant_setattr(self): - responses.get( - "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4", - json=load_mock( - "v1/users/20a79ce3-6e87-4522-9faf-be24228800a4.json" - ), - ) - - con = Client(api_key="12345", url="https://connect.example/") - carlos = con.users.get("20a79ce3-6e87-4522-9faf-be24228800a4") - - with pytest.raises( - AttributeError, - match=r"cannot set attributes: use update\(\) instead", - ): - carlos.first_name = "Carlitos" - @responses.activate def test_count(self): responses.get( @@ -352,7 +253,7 @@ def test_default(self): ) con = Client(api_key="12345", url="https://connect.example/") user = con.users.find_one() - assert user.username == "al" + assert user["username"] == "al" assert len(responses.calls) == 1 @responses.activate