summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordio <kulbinderdio@gmail.com>2023-08-28 15:28:33 +0100
committerGitHub <noreply@github.com>2023-08-28 14:28:33 +0000
commitdb9d462188d6115d9a3e215ea3b633bb26fcd97e (patch)
tree9195c68605dc2b517124abd2212a3073170cf29f
parentf8241a6b81f343f358b01927c0abd9ac693a9be6 (diff)
Feature/companies house (#4721)
* Addition of UK Companies House data. Allows you to search for companies and inspect their details and download any filings including accounts * reformatting of code * reformatting * reformat code * reformat * fix codespell issue * updated for changes in way key are handled * small reformatting * added timeout for requests * formatting * ruff order fixes * black reformatting * merge issue - removed function call_openbb * black format change * Added new Companies House functionality to SDK * formattng * more Formatting (black) * Ruff fix * change return type for function * forced type conversion * return type fix * correct error return type * fix return type * deleted companieshouse tests as they require API key * Extra validation added for when data not exist from remote calls * Added extra commands to en.yml * spelling mistake correction * fat finger trouble corrected * Save documents with identifying names and allow documents to be viewed directly within OpenBB * ruff updates * black changes * ruff change * Addition of currently loaded company information in menu * add entry in the API keys guide for Companies Hosue * adds images to api keys guide * adds section in SDK API Keys Guide for Companies House * test file for companieshouse_model * changed test data due to ruff line size limit * resolve merge issues * resolve merge issue * doc strings examples * added docstrings for companieshouse_model * allow download_filking_document methid in view class to be called from SDK * added docstring for download_filing_document SDK method * correct Companies house key literal * replace starnge . with , * add check_api_key decorator to each methopd in controller * implement next and previous for looping through filings * ruff changes * correct tests * companing filings now allows you specify category * ruff line length issue resolved * retrieves all filings and added charges command * bug fix * Added limit parameter to filings command, def :100 * fix ruff issue * added get_charges to sdk and to test class * resolved ruff line length issue * add some view tests * add some controller tests * applying fix not merged to develop yet, column_keep_types=['Company Number'] * fix i18n description * update test_print_help * actually update test_print_help.txt * Update en.yml - no colon * rerorder imports - ruff failurre * simple text change * ruff fix * help text change trying to fix pytest issue --------- Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com> Co-authored-by: James Maslek <jmaslek11@gmail.com>
-rw-r--r--openbb_terminal/alternative/alt_controller.py12
-rw-r--r--openbb_terminal/alternative/companieshouse/companieshouse_controller.py337
-rw-r--r--openbb_terminal/alternative/companieshouse/companieshouse_model.py481
-rw-r--r--openbb_terminal/alternative/companieshouse/companieshouse_view.py253
-rw-r--r--openbb_terminal/alternative/companieshouse/company.py14
-rw-r--r--openbb_terminal/alternative/companieshouse/company_doc.py31
-rw-r--r--openbb_terminal/alternative/companieshouse/filing_data.py19
-rw-r--r--openbb_terminal/core/config/paths_helper.py1
-rw-r--r--openbb_terminal/core/models/preferences_model.py1
-rw-r--r--openbb_terminal/core/sdk/controllers/alt_sdk_controller.py17
-rw-r--r--openbb_terminal/core/sdk/models/alt_sdk_model.py37
-rw-r--r--openbb_terminal/core/sdk/sdk_init.py2
-rw-r--r--openbb_terminal/core/sdk/trail_map.csv8
-rw-r--r--openbb_terminal/keys_controller.py30
-rw-r--r--openbb_terminal/keys_model.py77
-rw-r--r--openbb_terminal/miscellaneous/i18n/en.yml15
-rw-r--r--openbb_terminal/miscellaneous/models/hub_credentials.json1
-rw-r--r--openbb_terminal/sdk.py1
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/test_companieshouse_controller.py172
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/test_companieshouse_model.py299
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/test_companieshouse_view.py283
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_controller/test_print_help.txt10
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_view/test_display_charges.txt11
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_view/test_display_filings.txt5
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_view/test_display_officers.txt3
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_view/test_display_persons_with_significant_control.txt3
-rw-r--r--tests/openbb_terminal/alternative/companieshouse/txt/test_companieshouse_view/test_display_search.txt4
-rw-r--r--tests/openbb_terminal/alternative/txt/test_alt_controller/test_print_help.txt7
-rw-r--r--website/content/sdk/usage/guides/api-keys.md28
-rw-r--r--website/content/terminal/usage/guides/api-keys.md23
30 files changed, 2174 insertions, 11 deletions
diff --git a/openbb_terminal/alternative/alt_controller.py b/openbb_terminal/alternative/alt_controller.py
index cc4e7e1713d..4352e8193df 100644
--- a/openbb_terminal/alternative/alt_controller.py
+++ b/openbb_terminal/alternative/alt_controller.py
@@ -22,7 +22,7 @@ class AlternativeDataController(BaseController):
"""Alternative Controller class"""
CHOICES_COMMANDS: List[str] = ["hn"]
- CHOICES_MENUS = ["covid", "oss", "realestate"]
+ CHOICES_MENUS = ["covid", "oss", "realestate", "companieshouse"]
PATH = "/alternative/"
CHOICES_GENERATION = True
@@ -41,6 +41,7 @@ class AlternativeDataController(BaseController):
mt.add_menu("covid")
mt.add_menu("oss")
mt.add_menu("realestate")
+ mt.add_menu("companieshouse")
mt.add_raw("\n")
mt.add_cmd("hn")
console.print(text=mt.menu_text, menu="Alternative")
@@ -94,3 +95,12 @@ class AlternativeDataController(BaseController):
)
self.queue = self.load_class(RealEstateController, self.queue)
+
+ @log_start_end(log=logger)
+ def call_companieshouse(self, _):
+ """Process companieshouse command."""
+ from openbb_terminal.alternative.companieshouse.companieshouse_controller import (
+ CompaniesHouseController,
+ )
+
+ self.queue = self.load_class(CompaniesHouseController, self.queue)
diff --git a/openbb_terminal/alternative/companieshouse/companieshouse_controller.py b/openbb_terminal/alternative/companieshouse/companieshouse_controller.py
new file mode 100644
index 00000000000..40353ba5b00
--- /dev/null
+++ b/openbb_terminal/alternative/companieshouse/companieshouse_controller.py
@@ -0,0 +1,337 @@
+"""Companies House Controller."""
+__docformat__ = "numpy"
+
+import argparse
+import logging
+from typing import List, Optional
+
+from openbb_terminal.alternative.companieshouse import companieshouse_view
+from openbb_terminal.core.session.current_user import get_current_user
+from openbb_terminal.custom_prompt_toolkit import NestedCompleter
+from openbb_terminal.decorators import check_api_key, log_start_end
+from openbb_terminal.helper_funcs import (
+ EXPORT_ONLY_RAW_DATA_ALLOWED,
+ check_positive,
+)
+from openbb_terminal.menu import session
+from openbb_terminal.parent_classes import BaseController
+from openbb_terminal.rich_config import MenuText, console
+
+logger = logging.getLogger(__name__)
+
+
+class CompaniesHouseController(BaseController):
+
+ """Companies House Controller class."""
+
+ CHOICES_COMMANDS = [
+ "search",
+ "load",
+ "officers",
+ "signifcontrol",
+ "filings",
+ "filingdocument",
+ "charges",
+ ]
+ PATH = "/alternative/companieshouse/"
+ CHOICES_GENERATION = True
+
+ def __init__(self, queue: Optional[List[str]] = None):
+ """Construct Data."""
+ super().__init__(queue)
+
+ self.companyNo = ""
+ self.companyName = ""
+ self.filingCategory = ""
+ self.filing_total_count = 0
+ self.filing_end_index = 0
+ self.filing_start_index = 0
+ if session and get_current_user().preferences.USE_PROMPT_TOOLKIT:
+ choices: dict = self.choices_default
+ self.completer = NestedCompleter.from_nested_dict(choices)
+
+ def print_help(self):
+ """Print help"""
+ company_string = (
+ f"{self.companyNo} ({self.companyName})" if self.companyNo else ""
+ )
+
+ mt = MenuText("alternative/companieshouse/")
+ mt.add_param("_company", company_string)
+ mt.add_raw("\n")
+ mt.add_cmd("search")
+ mt.add_cmd("load")
+ mt.add_cmd("officers")
+ mt.add_cmd("signifcontrol")
+ mt.add_cmd("filings")
+ mt.add_cmd("filingdocument")
+ mt.add_cmd("charges")
+
+ console.print(text=mt.menu_text, menu="UK Companies House Data")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_search(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="search",
+ description="Select the company name to search for. [Source: UK Companies House]",
+ )
+ parser.add_argument(
+ "-n",
+ "--name",
+ help="name",
+ type=str.upper,
+ required="-h" not in other_args,
+ dest="name",
+ metavar="name",
+ nargs="+",
+ )
+
+ parser.add_argument(
+ "-l",
+ "--limit",
+ help="Number of entries to return",
+ type=check_positive,
+ required=False,
+ dest="limit",
+ metavar="limit",
+ default=20,
+ )
+
+ if (
+ other_args
+ and "-n" not in other_args[0]
+ and "--name" not in other_args[0]
+ and "-h" not in other_args
+ ):
+ other_args.insert(0, "-n")
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if ns_parser:
+ if ns_parser.name:
+ query = " ".join(ns_parser.name)
+ companieshouse_view.display_search(
+ query, ns_parser.limit, export=ns_parser.export
+ )
+ else:
+ console.print("[red]No entries found for search string[/red]\n")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_load(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="load",
+ description="Select the company number to get detailed info on. [Source: UK Companies House]",
+ )
+ parser.add_argument(
+ "-c",
+ "--companyNo",
+ help="companyNo",
+ type=str.upper,
+ required="-h" not in other_args,
+ dest="companyNo",
+ metavar="companyNo",
+ )
+
+ if (
+ other_args
+ and "-c" not in other_args[0]
+ and "--companyNo" not in other_args[0]
+ and "-h" not in other_args
+ ):
+ other_args.insert(0, "-c")
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if ns_parser and ns_parser.companyNo:
+ self.companyNo = ns_parser.companyNo
+ company = companieshouse_view.display_company_info(
+ ns_parser.companyNo, export=ns_parser.export
+ )
+ if company.dataAvailable():
+ self.companyName = company.name
+ self.filing_total_count = 0
+ self.filing_end_index = 0
+ console.print(company.name)
+ console.print(company.address)
+ console.print(company.lastAccounts)
+ else:
+ console.print(
+ f"[red]No data found for company number {ns_parser.companyNo}[/red]\n"
+ )
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_officers(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="officers",
+ description="Select the company number to retrieve officers for. [Source: UK Companies House]",
+ )
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if self.companyNo:
+ if ns_parser:
+ companieshouse_view.display_officers(
+ self.companyNo, export=ns_parser.export
+ )
+ else:
+ console.print("Must load a company prior to using this command")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_signifcontrol(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="signifcontrol",
+ description="Select the company number to retrieve persons with significant control of company. \
+ [Source: UK Companies House]",
+ )
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if self.companyNo:
+ if ns_parser:
+ companieshouse_view.display_persons_with_significant_control(
+ self.companyNo, export=ns_parser.export
+ )
+ else:
+ console.print("Must load a company prior to using this command")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_filings(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="filings",
+ description="Select the company number to retrieve filling history for. [Source: UK Companies House]",
+ )
+
+ parser.add_argument(
+ "-k",
+ "--category",
+ help="category",
+ type=str.lower,
+ required=False,
+ dest="category",
+ metavar="category",
+ choices=[
+ "accounts",
+ "address",
+ "capital",
+ "incorporation",
+ "officers",
+ "resolution",
+ ],
+ )
+
+ parser.add_argument(
+ "-l",
+ "--limit",
+ help="Number of entries to return",
+ type=check_positive,
+ required=False,
+ dest="limit",
+ metavar="limit",
+ default=100,
+ )
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if self.companyNo:
+ if ns_parser:
+ category = ns_parser.category if ns_parser.category else ""
+ self.filingCategory = category
+ filing_data = companieshouse_view.display_filings(
+ self.companyNo, category, ns_parser.limit, export=ns_parser.export
+ )
+ self.filing_total_count = filing_data.total_count
+ self.filing_end_index = filing_data.end_index
+ self.filing_start_index = filing_data.start_index
+ else:
+ console.print("Must load a company prior to using this command")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_filingdocument(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="filingdocument",
+ description="Select the company number and transaction ID to retrieve filling history for. \
+ [Source: UK Companies House]",
+ )
+
+ parser.add_argument(
+ "-t",
+ "--transactionID",
+ help="transactionID",
+ action="store",
+ required=("-h" not in other_args),
+ dest="transactionID",
+ metavar="transactionID",
+ )
+
+ if (
+ other_args
+ and "-t" not in other_args[0]
+ and "--transactionID" not in other_args[0]
+ and "-h" not in other_args
+ ):
+ other_args.insert(0, "-t")
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if self.companyNo:
+ if ns_parser:
+ companieshouse_view.download_filing_document(
+ self.companyNo,
+ self.companyName,
+ ns_parser.transactionID,
+ export=ns_parser.export,
+ )
+ else:
+ console.print("Must load a company prior to using this command")
+
+ @log_start_end(log=logger)
+ @check_api_key(["API_COMPANIESHOUSE_KEY"])
+ def call_charges(self, other_args: List[str]):
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="charges",
+ description="Select the company number to retrieve officers for. [Source: UK Companies House]",
+ )
+
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
+ )
+
+ if self.companyNo:
+ if ns_parser:
+ companieshouse_view.display_charges(
+ self.companyNo, export=ns_parser.export
+ )
+ else:
+ console.print("Must load a company prior to using this command")
diff --git a/openbb_terminal/alternative/companieshouse/companieshouse_model.py b/openbb_terminal/alternative/companieshouse/companieshouse_model.py
new file mode 100644
index 00000000000..462553956f8
--- /dev/null
+++ b/openbb_terminal/alternative/companieshouse/companieshouse_model.py
@@ -0,0 +1,481 @@
+""" UK Companies House Model """
+__docformat__ = "numpy"
+
+import logging
+
+import pandas as pd
+import requests
+
+from openbb_terminal.alternative.companieshouse.company import Company
+from openbb_terminal.alternative.companieshouse.company_doc import CompanyDocument
+from openbb_terminal.alternative.companieshouse.filing_data import Filing_data
+from openbb_terminal.core.session.constants import (
+ TIMEOUT,
+)
+from openbb_terminal.core.session.current_user import get_current_user
+from openbb_terminal.decorators import check_api_key, log_start_end
+
+logger = logging.getLogger(__name__)
+
+
+@log_start_end(log=logger)
+@check_api_key(["API_COMPANIESHOUSE_KEY"])
+def get_search_results(searchStr: str, limit: int = 20) -> pd.DataFrame:
+ """All companies with searchStr in their name.
+
+ Parameters
+ ----------
+ searchStr : str
+ The search string
+ limit : int
+ number of rows to return
+
+ Returns
+ -------
+ pd.DataFrame
+ All comapanies with the search string in their name.
+
+ Example
+ -------
+ >>> from openbb_terminal.sdk import openbb
+ >>> companies = openbb.alt.companieshouse.get_search_results("AstraZeneca")
+ """
+
+ df = pd.DataFrame()
+
+ if not searchStr:
+ return df
+
+ auth = requests.auth.HTTPBasicAuth(
+ get_current_user().credentials.API_COMPANIESHOUSE_KEY, ""
+ )
+ r = requests.get(
+ "https://api.company-information.service.gov.uk/search/companies?q="
+ + searchStr
+ + f"&items_per_page={limit}",
+ auth=auth,
+ timeout=TIMEOUT,
+ )
+ returned_data = r.json()
+ company_data = []
+ for index, item in enumerate(returned_data["items"]):
+ company_data.append(
+ {
+ "Name": item["title"],
+ "Company Number": item["company_number"],
+ "Status": item["company_status"],
+ }
+ )
+
+ df = pd.DataFrame(company_data)
+ return df
+
+
+@log_start_end(log=logger)
+@check_api_key(["API_COMPANIESHOUSE_KEY"])
+def get_company_info(company_number: str) -> Company:
+ """Gets company info by company number
+
+ Parameters
+ ----------
+ company_number : str
+ The company number. Use get_search_results() to lookup company numbers.
+
+ Returns
+ -------
+ self.address: str
+ Company address.
+ self.name: str
+ Company name.
+ self.dataAvailable(): bool
+ True if data is available.
+ self.lastAccounts: str
+ Period start and end.
+
+ Example
+ -------
+ >>> companies = openbb.alt.companieshouse.get_search_results("AstraZeneca")
+ >>> company_info = openbb.alt.companieshouse.get_company_info("02723534")
+ >>> name = company_info.name
+ """
+
+ auth = requests.auth.HTTPBasicAuth(
+ get_current_user().credentials.API_COMPANIESHOUSE_KEY, ""
+ )
+ r = requests.get(
+ f"https://api.company-information.service.gov.uk/company/{company_number}",
+ auth=auth,
+ timeout=TIMEOUT,
+ )
+
+ last_accounts = {}
+ returned_data = r.json()
+ if returned_data.get("company_name"):
+ company_name = returned_data["company_name"]
+ if returned_data.get("accounts"):
+ last_accounts = returned_data["accounts"]["last_accounts"]
+ address = returned_data["registered_office_address"]
+ address_lines = []
+ if address.get("address_line_1"):
+ address_lines.append(address.get("address_line_1"))
+ if address.get("address_line_2"):
+ address_lines.append(address.get("address_line_2"))
+ if address.get("locality"):
+ address_lines.append(address.get("locality"))
+ if address.get("region"):
+ address_lines.append(address.get("region"))
+ if address.get("postal_code"):
+ address_lines.append(address.get("postal_code"))
+ pretty_address = (
+ ",".join(address_lines)
+ if len(address_lines) > 0
+ else "No address data found"
+ )
+
+ if last_accounts:
+ pretty_accounts = "Period Start On : " + (
+ last_accounts.get("period_start_on") or ""
+ ) + " - " + "Type : " + (
+ last_accounts.get("type") or ""
+ ) + " - " + "Made Up To : " + (
+ last_accounts.get("made_up_to") or ""
+ ) + " - " "Period End On : " + (
+ last_accounts.get("period_end_on") or ""
+ )
+ else:
+ pretty_accounts = "No accounting period data found"
+
+ data = Company(company_name, pretty_address, pretty_accounts)
+ ret