Skip to content

Commit

Permalink
remove cache, fix packages implementation, enable packages api in int…
Browse files Browse the repository at this point in the history
…egration tests
  • Loading branch information
tdstein committed Nov 8, 2024
1 parent 92f7e15 commit 3eadab8
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 78 deletions.
2 changes: 2 additions & 0 deletions integration/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ services:
- test
connect:
image: ${DOCKER_CONNECT_IMAGE}:${DOCKER_CONNECT_IMAGE_TAG}
pull_policy: always
environment:
- CONNECT_BOOTSTRAP_ENABLED=true
- CONNECT_BOOTSTRAP_SECRETKEY=${CONNECT_BOOTSTRAP_SECRETKEY}
- CONNECT_APPLICATIONS_PACKAGEAUDITINGENABLED=true
networks:
- test
privileged: true
Expand Down
7 changes: 4 additions & 3 deletions integration/tests/posit/connect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from packaging import version
from packaging.version import parse

from posit import connect

client = connect.Client()
CONNECT_VERSION = version.parse(client.version)
print(CONNECT_VERSION)
version = client.version
assert version
CONNECT_VERSION = parse(version)
2 changes: 1 addition & 1 deletion integration/tests/posit/connect/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ def test_find_by(self):
task = bundle.deploy()
task.wait_for()

jobs = content.jobs.reload()
jobs = content.jobs
assert len(jobs) != 0
8 changes: 4 additions & 4 deletions integration/tests/posit/connect/test_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ def teardown_class(cls):
cls.content.delete()

def test(self):
# assert self.client.packages
assert self.client.packages
assert self.content.packages

def test_find_by(self):
# package = self.client.packages.find_by(name="flask")
# assert package
# assert package["name"] == "flask"
package = self.client.packages.find_by(name="flask")
assert package
assert package["name"] == "flask"

package = self.content.packages.find_by(name="flask")
assert package
Expand Down
15 changes: 11 additions & 4 deletions src/posit/connect/packages.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import posixpath
from typing import List, Literal, Optional, TypedDict, overload
from typing import Generator, Literal, Optional, TypedDict, overload

from typing_extensions import NotRequired, Required, Unpack

Expand Down Expand Up @@ -109,6 +109,9 @@ class _Package(TypedDict):
language: Required[Literal["python", "r"]]
"""Programming language ecosystem, options are 'python' and 'r'"""

language_version: Required[str]
"""Programming language version"""

name: Required[str]
"""The package name"""

Expand Down Expand Up @@ -139,12 +142,13 @@ def __init__(self, ctx, path):
def _create_instance(self, path, /, **attributes):
return Package(self._ctx, **attributes)

def fetch(self, **conditions) -> List["Package"]:
def fetch(self, **conditions) -> Generator["Package"]:
# todo - add pagination support to ActiveSequence
url = self._ctx.url + self._path
paginator = Paginator(self._ctx.session, url, conditions)
results = paginator.fetch_results()
return [self._create_instance("", **result) for result in results]
for page in paginator.fetch_pages():
results = page.results
yield from (self._create_instance("", **result) for result in results)

def find(self, uid):
raise NotImplementedError("The 'find' method is not support by the Packages API.")
Expand All @@ -153,6 +157,9 @@ class _FindBy(TypedDict, total=False):
language: NotRequired[Literal["python", "r"]]
"""Programming language ecosystem, options are 'python' and 'r'"""

language_version: NotRequired[str]
"""Programming language version"""

name: NotRequired[str]
"""The package name"""

Expand Down
86 changes: 45 additions & 41 deletions src/posit/connect/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
import warnings
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Generic, List, Optional, Sequence, TypeVar, overload

from typing_extensions import Self
from itertools import islice
from typing import (
TYPE_CHECKING,
Any,
Generic,
Iterable,
List,
Optional,
Sequence,
TypeVar,
overload,
)

