Skip to content

Commit dd755f5

Browse files
authored
Merge pull request #26 from Qwizi/menu
Added interactive menu
2 parents 32bdccf + ae2e29e commit dd755f5

File tree

6 files changed

+228
-7
lines changed

6 files changed

+228
-7
lines changed

task3_dsw/invoices.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
"""Invoices module."""
2+
from __future__ import annotations
3+
24
import csv
3-
import datetime
5+
import datetime # noqa: TCH003
46
import uuid
57
from pathlib import Path
68

7-
from pydantic import BaseModel, Field
9+
from pydantic import BaseModel, Field, field_validator
810

911
from task3_dsw import settings
1012

1113

1214
class Invoice(BaseModel):
1315
"""Invoice model."""
1416

15-
id: uuid.UUID = Field(default_factory=uuid.uuid4) # noqa: A003
17+
id: uuid.UUID = Field(default_factory=uuid.uuid4) # noqa: A003, RUF100
1618
amount: float
1719
currency: str
1820
date: datetime.date
1921

22+
@field_validator("currency")
23+
def currency_is_valid(cls, v) -> str: # noqa: N805, ANN001
24+
"""Validate currency code."""
25+
if v not in settings.CURRENCIES:
26+
msg = f"Currency code {v} is not valid."
27+
raise ValueError(msg)
28+
return v
29+
2030

