Skip to content

Commit fa3f2ed

Browse files
authored
Code quality improvements (#39)
* Remove unnecessary and poorly coded "point reward for chatting" functionality * Remove unused imports from Bank * Create BankUser protocol with withdraw and deposit methods * Add name method to BankUser protocol * Implement BankUser protocol method name() * Use dependency inversion on Bank's BotUser dependency using the BankUser protocol, decoupling it from BotUser * Make command batch fail on any Exception, not just ValueError * Test banking module * Add get_balance method to BankUser protocol * Use dependency inversion on PointAmount's BotUser dependency * Put test bankuser in a separate module for reuse * Rename exception raised by point converter, validate maximum number of digits and if bank_user is passed for all non-numeric amounts * Test point amount converter * Fix undefined reference on translation module
1 parent f49eb55 commit fa3f2ed

11 files changed

+256
-87
lines changed

hashtablebot/banking/bank.py

-24
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
from sqlalchemy.exc import NoResultFound
2-
from twitchio import Chatter
3-
4-
from hashtablebot.banking.commands import Deposit
51
from hashtablebot.banking.transaction import Transaction
6-
from hashtablebot.entity.bot_user import BotUser
7-
from hashtablebot.persistence.bot_user_dao import BotUserDao
8-
from hashtablebot.user_checks import is_chatter
92

103

114
class Bank:
@@ -39,20 +32,3 @@ def redo(self) -> None:
3932
transaction = self.redo_stack.pop()
4033
transaction.execute()
4134
self.undo_stack.append(transaction)
42-
43-
def reward_chatter(self, chatter: Chatter, reward: int):
44-
if reward <= 0 or not is_chatter(chatter):
45-
return
46-
47-
author_id = int(chatter.id)
48-
49-
try:
50-
author_bot_user: BotUser = BotUserDao.get_by_id(author_id)
51-
except NoResultFound:
52-
# TODO: I guess this breaks single responsibility principle...? Should probably refactor it
53-
author_bot_user = BotUser(id=author_id)
54-
55-
self.execute(Deposit(bot_user=author_bot_user, amount=reward))
56-
57-
# TODO: should probably remove this too, saving should not happen here
58-
BotUserDao.save(author_bot_user)

hashtablebot/banking/bank_user.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Protocol
2+
3+
4+
class BankUser(Protocol):
5+
def withdraw(self, amount: int):
6+
...
7+
8+
def deposit(self, amount: int):
9+
...
10+
11+
def name(self) -> str:
12+
...
13+
14+
def get_balance(self) -> int:
15+
...

hashtablebot/banking/commands.py

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,80 @@
11
import logging
22
from dataclasses import dataclass, field
33

4+
from hashtablebot.banking.bank_user import BankUser
45
from hashtablebot.banking.transaction import Transaction
5-
from hashtablebot.entity.bot_user import BotUser
66

77

88
@dataclass
99
class Deposit:
10-
bot_user: BotUser
10+
bank_user: BankUser
1111
amount: int
1212

1313
@property
1414
def transfer_details(self) -> str:
15-
return f"${self.amount} to bot_user {self.bot_user.id}"
15+
return f"${self.amount} to bot_user {self.bank_user.name()}"
1616

1717
def execute(self) -> None:
18-
self.bot_user.deposit(self.amount)
18+
self.bank_user.deposit(self.amount)
1919
logging.info(f"Deposited {self.transfer_details}")
2020

2121
def undo(self) -> None:
22-
self.bot_user.withdraw(self.amount)
22+
self.bank_user.withdraw(self.amount)
2323
logging.info(f"Undid deposit of {self.transfer_details}")
2424

2525
def redo(self) -> None:
26-
self.bot_user.deposit(self.amount)
26+
self.bank_user.deposit(self.amount)
2727
logging.info(f"Redid deposit of {self.transfer_details}")
2828

2929

3030
@dataclass
3131
class Withdrawal:
32-
bot_user: BotUser
32+
bank_user: BankUser
3333
amount: int
3434

3535
@property
3636
def transfer_details(self) -> str:
37-
return f"${self.amount/100} from bot_user {self.bot_user.id}"
37+
return f"${self.amount/100} from bot_user {self.bank_user.name()}"
3838

3939
def execute(self) -> None:
40-
self.bot_user.withdraw(self.amount)
40+
self.bank_user.withdraw(self.amount)
4141
logging.info(f"Withdrawn {self.transfer_details}")
4242

4343
def undo(self) -> None:
44-
self.bot_user.deposit(self.amount)
44+
self.bank_user.deposit(self.amount)
4545
logging.info(f"Undid withdrawal of {self.transfer_details}")
4646

4747
def redo(self) -> None:
48-
self.bot_user.withdraw(self.amount)
48+
self.bank_user.withdraw(self.amount)
4949
logging.info(f"Redid withdrawal of {self.transfer_details}")
5050

5151

5252
@dataclass
5353
class Transfer:
54-
from_bot_user: BotUser
55-
to_bot_user: BotUser
54+
from_bank_user: BankUser
55+
to_bank_user: BankUser
5656
amount: int
5757

5858
@property
5959
def transfer_details(self) -> str:
6060
return (
61-
f"${self.amount} from bot_user {self.from_bot_user.id}"
62-
f" to bot_user {self.to_bot_user.id}"
61+
f"${self.amount} from bot_user {self.from_bank_user.name()}"
62+
f" to bot_user {self.to_bank_user.name()}"
6363
)
6464

6565
def execute(self) -> None:
66-
self.from_bot_user.withdraw(self.amount)
67-
self.to_bot_user.deposit(self.amount)
66+
self.from_bank_user.withdraw(self.amount)
67+
self.to_bank_user.deposit(self.amount)
6868
logging.info(f"Transferred {self.transfer_details}")
6969

7070
def undo(self) -> None:
71-
self.to_bot_user.withdraw(self.amount)
72-
self.from_bot_user.deposit(self.amount)
71+
self.to_bank_user.withdraw(self.amount)
72+
self.from_bank_user.deposit(self.amount)
7373
logging.info(f"Undid transfer of {self.transfer_details}")
7474

7575
def redo(self) -> None:
76-
self.from_bot_user.withdraw(self.amount)
77-
self.to_bot_user.deposit(self.amount)
76+
self.from_bank_user.withdraw(self.amount)
77+
self.to_bank_user.deposit(self.amount)
7878
logging.info(f"Redid transfer of {self.transfer_details}")
7979

8080

@@ -88,7 +88,7 @@ def execute(self) -> None:
8888
for command in self.commands:
8989
command.execute()
9090
completed_commands.append(command)
91-
except ValueError:
91+
except Exception:
9292
for command in reversed(completed_commands):
9393
command.undo()
9494
raise

hashtablebot/bot_exceptions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def get_chat_message() -> str:
1717
return "You don't have enough elis coins elisLookingAtYou"
1818

1919

20-
class InvalidPointAmountError(ExceptionWithChatMessage):
20+
class PointConversionError(ExceptionWithChatMessage):
2121
@staticmethod
2222
def get_chat_message() -> str:
2323
return "An invalid point amount was passed elisLookingAtYou"

hashtablebot/entity/bot_user.py

+7
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@ def withdraw(self, amount: int):
4343
raise NotEnoughCoinError("Not enough funds")
4444

4545
self.balance -= amount
46+
47+
def name(self) -> str:
48+
# TODO: add name attribute to bot_user
49+
return str(self.id)
50+
51+
def get_balance(self) -> int:
52+
return self.balance

hashtablebot/hash_table_bot.py

+12-19
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from hashtablebot.banking.commands import Batch, Deposit, Withdrawal
1515
from hashtablebot.bot_exceptions import (
1616
ExceptionWithChatMessage,
17-
InvalidPointAmountError,
1817
NotEnoughCoinError,
18+
PointConversionError,
1919
)
2020
from hashtablebot.entity.bot_user import BotUser
2121
from hashtablebot.memory_entity.no_prefix_command import DefaultNoPrefix
@@ -137,13 +137,6 @@ async def event_message(self, message: Message):
137137
and not message.content.startswith(await self._prefix(self, message))
138138
)
139139
if message_is_not_command:
140-
# Distribute point reward for chatting
141-
try:
142-
Bank().reward_chatter(message.author, self._chatting_message_reward)
143-
144-
except Exception as e:
145-
logging.exception(e)
146-
147140
await self._translator.translate_user_message(message)
148141

