quite a bit of development around get rates input handling
This commit is contained in:
parent
46988352ca
commit
4f81ac2e62
8 changed files with 199 additions and 20 deletions
13
poetry.lock
generated
13
poetry.lock
generated
|
|
@ -135,6 +135,17 @@ files = [
|
||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "currencies"
|
||||||
|
version = "2020.12.12"
|
||||||
|
description = "Display money format and its filthy currencies, for all money lovers out there."
|
||||||
|
optional = false
|
||||||
|
python-versions = "<4"
|
||||||
|
files = [
|
||||||
|
{file = "currencies-2020.12.12-py3-none-any.whl", hash = "sha256:33b017bd11b0a70707ffa917e80cd8c4e07a1a6b412239ee19c10f145ed5f031"},
|
||||||
|
{file = "currencies-2020.12.12.tar.gz", hash = "sha256:400cf313b8f6f33a59dcc9c9723dbe458eb8ff18a74ad2b79eba8a295a44b556"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.7"
|
version = "3.7"
|
||||||
|
|
@ -226,4 +237,4 @@ requests = ">=2.19.1"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "fc10bcffee4d61f71d09d8ee3ecd62c907cf50c34e802bf747bc0e17770e7646"
|
content-hash = "2fea7228b5d6197e7c654a55dfc2415d44e9b6f98abd859a7067910a4fc89571"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ click = "^8.1.7"
|
||||||
python-dotenv = "^1.0.1"
|
python-dotenv = "^1.0.1"
|
||||||
pyfiglet = "^1.0.2"
|
pyfiglet = "^1.0.2"
|
||||||
xecd-rates-client = "^1.0.0"
|
xecd-rates-client = "^1.0.0"
|
||||||
|
currencies = "^2020.12.12"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,3 @@ def test_get_rates_breaks_without_output():
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(get_rates)
|
result = runner.invoke(get_rates)
|
||||||
assert result.exit_code == 2
|
assert result.exit_code == 2
|
||||||
|
|
||||||
|
|
||||||
def test_get_rates_replaces_future_dates_properly():
|
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_rates_rejects_start_date_after_end_date():
|
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_rates_rejects_invalid_currency_codes():
|
|
||||||
assert False
|
|
||||||
94
tests/tests_unit/test_input_handling.py
Normal file
94
tests/tests_unit/test_input_handling.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from currencies import Currency
|
||||||
|
from currencies.exceptions import CurrencyDoesNotExist
|
||||||
|
|
||||||
|
from xexe.inputs_handling import handle_get_rates_inputs
|
||||||
|
from xexe.utils import DateRange
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_works_with_full_correct_inputs():
|
||||||
|
handled_inputs = handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
end_date=datetime.datetime.now() + datetime.timedelta(days=7),
|
||||||
|
currencies="USD,EUR,GBP",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.csv",
|
||||||
|
)
|
||||||
|
expected_result = {
|
||||||
|
"date_range": DateRange(
|
||||||
|
start_date=datetime.datetime.now().date(),
|
||||||
|
end_date=(datetime.datetime.now() + datetime.timedelta(days=7)).date(),
|
||||||
|
),
|
||||||
|
"currencies": {Currency("USD"), Currency("EUR"), Currency("GBP")},
|
||||||
|
"dry_run": False,
|
||||||
|
"output": pathlib.Path("test_output.csv"),
|
||||||
|
}
|
||||||
|
assert handled_inputs == expected_result
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_raises_with_bad_currency_code():
|
||||||
|
|
||||||
|
with pytest.raises(CurrencyDoesNotExist):
|
||||||
|
handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
end_date=datetime.datetime.now() + datetime.timedelta(days=7),
|
||||||
|
currencies="not_a_currency,USD,not_this_either",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.csv",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_raises_with_start_date_after_end_date():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
end_date=datetime.datetime.now() - datetime.timedelta(days=7),
|
||||||
|
currencies="GBP,USD",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.csv",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_raises_with_output_different_than_csv():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
end_date=datetime.datetime.now() + datetime.timedelta(days=7),
|
||||||
|
currencies="GBP,USD",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.xlsx",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_brings_future_end_date_to_today():
|
||||||
|
handled_inputs = handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now() - datetime.timedelta(days=7),
|
||||||
|
end_date=datetime.datetime.now() + datetime.timedelta(days=7),
|
||||||
|
currencies="USD,EUR,GBP",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.csv",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert handled_inputs["date_range"].end_date == datetime.datetime.now().date()
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_input_rates_start_and_end_date_equal_works_fine():
|
||||||
|
handled_inputs = handle_get_rates_inputs(
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
end_date=datetime.datetime.now(),
|
||||||
|
currencies="USD,EUR,GBP",
|
||||||
|
dry_run=False,
|
||||||
|
output="test_output.csv",
|
||||||
|
)
|
||||||
|
expected_result = {
|
||||||
|
"date_range": DateRange(
|
||||||
|
start_date=datetime.datetime.now().date(),
|
||||||
|
end_date=(datetime.datetime.now() + datetime.timedelta(days=7)).date(),
|
||||||
|
),
|
||||||
|
"currencies": {Currency("USD"), Currency("EUR"), Currency("GBP")},
|
||||||
|
"dry_run": False,
|
||||||
|
"output": "test_output.csv",
|
||||||
|
}
|
||||||
|
assert handled_inputs == expected_result
|
||||||
|
|
@ -74,7 +74,7 @@ def get_rates(
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
output: pathlib.Path,
|
output: pathlib.Path,
|
||||||
):
|
):
|
||||||
handle_get_rates_inputs(
|
inputs = handle_get_rates_inputs(
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
currencies=currencies,
|
currencies=currencies,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import pathlib
|
import pathlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from currencies import Currency
|
||||||
|
|
||||||
|
DEFAULT_CURRENCIES = {Currency("EUR"), Currency("GBP"), Currency("USD")}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PATHS:
|
class PATHS:
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,51 @@
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import pathlib
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from currencies import Currency
|
||||||
|
|
||||||
|
from xexe.constants import DEFAULT_CURRENCIES
|
||||||
|
from xexe.utils import DateRange
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
def handle_get_rates_inputs(start_date, end_date, currencies, dry_run, output):
|
def handle_get_rates_inputs(
|
||||||
|
start_date: Union[datetime.datetime, datetime.date],
|
||||||
|
end_date: Union[datetime.datetime, datetime.date],
|
||||||
|
currencies: Union[None, str],
|
||||||
|
dry_run: bool,
|
||||||
|
output: Union[str, pathlib.Path],
|
||||||
|
):
|
||||||
logger.info("Handling inputs.")
|
logger.info("Handling inputs.")
|
||||||
logger.debug(f"Received start_date: {start_date}")
|
|
||||||
logger.debug(f"Received end_date: {end_date}")
|
date_range = DateRange(start_date=start_date.date(), end_date=end_date.date())
|
||||||
logger.debug(f"Received currencies: {currencies}")
|
|
||||||
logger.debug(f"dry_run state: {dry_run}")
|
if date_range.end_date > datetime.datetime.today().date():
|
||||||
logger.debug(f"Received output: {output}")
|
date_range.end_date = datetime.datetime.today().date()
|
||||||
|
|
||||||
|
if currencies:
|
||||||
|
# CLI input comes as string of comma-separated currency codes
|
||||||
|
currencies = [currency_code.strip() for currency_code in currencies.split(",")]
|
||||||
|
tmp = [Currency(currency_code) for currency_code in currencies]
|
||||||
|
currencies = tmp
|
||||||
|
|
||||||
|
if currencies is None:
|
||||||
|
currencies = DEFAULT_CURRENCIES
|
||||||
|
|
||||||
|
# The Path constructor is idempotent, so this works equally fine if output
|
||||||
|
# is a string or an actual Path object.
|
||||||
|
output = pathlib.Path(output)
|
||||||
|
if output.suffix != ".csv":
|
||||||
|
raise ValueError("Output must be a .csv file.")
|
||||||
|
|
||||||
|
prepared_inputs = {
|
||||||
|
"date_range": date_range,
|
||||||
|
"currencies": currencies,
|
||||||
|
"dry_run": dry_run,
|
||||||
|
"output": output,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(prepared_inputs)
|
||||||
|
return prepared_inputs
|
||||||
|
|
|
||||||
42
xexe/utils.py
Normal file
42
xexe/utils.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class DateRange:
|
||||||
|
|
||||||
|
def __init__(self, start_date: datetime.date, end_date: datetime.date):
|
||||||
|
if type(start_date) != datetime.date or type(end_date) != datetime.date:
|
||||||
|
raise TypeError("start_date and end_date must be date objects.")
|
||||||
|
|
||||||
|
if start_date > end_date:
|
||||||
|
raise ValueError("start_date can't be after end_date.")
|
||||||
|
|
||||||
|
self._start_date = start_date
|
||||||
|
self._end_date = end_date
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start_date(self):
|
||||||
|
return self._start_date
|
||||||
|
|
||||||
|
@start_date.setter
|
||||||
|
def start_date(self, value: datetime.date):
|
||||||
|
if type(value) != datetime.date:
|
||||||
|
raise TypeError("start_date must be a date object.")
|
||||||
|
|
||||||
|
if value > self._end_date:
|
||||||
|
raise ValueError("start_date can't be after end_date.")
|
||||||
|
|
||||||
|
self._start_date = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end_date(self):
|
||||||
|
return self._end_date
|
||||||
|
|
||||||
|
@end_date.setter
|
||||||
|
def end_date(self, value: datetime.date):
|
||||||
|
if type(value) != datetime.date:
|
||||||
|
raise TypeError("end_date must be a date object.")
|
||||||
|
|
||||||
|
if value < self._start_date:
|
||||||
|
raise ValueError("end_date can't be before start_date.")
|
||||||
|
|
||||||
|
self._end_date = value
|
||||||
Loading…
Add table
Add a link
Reference in a new issue