def __init__(self, config: Optional[str] = None): create_db() fill_db() # parse config if exists try: _config_path = Path(config) with open(_config_path) as file: _config = yaml.safe_load(file)["config"] except TypeError: LOGGER.INFO("No config file specified. Falling back to default") _config = {} except FileNotFoundError: LOGGER.INFO("Config file not found. Falling back to default") _config = {} self.name = _config.get("name", "BankLite") # defaults to BankLite self.currency = _config.get("currency", "USD") # defaults to USD self.initial_reserve = _config.get("initial_reserve", 1e9) # defaults to 1 billion self.conn = sqlite3.connect("banklite.db", check_same_thread=False) # main services self.ledger_service = LedgerService(conn=self.conn) self.account_service = AccountService(conn=self.conn) self.customer_service = CustomerService(conn=self.conn) # create reserve account self.reserve_account = self.account_service.create_reserve_account() # fill reserve with initial deposit self.ledger_service.record_entry( account=self.reserve_account, amount=self.initial_reserve )
def list_accounts( self, customer_id: Union[str, int]) -> Union[List[BaseAccount], None]: """ :param customer_id: ID used for identifying a customer account :return: """ LOGGER.INFO("SearchCustomerAccounts(customer_id=%s)", customer_id) try: data = self.conn.execute( f"SELECT * FROM accounts WHERE customer_id = {customer_id}" ).fetchall() except IndexError: LOGGER.DEBUG("CustomerNotFound(id=%s)", customer_id) return None ls = [] for row in data: acct_type = row[3] if acct_type == 1: ls.append(CheckingAccount(row[0], row[1])) else: ls.append(SavingsAccount(row[0], row[1], row[-1])) return ls
def open_account( self, acct_type: str, customer_id: str, deposit_amount: float, savings_rate: Optional[float] = 0.0, ) -> Union[BaseAccount, None]: """ Factory object creator for account objects :param acct_type: Either `checking` or `savings` :param customer_id: ID of customer opening account :param deposit_amount: Amount to deposit (must be positive value) :param savings_rate: If the account is a savings account, a savings rate must be passed in :return: Account object """ if acct_type == "checking": account = self.customer_service.open_checking_account(customer_id, deposit_amount) elif acct_type == "savings": account = self.customer_service.open_savings_account( customer_id, deposit_amount, savings_rate ) else: LOGGER.DEBUG("Invalid account type: %s", acct_type) return None return account
def create_commercial_customer(self, customer_zip: int, phone_number: str, email: str, ein: str, name: str) -> CommercialCustomer: """ :param customer_zip: Customer's zip code of operation :param phone_number: Customer's main office or financial office number :param email: Customer's main office of financial office email :param ein: Customer's EIN :param name: Customer's operating name """ customer = CommercialCustomer(create_id(), customer_zip, phone_number, email, ein, name) if self.customer_exists(customer): return self.get_customer_by_id(customer.customer_id) LOGGER.INFO("CustomerCreation(customer_id=%s)", customer.customer_id) self.conn.execute(f""" INSERT INTO customers VALUES ({customer.customer_id}, DATETIME('now'), '{customer.ein}', NULL, '{customer.first_name}', NULL, '{customer.zip}', '{customer.phone}', '{customer.email}') """) self.conn.commit() return customer
def create_retail_customer( self, customer_zip: int, phone_number: str, email: str, ssn: str, first_name: str, last_name: str, ) -> RetailCustomer: """ Creates a customer entry according to information passed in :param customer_zip: Zip code associated with customer :param phone_number: Phone number associated with customer :param email: Email associated with customer :param ssn: Social security number for customer :param first_name: Customer's first name :param last_name: Customer's last name """ customer = RetailCustomer(create_id(), customer_zip, phone_number, email, ssn, first_name, last_name) if self.customer_exists(customer): return self.get_customer_by_id(customer.customer_id) LOGGER.INFO("CustomerCreation(customer_id=%s)", customer.customer_id) self.conn.execute(f""" INSERT INTO customers VALUES ({customer.customer_id}, DATETIME('now'), NULL, '{customer.ssn}', '{customer.first_name}', '{customer.last_name}', '{customer.zip}', '{customer.phone}', '{customer.email}') """) self.conn.commit() return customer
def get_reserve_balance(self) -> float: """ :return: The total amount available in the reserve """ reserve_balance = self.conn.execute( "SELECT SUM(amount) FROM ledger").fetchone() LOGGER.INFO("GetReserveBalance(%f)", reserve_balance[0]) return reserve_balance[0]
def create_db(db_file: Optional[str] = "banklite.db") -> None: """ Create database and tables for banklite system :param db_file: Name of file (defaults to "banklite.db" """ # remove db files if not already cleaned up if _db_exists(): db_files = list(Path.cwd().rglob("*.db")) for file in db_files: LOGGER.DEBUG("FileDeleted(%s)", file.name) file.unlink() # create database with sqlite3.connect(db_file) as conn: for model in ALL_MODELS: LOGGER.INFO("TableCreated(%s)", model.table_name) conn.execute(_create_table(model))
def customer_exists(self, customer: BaseCustomer): cust_len = len( self.conn.execute( f"SELECT * FROM customers WHERE id={customer.customer_id}"). fetchall()) exists = True if cust_len != 0 else False if exists: LOGGER.DEBUG("CustomerAlreadyExists(account_id=%s)", customer.customer_id) return exists
def fill_db(db_file: Optional[str] = "banklite.db") -> None: """ Insert values into tables on initiation of banking system :param db_file: Name of file (defaults to "banklite.db" """ # create database with sqlite3.connect(db_file) as conn: for model in ALL_MODELS: if model.inserts: LOGGER.INFO("TableFilled(%s)", model.table_name) conn.execute(_insert_to_table(model))
def get_accounts_by_id( self, acct_ids: List[str] ) -> List[Union[ReserveAccount, CheckingAccount, SavingsAccount]]: """ Return an account object and associated data based on the accounts ID :param acct_ids: List of IDs for accounts to return :return: Account object associated with ID """ for acct_id in acct_ids: LOGGER.INFO("SearchAccount(id=%s)", acct_id) ls = [] data = self.conn.execute(f""" SELECT * FROM accounts WHERE id in ( {','.join([acct_id for acct_id in acct_ids])} ) """).fetchall() if len(data) > 0: for acct in data: acct_type = acct[3] if acct_type == 0: ls.append(ReserveAccount()) elif acct_type == 1: ls.append(CheckingAccount(acct[0], acct[1])) else: ls.append(SavingsAccount(acct[0], acct[1], acct[-1])) else: for acct_id in acct_ids: LOGGER.DEBUG("AccountNotFound(id=%s)", acct_id) for acct_id in acct_ids: if acct_id not in [acct.account_id for acct in ls]: LOGGER.DEBUG("AccountNotFound(id=%s)", acct_id) return ls
def get_account_by_id( self, acct_id: str ) -> Union[ReserveAccount, CheckingAccount, SavingsAccount, None]: """ Return an account object and associated data based on the accounts ID :param acct_id: ID of account to return :return: Account object associated with ID """ LOGGER.INFO("SearchAccount(id=%s)", acct_id) try: data = self.conn.execute( f"SELECT * FROM accounts WHERE id={acct_id}").fetchall()[0] acct_type = data[3] if acct_type == 0: return ReserveAccount() elif acct_type == 1: return CheckingAccount(data[0], data[1]) else: return SavingsAccount(data[0], data[1], data[-1]) except IndexError: LOGGER.DEBUG("AccountNotFound(id=%s)", acct_id) return None
def record_entry(self, account: BaseAccount, amount: float) -> None: """ Record entry in ledger table :param account: Account associated with ledger entry :param amount: Amount to record in ledger """ tx_id = create_id() LOGGER.INFO("LedgerEntry(%s, %s)", account.account_id, str(amount)) self.conn.execute(f""" INSERT INTO ledger VALUES ({tx_id}, {account.account_id}, DATETIME('now'), {amount}) """) self.conn.commit()
def get_customer_by_id( self, customer_id: Union[str, int] ) -> Union[RetailCustomer, CommercialCustomer, None]: """ :param customer_id: ID used for identifying a customer account :return: Customer object """ LOGGER.INFO("SearchCustomer(customer_id=%s)", customer_id) try: data = self.conn.execute( f"SELECT * FROM customers WHERE id = {customer_id}").fetchall( )[0] LOGGER.INFO("CustomerFound(customer_id=%s)", data[0]) if data[2] is None: return RetailCustomer(customer_id, data[6], data[7], data[8], data[3], data[4], data[5]) elif data[2] is not None and data[3] is None: return CommercialCustomer(customer_id, data[6], data[7], data[8], data[2], data[4]) except IndexError: LOGGER.DEBUG("CustomerNotFound(id=%s)", customer_id) return None
def account_exists(self, acct: BaseAccount) -> bool: """ Checks if the account passed in exists :param acct: Account to lookup :return: Boolean indication of if the account already exists or not """ acct_len = len( self.conn.execute( f"SELECT * FROM accounts WHERE id={acct.account_id}").fetchall( )) exists = True if acct_len != 0 else False if exists: LOGGER.DEBUG("AccountExists(account_id=%s)", acct.account_id) return exists
def create_savings_account(self, customer: BaseCustomer, rate: float) -> SavingsAccount: acct_id = create_id() acct = SavingsAccount(acct_id, customer.customer_id, rate) if self.account_exists(acct): return self.get_account_by_id(acct_id=acct_id) LOGGER.INFO("AccountCreation(account_id=%s)", acct.account_id) self.conn.execute(f""" INSERT INTO accounts VALUES( {acct.account_id}, {acct.customer_id}, DATETIME('NOW'), {acct.type}, {acct.rate} ) """) self.conn.commit() return acct
def create_reserve_account(self) -> ReserveAccount: """ Creates a record for the special reserve account """ acct = ReserveAccount() if self.account_exists(acct): return self.get_account_by_id(acct_id="1") LOGGER.INFO("AccountCreation(account_id=%s)", acct.account_id) self.conn.execute(f""" INSERT INTO accounts VALUES({acct.account_id}, NULL, DATETIME('NOW'), {acct.type}, {acct.rate}) """) self.conn.commit() return acct
def create_new_customer( self, customer_type: str, **kwargs ) -> Union[BaseCustomer, RetailCustomer, CommercialCustomer, None]: """ Factory object creator for customer objects :param customer_type: Either `retail` or `commercial` :return: Customer object """ if customer_type == "retail": customer = self.customer_service.create_retail_customer(**kwargs) elif customer_type == "commercial": customer = self.customer_service.create_commercial_customer(**kwargs) else: LOGGER.DEBUG("Invalid customer type: %s", customer_type) return None return customer
def create_checking_account(self, customer: BaseCustomer) -> CheckingAccount: """ Creates a record for a new checking account associated with the customer passed in :param customer: Customer associated with new account """ acct_id = create_id() acct = CheckingAccount(acct_id, customer.customer_id) if self.account_exists(acct): return self.get_account_by_id(acct_id=acct_id) LOGGER.INFO("AccountCreation(account_id=%s)", acct.account_id) self.conn.execute(f""" INSERT INTO accounts VALUES( {acct.account_id}, {acct.customer_id}, DATETIME('NOW'), {acct.type}, {acct.rate} ) """) self.conn.commit() return acct