149142
elif not invoked_no_prefix_command:
@@ -281,17 +274,17 @@ async def gamble(self, ctx: commands.Context, amount_str: str):
281274

282275
try:
283276
amount = PointAmountConverter.convert(amount_str, author)
284-
except InvalidPointAmountError as e:
277+
except PointConversionError as e:
285278
await ctx.reply(e.get_chat_message())
286279
logging.exception(e)
287280
return
288281

289282
try:
290283
if random.randint(0, 1):
291-
Bank().execute(Deposit(bot_user=author, amount=amount))
284+
Bank().execute(Deposit(bank_user=author, amount=amount))
292285
message = f"{ctx.author.name} won {amount} coins elisCoin !!! POGGERS They now have {author.balance}"
293286
else:
294-
Bank().execute(Withdrawal(bot_user=author, amount=amount))
287+
Bank().execute(Withdrawal(bank_user=author, amount=amount))
295288
message = f"{ctx.author.name} lost {amount} coins... peepoSad They now have {author.balance}"
296289

297290
except ExceptionWithChatMessage as e:
@@ -409,7 +402,7 @@ async def give(self, ctx: commands.Context, target_user: User, amount_str: str):
409402

410403
try:
411404
amount = PointAmountConverter.convert(amount_str, author_bot_user)
412-
except InvalidPointAmountError as e:
405+
except PointConversionError as e:
413406
logging.exception(e)
414407
await ctx.reply(e.get_chat_message())
415408
return
@@ -425,8 +418,8 @@ async def give(self, ctx: commands.Context, target_user: User, amount_str: str):
425418
Bank().execute(
426419
Batch(
427420
[
428-
Withdrawal(bot_user=author_bot_user, amount=amount),
429-
Deposit(bot_user=target_bot_user, amount=amount),
421+
Withdrawal(bank_user=author_bot_user, amount=amount),
422+
Deposit(bank_user=target_bot_user, amount=amount),
430423
]
431424
)
432425
)
@@ -499,7 +492,7 @@ async def duel(self, ctx: commands.Context, duel_target: User, amount_str: str):
499492

