1+ import pickle
12import warnings
23import mysql .connector
34import requests
@@ -106,11 +107,11 @@ def getpng(self, url: str) -> list | None:
106107
107108
108109class CircularChecker :
109- def __init__ (self , category : str | int , url : str = "https://bpsapi.rajtech.me/" , cache_method : str = 'sqlite' , ** kwargs ):
110+ def __init__ (self , category : str | int = None , url : str = "https://bpsapi.rajtech.me/" , cache_method : str = 'sqlite' , ** kwargs ):
110111 self .url = url
111112 self .category = category
112113 self .cache_method = cache_method
113- self ._cache = []
114+ self .latest_circular_id = self . get_cache ()
114115
115116 # Get category names from API
116117 json = requests .get (self .url + "categories" ).json ()
@@ -120,17 +121,14 @@ def __init__(self, category: str | int, url: str = "https://bpsapi.rajtech.me/",
120121 else :
121122 raise ConnectionError ("Invalid API Response. API says there are no categories." )
122123
123- if kwargs .get ("debug" ):
124- self .set_cache = self ._set_cache
125- self .refresh_cache = self ._refresh_cache
126-
127124 # If category id is passed
128- if type (self .category ) is int :
129- if not _min_category_id <= self .category :
130- raise ValueError ("Invalid category Number" )
131- else : # If category name is passed
132- if self .category not in categories :
133- raise ValueError ("Invalid category Name" )
125+ if category is not None :
126+ if type (self .category ) is int :
127+ if not _min_category_id <= self .category :
128+ raise ValueError ("Invalid category Number" )
129+ else : # If category name is passed
130+ if self .category not in categories :
131+ raise ValueError ("Invalid category Name" )
134132
135133 # For the sqlite cache method
136134 if self .cache_method == "sqlite" :
@@ -146,8 +144,6 @@ def __init__(self, category: str | int, url: str = "https://bpsapi.rajtech.me/",
146144 if not os .path .exists (self .db_path + f"/{ self .db_name } .db" ):
147145 os .mkdir (self .db_path )
148146
149- self ._con = sqlite3 .connect (self .db_path + f"/{ self .db_name } .db" )
150- self ._cur = self ._con .cursor ()
151147
152148 # For the mysql/mariadb cache method
153149 elif cache_method == "mysql" :
@@ -163,163 +159,102 @@ def __init__(self, category: str | int, url: str = "https://bpsapi.rajtech.me/",
163159 raise ValueError (
164160 "Invalid Database Parameters. One of db_name, db_user, db_host, db_port, db_password, db_table not passed into kwargs" )
165161
166- self . _con = mysql . connector . connect (
167- host = self . db_host , port = self . db_port , password = self . db_password ,
168- user = self .db_user , database = self . db_name ,
169- )
170- self . _cur = self . _con . cursor ( prepared = True )
162+ elif cache_method == 'pickle' :
163+ try :
164+ self .cache_file = kwargs [ 'cache_file' ]
165+ except KeyError :
166+ raise ValueError ( "Invalid cache file path" )
171167
172168 else :
173169 raise ValueError ("Invalid cache method. Only mysql and sqlite allowed" )
174170
175171 # Create a table to cache circulars if it's not there
176- self ._cur .execute (
177- f"""
178- CREATE TABLE IF NOT EXISTS { self .db_table } (
179- category TEXT,
180- id INTEGER UNIQUE,
181- title TEXT,
182- link TEXT
183- )
184- """
185- )
186- self ._con .commit ()
172+ if self .cache_method in ('sqlite' , 'mysql' ):
173+ con , cur = self ._get_db ()
174+
175+ cur .execute (
176+ f"""
177+ CREATE TABLE IF NOT EXISTS { self .db_table } (
178+ category TEXT,
179+ id INTEGER UNIQUE,
180+ title TEXT,
181+ link TEXT
182+ )
183+ """
184+ )
185+ con .commit ()
186+ con .close ()
187187
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'' )
188193
189- # Method to retrieve cache from the database
190- def get_cache (self ) -> list [list ] | list :
191- self ._cur .execute (f"SELECT id, title, link FROM { self .db_table } WHERE category = ?" , (self .category ,))
192- res = self ._cur .fetchall ()
194+ def _get_db (self ):
195+ if self .cache_method == 'mysql' :
196+ con = mysql .connector .connect (
197+ host = self .db_host , port = self .db_port , password = self .db_password ,
198+ user = self .db_user , database = self .db_name ,
199+ )
200+ cur = con .cursor (prepared = True )
193201
194- return res
202+ elif self .cache_method == 'sqlite' :
203+ con = sqlite3 .connect (self .db_path + f"/{ self .db_name } .db" )
204+ cur = con .cursor ()
205+ else :
206+ raise ValueError ("Method not supported for this cache method" )
195207
196- # Method to add multiple items to cache
197- def _set_cache (self , data ):
198- # data [ (id, title, link) ]
199- query = f"INSERT OR IGNORE INTO { self .db_table } (category, id, title, link) VALUES (?, ?, ?, ?)"
208+ return con , cur
200209
201- if self .cache_method == 'mysql' :
202- query = query .replace ("OR " , "" )
203210
204- self ._cur .executemany (query , tuple ((self .category , * d ) for d in data ))
205- self ._con .commit ()
211+ # Method to retrieve cache from the database
212+ def get_cache (self ) -> int | None :
213+ if self .cache_method in ('sqlite' , 'mysql' ):
214+ con , cur = self ._get_db ()
206215
207- # Method to add a single item to cache
208- def _add_to_cache (self , id_ : int , title : str , link : str ):
209- query = f"INSERT OR IGNORE INTO { self .db_table } (id, title, link) VALUES (?, ?, ?, ?)"
216+ cur .execute (f"SELECT latest_circular_id FROM { self .db_table } WHERE category = ?" , (self .category ,))
217+ res = cur .fetchone ()
210218
211- if self .cache_method == 'mysql' :
212- query = query .replace ("OR " , "" )
219+ elif self .cache_method == 'pickle' :
220+ with open (self .cache_file , 'rb' ) as f :
221+ res = pickle .load (f )
213222
214- self . _cur . execute ( query , ( self . category , id_ , title , link ))
223+ return res
215224
216- # Method to retrieve circulars from the API and insert into cache
217- def _refresh_cache (self ):
218- request = requests .get (f"{ self .url } list/{ self .category } " )
219- json : dict = request .json ()
225+ # Method to add multiple items to cache
226+ def _set_cache (self , circular_id : int ):
220227
221- if json .get ("data" ) is None or json .get ("http_status" ) is None :
222- raise ConnectionError ("Invalid API Response, it doesn't contain either 'data' or 'http_code'" )
228+ if self .cache_method in ('sqlite' , 'mysql' ):
229+ con , cur = self ._get_db ()
230+ query = f"INSERT OR IGNORE INTO { self .db_table } (category, latest_circular_id) VALUES (?, ?)"
223231
224- self ._cur .execute (f"SELECT id FROM { self .db_table } WHERE category = ?" , (self .category ,))
232+ if self .cache_method == 'mysql' :
233+ query = query .replace ("OR " , "" )
225234
226- # ((1234,), (4567,), ...) -> ('1234', '4567')
227- cached_ids : list = self ._cur .fetchall ()
228- cached_ids : tuple [str , ...] = tuple ([str (i [0 ]) for i in cached_ids ])
235+ cur .execute (query , (self .category , circular_id ,))
236+ con .commit ()
237+
238+ elif self .cache_method == 'pickle' :
239+ with open (self .cache_file , 'wb' ) as f :
240+ pickle .dumps (circular_id , f )
229241
230- if json ['http_status' ] == 200 :
231- self ._set_cache (
232- [
233- (i ['id' ], i ['title' ], i ['link' ])
234- for i in json ['data' ]
235- if i ['id' ] not in cached_ids # Add only new circulars to the database
236- ]
237- )
238242
239243 # Method to check for new circular(s)
240244 def check (self ) -> list [dict ] | list :
241- # First get cached circulars and store them in a variable 'cached_circular_ids'
242- # Then refresh cache and get the new list of circulars, and then compare and find new ones.
243- self ._cur .execute (f"SELECT id FROM { self .db_table } WHERE category = ?" , (self .category ,))
245+ res = requests .get (self .url + f"new-circulars/{ self .latest_circular_id } " ).json ()['data' ]
246+ # it's sorted in descending order
244247
245- cached_circular_ids = self . _cur . fetchall ()
246- cached_circular_ids = [ i [ 0 ] for i in cached_circular_ids ] # [(id, title, link)]
248+ if len ( res ) > 0 :
249+ self . _set_cache ( res [ 0 ][ 'id' ])
247250
248- self ._refresh_cache ()
249- new_circular_list = self .get_cache () #
251+ if self .category :
252+ res = [ circular for circular in res if circular [ 'category' ] == self .category ]
250253
251- # If there are new circulars
252- if len (new_circular_list ) > len (cached_circular_ids ):
253- new_circular_objects = [i for i in new_circular_list if i [0 ] not in cached_circular_ids ]
254-
255- # (id, title, link) -> {'id': id, 'title': title, 'link': link}
256- new_circular_objects = [
257- {
258- 'id' : i [0 ],
259- 'title' : i [1 ],
260- 'link' : i [2 ]
261- }
262- for i in new_circular_objects
263- ]
264-
265- # sort the new_circular_objects by circular id in ascending order
266- new_circular_objects .sort (key = lambda x : x ['id' ])
267- return new_circular_objects
254+ return res
268255
269- return []
270256
271257 # Close connections when object is deleted
272258 def __del__ (self ):
273- if hasattr (self , '_con' ):
274- self ._con .close ()
275-
276-
277- class CircularCheckerGroup :
278- def __init__ (self , * circular_checkers : CircularChecker , ** kwargs ):
279- self ._checkers = []
280-
281- # Add each checker to self._checkers
282- for checker in circular_checkers :
283- if type (checker ) is not CircularChecker :
284- raise ValueError ("Invalid CircularChecker Object" )
285- self ._checkers .append (checker )
286-
287- if bool (kwargs .get ("debug" )):
288- self .checkers = self ._checkers
289-
290- # Method to add a circular checker to this group
291- def add (self , checker : CircularChecker , * circular_checkers : CircularChecker ):
292- self ._checkers .append (checker )
293-
294- for checker in circular_checkers :
295- if type (checker ) is not CircularChecker :
296- raise ValueError ("Invalid CircularChecker Object" )
297- self ._checkers .append (checker )
298-
299- # Method to create a circular checker and add it to the group
300- def create (self , category , url : str = "https://bpsapi.rajtech.me/" , cache_method = None , ** kwargs ):
301- checker = CircularChecker (category , url , cache_method , ** kwargs )
302- self ._checkers .append (checker )
303-
304- # Method to check for new circulars in each one of the checkers
305- def check (self ) -> dict [list [dict ], ...] | dict :
306- return_dict = {}
307- for checker in self ._checkers :
308- return_dict [checker .category ] = checker .check ()
309- return return_dict
310-
311- # Method to refresh (sync) cache from API
312- def refresh_cache (self ):
313- for checker in self ._checkers :
314- checker .refresh_cache ()
315-
316- # Method to get the cache of all checkers
317- def get_cache (self ) -> dict [list [list ]] | dict :
318- return_dict = {}
319- for checker in self ._checkers :
320- return_dict [checker .category ] = checker .get_cache ()
321- return return_dict
259+ pass
322260
323- def __del__ (self ):
324- for checker in self ._checkers :
325- del checker
0 commit comments