Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 41 additions & 37 deletions src/posit/connect/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,35 +104,28 @@ def __init__(self, ctx: Context, path: str, pathinfo: str = "", uid: str = "guid
self._ctx = ctx
self._path = posixpath.join(path, pathinfo)
self._uid = uid
self._cache: Optional[List[T]] = None

def _get_or_fetch(self) -> List[T]:
"""
Fetch and cache the data from the API.
self._cache = None

This method sends a GET request to the `_endpoint` and parses the response as a list of JSON objects.
Each JSON object is used to instantiate an item of type `T` using the class specified by `_cls`.
The results are cached after the first request and reused for subsequent access unless reloaded.
def _make(self, result: dict) -> T:
uid = result[self._uid]
return self._create_instance(self._path, uid, **result)

Returns
-------
List[T]
A list of items of type `T` representing the fetched data.
"""
if self._cache is not None:
return self._cache
def cached(self) -> bool:
return self._cache is not None

endpoint = self._ctx.url + self._path
response = self._ctx.session.get(endpoint)
results = response.json()
@property
def _data(self) -> List[T]:
if self._cache is None:
self._cache = self.fetch()
return self._cache

self._cache = []
for result in results:
uid = result[self._uid]
instance = self._create_instance(self._path, uid, **result)
self._cache.append(instance)
@_data.setter
def _data(self, value: List[T]):
self._cache = value

return self._cache
@_data.deleter
def _data(self):
self._cache = None

@overload
def __getitem__(self, index: int) -> T: ...
Expand All @@ -141,20 +134,16 @@ def __getitem__(self, index: int) -> T: ...
def __getitem__(self, index: slice) -> Sequence[T]: ...

def __getitem__(self, index):
data = self._get_or_fetch()
return data[index]
return self._data[index]

def __len__(self) -> int:
data = self._get_or_fetch()
return len(data)
return len(self._data)

def __str__(self) -> str:
data = self._get_or_fetch()
return str(data)
return str(self._data)

def __repr__(self) -> str:
data = self._get_or_fetch()
return repr(data)
return repr(self._data)

@abstractmethod
def _create_instance(self, path: str, pathinfo: str, /, **kwargs: Any) -> T:
Expand All @@ -175,9 +164,25 @@ def reload(self) -> Self:
ActiveSequence
The current instance with cleared cache, ready to reload data on next access.
"""
self._cache = None
del self._data
return self

def fetch(self) -> List[T]:
"""
Fetch the collection.

Sends a GET request to fetch the collection from Connect.

Returns
-------
List[T]
A list of items of type `T` representing the fetched data.
"""
endpoint = self._ctx.url + self._path
response = self._ctx.session.get(endpoint)
results = response.json()
return [self._make(result) for result in results]


class ActiveFinderMethods(ActiveSequence[T], ABC):
"""Finder methods.
Expand All @@ -200,7 +205,7 @@ def find(self, uid) -> T:
-------
T
"""
if self._cache:
if self.cached():
# Check if the record already exists in the cache.
# It is assumed that local cache scan is faster than an additional HTTP request.
conditions = {self._uid: uid}
Expand All @@ -215,7 +220,7 @@ def find(self, uid) -> T:

# Invalidate the cache.
# It is assumed that the cache is stale since a record exists on the server and not in the cache.
self._cache = None
self.reload()

return result

Expand All @@ -234,5 +239,4 @@ def find_by(self, **conditions: Any) -> Optional[T]:
Optional[T]
The first record matching the conditions, or `None` if no match is found.
"""
data = self._get_or_fetch()
return next((v for v in data if v.items() >= conditions.items()), None)
return next((v for v in self._data if v.items() >= conditions.items()), None)