@@ -107,44 +107,46 @@ def getpng(self, url: str) -> list | None:
107107
108108
109109class CircularChecker :
110- def __init__ (self , category : str | int = None , url : str = "https://bpsapi.rajtech.me/" , cache_method : str = 'sqlite' , ** kwargs ):
111- self .url = url
110+ def __init__ (
111+ self , category : str | int = None ,
112+ api_url : str = "https://bpsapi.rajtech.me/" ,
113+ fallback_api_url : str = None ,
114+ cache_method : str = 'pickle' , ** kwargs
115+ ):
116+ self .api_url = api_url
117+ self .fallback_api_url = fallback_api_url
112118 self .category = category
113119 self .cache_method = cache_method
114- self .latest_circular_id = self .get_cache ()
115120
116121 # Get category names from API
117- json = requests . get ( self .url + "categories" ). json ( )
122+ categories = self ._send_api_request ( "categories" )
118123
119- if json ['http_status' ] == 200 :
120- categories = json ['data' ]
121- else :
122- raise ConnectionError ("Invalid API Response. API says there are no categories." )
123-
124- # If category id is passed
124+ # If this circular checker is supposed to be for a specific category of circulars only
125+ # Check if the category name or id is valid
125126 if category is not None :
126127 if type (self .category ) is int :
127128 if not _min_category_id <= self .category :
128129 raise ValueError ("Invalid category Number" )
129130 else : # If category name is passed
130131 if self .category not in categories :
131- raise ValueError ("Invalid category Name" )
132+ raise ValueError (f"Invalid category Name ({ self .category } )."
133+ f"Allowed are { categories } " )
132134
135+ # Check if all required variables for each cache method are passed in kwargs
136+ # And create a pickle file or database file on disk (sqlite) if it doesn't exist
133137 # For the sqlite cache method
134138 if self .cache_method == "sqlite" :
135139 try :
136- self .db_name = kwargs ['db_name' ]
137140 self .db_path = kwargs ['db_path' ]
138141 self .db_table = kwargs ['db_table' ]
139142 except KeyError :
140143 raise ValueError (
141144 "Invalid Database Parameters. One of db_name, db_path, db_table not passed into kwargs" )
142145
143146 # Create local db if it does not exist
144- if not os .path .exists (self .db_path + f"/ { self . db_name } .db" ):
147+ if not os .path .exists (self .db_path ):
145148 os .mkdir (self .db_path )
146149
147-
148150 # For the mysql/mariadb cache method
149151 elif cache_method == "mysql" :
150152 try :
@@ -159,37 +161,53 @@ def __init__(self, category: str | int = None, url: str = "https://bpsapi.rajtec
159161 raise ValueError (
160162 "Invalid Database Parameters. One of db_name, db_user, db_host, db_port, db_password, db_table not passed into kwargs" )
161163
164+ # For the pickle cache method
162165 elif cache_method == 'pickle' :
163166 try :
164167 self .cache_file = kwargs ['cache_file' ]
165168 except KeyError :
166169 raise ValueError ("Invalid cache file path" )
167170
171+ if not os .path .exists (self .cache_file ):
172+ with open (self .cache_file , 'wb' ) as f :
173+ f .write (b'' )
174+
168175 else :
169176 raise ValueError ("Invalid cache method. Only mysql and sqlite allowed" )
170177
171- # Create a table to cache circulars if it's not there
178+ # For sqlite and mysql, create a table if it doesn't exist in the database
172179 if self .cache_method in ('sqlite' , 'mysql' ):
173180 con , cur = self ._get_db ()
174181
175182 cur .execute (
176183 f"""
177184 CREATE TABLE IF NOT EXISTS { self .db_table } (
178- category TEXT,
179- id INTEGER UNIQUE,
180- title TEXT,
181- link TEXT
185+ category VARCHAR(15) PRIMARY KEY,
186+ latest_circular_id INTEGER
182187 )
183188 """
184189 )
185190 con .commit ()
186191 con .close ()
187192
188- # If cache method is pickle, create a file if it doesn't exist
189- elif self .cache_method == 'pickle' :
190- if not os .path .exists (self .cache_file ):
191- with open (self .cache_file , 'wb' ) as f :
192- f .write (b'' )
193+ if self .get_cache () is None :
194+ self .check ()
195+
196+ def _send_api_request (self , endpoint : str , fallback = False ) -> dict :
197+ try :
198+ api_url = self .fallback_api_url if fallback else self .api_url
199+ request = requests .get (api_url + endpoint , timeout = 5 )
200+ json = request .json ()
201+ except requests .exceptions .ConnectionError :
202+ if fallback :
203+ raise ConnectionError ("Both API URLs are down" )
204+ if self .fallback_api_url :
205+ warnings .warn ("API is down. Trying fallback API URL" )
206+ return self ._send_api_request (endpoint , True )
207+ else :
208+ raise ConnectionError ("API is down" )
209+
210+ return json ['data' ]
193211
194212 def _get_db (self ):
195213 if self .cache_method == 'mysql' :
@@ -216,45 +234,100 @@ def get_cache(self) -> int | None:
216234 cur .execute (f"SELECT latest_circular_id FROM { self .db_table } WHERE category = ?" , (self .category ,))
217235 res = cur .fetchone ()
218236
237+ if res is not None :
238+ res = res [0 ]
239+
219240 elif self .cache_method == 'pickle' :
220241 with open (self .cache_file , 'rb' ) as f :
221242 res = pickle .load (f )
243+ else :
244+ raise ValueError ("Method not supported for this cache method" )
245+
246+ if res is not None :
247+ res = int (res )
222248
223249 return res
224250
225- # Method to add multiple items to cache
226251 def _set_cache (self , circular_id : int ):
227252
228253 if self .cache_method in ('sqlite' , 'mysql' ):
229254 con , cur = self ._get_db ()
230- query = f"INSERT OR IGNORE INTO { self .db_table } ( category, latest_circular_id) VALUES (?, ?)"
255+ # cur.execute(f"DELETE FROM {self.db_table} WHERE category = ?", (self.cate))
231256
232- if self .cache_method == 'mysql' :
233- query = query .replace ("OR " , "" )
257+ query = f"REPLACE INTO { self .db_table } (category, latest_circular_id) VALUES (?, ?)"
234258
235259 cur .execute (query , (self .category , circular_id ,))
236260 con .commit ()
237261
238262 elif self .cache_method == 'pickle' :
239263 with open (self .cache_file , 'wb' ) as f :
240- pickle .dumps (circular_id , f )
264+ f . write ( pickle .dumps (circular_id ) )
241265
242266
243- # Method to check for new circular(s)
267+ # Method to check for new circulars
244268 def check (self ) -> list [dict ] | list :
245- res = requests .get (self .url + f"new-circulars/{ self .latest_circular_id } " ).json ()['data' ]
269+
270+ if cache := self .get_cache () is not None :
271+ res = self ._send_api_request (f'new-circulars/{ cache } ' )
272+ else :
273+ res = self ._send_api_request ('new-circulars/' )
246274 # it's sorted in descending order
247275
276+ # If the API found new circulars
248277 if len (res ) > 0 :
249278 self ._set_cache (res [0 ]['id' ])
250279
280+ # If this circular-checker is meant for only one category,
281+ # remove circulars of any other category
251282 if self .category :
252283 res = [circular for circular in res if circular ['category' ] == self .category ]
253284
285+ # remove the 'category' key from each of the circular objects
286+ for circular in res :
287+ del circular ['category' ]
254288 return res
255289
256290
257291 # Close connections when object is deleted
258- def __del__ (self ):
259- pass
260-
292+ # def __del__(self):
293+ #
294+ # if hasattr(self, '_con'):
295+ # self._con.close()
296+
297+
298+ class CircularCheckerGroup :
299+ def __init__ (self , * circular_checkers : CircularChecker ):
300+ self ._checkers = []
301+
302+ # Add each checker to self._checkers
303+ for checker in circular_checkers :
304+ if type (checker ) is not CircularChecker :
305+ raise ValueError ("Invalid CircularChecker Object" )
306+ self ._checkers .append (checker )
307+
308+
309+ # Method to add a circular checker to this group
310+ def add (self , checker : CircularChecker , * circular_checkers : CircularChecker ):
311+ self ._checkers .append (checker )
312+
313+ for checker in circular_checkers :
314+ if type (checker ) is not CircularChecker :
315+ raise ValueError ("Invalid CircularChecker Object" )
316+ self ._checkers .append (checker )
317+
318+ # Method to create a circular checker and add it to the group
319+ def create (self , category , url : str = "https://bpsapi.rajtech.me/" , cache_method = None , ** kwargs ):
320+ checker = CircularChecker (category , url , cache_method , ** kwargs )
321+ self ._checkers .append (checker )
322+
323+ # Method to check for new circulars in each one of the checkers
324+ def check (self ) -> dict [list [dict ], ...] | dict :
325+ return_dict = {}
326+ for checker in self ._checkers :
327+ return_dict [checker .category ] = checker .check ()
328+ return return_dict
329+
330+ #
331+ # def __del__(self):
332+ # for checker in self._checkers:
333+ # del checker
0 commit comments