diff --git a/hw_4/.idea/.gitignore b/hw_4/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/hw_4/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/hw_4/.idea/.name b/hw_4/.idea/.name new file mode 100644 index 0000000..11a5d8e --- /dev/null +++ b/hw_4/.idea/.name @@ -0,0 +1 @@ +main.py \ No newline at end of file diff --git a/hw_4/.idea/hw_4.iml b/hw_4/.idea/hw_4.iml new file mode 100644 index 0000000..2c80e12 --- /dev/null +++ b/hw_4/.idea/hw_4.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/hw_4/.idea/inspectionProfiles/profiles_settings.xml b/hw_4/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/hw_4/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/hw_4/.idea/misc.xml b/hw_4/.idea/misc.xml new file mode 100644 index 0000000..a0c5da8 --- /dev/null +++ b/hw_4/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/hw_4/.idea/modules.xml b/hw_4/.idea/modules.xml new file mode 100644 index 0000000..7bc33cf --- /dev/null +++ b/hw_4/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/hw_4/.idea/vcs.xml b/hw_4/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/hw_4/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hw_4/account.csv b/hw_4/account.csv new file mode 100644 index 0000000..4acb96d --- /dev/null +++ b/hw_4/account.csv @@ -0,0 +1,6 @@ +user_id,type,account_number,bank_id,currency,amount,status +1,credit,ID--dcd-123568744-,3,USD,1000,gold +3,debit,ID--xyz-987654-uvw,2,USD,500,silver +2,credit,ID--qwe-456789-asd,5,EUR,800,platinum +2,debit,ID--z1-654321-xyz,4,CAD,1200,gold +4,credit,ID--jkl-987654-mno,1,USD,1500,silver \ No newline at end of file diff --git a/hw_4/api.py b/hw_4/api.py new file mode 100644 index 0000000..645988e --- /dev/null +++ b/hw_4/api.py @@ -0,0 +1,349 @@ +import requests +import csv +import validate +from db_con_decor import db_connection +import os +from dotenv import load_dotenv, find_dotenv +import logging +from consts import URL +from datetime import datetime, timedelta +import random + +formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s') +my_logger = logging.getLogger('CustomLogger') +my_handler = logging.FileHandler('file.log') +my_handler.setFormatter(formatter) +my_logger.setLevel(logging.INFO) +my_logger.addHandler(my_handler) + +load_dotenv(find_dotenv()) + + +# gets dict from currencies and their values relative to the dollar +def get_currency_data(): + + response = requests.get(URL.format(os.getenv('API_KEY'))) + if response.status_code == requests.codes.ok: + data = response.json() + # make logs + my_logger.info("data_get successfully") + my_logger.info(data['data']) + return data['data'] + my_logger.error("data_get failure") + return None + + +# reads csv file converted to dict +def read_csv_to_dict(file_path): + with open(file_path, 'r') as file: + return list(csv.DictReader(file)) + + +def unpack_data(data): + keys = list(data.keys()) + return [data[key] for key in keys] if len(keys) > 1 else data[keys[0]] + + +# { filler_date: [{}, {} ]} +# { "row1": {}, "row2": {} } +# fills the table bank +@db_connection +def add_bank(cur, **kwargs): + data = unpack_data(kwargs) + my_logger.info(data) + # unpack the mass of dicts + my_logger.info(data) + cur.executemany("INSERT INTO Bank(name) VALUES(:Name)", data) # :param_name, :param_name2 + my_logger.info("Bank added successfully.") + + +# fills the table user +@db_connection +def add_user(cur, **kwargs): + data = unpack_data(kwargs) + my_logger.info(data) + for i in data: + i["name"] = validate.validate_user_full_name(i["user_name"])[0] + i["surname"] = validate.validate_user_full_name(i["user_name"])[1] + cur.executemany("INSERT INTO User(name,surname,birth_day,accounts) VALUES(:name ,:surname,:birthday,:accounts)", + data) + my_logger.info("User added successfully.") + + +def unpack_to_string(data): + result = '' + for tup in data: + for item in tup: + result += str(item) + ',' + return result.rstrip(',') + + +# fills the table account +def set_accounts(cur, data): + for i in data: + cur.execute("SELECT id FROM Account WHERE user_id = ?", (i["user_id"],)) + numb_of_acc = cur.fetchall() + account_str = unpack_to_string(numb_of_acc) + my_logger.info(account_str) + cur.execute("UPDATE User SET Accounts = ? WHERE id = ?", (account_str, i["user_id"],)) + + +@db_connection +def add_account(cur, **kwargs): + data = unpack_data(kwargs) + my_logger.info(data) + validate.valid_acc(data) + cur.executemany('''INSERT INTO Account(user_id, type, account_number, bank_id, currency, amount, status) + VALUES(:user_id,:type,:account_number,:bank_id,:currency,:amount,:status)''', + data) + set_accounts(cur, data) + my_logger.info("account added successfully.") + + +# update row in bd +@db_connection +def update_row(c, table_name, row_id, column, value): + c.execute(f"UPDATE {table_name} SET {column} = ? WHERE id = ?", (value, row_id, )) + return "success" + + +@db_connection +def delete_row(cur, row_id, fnc): + match fnc: + case "User": + cur.execute("DELETE FROM Account WHERE user_id = ?", (row_id,)) + cur.execute("DELETE FROM User WHERE id = ?", (row_id,)) + case "Bank": + cur.execute("DELETE FROM Account WHERE bank_id = ?", (row_id,)) + cur.execute("DELETE FROM Bank WHERE id = ?", (row_id,)) + case "Account": + cur.execute("DELETE FROM Account WHERE id = ?", (row_id,)) + my_logger.info(f"{fnc} delete successfully.") + return "success" + + +# it just clears the tables in the database +@db_connection +def clear_table(cur, table_name): + cur.execute(f"DELETE FROM {table_name}") + return 'success' + + +def add_table_by_file(path, table_name): + data = read_csv_to_dict(path) + match table_name: + case "Bank": + add_bank(table_filler=data) + case "User": + add_user(table_filler=data) + case "Account": + add_account(table_filler=data) + + +def convert_currency(currency_values, orig_currency, conv_currency, amount): + return round((amount / currency_values[orig_currency]) * currency_values[conv_currency], 2) + + +def get_bankname(id_user): + bank_id = get_data_from_table(table_name="Account", row_name="bank_id", row_id=id_user) + my_logger.info("get_bankname successfully.") + return get_data_from_table(table_name="Bank", row_name="name", row_id=bank_id) + + +@db_connection +def get_data_from_table(cur, row_name, table_name, row_id): + cur.execute(f"SELECT {row_name} FROM {table_name} WHERE id = ? ", (row_id,)) + return cur.fetchone()[0] + + +@db_connection +def insert_transaction(cur, bank_sender_name, sender_id, bank_receiver_name, + receiver_id, sender_currency, sent_amount, transfer_time): + cur.execute(''' + INSERT INTO BankTransaction + (bank_sender_name, account_sender_id, + bank_receiver_name, account_receiver_id, + sent_currency, sent_amount, datetime) + VALUES(?,?,?,?,?,?,?)''', + (bank_sender_name, sender_id, + bank_receiver_name, receiver_id, + sender_currency, sent_amount, transfer_time,)) + + +def transfer_money(sender_id, receiver_id, sent_amount, transfer_time=None): + currency_dict = get_currency_data() + my_logger.info('valid current success') + # pull the necessary data to fill the transaction table + bank_sender_name = get_bankname(sender_id) + bank_receiver_name = get_bankname(receiver_id) + sender_amount = get_data_from_table("Amount", "Account", sender_id) + receiver_amount = get_data_from_table("Amount", "Account", receiver_id) + sender_currency = get_data_from_table("Currency", "Account", sender_id) + receiver_currency = get_data_from_table("Currency", "Account", receiver_id) + sent_am_in_sender_cur = sent_amount + + if sender_amount <= sent_amount: + raise ValueError("not enough money in the account") + if receiver_currency != sender_currency: + sent_am_in_sender_cur = convert_currency(currency_dict, receiver_currency, sender_currency, sent_amount) + + # check the time + transfer_time = validate.add_transaction_time(transfer_time) + + new_sender_amount = sender_amount - sent_am_in_sender_cur + new_receiver_amount = receiver_amount + sent_am_in_sender_cur + + # change the sum values of the users + update_row("Account", sender_id, "amount", round(new_sender_amount, 2)) + update_row("Account", receiver_id, "amount", round(new_receiver_amount, 2)) + + # fill the table + insert_transaction(bank_sender_name, sender_id, bank_receiver_name, receiver_id, + sender_currency, sent_amount, transfer_time) + my_logger.info("success") + return "success" + + +@db_connection +def select_random_users_with_discounts(cursor): + cursor.execute("SELECT Id FROM User") + all_users = cursor.fetchall() + random_users = random.sample(all_users, min(10, len(all_users))) # Randomly select up to 10 users + user_discounts = {} + for user_id in random_users: + discount = random.choice([25, 30, 50]) # Randomly choose discount + user_discounts[user_id[0]] = discount + my_logger.info(user_discounts) + my_logger.info("select_random_users_with_discounts success") + return user_discounts + + +@db_connection +def user_with_highest_amount(cursor): + cursor.execute(''' + SELECT User_id + FROM Account + ORDER BY Amount DESC + LIMIT 1 + ''') + user_id = cursor.fetchone()[0] + name = get_data_from_table("name", "User", user_id) + my_logger.info(name) + my_logger.info("user_with_highest_amount success") + return name + + +@db_connection +def bank_with_biggest_capital(cursor): + currency_dict = get_currency_data() + # Extract all records from the Account table + cursor.execute("SELECT Bank_id, Currency, Amount FROM Account") + accounts = cursor.fetchall() + + # Create a dictionary to store total capital for each bank in dollars + bank_capital = {} + + # Create a dictionary to store the total capital for each bank in dollars + for bank_id, currency, amount in accounts: + amount_in_usd = convert_currency(currency_dict, currency, 'USD', amount) + if bank_id in bank_capital: + bank_capital[bank_id] += amount_in_usd + else: + bank_capital[bank_id] = amount_in_usd + + # Find the bank with the most capital + max_capital_bank_id = max(bank_capital, key=bank_capital.get) + my_logger.info(f"max capital bank id {max_capital_bank_id}") + my_logger.info("bank_with_biggest_capital success") + return max_capital_bank_id + + +@db_connection +def bank_serving_oldest_client(cursor): + cursor.execute("SELECT Id, Birth_day FROM User") + users = cursor.fetchall() + + cursor.execute("SELECT User_id, Bank_id FROM Account") + accounts = cursor.fetchall() + + # Find the oldest user + oldest_user_id = None + oldest_birth_day = datetime.max + + for user_id, birth_day in users: + birth_date = datetime.strptime(birth_day, '%Y-%m-%d') + if birth_date < oldest_birth_day: + oldest_birth_day = birth_date + oldest_user_id = user_id + + # Find the bank that owns the oldest user + for user_id, bank_id in accounts: + if user_id == oldest_user_id: + return bank_id + + +@db_connection +def print_table(cursor, table_name): + cursor.execute(f"PRAGMA table_info({table_name})") + column_names = [row[1] for row in cursor.fetchall()] + cursor.execute(f"SELECT * FROM {table_name}") + rows = cursor.fetchall() + + print(", ".join(column_names)) + for row in rows: + print(", ".join(map(str, row))) + + +@db_connection +def bank_with_highest_unique_users(cursor): + # Create a dictionary to store the number of unique users for each bank + unique_users_by_bank = {} + + # Select all transactions from the BankTransaction table + cursor.execute("SELECT Bank_sender_name, Account_sender_id FROM BankTransaction") + transactions = cursor.fetchall() + + # Count the number of unique users for each bank + for bank_name, account_id in transactions: + if bank_name not in unique_users_by_bank: + unique_users_by_bank[bank_name] = set() + unique_users_by_bank[bank_name].add(account_id) + + # Find the bank with the unique users + bank_with_highest_users = max(unique_users_by_bank.items(), key=lambda x: len(x[1])) + + return bank_with_highest_users[0] + + +@db_connection +def delete_users_with_incomplete_info(cursor): + while True: + cursor.execute("SELECT id FROM User WHERE Name IS NULL OR Surname IS NULL OR Birth_day IS NULL") + result = cursor.fetchone() + if result is None: + break + deleted_user_id = result[0] + cursor.execute("DELETE FROM Account WHERE User_id = ?", (deleted_user_id,)) + cursor.execute("DELETE FROM User WHERE id = ?", (deleted_user_id,)) + return "Deletion complete" + + +@db_connection +def get_user_transactions(c, user_id): + + # Calculate the date three months ago from today + end_date = datetime.now() + start_date = end_date - timedelta(days=90) + + # Convert dates to string format for SQL query + start_date_str = start_date.strftime('%Y-%m-%d %H:%M:%S') + end_date_str = end_date.strftime('%Y-%m-%d %H:%M:%S') + + query = ''' + SELECT * FROM BankTransaction + WHERE (Account_sender_id = ? OR Account_receiver_id = ?) + AND Datetime BETWEEN ? AND ? + ''' + c.execute(query, (user_id, user_id, start_date_str, end_date_str)) + return c.fetchall() + \ No newline at end of file diff --git a/hw_4/bank.csv b/hw_4/bank.csv new file mode 100644 index 0000000..a31bfa9 --- /dev/null +++ b/hw_4/bank.csv @@ -0,0 +1,11 @@ +Name +Bank of America +Wells Fargo +Chase Bank +Citibank +HSBC +TD Bank +PNC Bank +Capital One +US Bank +BB&T diff --git a/hw_4/bank.db b/hw_4/bank.db new file mode 100644 index 0000000..c7d0bcd Binary files /dev/null and b/hw_4/bank.db differ diff --git a/hw_4/consts.py b/hw_4/consts.py new file mode 100644 index 0000000..7a48541 --- /dev/null +++ b/hw_4/consts.py @@ -0,0 +1 @@ +URL = 'https://api.freecurrencyapi.com/v1/latest?apikey={}' diff --git a/hw_4/db_con_decor.py b/hw_4/db_con_decor.py new file mode 100644 index 0000000..bf97651 --- /dev/null +++ b/hw_4/db_con_decor.py @@ -0,0 +1,44 @@ +import sqlite3 +from functools import wraps + + +def db_connection(func): + """ + Декоратор для установки соединения с базой данных и его закрытия после выполнения функции. + + Args: + func (function): Функция базы данных, которая будет выполнена. + + Returns: + function: Вложенная функция-обертка, которая обеспечивает соединение и закрытие. + """ + # не работает + @wraps(func) + def wrapper(*args, **kwargs): + """ + Вложенная функция-обертка, обеспечивающая соединение и закрытие. + + Args: + *args: Позиционные аргументы, переданные в функцию. + **kwargs: Именованные аргументы, переданные в функцию. + + Returns: + any: Результат выполнения оригинальной функции. + """ + # Устанавливаем соединение с базой данных + conn = sqlite3.connect('bank.db') + # Создаем объект курсора + c = conn.cursor() + result = None + try: + # Вызываем оригинальную функцию, передавая ей курсор и все переданные аргументы + result = func(c, *args, **kwargs) + except Exception as e: + # Обрабатываем ошибки и логируем их + print(f"Error: {str(e)}") + finally: + conn.commit() + conn.close() + return result + # Возвращаем вложенную функцию-обертку в качестве декоратора + return wrapper diff --git a/hw_4/example.csv b/hw_4/example.csv new file mode 100644 index 0000000..1cd6798 --- /dev/null +++ b/hw_4/example.csv @@ -0,0 +1,5 @@ +full_name,birth_day,accounts +John Doe,1990-05-15,3 +Alice Smith,1985-10-20,5 +Michael Johnson,1978-03-25,2 +Emily Brown,1992-07-12,4 diff --git a/hw_4/initial_db_setup.py b/hw_4/initial_db_setup.py new file mode 100644 index 0000000..da0c335 --- /dev/null +++ b/hw_4/initial_db_setup.py @@ -0,0 +1,47 @@ +import sqlite3 + + +def create_database(unique_name=True, unique_surname=True): + # Establishing a connection to the database + conn = sqlite3.connect('bank.db') + c = conn.cursor() + + # Create table Bank + c.execute('''CREATE TABLE IF NOT EXISTS Bank ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE)''') + + # Create table BankTransaction + c.execute('''CREATE TABLE IF NOT EXISTS BankTransaction ( + id INTEGER PRIMARY KEY, + Bank_sender_name TEXT NOT NULL, + Account_sender_id INTEGER NOT NULL, + Bank_receiver_name TEXT NOT NULL, + Account_receiver_id INTEGER NOT NULL, + Sent_Currency TEXT NOT NULL, + Sent_Amount REAL NOT NULL, + Datetime TEXT)''') + + # Create table User + name_uniq = "UNIQUE" if unique_name else "" + surname_uniq = "UNIQUE" if unique_surname else "" + c.execute(f'''CREATE TABLE IF NOT EXISTS User ( + Id INTEGER PRIMARY KEY, + Name TEXT NOT NULL {name_uniq}, + Surname TEXT NOT NULL {surname_uniq}, + Birth_day TEXT, + Accounts INTEGER NOT NULL)''') + + # Create table Account + c.execute('''CREATE TABLE IF NOT EXISTS Account ( + Id INTEGER PRIMARY KEY, + User_id INTEGER NOT NULL, + Type TEXT NOT NULL, + Account_Number TEXT NOT NULL UNIQUE, + Bank_id INTEGER NOT NULL, + Currency TEXT NOT NULL, + Amount REAL NOT NULL, + Status TEXT)''') + + conn.commit() + conn.close() diff --git a/hw_4/main.py b/hw_4/main.py new file mode 100644 index 0000000..e93ffdc --- /dev/null +++ b/hw_4/main.py @@ -0,0 +1,68 @@ +import argparse +import api +import initial_db_setup + + +def init_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--add_path', type=str) + parser.add_argument('--table_name', type=str) + parser.add_argument('--row_id', type=int) + parser.add_argument('--column', type=str) + parser.add_argument('--value', type=str) + parser.add_argument('--delete_row_id', type=int) + parser.add_argument('--sender_id', type=int) + parser.add_argument('--receiver_id', type=int) + parser.add_argument('--sent_currency', type=str) + parser.add_argument('--sent_amount', type=int) + parser.add_argument('--time', type=str) + parser.add_argument('--clear_table', type=str) + parser.add_argument('--random_disc', type=str) + parser.add_argument('--highest_amount', type=str) + parser.add_argument('--bank_with_biggest_capital', type=str) + parser.add_argument('--bank_with_highest_unique_users', type=str) + parser.add_argument('--get_user_transactions', type=int) + parser.add_argument('--print_table', type=str) + parser.add_argument('--delete_incomplete', type=str) + args = parser.parse_args() + + return args + + +def do_func_with_args(args): + # to be able to perform multiple functions + if args.add_path and args.table_name: + api.add_table_by_file(args.add_path, args.table_name) + if args.table_name and args.row_id and args.column and args.value: + api.update_row(args.table_name, args.row_id, args.column, args.value) + if args.clear_table: + api.clear_table(args.clear_table) + if args.delete_row_id and args.table_name: + api.delete_row(args.delete_row_id, args.table_name) + if args.sender_id and args.receiver_id and args.sent_amount: + api.transfer_money(args.sender_id, args.receiver_id, args.sent_amount) + if args.sender_id and args.receiver_id and args.sent_amount and args.time: + api.transfer_money(args.sender_id, args.receiver_id, args.sent_amount, args.time) + if args.random_disc: + print(api.select_random_users_with_discounts()) + if args.highest_amount: + print(api.user_with_highest_amount()) + if args.bank_with_biggest_capital: + print(api.bank_with_biggest_capital()) + if args.bank_with_highest_unique_users: + print(api.bank_with_highest_unique_users()) + if args.get_user_transactions: + print(api.get_user_transactions()) + if args.print_table: + api.print_table(args.print_table) + if args.delete_incomplete: + print(api.delete_users_with_incomplete_info()) + + +def main(): + initial_db_setup.create_database() + do_func_with_args(init_args()) + + +if __name__ == "__main__": + main() diff --git a/hw_4/user.csv b/hw_4/user.csv new file mode 100644 index 0000000..9349b6c --- /dev/null +++ b/hw_4/user.csv @@ -0,0 +1,6 @@ +user_name,birthday,accounts +John1!! Doe,1990-05-05,0 +Jane Smith,1985-10-15,0 +Michael Johnson,1978-03-20,0 +Emily Brown,1995-08-08,0 +Alex Rodriguez,1982-12-30,0 \ No newline at end of file diff --git a/hw_4/validate.py b/hw_4/validate.py new file mode 100644 index 0000000..8a9e574 --- /dev/null +++ b/hw_4/validate.py @@ -0,0 +1,52 @@ +import re +from datetime import datetime + + +# Validation of user_full_name field +def validate_user_full_name(user_full_name): + # Delete all characters that are not letters + user_full_name = re.sub(r'[^a-zA-Z\s]', '', user_full_name) + # Separate first and last name by any whitespace characters + name, surname = user_full_name.strip().split(maxsplit=1) + return name, surname + + +# Validation of fields with a strict set of values +def validate_field_value(field, value, allowed_values): + if value not in allowed_values: + raise ValueError("error: not allowed value {} for field {}!".format(value, field)) + +def valid_acc(data): + type_values = ["credit", "debit"] + status_values = ["gold", "silver", "platinum"] + for i in data: + validate_field_value("type", i["type"], type_values) + validate_field_value("status", i["status"], status_values) + return True + +def validate_account_number(account_number): + # Replacing special characters with dashes + account_number = re.sub(r'[#%_?&]', '-', account_number) + + # Character count check + if len(account_number) < 18: + raise ValueError("error: too little") + if len(account_number) > 18: + raise ValueError("error: many chars") + + # Checking the format + if not account_number.startswith("ID--"): + raise ValueError("error: wrong format") + + # Checking for the right pattern + if not re.match(r'ID--[a-zA-Z]{1,3}-\d+-', account_number): + raise ValueError("an error: broken ID") + + return account_number + + +# Function for checking and adding transaction time +def add_transaction_time(transaction_time): + if not transaction_time: + transaction_time = datetime.now() + return transaction_time