Skip to content
This repository has been archived by the owner on Jan 14, 2025. It is now read-only.

Refactor codebase for improved readability and maintainability #22

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/bonbast/helpers/click_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 6 additions & 4 deletions src/bonbast/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down
45 changes: 29 additions & 16 deletions src/bonbast/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ================
Expand All @@ -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):
Expand Down Expand Up @@ -160,28 +161,35 @@ 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),
Text(buy_str, style=buy_color),
)
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,
)
Expand All @@ -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)

Expand Down
5 changes: 2 additions & 3 deletions src/bonbast/managers/storage_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import os
import pathlib
import sys
Expand Down Expand Up @@ -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
112 changes: 59 additions & 53 deletions src/bonbast/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ 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')

search = re.search(r"param\s*=\s*\"(.+)\"", r.text, re.MULTILINE)
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]]:
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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',
Expand All @@ -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(
Expand Down