500493
try:
501494
amount = PointAmountConverter.convert(amount_str, author_bot_user)
502-
except InvalidPointAmountError as e:
495+
except PointConversionError as e:
503496
logging.exception(e)
504497
await ctx.reply(e.get_chat_message())
505498
return
@@ -583,8 +576,8 @@ def store_answer(msg: Message):
583576
Bank().execute(
584577
Batch(
585578
[
586-
Deposit(bot_user=author_bot_user, amount=amount),
587-
Withdrawal(bot_user=duel_target_bot_user, amount=amount),
579+
Deposit(bank_user=author_bot_user, amount=amount),
580+
Withdrawal(bank_user=duel_target_bot_user, amount=amount),
588581
]
589582
)
590583
)
@@ -596,8 +589,8 @@ def store_answer(msg: Message):
596589
Bank().execute(
597590
Batch(
598591
[
599-
Withdrawal(bot_user=author_bot_user, amount=amount),
600-
Deposit(bot_user=duel_target_bot_user, amount=amount),
592+
Withdrawal(bank_user=author_bot_user, amount=amount),
593+
Deposit(bank_user=duel_target_bot_user, amount=amount),
601594
]
602595
)
603596
)
+27-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from hashtablebot.bot_exceptions import InvalidPointAmountError
2-
from hashtablebot.entity.bot_user import BotUser
1+
from hashtablebot.banking.bank_user import BankUser
2+
from hashtablebot.bot_exceptions import PointConversionError
33

44

