Skip to content

Commit

Permalink
refactor: replace ctx.params, ctx.session, and ctx.url with `ct…
Browse files Browse the repository at this point in the history
…x.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 <[email protected]>
  • Loading branch information
tdstein and schloerke authored Dec 16, 2024
1 parent 4f6096b commit e6809f3
Show file tree
Hide file tree
Showing 30 changed files with 238 additions and 339 deletions.
18 changes: 9 additions & 9 deletions src/posit/connect/_api_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand All @@ -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()


Expand All @@ -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()
Expand All @@ -60,13 +60,13 @@ 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(
self: ApiCallProtocol,
*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()
37 changes: 17 additions & 20 deletions src/posit/connect/bundles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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)
11 changes: 5 additions & 6 deletions src/posit/connect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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:
Expand Down
29 changes: 14 additions & 15 deletions src/posit/connect/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import posixpath
import time
from posixpath import dirname
from typing import (
TYPE_CHECKING,
Any,
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down
8 changes: 1 addition & 7 deletions src/posit/connect/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -31,17 +28,14 @@ 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)

@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")

Expand Down
17 changes: 9 additions & 8 deletions src/posit/connect/cursors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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())
Loading

0 comments on commit e6809f3

Please sign in to comment.