Skip to content

Commit 437c515

Browse files
committed
adds docstrings
1 parent 1066ca3 commit 437c515

File tree

3 files changed

+186
-24
lines changed

3 files changed

+186
-24
lines changed

integration/tests/posit/connect/test_jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from . import CONNECT_VERSION
99

1010

11-
class TestContent:
11+
class TestJobs:
1212
@classmethod
1313
def setup_class(cls):
1414
cls.client = connect.Client()

src/posit/connect/resources.py

Lines changed: 183 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import posixpath
21
import warnings
32
from abc import ABC, abstractmethod
43
from dataclasses import dataclass
54
from typing import Any, Generic, List, Optional, Sequence, Type, TypeVar, overload
65

76
import requests
87

9-
from posit.connect.context import Context
10-
8+
from .context import Context
119
from .urls import Url
1210

1311

@@ -50,19 +48,106 @@ def __init__(self, params: ResourceParameters) -> None:
5048
self.params = params
5149

5250

53-
T = TypeVar("T", bound="Active", covariant=True)
51+
class Active(Resource):
52+
"""
53+
A base class representing an active resource.
5454
55+
Extends the `Resource` class and provides additional functionality for via the session context and an optional parent resource.
56+
57+
Parameters
58+
----------
59+
ctx : Context
60+
The context object containing the session and URL for API interactions.
61+
parent : Optional[Active], optional
62+
An optional parent resource that establishes a hierarchical relationship, by default None.
63+
**kwargs : dict
64+
Additional keyword arguments passed to the parent `Resource` class.
65+
66+
Attributes
67+
----------
68+
_ctx : Context
69+
The session context.
70+
_parent : Optional[Active]
71+
The parent resource, if provided, which establishes a hierarchical relationship.
72+
"""
5573

56-
class Active(Resource):
5774
def __init__(self, ctx: Context, parent: Optional["Active"] = None, **kwargs):
75+
"""
76+
Initialize the `Active` resource.
77+
78+
Parameters
79+
----------
80+
ctx : Context
81+
The context object containing session and URL for API interactions.
82+
parent : Optional[Active], optional
83+
An optional parent resource to establish a hierarchical relationship, by default None.
84+
**kwargs : dict
85+
Additional keyword arguments passed to the parent `Resource` class.
86+
"""
5887
params = ResourceParameters(ctx.session, ctx.url)
5988
super().__init__(params, **kwargs)
6089
self._ctx = ctx
6190
self._parent = parent
6291

6392

64-
class ActiveReader(ABC, Generic[T], Sequence[T]):
65-
def __init__(self, cls: Type[T], ctx: Context, parent: Optional[Active] = None):
93+
T_co = TypeVar("T_co", bound="Active", covariant=True)
94+
"""A covariant type variable that is bound to the `Active` class, meaning that `T_co` must be or derive from `Active`."""
95+
96+
97+
class ActiveSequence(ABC, Generic[T_co], Sequence[T_co]):
98+
"""
99+
A sequence abstraction for any HTTP GET endpoint that returns a collection.
100+
101+
It lazily fetches data on demand, caches the results, and allows for standard sequence operations like indexing and slicing.
102+
103+
Parameters
104+
----------
105+
cls : Type[T_co]
106+
The class used to represent each item in the sequence.
107+
ctx : Context
108+
The context object that holds the HTTP session used for sending the GET request.
109+
parent : Optional[Active], optional
110+
An optional parent resource to establish a nested relationship, by default None.
111+
112+
Attributes
113+
----------
114+
_cls : Type[T_co]
115+
The class used to instantiate each item in the sequence.
116+
_ctx : Context
117+
The context containing the HTTP session used to interact with the API.
118+
_parent : Optional[Active]
119+
Optional parent resource for maintaining hierarchical relationships.
120+
_cache : Optional[List[T_co]]
121+
Cached list of items returned from the API. Set to None initially, and populated after the first request.
122+
123+
Abstract Properties
124+
-------------------
125+
_endpoint : str
126+
The API endpoint URL for the HTTP GET request. Subclasses are required to implement this property.
127+
128+
Methods
129+
-------
130+
_data() -> List[T_co]
131+
Fetch and cache the data from the API. This method sends a GET request to `_endpoint`, parses the
132+
response as JSON, and instantiates each item using `cls`.
133+
134+
__getitem__(index) -> Union[T_co, Sequence[T_co]]
135+
Retrieve an item or slice from the sequence. Indexing follows the standard Python sequence semantics.
136+
137+
__len__() -> int
138+
Return the number of items in the sequence.
139+
140+
__str__() -> str
141+
Return a string representation of the cached data.
142+
143+
__repr__() -> str
144+
Return a detailed string representation of the cached data.
145+
146+
reload() -> ActiveSequence
147+
Clear the cache and mark to reload the data from the API on the next operation.
148+
"""
149+
150+
def __init__(self, cls: Type[T_co], ctx: Context, parent: Optional[Active] = None):
66151
super().__init__()
67152
self._cls = cls
68153
self._ctx = ctx
@@ -72,10 +157,33 @@ def __init__(self, cls: Type[T], ctx: Context, parent: Optional[Active] = None):
72157
@property
73158
@abstractmethod
74159
def _endpoint(self) -> str:
160+
"""
161+
Abstract property to define the endpoint URL for the GET request.
162+
163+
Subclasses must implement this property to return the API endpoint URL that will
164+
be queried to fetch the data.
165+
166+
Returns
167+
-------
168+
str
169+
The API endpoint URL.
170+
"""
75171
raise NotImplementedError()
76172