55
class PointAmountConverter:
@@ -11,47 +11,56 @@ class PointAmountConverter:
1111
"""
1212

1313
@staticmethod
14-
def convert(amount: str, bot_user: BotUser) -> int:
14+
def convert(amount: str, bank_user: BankUser) -> int:
1515
"""
1616
Convert an amount of points from any format to an integer value.
17-
:param bot_user:
17+
:param bank_user:
1818
:param amount: string with a percentage, number or word describing an amount
19-
:raises: InvalidPointAmountError
19+
:raises: PointConversionError if one of these conditions is met:
20+
- the amount passed is invalid
21+
- bank_user is not passed for a numeric amount
22+
- amount exceeds 20 digits
2023
"""
24+
if len(amount) > 20:
25+
raise PointConversionError("Amount exceeds 20 digits!")
26+
2127
if amount.isnumeric():
2228
return PointAmountConverter._convert_from_num(amount)
29+
30+
if not bank_user:
31+
raise PointConversionError(
32+
"Non-numeric amounts require a BankUser to be passed!"
33+
)
34+
2335
elif amount.endswith("%"):
24-
return PointAmountConverter._convert_from_percentage(amount, bot_user)
36+
return PointAmountConverter._convert_from_percentage(amount, bank_user)
2537
else:
26-
return PointAmountConverter._convert_from_keyword(amount, bot_user)
38+
return PointAmountConverter._convert_from_keyword(amount, bank_user)
2739

2840
@staticmethod
2941
def _convert_from_num(amount: str) -> int:
3042
try:
3143
int_amount = int(amount)
3244
except ValueError:
33-
raise InvalidPointAmountError("An invalid amount of points was passed!")
45+
raise PointConversionError("An invalid amount of points was passed!")
3446
else:
3547
return int_amount
3648

3749
@staticmethod
38-
def _convert_from_percentage(amount: str, bot_user: BotUser) -> int:
50+
def _convert_from_percentage(amount: str, bank_user: BankUser) -> int:
3951
try:
4052
int_percentage = int(amount[:-1])
4153
except ValueError:
42-
raise InvalidPointAmountError("An invalid percentage was passed!")
54+
raise PointConversionError("An invalid percentage was passed!")
4355
else:
44-
if not bot_user:
45-
raise InvalidPointAmountError("Not a valid user!")
46-
47-
return (bot_user.balance * int_percentage) // 100
56+
return (bank_user.get_balance() * int_percentage) // 100
4857

4958
@staticmethod
50-
def _convert_from_keyword(amount: str, bot_user: BotUser) -> int:
59+
def _convert_from_keyword(amount: str, bank_user: BankUser) -> int:
5160
match amount:
5261
case "all":
53-
return bot_user.balance
62+
return bank_user.get_balance()
5463
case "half":
55-
return bot_user.balance // 2
64+
return bank_user.get_balance() // 2
5665
case _:
57-
raise InvalidPointAmountError("Not a valid amount")
66+
raise PointConversionError("Not a valid amount")

hashtablebot/translation.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ async def translate_user_message(self, message: Message) -> None:
130130
logging.debug(
131131
f"Translating '{message.content}' from '{user.source_lang}' to '{user.target_lang}'"
132132
)
133-
translated_msg = tss.google(
133+
translated_msg = tss.server.google(
134134
message.content, user.source_lang, user.target_lang
135135
)
136136
except Exception as e:

tests/bank_user_test.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from dataclasses import dataclass
2+
3+
from hashtablebot.bot_exceptions import NotEnoughCoinError
4+
5+
6+
@dataclass
7+
class BankUserTest:
8+
"""
9+
Test implementation of BankUser Protocol
10+
"""
11+
12+
balance: int
13+
14+
def deposit(self, amount: int):
15+
self.balance += amount
16+
17+
def withdraw(self, amount: int):
18+
if amount > self.balance:
19+
raise NotEnoughCoinError("Not enough funds")
20+
21+
self.balance -= amount
22+
23+
def name(self) -> str:
24+
return "Name"
25+
26+
def get_balance(self) -> int:
27+
return self.balance

0 commit comments

Comments
 (0)