diff --git a/src/bonbast/helpers/click_callbacks.py b/src/bonbast/helpers/click_callbacks.py index 2d4c4975b..2c46f8cc8 100644 --- a/src/bonbast/helpers/click_callbacks.py +++ b/src/bonbast/helpers/click_callbacks.py @@ -9,9 +9,8 @@ def print_version(ctx, param, value): if not value or ctx.resilient_parsing: return - else: - click.echo(bonbast_version) - ctx.exit() + click.echo(bonbast_version) + ctx.exit() def parse_show_only(ctx, param, value): diff --git a/src/bonbast/helpers/utils.py b/src/bonbast/helpers/utils.py index 35a29c3a1..b8bb7e87b 100644 --- a/src/bonbast/helpers/utils.py +++ b/src/bonbast/helpers/utils.py @@ -10,10 +10,10 @@ class Singleton(type): _instances = {} - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] + def __call__(self, *args, **kwargs): + if self not in self._instances: + self._instances[self] = super(Singleton, self).__call__(*args, **kwargs) + return self._instances[self] def format_toman(price: float) -> str: @@ -37,9 +37,11 @@ def wrapper_retry(*args, **kwargs): for i in range(retry_count): try: return func(*args, **kwargs) + except RetryError as e: if i == retry_count - 1: raise SystemExit(e.message) + except Exception as e: # noqa if retry_delay is not None: time.sleep(retry_delay) diff --git a/src/bonbast/main.py b/src/bonbast/main.py index dfdec96da..5487925cd 100644 --- a/src/bonbast/main.py +++ b/src/bonbast/main.py @@ -28,10 +28,11 @@ def get_prices(show_only: List[str] = None): token = token_manager.generate() try: response = get_prices_from_api(token.value) - if show_only is not None and len(show_only) > 0: + if show_only: response = list(response) for index, item in enumerate(response): - response[index] = [item for item in item if item.code.lower() in show_only] + response[index] = [ + item for item in item if item.code.lower() in show_only] response = tuple(response) return response @@ -76,7 +77,6 @@ def cli(ctx, show_only): def live(ctx): if ctx.invoked_subcommand is None: print('Show full table with live prices / up and down arrows') - pass # ================ bonbast live graph ================ @@ -101,7 +101,8 @@ def live_simple(interval, show_only): collections = get_prices(show_only) prices_text = Text() - prices_text.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n\n', style='bold') + prices_text.append( + f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n\n', style='bold') for collection_index, collection in enumerate(collections): for index, model in enumerate(collection): @@ -160,12 +161,16 @@ def live_currency(interval, currency): # add a row with the new information if type(item) is Currency or type(item) is Coin: sell_symbol = f' {get_change_char(item.sell, old_model.sell)}' if old_model is not None else '' - sell_str = (item.formatted_sell if item.sell is not None else '-') + sell_symbol - sell_color = get_color(item.sell, None if old_model is None else old_model.sell) + sell_str = ( + item.formatted_sell if item.sell is not None else '-') + sell_symbol + sell_color = get_color( + item.sell, None if old_model is None else old_model.sell) buy_symbol = f' {get_change_char(item.buy, old_model.buy)}' if old_model is not None else '' - buy_str = (item.formatted_buy if item.buy is not None else '-') + buy_symbol - buy_color = get_color(item.buy, None if old_model is None else old_model.buy) + buy_str = ( + item.formatted_buy if item.buy is not None else '-') + buy_symbol + buy_color = get_color( + item.buy, None if old_model is None else old_model.buy) price = ( Text(sell_str, style=sell_color), @@ -173,15 +178,18 @@ def live_currency(interval, currency): ) else: price_char = f' {get_change_char(item.price, old_model.price)}' if old_model is not None else '' - price_str = (item.formatted_price if item.price is not None else '-') + price_char - price_color = get_color(item.price, None if old_model is None else old_model.price) + price_str = ( + item.formatted_price if item.price is not None else '-') + price_char + price_color = get_color( + item.price, None if old_model is None else old_model.price) price = ( Text(price_str, style=price_color), ) table.add_row( - Text(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), style=DEFAULT_TEXT_COLOR), + Text(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + style=DEFAULT_TEXT_COLOR), Text(item.name, style=DEFAULT_TEXT_COLOR), *price, ) @@ -201,21 +209,26 @@ def live_currency(interval, currency): @click.pass_context def convert(ctx, source, destination, amount, only_buy, only_sell): if source is None and destination is None: - raise click.BadOptionUsage('', 'Please specify source or destination currency') + raise click.BadOptionUsage( + '', 'Please specify source or destination currency') if source is not None and destination is not None: - raise click.BadOptionUsage('', 'Don\'t use --source and --destination together') + raise click.BadOptionUsage( + '', 'Don\'t use --source and --destination together') if only_buy and only_sell: - raise click.BadOptionUsage('', 'Don\'t use --only-buy and --only-sell together') + raise click.BadOptionUsage( + '', 'Don\'t use --only-buy and --only-sell together') currencies_list, _, __ = get_prices() if source is not None: - currency = next(currency for currency in currencies_list if currency.code.lower() == source.lower()) + currency = next( + currency for currency in currencies_list if currency.code.lower() == source.lower()) buy = format_toman(int(amount * currency.buy)) sell = format_toman(int(amount * currency.sell)) else: - currency = next(currency for currency in currencies_list if currency.code.lower() == destination.lower()) + currency = next(currency for currency in currencies_list if currency.code.lower( + ) == destination.lower()) buy = format_price(amount / currency.sell) sell = format_price(amount / currency.buy) diff --git a/src/bonbast/managers/storage_manager.py b/src/bonbast/managers/storage_manager.py index d27ea316b..33def5a1c 100644 --- a/src/bonbast/managers/storage_manager.py +++ b/src/bonbast/managers/storage_manager.py @@ -1,3 +1,4 @@ +import contextlib import os import pathlib import sys @@ -56,7 +57,5 @@ def load_file(self) -> str: def delete_file(self) -> None: """ Delete the saved token from datadir """ - try: + with contextlib.suppress(FileNotFoundError): os.remove(self.file_path) - except FileNotFoundError: - pass diff --git a/src/bonbast/server.py b/src/bonbast/server.py index 6db2e44ea..e81eed378 100644 --- a/src/bonbast/server.py +++ b/src/bonbast/server.py @@ -52,8 +52,10 @@ def get_token_from_main_page(): try: r = requests.get(BASE_URL, headers=headers) r.raise_for_status() + except requests.exceptions.HTTPError as err: raise SystemExit(err) + except requests.exceptions.ConnectionError as _: raise SystemExit('Error: Failed to connect to bonbast') @@ -61,7 +63,7 @@ def get_token_from_main_page(): if search is None or search.group(1) is None: raise SystemExit('Error: token not found in the main page') - return search.group(1) + return search[1] def get_prices_from_api(token: str) -> Tuple[List[Currency], List[Coin], List[Gold]]: @@ -107,44 +109,38 @@ def get_prices_from_api(token: str) -> Tuple[List[Currency], List[Coin], List[Go if 'reset' in r: raise ResetAPIError('Error: token is expired') - currencies: List[Currency] = [] - coins: List[Coin] = [] - golds: List[Gold] = [] - - for currency in Currency.VALUES: - if f'{currency}{BUY}' in r and f'{currency}{SELL}' in r: - currencies.append(Currency( - currency.upper(), - Currency.VALUES[currency], - sell=int(r[f'{currency}{SELL}']), - buy=int(r[f'{currency}{BUY}']), - )) - - for coin in Coin.VALUES: - if f'{coin}' in r and f'{coin}{BUY}' in r: - coins.append(Coin( - coin, - Coin.VALUES[coin], - sell=int(r[coin]), - buy=int(r[f'{coin}{BUY}']), - )) - - for gold in Gold.VALUES: - if f'{gold}' in r: - golds.append(Gold( - gold, - Gold.VALUES[gold], - price=int(r[gold]) - )) + coins: List[Coin] = [ + Coin( + coin, + Coin.VALUES[coin], + sell=int(r[coin]), + buy=int(r[f'{coin}{BUY}']), + ) + for coin in Coin.VALUES + if f'{coin}' in r and f'{coin}{BUY}' in r + ] + + golds: List[Gold] = [ + Gold(gold, Gold.VALUES[gold], price=int(r[gold])) + for gold in Gold.VALUES + if f'{gold}' in r + ] + + currencies: List[Currency] = [ + Currency( + currency.upper(), + Currency.VALUES[currency], + sell=int(r[f'{currency}{SELL}']), + buy=int(r[f'{currency}{BUY}']), + ) + for currency in Currency.VALUES + if f'{currency}{BUY}' in r and f'{currency}{SELL}' in r + ] return currencies, coins, golds -def get_graph_data( - currency: str, - start_date: datetime = datetime.today() - timedelta(days=30), - end_date: datetime = datetime.today(), -) -> Dict[str, int]: +def get_graph_data(currency: str, start_date: datetime = datetime.now() - timedelta(days=30), end_date: datetime = datetime.now()) -> Dict[str, int]: """ This function will make a request to bonbast.com/graph and make them in two array. """ @@ -168,36 +164,45 @@ def get_graph_data( } try: - request = requests.get(f'{BASE_URL}/graph/{currency}/{start_date.date()}/{end_date.date()}', headers=headers) + request = requests.get( + f'{BASE_URL}/graph/{currency}/{start_date.date()}/{end_date.date()}', headers=headers) request.raise_for_status() except requests.exceptions.HTTPError as err: - raise SystemExit(err) + raise SystemExit(err) from err soup = BeautifulSoup(request.text, 'html.parser') for data in soup.find_all("script"): # get variables from script and convert them to list if "data: {" in data.text: - price_list = data.text.split("data: [")[1].split("]")[0].split(",") - date_list = data.text.split("labels: [")[1].split("]")[0].split(',') + return get_graph_data(data) - if len(price_list) != len(date_list): - raise SystemExit('Error: data inconsistency') - dic = {} - for i in range(len(price_list)): - price = int(price_list[i]) - date = re.search(r'\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])', date_list[i]).group(0) - dic[date] = price +def get_graph_data(data): + price_list = data.text.split("data: [")[1].split("]")[0].split(",") + date_list = data.text.split("labels: [")[1].split("]")[0].split(',') - return dic + if len(price_list) != len(date_list): + raise SystemExit('Error: data inconsistency') + + dic = {} + for i in range(len(price_list)): + price = int(price_list[i]) + date = re.search( + r'\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])', date_list[i] + )[0] + dic[date] = price + + return dic def get_history(date: datetime = datetime.today() - timedelta(days=1)) -> List[Currency]: if date.date() < datetime(2012, 10, 9).date(): - raise SystemExit('Error: date is too far in the past. Date must be greater than 2012-10-09') + raise SystemExit( + 'Error: date is too far in the past. Date must be greater than 2012-10-09') - if date.date() >= datetime.today().date(): - raise SystemExit(f'Error: date must be less than today({date.today().date()}).') + if date.date() >= datetime.now().date(): + raise SystemExit( + f'Error: date must be less than today({date.today().date()}).') headers = { 'authority': 'bonbast.com', @@ -216,17 +221,18 @@ def get_history(date: datetime = datetime.today() - timedelta(days=1)) -> List[C } try: - request = requests.get(f'{BASE_URL}/archive/{date.strftime("%Y/%m/%d")}', headers=headers) + request = requests.get( + f'{BASE_URL}/archive/{date.strftime("%Y/%m/%d")}', headers=headers) request.raise_for_status() except requests.exceptions.HTTPError as err: - raise SystemExit(err) + raise SystemExit(err) from err soup = BeautifulSoup(request.text, 'html.parser') tables = soup.findAll("table") # first and second table are currencies currencies: List[Currency] = [] - for table in tables[0:1]: + for table in tables[:1]: for row in table.findAll('tr')[1:]: cells = row.findAll("td") currencies.append(Currency(