11import warnings
22from abc import ABC , abstractmethod
33from dataclasses import dataclass
4- from typing import Any , Generic , List , Optional , Sequence , Type , TypeVar , overload
4+ from typing import Any , Generic , List , Optional , Sequence , TypeVar , overload
55
66import requests
7+ from typing_extensions import Self
78
89from .context import Context
910from .urls import Url
@@ -48,39 +49,18 @@ def __init__(self, params: ResourceParameters) -> None:
4849 self .params = params
4950
5051
51- class Active (Resource ):
52- """
53- A base class representing an active resource.
54-
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- """
73-
52+ class Active (ABC , Resource ):
7453 def __init__ (self , ctx : Context , parent : Optional ["Active" ] = None , ** kwargs ):
75- """
76- Initialize the `Active` resource.
54+ """A base class representing an active resource.
55+
56+ Extends the `Resource` class and provides additional functionality for via the session context and an optional parent resource.
7757
7858 Parameters
7959 ----------
8060 ctx : Context
81- The context object containing session and URL for API interactions.
61+ The context object containing the session and URL for API interactions.
8262 parent : Optional[Active], optional
83- An optional parent resource to establish a hierarchical relationship, by default None.
63+ An optional parent resource that establishes a hierarchical relationship, by default None.
8464 **kwargs : dict
8565 Additional keyword arguments passed to the parent `Resource` class.
8666 """
@@ -90,69 +70,27 @@ def __init__(self, ctx: Context, parent: Optional["Active"] = None, **kwargs):
9070 self ._parent = parent
9171
9272
93- T_co = TypeVar ("T_co " , bound = "Active" , covariant = True )
94- """A covariant type variable that is bound to the `Active` class and must inherit from it. """
73+ T = TypeVar ("T " , bound = "Active" )
74+ """A type variable that is bound to the `Active` class"""
9575
9676
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.
77+ class ActiveSequence (ABC , Generic [T ], Sequence [T ]):
78+ def __init__ (self , ctx : Context , parent : Optional [Active ] = None ):
79+ """A sequence abstraction for any HTTP GET endpoint that returns a collection.
10280
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.
81+ It lazily fetches data on demand, caches the results, and allows for standard sequence operations like indexing and slicing.
11182
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 ):
83+ Parameters
84+ ----------
85+ ctx : Context
86+ The context object that holds the HTTP session used for sending the GET request.
87+ parent : Optional[Active], optional
88+ An optional parent resource to establish a nested relationship, by default None.
89+ """
15190 super ().__init__ ()
152- self ._cls = cls
15391 self ._ctx = ctx
15492 self ._parent = parent
155- self ._cache = None
93+ self ._cache : Optional [ List [ T ]] = None
15694
15795 @property
15896 @abstractmethod
@@ -171,32 +109,32 @@ def _endpoint(self) -> str:
171109 raise NotImplementedError ()
172110
173111 @property
174- def _data (self ) -> List [T_co ]:
112+ def _data (self ) -> List [T ]:
175113 """
176114 Fetch and cache the data from the API.
177115
178116 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`.
117+ Each JSON object is used to instantiate an item of type `T ` using the class specified by `_cls`.
180118 The results are cached after the first request and reused for subsequent access unless reloaded.
181119
182120 Returns
183121 -------
184- List[T_co ]
185- A list of items of type `T_co ` representing the fetched data.
122+ List[T ]
123+ A list of items of type `T ` representing the fetched data.
186124 """
187125 if self ._cache :
188126 return self ._cache
189127
190128 response = self ._ctx .session .get (self ._endpoint )
191129 results = response .json ()
192- self ._cache = [self ._cls ( self . _ctx , self . _parent , ** result ) for result in results ]
130+ self ._cache = [self ._create_instance ( ** result ) for result in results ]
193131 return self ._cache
194132
195133 @overload
196- def __getitem__ (self , index : int ) -> T_co : ...
134+ def __getitem__ (self , index : int ) -> T : ...
197135
198136 @overload
199- def __getitem__ (self , index : slice ) -> Sequence [T_co ]: ...
137+ def __getitem__ (self , index : slice ) -> Sequence [T ]: ...
200138
201139 def __getitem__ (self , index ):
202140 return self ._data [index ]
@@ -210,7 +148,17 @@ def __str__(self) -> str:
210148 def __repr__ (self ) -> str :
211149 return repr (self ._data )
212150
213- def reload (self ) -> "ActiveSequence" :
151+ @abstractmethod
152+ def _create_instance (self , ** kwargs ) -> T :
153+ """Create an instance of 'T'.
154+
155+ Returns
156+ -------
157+ T
158+ """
159+ raise NotImplementedError ()
160+
161+ def reload (self ) -> Self :
214162 """
215163 Clear the cache and reload the data from the API on the next access.
216164
@@ -223,31 +171,25 @@ def reload(self) -> "ActiveSequence":
223171 return self
224172
225173
226- class ActiveFinderMethods (ActiveSequence [T_co ], ABC , Generic [T_co ]):
227- """
228- Finder methods.
174+ class ActiveFinderMethods (ActiveSequence [T ], ABC , Generic [T ]):
175+ def __init__ ( self , ctx : Context , parent : Optional [ Active ] = None , uid : str = "guid" ):
176+ """ Finder methods.
229177
230- Provides various finder methods for locating records in any endpoint supporting HTTP GET requests.
178+ Provides various finder methods for locating records in any endpoint supporting HTTP GET requests.
231179
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-
247- _uid : str = "guid"
248- """The default field name used to uniquely identify records. Defaults to 'guid'."""
180+ Parameters
181+ ----------
182+ ctx : Context
183+ The context containing the HTTP session used to interact with the API.
184+ parent : Optional[Active], optional
185+ Optional parent resource for maintaining hierarchical relationships, by default None
186+ uid : str, optional
187+ The default field name used to uniquely identify records, by default "guid"
188+ """
189+ super ().__init__ (ctx , parent )
190+ self ._uid = uid
249191
250- def find (self , uid ) -> T_co :
192+ def find (self , uid ) -> T :
251193 """
252194 Find a record by its unique identifier.
253195
@@ -260,28 +202,29 @@ def find(self, uid) -> T_co:
260202
261203 Returns
262204 -------
263- T_co
205+ T
264206
265207 Raises
266208 ------
267209 ValueError
268210 If no record is found.
269211 """
212+ # todo - add some more comments about this
270213 if self ._cache :
271214 conditions = {self ._uid : uid }
272215 result = self .find_by (** conditions )
273216 else :
274217 endpoint = self ._endpoint + uid
275218 response = self ._ctx .session .get (endpoint )
276219 result = response .json ()
277- result = self ._cls ( self . _ctx , self . _parent , ** result )
220+ result = self ._create_instance ( ** result )
278221
279222 if not result :
280223 raise ValueError (f"Failed to find instance where { self ._uid } is '{ uid } '" )
281224
282225 return result
283226
284- def find_by (self , ** conditions : Any ) -> Optional [T_co ]:
227+ def find_by (self , ** conditions : Any ) -> Optional [T ]:
285228 """
286229 Find the first record matching the specified conditions.
287230
@@ -293,7 +236,7 @@ def find_by(self, **conditions: Any) -> Optional[T_co]:
293236
294237 Returns
295238 -------
296- Optional[T_co ]
239+ Optional[T ]
297240 The first record matching the conditions, or `None` if no match is found.
298241 """
299242 return next ((v for v in self ._data if v .items () >= conditions .items ()), None )
0 commit comments