77173
@property
78-
def _data(self) -> List[T]:
174+
def _data(self) -> List[T_co]:
175+
"""
176+
Fetch and cache the data from the API.
177+
178+
This method sends a GET request to the `_endpoint` and parses the response as a list of JSON objects.
179+
Each JSON object is used to instantiate an item of type `T_co` using the class specified by `_cls`.
180+
The results are cached after the first request and reused for subsequent access unless reloaded.
181+
182+
Returns
183+
-------
184+
List[T_co]
185+
A list of items of type `T_co` representing the fetched data.
186+
"""
79187
if self._cache:
80188
return self._cache
81189

@@ -85,39 +193,85 @@ def _data(self) -> List[T]:
85193
return self._cache
86194

87195
@overload
88-
def __getitem__(self, index: int) -> T: ...
196+
def __getitem__(self, index: int) -> T_co: ...
89197

90198
@overload
91-
def __getitem__(self, index: slice) -> Sequence[T]: ...
199+
def __getitem__(self, index: slice) -> Sequence[T_co]: ...
92200

93201
def __getitem__(self, index):
94-
"""Retrieve an item or slice from the sequence."""
95202
return self._data[index]
96203

97-
def __len__(self):
98-
"""Return the length of the sequence."""
204+
def __len__(self) -> int:
99205
return len(self._data)
100206

101-
def __str__(self):
207+
def __str__(self) -> str:
102208
return str(self._data)
103209

104-
def __repr__(self):
210+
def __repr__(self) -> str:
105211
return repr(self._data)
106212

107-
def reload(self):
213+
def reload(self) -> "ActiveSequence":
214+
"""
215+
Clear the cache and reload the data from the API on the next access.
216+
217+
Returns
218+
-------
219+
ActiveSequence
220+
The current instance with cleared cache, ready to reload data on next access.
221+
"""
108222
self._cache = None
109223
return self
110224

111225

112-
class ActiveFinderMethods(ActiveReader[T], ABC, Generic[T]):
226+
class ActiveFinderMethods(ActiveSequence[T_co], ABC, Generic[T_co]):
227+
"""
228+
Finder methods.
229+
230+
Provides various finder methods for locating records in any endpoint supporting HTTP GET requests.
231+
232+
Attributes
233+
----------
234+
_uid : str
235+
The default field name used to uniquely identify records. Defaults to 'guid'.
236+
237+
Methods
238+
-------
239+
find(uid) -> T_co
240+
Finds and returns a record by its unique identifier (`uid`). If a cached result exists, it searches within the cache;
241+
otherwise, it makes a GET request to retrieve the data from the endpoint.
242+
243+
find_by(**conditions: Any) -> Optional[T_co]
244+
Finds the first record that matches the provided conditions. If no record is found, returns None.
245+
"""
246+
113247
_uid: str = "guid"
248+
"""The default field name used to uniquely identify records. Defaults to 'guid'."""
249+
250+
def find(self, uid) -> T_co:
251+
"""
252+
Find a record by its unique identifier.
253+
254+
Fetches a record either by searching the cache or by making a GET request to the endpoint.
114255
115-
def find(self, uid) -> T:
256+
Parameters
257+
----------
258+
uid : Any
259+
The unique identifier of the record.
260+
261+
Returns
262+
-------
263+
T_co
264+
265+
Raises
266+
------
267+
ValueError
268+
If no record is found.
269+
"""
116270
if self._cache:
117271
conditions = {self._uid: uid}
118272
result = self.find_by(**conditions)
119273
else:
120-
endpoint = posixpath.join(self._endpoint + uid)
274+
endpoint = self._endpoint + uid
121275
response = self._ctx.session.get(endpoint)
122276
result = response.json()
123277
result = self._cls(self._ctx, self._parent, **result)
@@ -127,13 +281,19 @@ def find(self, uid) -> T:
127281

128282
return result
129283

130-
def find_by(self, **conditions: Any) -> Optional[T]:
131-
"""Finds the first record matching the specified conditions.
284+
def find_by(self, **conditions: Any) -> Optional[T_co]:
285+
"""
286+
Find the first record matching the specified conditions.
287+
288+
There is no implied ordering, so if order matters, you should specify it yourself.
132289
133-
There is no implied ordering so if order matters, you should specify it yourself.
290+
Parameters
291+
----------
292+
**conditions : Any
134293
135294
Returns
136295
-------
137-
Optional[T]
296+
Optional[T_co]
297+
The first record matching the conditions, or `None` if no match is found.
138298
"""
139299
return next((v for v in self._data if v.items() >= conditions.items()), None)

tests/posit/connect/test_jobs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def test(self):
105105
assert job
106106
assert job["key"] == "tHawGvHZTosJA2Dx"
107107

108+
108109
class TestJobsReload:
109110
@responses.activate
110111
def test(self):
@@ -129,6 +130,7 @@ def test(self):
129130
assert len(content.jobs) == 1
130131
assert mock_get.call_count == 2
131132

133+
132134
class TestJobDestory:
133135
@responses.activate
134136
def test(self):

0 commit comments

Comments
 (0)