1- import posixpath
21import warnings
32from abc import ABC , abstractmethod
43from dataclasses import dataclass
54from typing import Any , Generic , List , Optional , Sequence , Type , TypeVar , overload
65
76import requests
87
9- from posit .connect .context import Context
10-
8+ from .context import Context
119from .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 )
0 commit comments