77import sys
88import textwrap
99from datetime import datetime , date , time , timedelta
10- from os import environ , path
11- from random import uniform , choice
10+ from os import environ
11+ from random import uniform
1212from time import sleep
1313from typing import NamedTuple , Optional , Tuple
1414
@@ -27,11 +27,6 @@ def get_public_ip():
2727 return response
2828
2929
30- class DummyLogger (object ):
31- def __getattr__ (self , name ):
32- return lambda * args , ** kwargs : None
33-
34-
3530class EtsyStoreStats (NamedTuple ):
3631 """Used to format stats from Etsy store"""
3732 favorite_count : Optional [int ] = None
@@ -58,32 +53,35 @@ def get_timedelta_from_now(start: datetime) -> timedelta:
5853
5954class AIOEtsyStats :
6055 """Class to store and record stats for Etsy"""
56+
6157 def __init__ (self , shop : str , default_reset_hour : int = 14 , scrape_interval_minutes : int = 10 ,
6258 aio_username : str = None , aio_password : str = None ,
6359 discord_webhook : str = None , discord_avatar_url : str = None ,
6460 selenium_host : str = None , selenium_port : int = None ):
61+ # region Logging
62+ logging .basicConfig ()
63+ self .logger = logging .Logger (name = type (self ).__name__ )
64+
65+ handler_stdout = logging .StreamHandler (sys .stdout )
66+ handler_stdout .setFormatter (logging .Formatter ("%(asctime)s %(levelname)s %(message)s" ))
67+ handler_stdout .setLevel (logging .DEBUG )
68+ self .logger .addHandler (handler_stdout )
69+ # endregion
70+
71+ # region Class basics
6572 self .shop = shop
6673 self .scrape_url = f"https://www.etsy.com/shop/{ shop } /sold"
6774 self .default_reset_hour = default_reset_hour
6875 self .scrape_interval_minutes = scrape_interval_minutes
6976 self .selenium_host = selenium_host
7077 self .selenium_port = selenium_port
71- self .driver = None
72- self .set_selenium (selenium_host = selenium_host , selenium_port = selenium_port )
78+ # endregion
7379
7480 # Get the current stats just incase this hasn't been set up before or AIO is not used
75- self .logger = DummyLogger () # Temporary
81+ self .logger . debug ( f"Getting Etsy stats for { self . shop } to use for loading" )
7682 stats = self .scrape_etsy_stats ()
7783
78- # region Logging
79- logging .basicConfig ()
80- self .logger = logging .Logger (name = type (self ).__name__ )
81-
82- handler_stdout = logging .StreamHandler (sys .stdout )
83- handler_stdout .setFormatter (logging .Formatter ("%(asctime)s %(levelname)s %(message)s" ))
84- handler_stdout .setLevel (logging .DEBUG )
85- self .logger .addHandler (handler_stdout )
86-
84+ # region Discord
8785 if discord_webhook :
8886 discord_handler = DiscordHandler (
8987 service_name = type (self ).__name__ ,
@@ -127,7 +125,6 @@ def __init__(self, shop: str, default_reset_hour: int = 14, scrape_interval_minu
127125 if not existing_feed_group :
128126 self .logger .debug (f"Creating Feed Group \" { self .shop } \" " )
129127 self ._aio .create_group (group = Group (name = self .shop , key = self .shop .lower ()))
130-
131128 feeds = [
132129 (Feed (name = "Daily Order Count" , key = "daily-order-count" ), "0" ),
133130 (Feed (name = "Favorite Count" , key = "favorite-count" ), stats .favorite_count ),
@@ -196,23 +193,23 @@ def _atexit(self):
196193 -# Exiting on host `{ socket .gethostname ()} `
197194 """ ).strip ())
198195
199- def set_selenium (self , selenium_host : str , selenium_port : int ) -> None :
200- self .selenium_host = selenium_host
201- self .selenium_port = selenium_port
196+ def _get_webdriver (self ):
202197 if self .selenium_host and self .selenium_port :
203- print ("Waiting up to 20s for selenium port to be open" )
198+ self . logger . debug ("Waiting up to 20s for selenium port to be open" )
204199 start = datetime .now ()
205200 while (test_port (self .selenium_host , self .selenium_port ) != 0 ) \
206201 and (get_timedelta_from_now (start ) < timedelta (seconds = 20 )):
207202 sleep (1 )
208203
209204 if self .selenium_host and self .selenium_port :
210- self . driver = webdriver .Remote (
211- command_executor = f"http://{ self .selenium_host } :{ self .selenium_port } /wd/hub " ,
205+ driver = webdriver .Remote (
206+ command_executor = f"http://{ self .selenium_host } :{ self .selenium_port } " ,
212207 options = webdriver .ChromeOptions ()
213208 )
214209 else :
215- self .driver = webdriver .Chrome ()
210+ driver = webdriver .Chrome ()
211+
212+ return driver
216213
217214 def _get_starting_stats (self ) -> dict :
218215 """Gets starting-stats feed and parses the json to dictionary"""
@@ -222,18 +219,24 @@ def _get_starting_stats(self) -> dict:
222219
223220 def _get_selenium (self , url : str ) -> Tuple [str , str ]:
224221 """Gets webpage content with selenium"""
222+ driver = None
225223 content = None
226224 title = None
227225 try :
228- self .driver .get (url )
229- title = self .driver .title
230- content = self .driver .page_source
226+ driver = self ._get_webdriver ()
227+ driver .get (url )
228+ title = driver .title
229+ content = driver .page_source
231230
232231 if not content :
233232 self .logger .debug (f"No content for url { url } . Page title: { title } " )
234233 except Exception as e :
235234 self .logger .warning (f"An error occurred getting page { url } with Selenium Firefox" )
236235 self .logger .exception (e )
236+ finally :
237+ if driver :
238+ driver .close ()
239+ driver .quit ()
237240
238241 return title , content
239242
@@ -264,7 +267,7 @@ def _send_aio(self, feed: str, value):
264267 """Helper function to send values to AIO and parse for errors"""
265268 if self ._aio :
266269 feed = self ._get_feed_name (feed = feed )
267-
270+
268271 try :
269272 self .logger .debug (f"Updating AIO feed { feed } to { value } " )
270273 if isinstance (value , dict ):
@@ -279,7 +282,7 @@ def _receive_aio(self, feed: str, default_value: object = None, silent: bool = F
279282 return_val = default_value
280283 if self ._aio :
281284 feed = self ._get_feed_name (feed = feed )
282-
285+
283286 try :
284287 response = self ._aio .receive (feed = feed )
285288 if not silent :
@@ -301,15 +304,14 @@ def _reset_counts(self) -> None:
301304 self .logger .info (textwrap .dedent (f"""
302305 { type (self ).__name__ } for **{ self .shop } **
303306
304- -# Reset time of { self .reset_datetime } has passed
307+ -# Reset time of ** { self .reset_datetime :%Y-%m-%d %H:%M:%S%z } ** has passed
305308 -# Daily Order Count was `{ self .daily_order_count } `
306- -# Daily Favorites was `{ self .favorite_count - self .starting_favorite_count }
309+ -# Daily Favorites was `{ self .favorite_count - self .starting_favorite_count } `
307310 -# Daily Rating was `{ (self .rating - self .starting_rating ):4f} `
308- -# Daily Ratings was `{ self .rating_count - self .rating } `
311+ -# Daily Rating Count was `{ self .rating_count - self .starting_rating_count } `
309312 -# Daily Sold was `{ self .sold_count - self .starting_sold_count } `
310- -# Next reset is { new_reset_datetime }
313+ -# Next reset is ** { new_reset_datetime :%Y-%m-%d %H:%M:%S%z } **
311314 -# Public IP is `{ get_public_ip ()} `
312- -# Shawn ❤️ Nicole
313315 """ ).strip ())
314316
315317 # Update all things to be equal to current stats
@@ -428,7 +430,7 @@ def scrape_etsy_stats(self) -> EtsyStoreStats:
428430 errors += 1
429431 # endregion
430432
431- return EtsyStoreStats (favorite_count = favorite_count , rating = rating , rating_count = rating_count ,
433+ return EtsyStoreStats (favorite_count = favorite_count , rating = rating , rating_count = rating_count ,
432434 sold_count = sold_count , avatar_url = avatar_url , errors = errors )
433435
434436 def _log_current_stats (self ):
@@ -458,7 +460,6 @@ def collect_and_publish(self) -> None:
458460
459461 # If we passed reset_datetime, process the reset using the current stats
460462 if datetime .now () > self .reset_datetime :
461- self .logger .info (f"Reset time of { self .reset_datetime } has been passed" )
462463 self ._reset_counts ()
463464
464465 # region Process Stats
@@ -525,9 +526,9 @@ def _add_scheduled_job(self):
525526 """Used to add the job. Can be called again if you have to remove it from the schedule"""
526527 minutes = self .scrape_interval_minutes
527528 if minutes > 9 :
528- schedule .every (minutes ).to (minutes + 5 ).minutes .do (self .collect_and_publish )
529+ schedule .every (minutes ).to (minutes + 5 ).minutes .do (self .collect_and_publish )
529530 else :
530- schedule .every (minutes ).to (minutes + 1 ).minutes .do (self .collect_and_publish )
531+ schedule .every (minutes ).to (minutes + 1 ).minutes .do (self .collect_and_publish )
531532
532533 def main (self ):
533534 """Run this to have this run on a schedule"""
@@ -542,7 +543,6 @@ def main(self):
542543
543544
544545if __name__ == "__main__" :
545-
546546 client = AIOEtsyStats (shop = environ .get ("ETSY_STORE_NAME" ),
547547 default_reset_hour = int (environ .get ("DEFAULT_RESET_HOUR" , 14 )),
548548 scrape_interval_minutes = int (environ .get ("SCRAPE_INTERVAL_MINUTES" , 5 )),
0 commit comments