2131
def create_invoices_file() -> None:
2232
"""Create invoices file."""
@@ -30,6 +40,19 @@ def add_invoice_to_file(data: Invoice) -> Invoice:
3040
"""
3141
Add invoice to file.
3242
43+
Example:
44+
-------
45+
```python
46+
47+
from task3_dsw.invoices import add_invoice_to_file, Invoice
48+
49+
invoice = add_invoice_to_file(data=Invoice(
50+
amount=100,
51+
currency="USD",
52+
date="2021-01-01",
53+
))
54+
```
55+
3356
Args:
3457
----
3558
data: Invoice to be added.
@@ -63,3 +86,20 @@ def get_invoice_from_file(invoice_id: uuid.UUID) -> Invoice or None:
6386
if row[0] == str(invoice_id):
6487
return Invoice(id=row[0], amount=row[1], currency=row[2], date=row[3])
6588
return None
89+
90+
91+
def get_invoices_from_file() -> list[Invoice]:
92+
"""
93+
Get invoices from file.
94+
95+
Returns
96+
-------
97+
List[Invoice]: List of invoices.
98+
"""
99+
with Path.open(settings.INVOICES_FILE_PATH, "r", newline="") as invoices_file:
100+
reader = csv.reader(invoices_file)
101+
next(reader)
102+
return [
103+
Invoice(id=row[0], amount=row[1], currency=row[2], date=row[3])
104+
for row in reader
105+
]

task3_dsw/logger.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import logging
44
import sys
55

6+
from task3_dsw import settings
7+
68
logger = logging.getLogger("Task3 DSW Logger")
79

810
stdout = logging.StreamHandler(stream=sys.stdout)
@@ -14,4 +16,7 @@
1416
stdout.setFormatter(fmt)
1517
logger.addHandler(stdout)
1618

17-
logger.setLevel(logging.INFO)
19+
if settings.DEBUG:
20+
logger.setLevel(logging.DEBUG)
21+
else:
22+
logger.setLevel(logging.ERROR)

task3_dsw/main.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
create_invoices_file,
99
)
1010
from task3_dsw.logger import logger
11+
from task3_dsw.menu import (
12+
AddInvoiceAction,
13+
AddPaymentAction,
14+
ExitAction,
15+
InteractiveMenu,
16+
)
1117
from task3_dsw.payments import (
1218
create_payments_file,
1319
)
@@ -37,6 +43,7 @@ def create_parser() -> argparse.ArgumentParser:
3743
type=str,
3844
help="File with invoices",
3945
)
46+
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode.")
4047
return parser
4148

4249

@@ -54,14 +61,34 @@ def main() -> None:
5461
if args.currencies:
5562
settings.CURRENCIES = args.currencies
5663

64+
if args.verbose:
65+
settings.DEBUG = args.verbose
66+
logger.setLevel("DEBUG")
67+
5768
# initialize NBPApiClient with settings
5869
nbp_api_client = nbp_api.NBPApiClient()
5970

6071
if args.interactive:
61-
logger.warning("We are in interactive mode.")
72+
logger.debug("We are in interactive mode.")
73+
# initialize InteractiveMenu with nbp_api_client
74+
interactive_menu = InteractiveMenu()
75+
interactive_menu.add_action(
76+
AddInvoiceAction(
77+
name="Add invoice", tag="add_invoice", description="Add invoice"
78+
)
79+
)
80+
interactive_menu.add_action(
81+
AddPaymentAction(
82+
name="Add payment", tag="add_payment", description="Add payment"
83+
)
84+
)
85+
interactive_menu.add_action(
86+
ExitAction(name="Exit", tag="exit", description="Exit")
87+
)
88+
interactive_menu.run()
6289
else:
63-
logger.info("We are in non-interactive mode.")
64-
logger.info(
90+
logger.debug("We are in non-interactive mode.")
91+
logger.debug(
6592
nbp_api_client.get_exchange_rate(
6693
data=nbp_api.ExchangeRateSchema(code="xD", date="2021-01-04")
6794
)

task3_dsw/menu.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Module for creating interactive menu in console."""
2+
import sys
3+
4+
from task3_dsw.invoices import (
5+
Invoice,
6+
add_invoice_to_file,
7+
get_invoice_from_file,
8+
)
9+
from task3_dsw.logger import logger
10+
from task3_dsw.payments import Payment, add_payment_to_file
11+
12+
13+
class Action:
14+
"""Action class for creating action in interactive menu."""
15+
16+
def __init__(self, name: str, tag: str, description: str) -> None:
17+
"""Initialize Action class."""
18+
self.name = name
19+
self.tag = tag
20+
self.description = description
21+
22+
def __str__(self) -> str:
23+
"""Return name of action."""
24+
return f"{self.name}: {self.description}"
25+
26+
def execute(self) -> None:
27+
"""Execute action."""
28+
logger.debug("Execute action: %s", self.name)
29+
30+
31+
class ExitAction(Action):
32+
"""ExitAction class for creating exit action in interactive menu."""
33+
34+
def execute(self) -> None:
35+
"""Execute action."""
36+
logger.debug("Execute action: %s", self.name)
37+
sys.exit(0)
38+
39+
40+
class AddInvoiceAction(Action):
41+
"""AddInvoiceAction class for creating add invoice action in interactive menu."""
42+
43+
def execute(self) -> None:
44+
"""Execute action for adding invoice."""
45+
try:
46+
amount = float(input("Enter amount: "))
47+
currency = input("Enter currency: ")
48+
date = input("Enter date: ")
49+
invoice_schema = add_invoice_to_file(
50+
data=Invoice(amount=amount, currency=currency, date=date)
51+
)
52+
logger.debug("Added invoice: %s", invoice_schema)
53+
except ValueError as e:
54+
logger.error("Invalid value: %s", e)
55+
except FileNotFoundError as e:
56+
logger.error("Something went wrong: %s", e)
57+
58+
59+
class AddPaymentAction(Action):
60+
"""AddPaymentAction class for creating add payment action in interactive menu."""
61+
62+
def execute(self) -> None:
63+
"""Execute action for adding payment."""
64+
try:
65+
invoice_id = input("Enter invoice id: ")
66+
amount = float(input("Enter amount: "))
67+
currency = input("Enter currency: ")
68+
date = input("Enter date: ")
69+
70+
invoice_from_file = get_invoice_from_file(invoice_id=invoice_id)
71+
if invoice_from_file is None:
72+
logger.error(f"Invoice with id {invoice_id} not exists.")
73+
return
74+
payment_schema = add_payment_to_file(
75+
data=Payment(
76+
amount=amount, currency=currency, date=date, invoice_id=invoice_id
77+
)
78+
)
79+
logger.debug("Added payment: %s", payment_schema)
80+
except ValueError as e:
81+
logger.error("Invalid value: %s", e)
82+
83+
except FileNotFoundError as e:
84+
logger.error("Something went wrong: %s", e)
85+
86+
87+
class InteractiveMenu:
88+
"""InteractiveMenu class for creating interactive menu in console."""
89+
90+
def __init__(self) -> None:
91+
"""Initialize InteractiveMenu class."""
92+
self.actions = []
93+
94+
def run(self) -> None:
95+
"""Run interactive menu."""
96+
while True:
97+
self.print_actions()
98+
action_id = input("Choose action: ")
99+
logger.debug("User choose action: %s", action_id)
100+
self.run_action(int(action_id))
101+
102+
def add_action(self, action: Action) -> None:
103+
"""
104+
Add action to interactive menu.
105+
106+
Args:
107+
----
108+
action: action to add
109+
"""
110+
self.actions.append(action)
111+
112+
def print_actions(self) -> None:
113+
"""Print all actions in interactive menu."""
114+
for action in self.actions:
115+
print("{}. {}".format(self.actions.index(action) + 1, action)) # noqa: UP032, T201
116+
117+
def run_action(self, action_id: int) -> None:
118+
"""
119+
Run action from interactive menu.
120+
121+
Args:
122+
----
123+
action_id: id of action
124+
125+
Raises:
126+
------
127+
IndexError: if action with given id not exists
128+
"""
129+
try:
130+
action = self.actions[action_id - 1]
131+
logger.debug("Run action: %s", action)
132+
action.execute()
133+
except IndexError:
134+
logger.error("Action with id %s not exists.", action_id)

task3_dsw/payments.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ def add_payment_to_file(data: Payment) -> Payment:
2727
"""
2828
Add payment to file.
2929
30+
Example:
31+
-------
32+
```python
33+
from task3_dsw.payments import add_payment_to_file, Payment
34+
35+
payment = add_payment_to_file(data=Payment(
36+
invoice_id="123e4567-e89b-12d3-a456-426614174000",
37+
amount=100,
38+
currency="USD",
39+
date="2021-01-01",
40+
))
41+
```
42+
3043
Args:
3144
----
3245
file_path: Path to file.

task3_dsw/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ class Settings(BaseSettings):
1010
1111
Attributes
1212
----------
13+
DEBUG: bool - debug mode
1314
CURRENCIES: list[str] - list of valid currencies
1415
INVOICES_FILE_PATH: str - path to invoices file
1516
PAYMENTS_FILE_PATH: str - path to payments file
1617
"""
1718

19+
DEBUG: bool = False
1820
INVOICES_FILE_PATH: str = "./data/invoices.csv"
1921
PAYMENTS_FILE_PATH: str = "./data/payments.csv"
2022
CURRENCIES: list[str] = ["EUR", "USD", "GBP"]

0 commit comments

Comments
 (0)