From e6809f3ff2ea1741afde4ae94eef337dbcc9a267 Mon Sep 17 00:00:00 2001 From: Taylor Steinberg Date: Mon, 16 Dec 2024 14:21:58 -0500 Subject: [PATCH] refactor: replace `ctx.params`, `ctx.session`, and `ctx.url` with `ctx.client` (#361) Now that Client exists on the Context, we can remove all usage of `ctx.params`, `ctx.session`, and `ctx.url` with calls to `ctx.client`. --------- Co-authored-by: Barret Schloerke --- src/posit/connect/_api_call.py | 18 +++--- src/posit/connect/bundles.py | 37 +++++------ src/posit/connect/client.py | 11 ++-- src/posit/connect/content.py | 29 +++++---- src/posit/connect/context.py | 8 +-- src/posit/connect/cursors.py | 17 ++--- src/posit/connect/env.py | 20 +++--- src/posit/connect/groups.py | 12 +--- src/posit/connect/me.py | 4 +- src/posit/connect/metrics/metrics.py | 2 +- src/posit/connect/metrics/shiny_usage.py | 10 ++- src/posit/connect/metrics/usage.py | 8 +-- src/posit/connect/metrics/visits.py | 10 ++- src/posit/connect/oauth/associations.py | 28 ++++----- src/posit/connect/oauth/integrations.py | 25 +++----- src/posit/connect/oauth/oauth.py | 23 +++---- src/posit/connect/oauth/sessions.py | 14 ++--- src/posit/connect/permissions.py | 31 ++++------ src/posit/connect/resources.py | 35 ++--------- src/posit/connect/tasks.py | 8 +-- src/posit/connect/users.py | 10 +-- src/posit/connect/vanities.py | 35 ++++++----- src/posit/connect/variants.py | 17 +++-- .../posit/connect/metrics/test_shiny_usage.py | 12 ++-- tests/posit/connect/metrics/test_visits.py | 12 ++-- tests/posit/connect/test_content.py | 17 +---- tests/posit/connect/test_context.py | 6 +- tests/posit/connect/test_permissions.py | 62 +++++++++---------- tests/posit/connect/test_resources.py | 2 +- tests/posit/connect/test_vanities.py | 54 ++++++---------- 30 files changed, 238 insertions(+), 339 deletions(-) diff --git a/src/posit/connect/_api_call.py b/src/posit/connect/_api_call.py index f90244aa..56ce70f6 100644 --- a/src/posit/connect/_api_call.py +++ b/src/posit/connect/_api_call.py @@ -19,13 +19,13 @@ def _patch_api(self, *path, json: Jsonifiable | None) -> Jsonifiable: ... def _put_api(self, *path, json: Jsonifiable | None) -> Jsonifiable: ... -def endpoint(ctx: Context, *path) -> str: - return ctx.url + posixpath.join(*path) +def endpoint(*path) -> str: + return posixpath.join(*path) # Helper methods for API interactions def get_api(ctx: Context, *path) -> Jsonifiable: - response = ctx.session.get(endpoint(ctx, *path)) + response = ctx.client.get(*path) return response.json() @@ -34,7 +34,7 @@ def put_api( *path, json: Jsonifiable | None, ) -> Jsonifiable: - response = ctx.session.put(endpoint(ctx, *path), json=json) + response = ctx.client.put(*path, json=json) return response.json() @@ -43,14 +43,14 @@ def put_api( class ApiCallMixin: def _endpoint(self: ApiCallProtocol, *path) -> str: - return endpoint(self._ctx, self._path, *path) + return endpoint(self._path, *path) def _get_api(self: ApiCallProtocol, *path) -> Jsonifiable: - response = self._ctx.session.get(self._endpoint(*path)) + response = self._ctx.client.get(self._endpoint(*path)) return response.json() def _delete_api(self: ApiCallProtocol, *path) -> Jsonifiable | None: - response = self._ctx.session.delete(self._endpoint(*path)) + response = self._ctx.client.delete(self._endpoint(*path)) if len(response.content) == 0: return None return response.json() @@ -60,7 +60,7 @@ def _patch_api( *path, json: Jsonifiable | None, ) -> Jsonifiable: - response = self._ctx.session.patch(self._endpoint(*path), json=json) + response = self._ctx.client.patch(self._endpoint(*path), json=json) return response.json() def _put_api( @@ -68,5 +68,5 @@ def _put_api( *path, json: Jsonifiable | None, ) -> Jsonifiable: - response = self._ctx.session.put(self._endpoint(*path), json=json) + response = self._ctx.client.put(self._endpoint(*path), json=json) return response.json() diff --git a/src/posit/connect/bundles.py b/src/posit/connect/bundles.py index c6a8a265..515751fa 100644 --- a/src/posit/connect/bundles.py +++ b/src/posit/connect/bundles.py @@ -3,10 +3,13 @@ from __future__ import annotations import io -from typing import List +from typing import TYPE_CHECKING, List from . import resources, tasks +if TYPE_CHECKING: + from .context import Context + class BundleMetadata(resources.Resource): pass @@ -15,13 +18,12 @@ class BundleMetadata(resources.Resource): class Bundle(resources.Resource): @property def metadata(self) -> BundleMetadata: - return BundleMetadata(self.params, **self.get("metadata", {})) + return BundleMetadata(self._ctx, **self.get("metadata", {})) def delete(self) -> None: """Delete the bundle.""" path = f"v1/content/{self['content_guid']}/bundles/{self['id']}" - url = self.params.url + path - self.params.session.delete(url) + self._ctx.client.delete(path) def deploy(self) -> tasks.Task: """Deploy the bundle. @@ -40,10 +42,9 @@ def deploy(self) -> tasks.Task: None """ path = f"v1/content/{self['content_guid']}/deploy" - url = self.params.url + path - response = self.params.session.post(url, json={"bundle_id": self["id"]}) + response = self._ctx.client.post(path, json={"bundle_id": self["id"]}) result = response.json() - ts = tasks.Tasks(self.params) + ts = tasks.Tasks(self._ctx) return ts.get(result["task_id"]) def download(self, output: io.BufferedWriter | str) -> None: @@ -78,8 +79,7 @@ def download(self, output: io.BufferedWriter | str) -> None: ) path = f"v1/content/{self['content_guid']}/bundles/{self['id']}/download" - url = self.params.url + path - response = self.params.session.get(url, stream=True) + response = self._ctx.client.get(path, stream=True) if isinstance(output, io.BufferedWriter): for chunk in response.iter_content(): output.write(chunk) @@ -109,10 +109,10 @@ class Bundles(resources.Resources): def __init__( self, - params: resources.ResourceParameters, + ctx: Context, content_guid: str, ) -> None: - super().__init__(params) + super().__init__(ctx) self.content_guid = content_guid def create(self, archive: io.BufferedReader | bytes | str) -> Bundle: @@ -164,10 +164,9 @@ def create(self, archive: io.BufferedReader | bytes | str) -> Bundle: ) path = f"v1/content/{self.content_guid}/bundles" - url = self.params.url + path - response = self.params.session.post(url, data=data) + response = self._ctx.client.post(path, data=data) result = response.json() - return Bundle(self.params, **result) + return Bundle(self._ctx, **result) def find(self) -> List[Bundle]: """Find all bundles. @@ -178,10 +177,9 @@ def find(self) -> List[Bundle]: List of all found bundles. """ path = f"v1/content/{self.content_guid}/bundles" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) results = response.json() - return [Bundle(self.params, **result) for result in results] + return [Bundle(self._ctx, **result) for result in results] def find_one(self) -> Bundle | None: """Find a bundle. @@ -208,7 +206,6 @@ def get(self, uid: str) -> Bundle: The bundle with the specified ID. """ path = f"v1/content/{self.content_guid}/bundles/{uid}" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) result = response.json() - return Bundle(self.params, **result) + return Bundle(self._ctx, **result) diff --git a/src/posit/connect/client.py b/src/posit/connect/client.py index 89d878b0..f0f5c3a1 100644 --- a/src/posit/connect/client.py +++ b/src/posit/connect/client.py @@ -14,7 +14,7 @@ from .groups import Groups from .metrics import Metrics from .oauth import OAuth -from .resources import ResourceParameters, _PaginatedResourceSequence, _ResourceSequence +from .resources import _PaginatedResourceSequence, _ResourceSequence from .system import System from .tags import Tags from .tasks import Tasks @@ -160,7 +160,6 @@ def __init__(self, *args, **kwargs) -> None: session.hooks["response"].append(hooks.check_for_deprecation_header) session.hooks["response"].append(hooks.handle_errors) self.session = session - self.resource_params = ResourceParameters(session, self.cfg.url) self._ctx = Context(self) @property @@ -208,7 +207,7 @@ def tasks(self) -> Tasks: tasks.Tasks The tasks resource instance. """ - return Tasks(self.resource_params) + return Tasks(self._ctx) @property def users(self) -> Users: @@ -282,7 +281,7 @@ def metrics(self) -> Metrics: >>> len(events) 24 """ - return Metrics(self.resource_params) + return Metrics(self._ctx) @property @requires(version="2024.08.0") @@ -295,7 +294,7 @@ def oauth(self) -> OAuth: OAuth The oauth API instance. """ - return OAuth(self.resource_params, self.cfg.api_key) + return OAuth(self._ctx, self.cfg.api_key) @property @requires(version="2024.11.0") @@ -304,7 +303,7 @@ def packages(self) -> Packages: @property def vanities(self) -> Vanities: - return Vanities(self.resource_params) + return Vanities(self._ctx) @property def system(self) -> System: diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index 64448c8c..a98e9a64 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -4,7 +4,6 @@ import posixpath import time -from posixpath import dirname from typing import ( TYPE_CHECKING, Any, @@ -25,7 +24,7 @@ from .errors import ClientError from .oauth.associations import ContentItemAssociations from .permissions import Permissions -from .resources import Active, Resource, ResourceParameters, Resources, _ResourceSequence +from .resources import Active, Resource, Resources, _ResourceSequence from .tags import ContentItemTags from .vanities import VanityMixin from .variants import Variants @@ -162,13 +161,13 @@ def update( class ContentItemOAuth(Resource): - def __init__(self, params: ResourceParameters, content_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, content_guid: str) -> None: + super().__init__(ctx) self["content_guid"] = content_guid @property def associations(self) -> ContentItemAssociations: - return ContentItemAssociations(self.params, content_guid=self["content_guid"]) + return ContentItemAssociations(self._ctx, content_guid=self["content_guid"]) class ContentItemOwner(Resource): @@ -255,12 +254,12 @@ def __init__( def __getitem__(self, key: Any) -> Any: v = super().__getitem__(key) if key == "owner" and isinstance(v, dict): - return ContentItemOwner(params=self.params, **v) + return ContentItemOwner(self._ctx, **v) return v @property def oauth(self) -> ContentItemOAuth: - return ContentItemOAuth(self.params, content_guid=self["guid"]) + return ContentItemOAuth(self._ctx, content_guid=self["guid"]) @property def repository(self) -> ContentItemRepository | None: @@ -316,7 +315,7 @@ def deploy(self) -> tasks.Task: path = f"v1/content/{self['guid']}/deploy" response = self._ctx.client.post(path, json={"bundle_id": None}) result = response.json() - ts = tasks.Tasks(self.params) + ts = tasks.Tasks(self._ctx) return ts.get(result["task_id"]) def render(self) -> Task: @@ -369,8 +368,8 @@ 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._ctx.url), f"content/{self['guid']}") - self._ctx.session.get(url) + path = f"../content/{self['guid']}" + self._ctx.client.get(path) return None else: raise ValueError( @@ -447,15 +446,15 @@ def update( @property def bundles(self) -> Bundles: - return Bundles(self.params, self["guid"]) + return Bundles(self._ctx, self["guid"]) @property def environment_variables(self) -> EnvVars: - return EnvVars(self.params, self["guid"]) + return EnvVars(self._ctx, self["guid"]) @property def permissions(self) -> Permissions: - return Permissions(self.params, self["guid"]) + return Permissions(self._ctx, self["guid"]) @property def owner(self) -> dict: @@ -472,7 +471,7 @@ def owner(self) -> dict: @property def _variants(self) -> Variants: - return Variants(self.params, self["guid"]) + return Variants(self._ctx, self["guid"]) @property def is_interactive(self) -> bool: @@ -540,7 +539,7 @@ def __init__( *, owner_guid: str | None = None, ) -> None: - super().__init__(ctx.client.resource_params) + super().__init__(ctx) self.owner_guid = owner_guid self._ctx = ctx diff --git a/src/posit/connect/context.py b/src/posit/connect/context.py index 6faaf545..ec7ff55a 100644 --- a/src/posit/connect/context.py +++ b/src/posit/connect/context.py @@ -7,10 +7,7 @@ from packaging.version import Version if TYPE_CHECKING: - import requests - from .client import Client - from .urls import Url def requires(version: str): @@ -31,8 +28,6 @@ def wrapper(instance: ContextManager, *args, **kwargs): class Context: def __init__(self, client: Client): - self.session: requests.Session = client.session - self.url: Url = client.cfg.url # Since this is a child object of the client, we use a weak reference to avoid circular # references (which would prevent garbage collection) self.client: Client = weakref.proxy(client) @@ -40,8 +35,7 @@ def __init__(self, client: Client): @property def version(self) -> str | None: if not hasattr(self, "_version"): - endpoint = self.url + "server_settings" - response = self.session.get(endpoint) + response = self.client.get("server_settings") result = response.json() self._version: str | None = result.get("version") diff --git a/src/posit/connect/cursors.py b/src/posit/connect/cursors.py index 3c8a3c62..22f406e1 100644 --- a/src/posit/connect/cursors.py +++ b/src/posit/connect/cursors.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Generator, List if TYPE_CHECKING: - import requests + from .context import Context # The maximum page size supported by the API. _MAX_PAGE_SIZE = 500 @@ -19,15 +19,16 @@ class CursorPage: class CursorPaginator: def __init__( self, - session: requests.Session, - url: str, + ctx: Context, + path: str, params: dict[str, Any] | None = None, ) -> None: if params is None: params = {} - self.session = session - self.url = url - self.params = params + + self._ctx = ctx + self._path = path + self._params = params def fetch_results(self) -> List[dict]: """Fetch results. @@ -74,9 +75,9 @@ def fetch_page(self, next_page: str | None = None) -> CursorPage: Page """ params = { - **self.params, + **self._params, "next": next_page, "limit": _MAX_PAGE_SIZE, } - response = self.session.get(self.url, params=params) + response = self._ctx.client.get(self._path, params=params) return CursorPage(**response.json()) diff --git a/src/posit/connect/env.py b/src/posit/connect/env.py index 60a58b02..5ca26cd2 100644 --- a/src/posit/connect/env.py +++ b/src/posit/connect/env.py @@ -1,13 +1,16 @@ from __future__ import annotations -from typing import Any, Iterator, List, Mapping, MutableMapping, Optional +from typing import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Optional -from .resources import ResourceParameters, Resources +from .resources import Resources + +if TYPE_CHECKING: + from .context import Context class EnvVars(Resources, MutableMapping[str, Optional[str]]): - def __init__(self, params: ResourceParameters, content_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, content_guid: str) -> None: + super().__init__(ctx) self.content_guid = content_guid def __delitem__(self, key: str, /) -> None: @@ -63,8 +66,7 @@ def clear(self) -> None: >>> clear() """ path = f"v1/content/{self.content_guid}/environment" - url = self.params.url + path - self.params.session.put(url, json=[]) + self._ctx.client.put(path, json=[]) def create(self, key: str, value: str, /) -> None: """Create an environment variable. @@ -121,8 +123,7 @@ def find(self) -> List[str]: ['DATABASE_URL'] """ path = f"v1/content/{self.content_guid}/environment" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) return response.json() def items(self): @@ -194,5 +195,4 @@ 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.params.url + path - self.params.session.patch(url, json=body) + self._ctx.client.patch(path, json=body) diff --git a/src/posit/connect/groups.py b/src/posit/connect/groups.py index bea96d71..717b18d9 100644 --- a/src/posit/connect/groups.py +++ b/src/posit/connect/groups.py @@ -16,7 +16,7 @@ class Group(Resource): def __init__(self, ctx: Context, **kwargs) -> None: - super().__init__(ctx.client.resource_params, **kwargs) + super().__init__(ctx, **kwargs) self._ctx: Context = ctx @property @@ -66,9 +66,8 @@ def delete(self) -> None: class GroupMembers(Resources): def __init__(self, ctx: Context, group_guid: str) -> None: - super().__init__(ctx.client.resource_params) + super().__init__(ctx) self._group_guid = group_guid - self._ctx: Context = ctx @overload def add(self, user: User, /) -> None: ... @@ -260,10 +259,6 @@ def count(self) -> int: class Groups(Resources): """Groups resource.""" - def __init__(self, ctx: Context) -> None: - super().__init__(ctx.client.resource_params) - self._ctx: Context = ctx - @overload def create(self, *, name: str, unique_id: str | None) -> Group: """Create a group. @@ -415,7 +410,6 @@ def count(self) -> int: * https://docs.posit.co/connect/api/#get-/v1/groups """ path = "v1/groups" - url = self._ctx.url + path - response: requests.Response = self._ctx.session.get(url, params={"page_size": 1}) + response: requests.Response = self._ctx.client.get(path, params={"page_size": 1}) result: dict = response.json() return result["total"] diff --git a/src/posit/connect/me.py b/src/posit/connect/me.py index ee795724..4e4fe4c0 100644 --- a/src/posit/connect/me.py +++ b/src/posit/connect/me.py @@ -14,6 +14,6 @@ def get(ctx: Context) -> User: ------- User: The current user. """ - url = ctx.url + "v1/user" - response = ctx.session.get(url) + path = "v1/user" + response = ctx.client.get(path) return User(ctx, **response.json()) diff --git a/src/posit/connect/metrics/metrics.py b/src/posit/connect/metrics/metrics.py index a205c517..7012b15d 100644 --- a/src/posit/connect/metrics/metrics.py +++ b/src/posit/connect/metrics/metrics.py @@ -15,4 +15,4 @@ class Metrics(resources.Resources): @property def usage(self) -> Usage: - return Usage(self.params) + return Usage(self._ctx) diff --git a/src/posit/connect/metrics/shiny_usage.py b/src/posit/connect/metrics/shiny_usage.py index 050f801a..68a391be 100644 --- a/src/posit/connect/metrics/shiny_usage.py +++ b/src/posit/connect/metrics/shiny_usage.py @@ -105,12 +105,11 @@ def find(self, **kwargs) -> List[ShinyUsageEvent]: params = rename_params(kwargs) path = "/v1/instrumentation/shiny/usage" - url = self.params.url + path - paginator = CursorPaginator(self.params.session, url, params=params) + paginator = CursorPaginator(self._ctx, path, params=params) results = paginator.fetch_results() return [ ShinyUsageEvent( - self.params, + self._ctx, **result, ) for result in results @@ -161,13 +160,12 @@ def find_one(self, **kwargs) -> ShinyUsageEvent | None: """ params = rename_params(kwargs) path = "/v1/instrumentation/shiny/usage" - url = self.params.url + path - paginator = CursorPaginator(self.params.session, url, params=params) + paginator = CursorPaginator(self._ctx, path, params=params) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) visits = ( ShinyUsageEvent( - self.params, + self._ctx, **result, ) for result in results diff --git a/src/posit/connect/metrics/usage.py b/src/posit/connect/metrics/usage.py index 41bc3db4..6714cca7 100644 --- a/src/posit/connect/metrics/usage.py +++ b/src/posit/connect/metrics/usage.py @@ -26,7 +26,7 @@ def from_event( @staticmethod def from_visit_event(event: visits.VisitEvent) -> UsageEvent: return UsageEvent( - event.params, + event._ctx, content_guid=event.content_guid, user_guid=event.user_guid, variant_key=event.variant_key, @@ -43,7 +43,7 @@ def from_shiny_usage_event( event: shiny_usage.ShinyUsageEvent, ) -> UsageEvent: return UsageEvent( - event.params, + event._ctx, content_guid=event.content_guid, user_guid=event.user_guid, variant_key=None, @@ -197,7 +197,7 @@ def find(self, **kwargs) -> List[UsageEvent]: events = [] finders = (visits.Visits, shiny_usage.ShinyUsage) for finder in finders: - instance = finder(self.params) + instance = finder(self._ctx) events.extend( [ UsageEvent.from_event(event) @@ -251,7 +251,7 @@ def find_one(self, **kwargs) -> UsageEvent | None: """ finders = (visits.Visits, shiny_usage.ShinyUsage) for finder in finders: - instance = finder(self.params) + instance = finder(self._ctx) event = instance.find_one(**kwargs) # type: ignore[attr-defined] if event: return UsageEvent.from_event(event) diff --git a/src/posit/connect/metrics/visits.py b/src/posit/connect/metrics/visits.py index 59b3acfb..393aae36 100644 --- a/src/posit/connect/metrics/visits.py +++ b/src/posit/connect/metrics/visits.py @@ -137,12 +137,11 @@ def find(self, **kwargs) -> List[VisitEvent]: params = rename_params(kwargs) path = "/v1/instrumentation/content/visits" - url = self.params.url + path - paginator = CursorPaginator(self.params.session, url, params=params) + paginator = CursorPaginator(self._ctx, path, params=params) results = paginator.fetch_results() return [ VisitEvent( - self.params, + self._ctx, **result, ) for result in results @@ -193,13 +192,12 @@ def find_one(self, **kwargs) -> VisitEvent | None: """ params = rename_params(kwargs) path = "/v1/instrumentation/content/visits" - url = self.params.url + path - paginator = CursorPaginator(self.params.session, url, params=params) + paginator = CursorPaginator(self._ctx, path, params=params) pages = paginator.fetch_pages() results = (result for page in pages for result in page.results) visits = ( VisitEvent( - self.params, + self._ctx, **result, ) for result in results diff --git a/src/posit/connect/oauth/associations.py b/src/posit/connect/oauth/associations.py index efb85c60..9fc999e6 100644 --- a/src/posit/connect/oauth/associations.py +++ b/src/posit/connect/oauth/associations.py @@ -2,7 +2,8 @@ from typing import List -from ..resources import Resource, ResourceParameters, Resources +from ..context import Context +from ..resources import Resource, Resources class Association(Resource): @@ -12,8 +13,8 @@ class Association(Resource): class IntegrationAssociations(Resources): """IntegrationAssociations resource.""" - def __init__(self, params: ResourceParameters, integration_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, integration_guid: str) -> None: + super().__init__(ctx) self.integration_guid = integration_guid def find(self) -> List[Association]: @@ -24,12 +25,10 @@ def find(self) -> List[Association]: List[Association] """ path = f"v1/oauth/integrations/{self.integration_guid}/associations" - url = self.params.url + path - - response = self.params.session.get(url) + response = self._ctx.client.get(path) return [ Association( - self.params, + self._ctx, **result, ) for result in response.json() @@ -39,8 +38,8 @@ def find(self) -> List[Association]: class ContentItemAssociations(Resources): """ContentItemAssociations resource.""" - def __init__(self, params: ResourceParameters, content_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx, content_guid: str): + super().__init__(ctx) self.content_guid = content_guid def find(self) -> List[Association]: @@ -51,11 +50,10 @@ def find(self) -> List[Association]: List[Association] """ path = f"v1/content/{self.content_guid}/oauth/integrations/associations" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) return [ Association( - self.params, + self._ctx, **result, ) for result in response.json() @@ -66,13 +64,11 @@ def delete(self) -> None: data = [] path = f"v1/content/{self.content_guid}/oauth/integrations/associations" - url = self.params.url + path - self.params.session.put(url, json=data) + self._ctx.client.put(path, json=data) def update(self, integration_guid: str) -> None: """Set integration associations.""" data = [{"oauth_integration_guid": integration_guid}] path = f"v1/content/{self.content_guid}/oauth/integrations/associations" - url = self.params.url + path - self.params.session.put(url, json=data) + self._ctx.client.put(path, json=data) diff --git a/src/posit/connect/oauth/integrations.py b/src/posit/connect/oauth/integrations.py index ccbaaf73..540cd6f0 100644 --- a/src/posit/connect/oauth/integrations.py +++ b/src/posit/connect/oauth/integrations.py @@ -11,13 +11,12 @@ class Integration(Resource): @property def associations(self) -> IntegrationAssociations: - return IntegrationAssociations(self.params, integration_guid=self["guid"]) + return IntegrationAssociations(self._ctx, integration_guid=self["guid"]) def delete(self) -> None: """Delete the OAuth integration.""" path = f"v1/oauth/integrations/{self['guid']}" - url = self.params.url + path - self.params.session.delete(url) + self._ctx.client.delete(path) @overload def update( @@ -44,8 +43,8 @@ def update(self, *args, **kwargs) -> None: def update(self, *args, **kwargs) -> None: """Update the OAuth integration.""" body = dict(*args, **kwargs) - url = self.params.url + f"v1/oauth/integrations/{self['guid']}" - response = self.params.session.patch(url, json=body) + path = f"v1/oauth/integrations/{self['guid']}" + response = self._ctx.client.patch(path, json=body) super().update(**response.json()) @@ -99,9 +98,8 @@ def create(self, **kwargs) -> Integration: Integration """ path = "v1/oauth/integrations" - url = self.params.url + path - response = self.params.session.post(url, json=kwargs) - return Integration(self.params, **response.json()) + response = self._ctx.client.post(path, json=kwargs) + return Integration(self._ctx, **response.json()) def find(self) -> List[Integration]: """Find OAuth integrations. @@ -111,12 +109,10 @@ def find(self) -> List[Integration]: List[Integration] """ path = "v1/oauth/integrations" - url = self.params.url + path - - response = self.params.session.get(url) + response = self._ctx.client.get(path) return [ Integration( - self.params, + self._ctx, **result, ) for result in response.json() @@ -134,6 +130,5 @@ def get(self, guid: str) -> Integration: Integration """ path = f"v1/oauth/integrations/{guid}" - url = self.params.url + path - response = self.params.session.get(url) - return Integration(self.params, **response.json()) + response = self._ctx.client.get(path) + return Integration(self._ctx, **response.json()) diff --git a/src/posit/connect/oauth/oauth.py b/src/posit/connect/oauth/oauth.py index 6d53eeb6..90df7270 100644 --- a/src/posit/connect/oauth/oauth.py +++ b/src/posit/connect/oauth/oauth.py @@ -1,14 +1,17 @@ from __future__ import annotations import os -from typing import Optional +from typing import TYPE_CHECKING, Optional from typing_extensions import TypedDict -from ..resources import ResourceParameters, Resources +from ..resources import Resources from .integrations import Integrations from .sessions import Sessions +if TYPE_CHECKING: + from ..context import Context + GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" USER_SESSION_TOKEN_TYPE = "urn:posit:connect:user-session-token" CONTENT_SESSION_TOKEN_TYPE = "urn:posit:connect:content-session-token" @@ -36,20 +39,18 @@ def _get_content_session_token() -> str: class OAuth(Resources): - def __init__(self, params: ResourceParameters, api_key: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, api_key: str) -> None: + super().__init__(ctx) self.api_key = api_key - - def _get_credentials_url(self) -> str: - return self.params.url + "v1/oauth/integrations/credentials" + self._path = "v1/oauth/integrations/credentials" @property def integrations(self): - return Integrations(self.params) + return Integrations(self._ctx) @property def sessions(self): - return Sessions(self.params) + return Sessions(self._ctx) def get_credentials(self, user_session_token: Optional[str] = None) -> Credentials: """Perform an oauth credential exchange with a user-session-token.""" @@ -60,7 +61,7 @@ def get_credentials(self, user_session_token: Optional[str] = None) -> Credentia if user_session_token: data["subject_token"] = user_session_token - response = self.params.session.post(self._get_credentials_url(), data=data) + response = self._ctx.client.post(self._path, data=data) return Credentials(**response.json()) def get_content_credentials(self, content_session_token: Optional[str] = None) -> Credentials: @@ -71,7 +72,7 @@ def get_content_credentials(self, content_session_token: Optional[str] = None) - data["subject_token_type"] = CONTENT_SESSION_TOKEN_TYPE data["subject_token"] = content_session_token or _get_content_session_token() - response = self.params.session.post(self._get_credentials_url(), data=data) + response = self._ctx.client.post(self._path, data=data) return Credentials(**response.json()) diff --git a/src/posit/connect/oauth/sessions.py b/src/posit/connect/oauth/sessions.py index da216043..503ea254 100644 --- a/src/posit/connect/oauth/sessions.py +++ b/src/posit/connect/oauth/sessions.py @@ -10,8 +10,7 @@ class Session(Resource): def delete(self) -> None: path = f"v1/oauth/sessions/{self['guid']}" - url = self.params.url + path - self.params.session.delete(url) + self._ctx.client.delete(path) class Sessions(Resources): @@ -26,10 +25,10 @@ def find( def find(self, **kwargs) -> List[Session]: ... def find(self, **kwargs) -> List[Session]: - url = self.params.url + "v1/oauth/sessions" - response = self.params.session.get(url, params=kwargs) + path = "v1/oauth/sessions" + response = self._ctx.client.get(path, params=kwargs) results = response.json() - return [Session(self.params, **result) for result in results] + return [Session(self._ctx, **result) for result in results] def get(self, guid: str) -> Session: """Get an OAuth session. @@ -43,6 +42,5 @@ def get(self, guid: str) -> Session: Session """ path = f"v1/oauth/sessions/{guid}" - url = self.params.url + path - response = self.params.session.get(url) - return Session(self.params, **response.json()) + response = self._ctx.client.get(path) + return Session(self._ctx, **response.json()) diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index 0db29487..e8afd9c1 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -6,9 +6,10 @@ from requests.sessions import Session as Session -from .resources import Resource, ResourceParameters, Resources +from .resources import Resource, Resources if TYPE_CHECKING: + from .context import Context from .groups import Group from .users import User @@ -17,8 +18,7 @@ class Permission(Resource): def destroy(self) -> None: """Destroy the permission.""" path = f"v1/content/{self['content_guid']}/permissions/{self['id']}" - url = self.params.url + path - self.params.session.delete(url) + self._ctx.client.delete(path) @overload def update(self, *args, role: str, **kwargs) -> None: @@ -44,17 +44,13 @@ def update(self, *args, **kwargs) -> None: body.update(dict(*args)) body.update(**kwargs) path = f"v1/content/{self['content_guid']}/permissions/{self['id']}" - url = self.params.url + path - response = self.params.session.put( - url, - json=body, - ) + response = self._ctx.client.put(path, json=body) super().update(**response.json()) class Permissions(Resources): - def __init__(self, params: ResourceParameters, content_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, content_guid: str) -> None: + super().__init__(ctx) self.content_guid = content_guid def count(self) -> int: @@ -163,9 +159,8 @@ def create( kwargs["principal_type"] = principal_type path = f"v1/content/{self.content_guid}/permissions" - url = self.params.url + path - response = self.params.session.post(url, json=kwargs) - return Permission(params=self.params, **response.json()) + response = self._ctx.client.post(path, json=kwargs) + return Permission(self._ctx, **response.json()) def find(self, **kwargs) -> List[Permission]: """Find permissions. @@ -175,8 +170,7 @@ def find(self, **kwargs) -> List[Permission]: List[Permission] """ path = f"v1/content/{self.content_guid}/permissions" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) kwargs_items = kwargs.items() results = response.json() if len(kwargs_items) > 0: @@ -185,7 +179,7 @@ def find(self, **kwargs) -> List[Permission]: for result in results if isinstance(result, dict) and (result.items() >= kwargs_items) ] - return [Permission(self.params, **result) for result in results] + return [Permission(self._ctx, **result) for result in results] def find_one(self, **kwargs) -> Permission | None: """Find a permission. @@ -210,9 +204,8 @@ def get(self, uid: str) -> Permission: Permission """ path = f"v1/content/{self.content_guid}/permissions/{uid}" - url = self.params.url + path - response = self.params.session.get(url) - return Permission(self.params, **response.json()) + response = self._ctx.client.get(path) + return Permission(self._ctx, **response.json()) def destroy(self, permission: str | Group | User | Permission, /) -> None: """Remove supplied content item permission. diff --git a/src/posit/connect/resources.py b/src/posit/connect/resources.py index 78bf87f5..66e6058f 100644 --- a/src/posit/connect/resources.py +++ b/src/posit/connect/resources.py @@ -3,7 +3,6 @@ import posixpath import warnings from abc import ABC -from dataclasses import dataclass from typing import ( TYPE_CHECKING, Any, @@ -11,38 +10,17 @@ Sequence, ) -from posit.connect.paginator import Paginator - from .context import Context +from .paginator import Paginator if TYPE_CHECKING: - import requests - from .context import Context - from .urls import Url - - -@dataclass(frozen=True) -class ResourceParameters: - """Shared parameter object for resources. - - Attributes - ---------- - session: requests.Session - A `requests.Session` object. Provides cookie persistence, connection-pooling, and - configuration. - url: str - The Connect API base URL (e.g., https://connect.example.com/__api__) - """ - - session: requests.Session - url: Url class Resource(dict): - def __init__(self, /, params: ResourceParameters, **kwargs): - self.params = params + def __init__(self, ctx: Context, /, **kwargs): super().__init__(**kwargs) + self._ctx = ctx def __getattr__(self, name): if name in self: @@ -60,8 +38,8 @@ def update(self, *args, **kwargs): class Resources: - def __init__(self, params: ResourceParameters) -> None: - self.params = params + def __init__(self, ctx: Context) -> None: + self._ctx = ctx class Active(ABC, Resource): @@ -79,8 +57,7 @@ def __init__(self, ctx: Context, path: str, /, **attributes): **attributes : dict Resource attributes passed """ - params = ResourceParameters(ctx.session, ctx.url) - super().__init__(params, **attributes) + super().__init__(ctx, **attributes) self._ctx = ctx self._path = path diff --git a/src/posit/connect/tasks.py b/src/posit/connect/tasks.py index 0319b05a..907a458f 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -91,8 +91,7 @@ def update(self, *args, **kwargs) -> None: """ params = dict(*args, **kwargs) path = f"v1/tasks/{self['id']}" - url = self.params.url + path - response = self.params.session.get(url, params=kwargs) + response = self._ctx.client.get(path, params=params) result = response.json() super().update(**result) @@ -154,7 +153,6 @@ def get(self, uid: str, **kwargs) -> Task: Task """ path = f"v1/tasks/{uid}" - url = self.params.url + path - response = self.params.session.get(url, params=kwargs) + response = self._ctx.client.get(path, params=kwargs) result = response.json() - return Task(self.params, **result) + return Task(self._ctx, **result) diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py index f9c0ea7e..f002cf88 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -17,10 +17,6 @@ class User(Resource): - def __init__(self, ctx: Context, /, **attributes) -> None: - super().__init__(ctx.client.resource_params, **attributes) - self._ctx: Context = ctx - @property def content(self) -> Content: return Content(self._ctx, owner_guid=self["guid"]) @@ -161,7 +157,7 @@ def groups(self) -> UserGroups: class UserGroups(Resources): def __init__(self, ctx: Context, user_guid: str) -> None: - super().__init__(ctx.client.resource_params) + super().__init__(ctx) self._ctx: Context = ctx self._user_guid: str = user_guid @@ -306,10 +302,6 @@ def find(self) -> List[Group]: class Users(Resources): """Users resource.""" - def __init__(self, ctx: Context) -> None: - super().__init__(ctx.client.resource_params) - self._ctx: Context = ctx - class CreateUser(TypedDict): """Create user request.""" diff --git a/src/posit/connect/vanities.py b/src/posit/connect/vanities.py index 5f9a1679..75becc6f 100644 --- a/src/posit/connect/vanities.py +++ b/src/posit/connect/vanities.py @@ -2,8 +2,9 @@ from typing_extensions import NotRequired, Required, TypedDict, Unpack +from .context import Context from .errors import ClientError -from .resources import Resource, ResourceParameters, Resources +from .resources import Resource, Resources class Vanity(Resource): @@ -54,7 +55,7 @@ class VanityAttributes(TypedDict): def __init__( self, /, - params: ResourceParameters, + ctx: Context, *, after_destroy: Optional[AfterDestroyCallback] = None, **kwargs: Unpack[VanityAttributes], @@ -63,11 +64,11 @@ def __init__( Parameters ---------- - params : ResourceParameters + ctx : Context after_destroy : AfterDestroyCallback, optional Called after the Vanity is successfully destroyed, by default None """ - super().__init__(params, **kwargs) + super().__init__(ctx, **kwargs) self._after_destroy = after_destroy self._content_guid = kwargs["content_guid"] @@ -87,8 +88,8 @@ def destroy(self) -> None: ---- This action requires administrator privileges. """ - endpoint = self.params.url + f"v1/content/{self._content_guid}/vanity" - self.params.session.delete(endpoint) + path = f"v1/content/{self._content_guid}/vanity" + self._ctx.client.delete(path) if self._after_destroy: self._after_destroy() @@ -108,10 +109,10 @@ def all(self) -> List[Vanity]: ----- This action requires administrator privileges. """ - endpoint = self.params.url + "v1/vanities" - response = self.params.session.get(endpoint) + path = "v1/vanities" + response = self._ctx.client.get(path) results = response.json() - return [Vanity(self.params, **result) for result in results] + return [Vanity(self._ctx, **result) for result in results] class VanityMixin(Resource): @@ -122,8 +123,8 @@ class HasGuid(TypedDict): guid: Required[str] - def __init__(self, params: ResourceParameters, **kwargs: Unpack[HasGuid]): - super().__init__(params, **kwargs) + def __init__(self, ctx: Context, **kwargs: Unpack[HasGuid]): + super().__init__(ctx, **kwargs) self._content_guid = kwargs["guid"] self._vanity: Optional[Vanity] = None @@ -215,10 +216,10 @@ def create_vanity(self, **kwargs: Unpack[CreateVanityRequest]) -> Vanity: -------- If setting force=True, the destroy operation performed on the other vanity is irreversible. """ - endpoint = self.params.url + f"v1/content/{self._content_guid}/vanity" - response = self.params.session.put(endpoint, json=kwargs) + path = f"v1/content/{self._content_guid}/vanity" + response = self._ctx.client.put(path, json=kwargs) result = response.json() - return Vanity(self.params, **result) + return Vanity(self._ctx, **result) def find_vanity(self) -> Vanity: """Find the vanity. @@ -227,7 +228,7 @@ def find_vanity(self) -> Vanity: ------- Vanity """ - endpoint = self.params.url + f"v1/content/{self._content_guid}/vanity" - response = self.params.session.get(endpoint) + path = f"v1/content/{self._content_guid}/vanity" + response = self._ctx.client.get(path) result = response.json() - return Vanity(self.params, **result) + return Vanity(self._ctx, **result) diff --git a/src/posit/connect/variants.py b/src/posit/connect/variants.py index eb6a28c0..ba8597c1 100644 --- a/src/posit/connect/variants.py +++ b/src/posit/connect/variants.py @@ -1,25 +1,24 @@ from typing import List -from .resources import Resource, ResourceParameters, Resources +from .context import Context +from .resources import Resource, Resources from .tasks import Task class Variant(Resource): def render(self) -> Task: path = f"variants/{self['id']}/render" - url = self.params.url + path - response = self.params.session.post(url) - return Task(self.params, **response.json()) + response = self._ctx.client.post(path) + return Task(self._ctx, **response.json()) class Variants(Resources): - def __init__(self, params: ResourceParameters, content_guid: str) -> None: - super().__init__(params) + def __init__(self, ctx: Context, content_guid: str) -> None: + super().__init__(ctx) self.content_guid = content_guid def find(self) -> List[Variant]: path = f"applications/{self.content_guid}/variants" - url = self.params.url + path - response = self.params.session.get(url) + response = self._ctx.client.get(path) results = response.json() or [] - return [Variant(self.params, **result) for result in results] + return [Variant(self._ctx, **result) for result in results] diff --git a/tests/posit/connect/metrics/test_shiny_usage.py b/tests/posit/connect/metrics/test_shiny_usage.py index 01988a21..2e8a49e8 100644 --- a/tests/posit/connect/metrics/test_shiny_usage.py +++ b/tests/posit/connect/metrics/test_shiny_usage.py @@ -1,12 +1,10 @@ from unittest import mock -import requests import responses from responses import matchers +from posit import connect from posit.connect.metrics import shiny_usage -from posit.connect.resources import ResourceParameters -from posit.connect.urls import Url from ..api import load_mock, load_mock_dict @@ -68,10 +66,10 @@ def test(self): ] # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") # invoke - events = shiny_usage.ShinyUsage(params).find() + events = shiny_usage.ShinyUsage(c._ctx).find() # assert assert mock_get[0].call_count == 1 @@ -110,10 +108,10 @@ def test(self): ] # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") # invoke - event = shiny_usage.ShinyUsage(params).find_one() + event = shiny_usage.ShinyUsage(c._ctx).find_one() # assert assert mock_get[0].call_count == 1 diff --git a/tests/posit/connect/metrics/test_visits.py b/tests/posit/connect/metrics/test_visits.py index a8c12449..12fb954f 100644 --- a/tests/posit/connect/metrics/test_visits.py +++ b/tests/posit/connect/metrics/test_visits.py @@ -1,12 +1,10 @@ from unittest import mock -import requests import responses from responses import matchers +from posit import connect from posit.connect.metrics import visits -from posit.connect.resources import ResourceParameters -from posit.connect.urls import Url from ..api import load_mock, load_mock_dict @@ -81,10 +79,10 @@ def test(self): ] # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") # invoke - events = visits.Visits(params).find() + events = visits.Visits(c._ctx).find() # assert assert mock_get[0].call_count == 1 @@ -125,10 +123,10 @@ def test(self): ] # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") # invoke - event = visits.Visits(params).find_one() + event = visits.Visits(c._ctx).find_one() # assert assert mock_get[0].call_count == 1 diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index f18b46f5..27f5318b 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -4,8 +4,6 @@ from posit.connect.client import Client from posit.connect.content import ContentItem, ContentItemRepository -from posit.connect.context import Context -from posit.connect.resources import ResourceParameters from .api import load_mock, load_mock_dict @@ -550,9 +548,8 @@ def test_app_mode_is_other(self): class TestContentRepository: - @property - def base_url(self): - return "http://connect.example" + base_url = "http://connect.example" + client = Client(base_url, "12345") @property def content_guid(self): @@ -566,17 +563,9 @@ def content_item(self): def endpoint(self): return f"{self.base_url}/__api__/v1/content/{self.content_guid}/repository" - @property - def client(self): - return Client(self.base_url, "12345") - @property def ctx(self): - return Context(self.client) - - @property - def params(self): - return ResourceParameters(self.ctx.session, self.ctx.url) + return self.client._ctx def mock_repository_info(self): content_item = self.content_item diff --git a/tests/posit/connect/test_context.py b/tests/posit/connect/test_context.py index c764effe..3e5d25e5 100644 --- a/tests/posit/connect/test_context.py +++ b/tests/posit/connect/test_context.py @@ -64,7 +64,8 @@ def test_unknown(self): json={}, ) - ctx = Context(Client("http://connect.example", "12345")) + c = Client("http://connect.example", "12345") + ctx = c._ctx assert ctx.version is None @@ -75,7 +76,8 @@ def test_known(self): json={"version": "2024.09.24"}, ) - ctx = Context(Client("http://connect.example", "12345")) + c = Client("http://connect.example", "12345") + ctx = c._ctx assert ctx.version == "2024.09.24" diff --git a/tests/posit/connect/test_permissions.py b/tests/posit/connect/test_permissions.py index 9260e575..e1b16461 100644 --- a/tests/posit/connect/test_permissions.py +++ b/tests/posit/connect/test_permissions.py @@ -2,16 +2,14 @@ import uuid import pytest -import requests import responses from responses import matchers +from posit import connect from posit.connect.client import Client from posit.connect.context import Context from posit.connect.groups import Group from posit.connect.permissions import Permission, Permissions -from posit.connect.resources import ResourceParameters -from posit.connect.urls import Url from posit.connect.users import User from .api import load_mock, load_mock_dict, load_mock_list @@ -30,9 +28,9 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") fake_permission = load_mock_dict(f"v1/content/{content_guid}/permissions/{uid}.json") - permission = Permission(params, **fake_permission) + permission = Permission(c._ctx, **fake_permission) # invoke permission.destroy() @@ -74,9 +72,9 @@ def test_request_shape(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) + c = connect.Client("https://connect.example", "12345") permission = Permission( - params, + c._ctx, id=uid, content_guid=content_guid, principal_guid=principal_guid, @@ -117,8 +115,8 @@ def test_role_update(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permission = Permission(params, id=uid, content_guid=content_guid, role=old_role) + c = connect.Client("https://connect.example", "12345") + permission = Permission(c._ctx, id=uid, content_guid=content_guid, role=old_role) # assert role change with respect to api response assert permission["role"] == old_role @@ -140,8 +138,8 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permissions = Permissions(params, content_guid=content_guid) + c = connect.Client("https://connect.example", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) # invoke count = permissions.count() @@ -182,8 +180,8 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permissions = Permissions(params, content_guid=content_guid) + c = connect.Client("https://connect.example", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) # invoke permission = permissions.create( @@ -199,10 +197,10 @@ def test_assertions(self): # setup principal_guid = "principal_guid" content_guid = "content_guid" - client = Client("https://connect.example/__api__", "12345") - permissions = Permissions(client.resource_params, content_guid=content_guid) - user = User(client._ctx, guid=principal_guid) - group = User(client._ctx, guid=principal_guid) + c = Client("https://connect.example/__api__", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) + user = User(c._ctx, guid=principal_guid) + group = User(c._ctx, guid=principal_guid) # behavior with pytest.raises(TypeError, match="str"): @@ -249,10 +247,10 @@ def test_user_group(self): ) # setup - client = Client("https://connect.example/__api__", "12345") - permissions = Permissions(client.resource_params, content_guid=content_guid) - user = User(client._ctx, guid=user_guid) - group = Group(client._ctx, guid=group_guid) + c = Client("https://connect.example/__api__", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) + user = User(c._ctx, guid=user_guid) + group = Group(c._ctx, guid=group_guid) # invoke user_perm = permissions.create(user, role="viewer") @@ -269,13 +267,13 @@ def test_user_group(self): assert created_permissions == [ Permission( - client.resource_params, + c._ctx, principal_guid=user_guid, principal_type="user", role="viewer", ), Permission( - client.resource_params, + c._ctx, principal_guid=group_guid, principal_type="group", role="viewer", @@ -297,8 +295,8 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permissions = Permissions(params, content_guid=content_guid) + c = connect.Client("https://connect.example", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) # invoke permissions = permissions.find() @@ -321,8 +319,8 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permissions = Permissions(params, content_guid=content_guid) + c = connect.Client("https://connect.example", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) # invoke permission = permissions.find_one() @@ -346,8 +344,8 @@ def test(self): ) # setup - params = ResourceParameters(requests.Session(), Url("https://connect.example/__api__")) - permissions = Permissions(params, content_guid=content_guid) + c = connect.Client("https://connect.example", "12345") + permissions = Permissions(c._ctx, content_guid=content_guid) # invoke permission = permissions.get(uid) @@ -404,14 +402,12 @@ def test_destroy(self): # setup c = Client(api_key="12345", url="https://connect.example/") ctx = Context(c) - permissions = Permissions(ctx.client.resource_params, content_guid=content_guid) + permissions = Permissions(ctx, content_guid=content_guid) # (Doesn't match any permissions, but that's okay) user_to_remove = User(ctx, **fake_user) group_to_remove = Group(ctx, **fake_group) - permission_to_remove = Permission( - ctx.client.resource_params, **fake_manual_user_permission - ) + permission_to_remove = Permission(ctx, **fake_manual_user_permission) # invoke permissions.destroy(permission_to_remove["id"]) diff --git a/tests/posit/connect/test_resources.py b/tests/posit/connect/test_resources.py index 6ac6d204..28cf6e0e 100644 --- a/tests/posit/connect/test_resources.py +++ b/tests/posit/connect/test_resources.py @@ -22,7 +22,7 @@ def test_init(self): v = "bar" d = {k: v} r = FakeResource(p, **d) - assert r.params == p + assert r._ctx == p def test__getitem__(self): warnings.filterwarnings("ignore", category=FutureWarning) diff --git a/tests/posit/connect/test_vanities.py b/tests/posit/connect/test_vanities.py index cfa3dd4a..3811a372 100644 --- a/tests/posit/connect/test_vanities.py +++ b/tests/posit/connect/test_vanities.py @@ -1,11 +1,9 @@ from unittest.mock import Mock -import requests import responses from responses.matchers import json_params_matcher -from posit.connect.resources import ResourceParameters -from posit.connect.urls import Url +from posit import connect from posit.connect.vanities import Vanities, Vanity, VanityMixin @@ -13,14 +11,12 @@ class TestVanityDestroy: @responses.activate def test_destroy_sends_delete_request(self): content_guid = "8ce6eaca-60af-4c2f-93a0-f5f3cddf5ee5" - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/content/{content_guid}/vanity" mock_delete = responses.delete(endpoint) - session = requests.Session() - url = Url(base_url) - params = ResourceParameters(session, url) - vanity = Vanity(params, content_guid=content_guid, path=Mock(), created_time=Mock()) + c = connect.Client("https://connect.example", "12345") + vanity = Vanity(c._ctx, content_guid=content_guid, path=Mock(), created_time=Mock()) vanity.destroy() @@ -29,16 +25,14 @@ def test_destroy_sends_delete_request(self): @responses.activate def test_destroy_calls_after_destroy_callback(self): content_guid = "8ce6eaca-60af-4c2f-93a0-f5f3cddf5ee5" - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/content/{content_guid}/vanity" responses.delete(endpoint) - session = requests.Session() - url = Url(base_url) + c = connect.Client("https://connect.example", "12345") after_destroy = Mock() - params = ResourceParameters(session, url) vanity = Vanity( - params, + c._ctx, after_destroy=after_destroy, content_guid=content_guid, path=Mock(), @@ -53,14 +47,12 @@ def test_destroy_calls_after_destroy_callback(self): class TestVanitiesAll: @responses.activate def test_all_sends_get_request(self): - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/vanities" mock_get = responses.get(endpoint, json=[]) - session = requests.Session() - url = Url(base_url) - params = ResourceParameters(session, url) - vanities = Vanities(params) + c = connect.Client("https://connect.example", "12345") + vanities = Vanities(c._ctx) vanities.all() @@ -71,14 +63,12 @@ class TestVanityMixin: @responses.activate def test_vanity_getter_returns_vanity(self): guid = "8ce6eaca-60af-4c2f-93a0-f5f3cddf5ee5" - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/content/{guid}/vanity" mock_get = responses.get(endpoint, json={"content_guid": guid, "path": "my-dashboard"}) - session = requests.Session() - url = Url(base_url) - params = ResourceParameters(session, url) - content = VanityMixin(params, guid=guid) + c = connect.Client("https://connect.example", "12345") + content = VanityMixin(c._ctx, guid=guid) assert content.vanity == "my-dashboard" assert mock_get.call_count == 1 @@ -86,7 +76,7 @@ def test_vanity_getter_returns_vanity(self): @responses.activate def test_vanity_setter_with_string(self): guid = "8ce6eaca-60af-4c2f-93a0-f5f3cddf5ee5" - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/content/{guid}/vanity" path = "example" mock_put = responses.put( @@ -95,10 +85,8 @@ def test_vanity_setter_with_string(self): match=[json_params_matcher({"path": path})], ) - session = requests.Session() - url = Url(base_url) - params = ResourceParameters(session, url) - content = VanityMixin(params, guid=guid) + c = connect.Client("https://connect.example", "12345") + content = VanityMixin(c._ctx, guid=guid) content.vanity = path assert content.vanity == path @@ -107,15 +95,13 @@ def test_vanity_setter_with_string(self): @responses.activate def test_vanity_deleter(self): guid = "8ce6eaca-60af-4c2f-93a0-f5f3cddf5ee5" - base_url = "http://connect.example/__api__" + base_url = "https://connect.example/__api__" endpoint = f"{base_url}/v1/content/{guid}/vanity" mock_delete = responses.delete(endpoint) - session = requests.Session() - url = Url(base_url) - params = ResourceParameters(session, url) - content = VanityMixin(params, guid=guid) - content._vanity = Vanity(params, path=Mock(), content_guid=guid, created_time=Mock()) + c = connect.Client("https://connect.example", "12345") + content = VanityMixin(c._ctx, guid=guid) + content._vanity = Vanity(c._ctx, path=Mock(), content_guid=guid, created_time=Mock()) del content.vanity assert content._vanity is None