if TYPE_CHECKING:
import requests
Expand Down Expand Up @@ -101,14 +110,13 @@ def __init__(self, ctx: Context, path: str, uid: str = "guid"):
self._ctx = ctx
self._path = path
self._uid = uid
self._cache = None

@abstractmethod
def _create_instance(self, path: str, /, **kwargs: Any) -> T:
"""Create an instance of 'T'."""
raise NotImplementedError()

def fetch(self, **conditions) -> List[T]:
def fetch(self, **conditions) -> Iterable[T]:
"""Fetch the collection.
Fetches the collection directly from Connect. This operation does not effect the cache state.
Expand All @@ -122,61 +130,57 @@ def fetch(self, **conditions) -> List[T]:
results = response.json()
return [self._to_instance(result) for result in results]

def reload(self) -> Self:
"""Reloads the collection from Connect.
Returns
-------
Self
"""
self._cache = None
return self

def _to_instance(self, result: dict) -> T:
"""Converts a result into an instance of T."""
uid = result[self._uid]
path = posixpath.join(self._path, uid)
return self._create_instance(path, **result)

@property
def _data(self) -> List[T]:
"""Get the collection.
Fetches the collection from Connect and caches the result. Subsequent invocations return the cached results unless the cache is explicitly reset.
Returns
-------
List[T]
See Also
--------
cached
reload
"""
if self._cache is None:
self._cache = self.fetch()
return self._cache

@overload
def __getitem__(self, index: int) -> T: ...

@overload
def __getitem__(self, index: slice) -> Sequence[T]: ...

def __getitem__(self, index):
return self._data[index]
def __getitem__(self, index) -> Sequence[T] | T:
data = self.fetch()

if isinstance(index, int):
if index < 0:
# Handle negative indexing
data = list(data)
return data[index]
for i, value in enumerate(data):
if i == index:
return value
raise KeyError(f"Index {index} is out of range.")

if isinstance(index, slice):
# Handle slicing with islice
start = index.start or 0
stop = index.stop
step = index.step or 1
if step == 0:
raise ValueError("slice step cannot be zero")
return [
value
for i, value in enumerate(islice(data, start, stop))
if (i + start) % step == 0
]

raise TypeError(f"Index must be int or slice, not {type(index).__name__}.")

def __iter__(self):
return iter(self._data)
return iter(self.fetch())

def __len__(self) -> int:
return len(self._data)
return len(list(self.fetch()))

def __str__(self) -> str:
return str(self._data)
return str(list(self.fetch()))

def __repr__(self) -> str:
return repr(self._data)
return repr(list(self.fetch()))


class ActiveFinderMethods(ActiveSequence[T], ABC):
Expand Down Expand Up @@ -220,5 +224,5 @@ def find_by(self, **conditions) -> Optional[T]:
Optional[T]
The first record matching the conditions, or `None` if no match is found.
"""
collection = self.fetch()
collection = self.fetch(**conditions)
return next((v for v in collection if v.items() >= conditions.items()), None)
25 changes: 0 additions & 25 deletions tests/posit/connect/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,6 @@ def test(self):
assert job["key"] == "tHawGvHZTosJA2Dx"


class TestJobsReload:
@responses.activate
def test(self):
responses.get(
"https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066",
json=load_mock("v1/content/f2f37341-e21d-3d80-c698-a935ad614066.json"),
)

mock_get = responses.get(
"https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/jobs",
json=load_mock("v1/content/f2f37341-e21d-3d80-c698-a935ad614066/jobs.json"),
)

c = Client("https://connect.example", "12345")
content = c.content.get("f2f37341-e21d-3d80-c698-a935ad614066")

assert len(content.jobs) == 1
assert mock_get.call_count == 1

content.jobs.reload()

assert len(content.jobs) == 1
assert mock_get.call_count == 2


class TestJobDestory:
@responses.activate
def test(self):
Expand Down

0 comments on commit 3eadab8

Please sign in to comment.