From 44dee0b762d8f952517477d91deab838e0f56d9d Mon Sep 17 00:00:00 2001 From: Daniil Fajnberg Date: Fri, 26 Nov 2021 21:38:10 +0100 Subject: [PATCH] finished tests --- src/mwfin/constants.py | 1 + src/mwfin/functions.py | 14 ++++---- tests/test_functions.py | 72 ++++++++++++++++++++--------------------- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/mwfin/constants.py b/src/mwfin/constants.py index 9e1f0aa..4f6468e 100644 --- a/src/mwfin/constants.py +++ b/src/mwfin/constants.py @@ -8,6 +8,7 @@ FIN_STMT_URL_SUFFIX = { IS: '', CF: '/cash-flow' } +END_DATE = 'End Date' # All items marked `False` do not need to be scraped # because they are calculated from other items (e.g. growth or ratios). diff --git a/src/mwfin/functions.py b/src/mwfin/functions.py index 59f2ed7..12ad9ff 100644 --- a/src/mwfin/functions.py +++ b/src/mwfin/functions.py @@ -4,6 +4,8 @@ from aiohttp.client import ClientSession from bs4 import BeautifulSoup from bs4.element import ResultSet, Tag +from . import constants + # The resulting dictionary's keys correspond to the name of the item (row) in the financial statement, # while its values will always be tuples with a length corresponding to the number of periods (columns) @@ -56,15 +58,15 @@ def extract_all_data(soup: BeautifulSoup) -> ResultDict: pass -async def _get_financial_statement(statement: str, ticker_symbol: str, yearly: bool = True, quarterly: bool = True, +async def _get_financial_statement(statement: str, ticker_symbol: str, quarterly: bool = False, session: ClientSession = None) -> ResultDict: """ Returns data from the specified financial statement of the specified company. """ - pass # TODO: Don't allow both yearly and quarterly, instead only have `quarterly` Flag + pass -async def get_balance_sheet(ticker_symbol: str, yearly: bool = True, quarterly: bool = True, +async def get_balance_sheet(ticker_symbol: str, quarterly: bool = False, session: ClientSession = None) -> ResultDict: """ Returns data from the balance sheet of the specified company. @@ -72,7 +74,7 @@ async def get_balance_sheet(ticker_symbol: str, yearly: bool = True, quarterly: pass -async def get_income_statement(ticker_symbol: str, yearly: bool = True, quarterly: bool = True, +async def get_income_statement(ticker_symbol: str, quarterly: bool = False, session: ClientSession = None) -> ResultDict: """ Returns data from the income statement of the specified company. @@ -80,7 +82,7 @@ async def get_income_statement(ticker_symbol: str, yearly: bool = True, quarterl pass -async def get_cash_flow_statement(ticker_symbol: str, yearly: bool = True, quarterly: bool = True, +async def get_cash_flow_statement(ticker_symbol: str, quarterly: bool = False, session: ClientSession = None) -> ResultDict: """ Returns data from the cash flow statement of the specified company. @@ -88,7 +90,7 @@ async def get_cash_flow_statement(ticker_symbol: str, yearly: bool = True, quart pass -async def get_company_financials(ticker_symbol: str, yearly: bool = True, quarterly: bool = True, +async def get_company_financials(ticker_symbol: str, quarterly: bool = False, session: ClientSession = None) -> ResultDict: """ Returns all fundamentals (balance sheet, income statement and cash flow statement) of the specified company. diff --git a/tests/test_functions.py b/tests/test_functions.py index a466d12..c8e6c23 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -5,7 +5,7 @@ from unittest.mock import patch, MagicMock, AsyncMock, call from bs4 import BeautifulSoup from mwfin import functions -from mwfin.constants import HTML_PARSER, BASE_URL, FIN_STMT_URL_SUFFIX, IS, BS, CF +from mwfin.constants import HTML_PARSER, BASE_URL, FIN_STMT_URL_SUFFIX, IS, BS, CF, END_DATE THIS_DIR = Path(__file__).parent @@ -81,7 +81,7 @@ class FunctionsTestCase(IsolatedAsyncioTestCase): test_row_data = ('item_name', (123, 456)) mock_extract_row_data.return_value = test_row_data expected_output = { - None: test_end_dates, + END_DATE: test_end_dates, test_row_data[0]: test_row_data[1], test_row_data[0]: test_row_data[1], } @@ -94,23 +94,14 @@ class FunctionsTestCase(IsolatedAsyncioTestCase): @patch.object(functions, 'extract_all_data') @patch.object(functions, 'soup_from_url') async def test__get_financial_statement(self, mock_soup_from_url, mock_extract_all_data): - # TODO: separate dictionaries for different periods? mock_session = MagicMock() - test_ticker = 'bar' - test_url = f'{BASE_URL}/{test_ticker}/financials{FIN_STMT_URL_SUFFIX[BS]}' + test_ticker, statement = 'bar', BS + test_url = f'{BASE_URL}/{test_ticker}/financials{FIN_STMT_URL_SUFFIX[statement]}' mock_soup_from_url.return_value = mock_soup = MagicMock() - mock_extract_all_data.return_value = mock_data = {'foo': 'bar'} + mock_extract_all_data.return_value = expected_output = {'foo': 'bar'} - yearly, quarterly = False, False - expected_output = {} - output = await functions._get_financial_statement(BS, test_ticker, yearly, quarterly, mock_session) - self.assertDictEqual(expected_output, output) - mock_soup_from_url.assert_not_called() - mock_extract_all_data.assert_not_called() - - yearly = True - expected_output = mock_data - output = await functions._get_financial_statement(BS, test_ticker, yearly, quarterly, mock_session) + quarterly = False + output = await functions._get_financial_statement(statement, test_ticker, quarterly, mock_session) self.assertDictEqual(expected_output, output) mock_soup_from_url.assert_called_once_with(test_url, mock_session) mock_extract_all_data.assert_called_once_with(mock_soup) @@ -118,45 +109,52 @@ class FunctionsTestCase(IsolatedAsyncioTestCase): mock_extract_all_data.reset_mock() quarterly = True - output = await functions._get_financial_statement(BS, test_ticker, yearly, quarterly, mock_session) + output = await functions._get_financial_statement(statement, test_ticker, quarterly, mock_session) self.assertDictEqual(expected_output, output) - mock_soup_from_url.assert_has_calls([ - call(test_url, mock_session), - call(test_url + '/quarter', mock_session), - ]) - mock_extract_all_data.assert_has_calls([call(mock_soup), call(mock_soup)]) + mock_soup_from_url.assert_called_once_with(test_url + '/quarter', mock_session) + mock_extract_all_data.assert_called_once_with(mock_soup) @patch.object(functions, '_get_financial_statement') async def test_get_balance_sheet(self, mock__get_financial_statement): - symbol, yearly, quarterly, mock_session = 'foo', True, False, MagicMock() + symbol, quarterly, mock_session = 'foo', False, MagicMock() mock__get_financial_statement.return_value = expected_output = 'bar' - output = functions.get_balance_sheet(symbol, yearly, quarterly, mock_session) + output = await functions.get_balance_sheet(symbol, quarterly, mock_session) self.assertEqual(expected_output, output) - mock__get_financial_statement.assert_called_once_with(BS, symbol, yearly, quarterly, mock_session) + mock__get_financial_statement.assert_called_once_with(BS, symbol, quarterly, mock_session) @patch.object(functions, '_get_financial_statement') async def test_get_income_statement(self, mock__get_financial_statement): - symbol, yearly, quarterly, mock_session = 'foo', True, False, MagicMock() + symbol, quarterly, mock_session = 'foo', False, MagicMock() mock__get_financial_statement.return_value = expected_output = 'bar' - output = functions.get_income_statement(symbol, yearly, quarterly, mock_session) + output = await functions.get_income_statement(symbol, quarterly, mock_session) self.assertEqual(expected_output, output) - mock__get_financial_statement.assert_called_once_with(IS, symbol, yearly, quarterly, mock_session) + mock__get_financial_statement.assert_called_once_with(IS, symbol, quarterly, mock_session) @patch.object(functions, '_get_financial_statement') async def test_get_cash_flow_statement(self, mock__get_financial_statement): - symbol, yearly, quarterly, mock_session = 'foo', True, False, MagicMock() + symbol, quarterly, mock_session = 'foo', False, MagicMock() mock__get_financial_statement.return_value = expected_output = 'bar' - output = functions.get_cash_flow_statement(symbol, yearly, quarterly, mock_session) + output = await functions.get_cash_flow_statement(symbol, quarterly, mock_session) self.assertEqual(expected_output, output) - mock__get_financial_statement.assert_called_once_with(CF, symbol, yearly, quarterly, mock_session) + mock__get_financial_statement.assert_called_once_with(CF, symbol, quarterly, mock_session) @patch.object(functions, 'get_cash_flow_statement') @patch.object(functions, 'get_income_statement') @patch.object(functions, 'get_balance_sheet') async def test_get_company_financials(self, mock_get_bs, mock_get_is, mock_get_cf): - symbol, yearly, quarterly, mock_session = 'foo', True, False, MagicMock() - mock_get_bs.return_value = {'a': 1} - mock_get_is.return_value = {'b': 2} - mock_get_cf.return_value = {'c': 3} - expected_output = {'a': 1, 'b': 2, 'c': 3} - # TODO: unfinished + mock_end_dates = ('bar', 'baz') + mock_get_bs.return_value = {END_DATE: mock_end_dates, 'a': (1, 2)} + mock_get_is.return_value = {END_DATE: mock_end_dates, 'b': (2, 3)} + mock_get_cf.return_value = {END_DATE: mock_end_dates, 'c': (3, 4)} + expected_output = { + END_DATE: mock_end_dates, + 'a': (1, 2), + 'b': (2, 3), + 'c': (3, 4) + } + symbol, quarterly, mock_session = 'foo', False, MagicMock() + output = await functions.get_company_financials(symbol, quarterly, mock_session) + self.assertDictEqual(expected_output, output) + mock_get_bs.assert_called_once_with(symbol, quarterly, mock_session) + mock_get_is.assert_called_once_with(symbol, quarterly, mock_session) + mock_get_cf.assert_called_once_with(symbol, quarterly, mock_session)