Skip to content

Commit 3eadab8

Browse files
committed
remove cache, fix packages implementation, enable packages api in integration tests
1 parent 92f7e15 commit 3eadab8

File tree

7 files changed

+67
-78
lines changed

7 files changed

+67
-78
lines changed

integration/compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ services:
2121
- test
2222
connect:
2323
image: ${DOCKER_CONNECT_IMAGE}:${DOCKER_CONNECT_IMAGE_TAG}
24+
pull_policy: always
2425
environment:
2526
- CONNECT_BOOTSTRAP_ENABLED=true
2627
- CONNECT_BOOTSTRAP_SECRETKEY=${CONNECT_BOOTSTRAP_SECRETKEY}
28+
- CONNECT_APPLICATIONS_PACKAGEAUDITINGENABLED=true
2729
networks:
2830
- test
2931
privileged: true
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from packaging import version
1+
from packaging.version import parse
22

33
from posit import connect
44

55
client = connect.Client()
6-
CONNECT_VERSION = version.parse(client.version)
7-
print(CONNECT_VERSION)
6+
version = client.version
7+
assert version
8+
CONNECT_VERSION = parse(version)

integration/tests/posit/connect/test_jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ def test_find_by(self):
4949
task = bundle.deploy()
5050
task.wait_for()
5151

52-
jobs = content.jobs.reload()
52+
jobs = content.jobs
5353
assert len(jobs) != 0

integration/tests/posit/connect/test_packages.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ def teardown_class(cls):
2828
cls.content.delete()
2929

3030
def test(self):
31-
# assert self.client.packages
31+
assert self.client.packages
3232
assert self.content.packages
3333

3434
def test_find_by(self):
35-
# package = self.client.packages.find_by(name="flask")
36-
# assert package
37-
# assert package["name"] == "flask"
35+
package = self.client.packages.find_by(name="flask")
36+
assert package
37+
assert package["name"] == "flask"
3838

3939
package = self.content.packages.find_by(name="flask")
4040
assert package

src/posit/connect/packages.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import posixpath
4-
from typing import List, Literal, Optional, TypedDict, overload
4+
from typing import Generator, Literal, Optional, TypedDict, overload
55

66
from typing_extensions import NotRequired, Required, Unpack
77

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

112+
language_version: Required[str]
113+
"""Programming language version"""
114+
112115
name: Required[str]
113116
"""The package name"""
114117

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

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

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

160+
language_version: NotRequired[str]
161+
"""Programming language version"""
162+
156163
name: NotRequired[str]
157164
"""The package name"""
158165

src/posit/connect/resources.py

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44
import warnings
55
from abc import ABC, abstractmethod
66
from dataclasses import dataclass
7-
from typing import TYPE_CHECKING, Any, Generic, List, Optional, Sequence, TypeVar, overload
8-
9-
from typing_extensions import Self
7+
from itertools import islice
8+
from typing import (
9+
TYPE_CHECKING,
10+
Any,
11+
Generic,
12+
Iterable,
13+
List,
14+
Optional,
15+
Sequence,
16+
TypeVar,
17+
overload,
18+
)
1019

1120
if TYPE_CHECKING:
1221
import requests
@@ -101,14 +110,13 @@ def __init__(self, ctx: Context, path: str, uid: str = "guid"):
101110
self._ctx = ctx
102111
self._path = path
103112
self._uid = uid
104-
self._cache = None
105113

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

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

125-
def reload(self) -> Self:
126-
"""Reloads the collection from Connect.
127-
128-
Returns
129-
-------
130-
Self
131-
"""
132-
self._cache = None
133-
return self
134-
135133
def _to_instance(self, result: dict) -> T:
136134
"""Converts a result into an instance of T."""
137135
uid = result[self._uid]
138136
path = posixpath.join(self._path, uid)
139137
return self._create_instance(path, **result)
140138

141-
@property
142-
def _data(self) -> List[T]:
143-
"""Get the collection.
144-
145-
Fetches the collection from Connect and caches the result. Subsequent invocations return the cached results unless the cache is explicitly reset.
146-
147-
Returns
148-
-------
149-
List[T]
150-
151-
See Also
152-
--------
153-
cached
154-
reload
155-
"""
156-
if self._cache is None:
157-
self._cache = self.fetch()
158-
return self._cache
159-
160139
@overload
161140
def __getitem__(self, index: int) -> T: ...
162141

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

166-
def __getitem__(self, index):
167-
return self._data[index]
145+
def __getitem__(self, index) -> Sequence[T] | T:
146+
data = self.fetch()
147+
148+
if isinstance(index, int):
149+
if index < 0:
150+
# Handle negative indexing
151+
data = list(data)
152+
return data[index]
153+
for i, value in enumerate(data):
154+
if i == index:
155+
return value
156+
raise KeyError(f"Index {index} is out of range.")
157+
158+
if isinstance(index, slice):
159+
# Handle slicing with islice
160+
start = index.start or 0
161+
stop = index.stop
162+
step = index.step or 1
163+
if step == 0:
164+
raise ValueError("slice step cannot be zero")
165+
return [
166+
value
167+
for i, value in enumerate(islice(data, start, stop))
168+
if (i + start) % step == 0
169+
]
170+
171+
raise TypeError(f"Index must be int or slice, not {type(index).__name__}.")
168172

169173
def __iter__(self):
170-
return iter(self._data)
174+
return iter(self.fetch())
171175

172176
def __len__(self) -> int:
173-
return len(self._data)
177+
return len(list(self.fetch()))
174178

175179
def __str__(self) -> str:
176-
return str(self._data)
180+
return str(list(self.fetch()))
177181

178182
def __repr__(self) -> str:
179-
return repr(self._data)
183+
return repr(list(self.fetch()))
180184

181185

182186
class ActiveFinderMethods(ActiveSequence[T], ABC):
@@ -220,5 +224,5 @@ def find_by(self, **conditions) -> Optional[T]:
220224
Optional[T]
221225
The first record matching the conditions, or `None` if no match is found.
222226
"""
223-
collection = self.fetch()
227+
collection = self.fetch(**conditions)
224228
return next((v for v in collection if v.items() >= conditions.items()), None)

tests/posit/connect/test_jobs.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,6 @@ def test(self):
8787
assert job["key"] == "tHawGvHZTosJA2Dx"
8888

8989

90-
class TestJobsReload:
91-
@responses.activate
92-
def test(self):
93-
responses.get(
94-
"https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066",
95-
json=load_mock("v1/content/f2f37341-e21d-3d80-c698-a935ad614066.json"),
96-
)
97-
98-
mock_get = responses.get(
99-
"https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/jobs",
100-
json=load_mock("v1/content/f2f37341-e21d-3d80-c698-a935ad614066/jobs.json"),
101-
)
102-
103-
c = Client("https://connect.example", "12345")
104-
content = c.content.get("f2f37341-e21d-3d80-c698-a935ad614066")
105-
106-
assert len(content.jobs) == 1
107-
assert mock_get.call_count == 1
108-
109-
content.jobs.reload()
110-
111-
assert len(content.jobs) == 1
112-
assert mock_get.call_count == 2
113-
114-
11590
class TestJobDestory:
11691
@responses.activate
11792
def test(self):

0 commit comments

Comments
 (0)