diff --git a/OR25-L-Interview Data.xlsx b/OR25-L-Interview Data.xlsx index 6d0e3f3..9eba171 100644 Binary files a/OR25-L-Interview Data.xlsx and b/OR25-L-Interview Data.xlsx differ diff --git a/ReadDB.py b/ReadDB.py index 1e7d54c..e1ef66e 100644 --- a/ReadDB.py +++ b/ReadDB.py @@ -5,12 +5,10 @@ import time from NoSheet import NoSheet import datetime -import os - """ -TODO update to possibly not use pandas and update to use the new template -TODO update name of function to be more clear +TODO change to use new tempate +TODO change names to be more clear """ def ReadDatabase(): @@ -33,54 +31,51 @@ def ReadDatabase(): excel_file_path = f"OR{year_donation}-L-Interview Data.xlsx" lock_file_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" - if not (os.path.isfile(excel_file_path) or os.path.isfile(lock_file_path)): - NoSheet() - else: - # Retry parameters - max_retries = 60 # Maximum number of retries if the file is locked - retry_interval = 0.5 # Wait time (in seconds) between retries + # Retry parameters + max_retries = 60 # Maximum number of retries if the file is locked + retry_interval = 0.5 # Wait time (in seconds) between retries - retries = 0 - while retries < max_retries: - try: - # Attempt to acquire a shared read (non-blocking) access - with FileLock(lock_file_path, timeout=0): # Non-blocking, checks if the lock exists - # Load the Excel file into a pandas DataFrame - df = pd.read_excel(excel_file_path) + retries = 0 + while retries < max_retries: + try: + # Attempt to acquire a shared read (non-blocking) access + with FileLock(lock_file_path, timeout=0): # Non-blocking, checks if the lock exists + # Load the Excel file into a pandas DataFrame + df = pd.read_excel(excel_file_path) - # Initialize the dictionary to store the structured data - interview_data = {} + # Initialize the dictionary to store the structured data + interview_data = {} - # Group the DataFrame by Date, Start Time, and Slot for organization - for _, row in df.iterrows(): - date = str(row['Date']) - start_time = str(row['Start Time']) - slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + # Group the DataFrame by Date, Start Time, and Slot for organization + for _, row in df.iterrows(): + date = str(row['Date']) + start_time = str(row['Start Time']) + slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 - # Returns the number of interviewees in the slot; returns 0 if empty - interviewee_amount = len(str(row['Interviewee Name']).split()) if str(row['Interviewee Name']) != "nan" else 0 + # Returns the number of interviewees in the slot; returns 0 if empty + interviewee_amount = len(str(row['Interviewee Name']).split()) if str(row['Interviewee Name']) != "nan" else 0 - # Check if the slot is available for an interviewee to attend - available_slots = interviewee_amount != slot - if available_slots: - # Initialize nested structure if not present - if date not in interview_data: - interview_data[date] = {} - # Add the start time and duration if not present - if start_time not in interview_data[date]: - interview_data[date][start_time] = { - 'Meeting Duration': row['Meeting Duration'], - } - return interview_data # Successfully read the database + # Check if the slot is available for an interviewee to attend + available_slots = interviewee_amount != slot + if available_slots: + # Initialize nested structure if not present + if date not in interview_data: + interview_data[date] = {} + # Add the start time and duration if not present + if start_time not in interview_data[date]: + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + } + return interview_data # Successfully read the database - except Timeout: - # File is locked; wait and retry - retries += 1 - print(f"File is locked, retrying ({retries}/{max_retries})...") - time.sleep(retry_interval) + except Timeout: + # File is locked; wait and retry + retries += 1 + print(f"File is locked, retrying ({retries}/{max_retries})...") + time.sleep(retry_interval) - # If max retries are exceeded, raise an error - raise RuntimeError("Unable to access the database after multiple attempts due to a file lock.") + # If max retries are exceeded, raise an error + raise RuntimeError("Unable to access the database after multiple attempts due to a file lock.") # Example usage of the ReadDatabase function if __name__ == "__main__": diff --git a/WriteDB.py b/WriteDB.py index c5e9a25..ef5c364 100644 --- a/WriteDB.py +++ b/WriteDB.py @@ -4,15 +4,13 @@ from openpyxl import load_workbook from send_email import send_email from filelock import FileLock -from NoSheet import NoSheet import datetime -import os """ -TODO update to possibly not use pandas and update to use the new template -TODO update name of functions to be more clear +TODO make it work with the new template +TODO update names to be more clear +TODO try to remove pandas """ - def ReadDatabase(): """ Reads the Database to retrieve available interview slots @@ -30,41 +28,37 @@ def ReadDatabase(): # name based off the 2025 naming system file_path = f"OR{year_donation}-L-Interview Data.xlsx" lock_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" + + # Use a file-based lock for thread-safe and process-safe access + with FileLock(lock_path): + # Load the Excel file into a pandas DataFrame with specific columns + df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) - # checks for if the file exisits for the year otherwise it will create one - if not (os.path.isfile(file_path) or os.path.isfile(lock_path)): - NoSheet() - else: - # Use a file-based lock for thread-safe and process-safe access - with FileLock(lock_path): - # Load the Excel file into a pandas DataFrame with specific columns - df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) - - # Initialize the dictionary to store structured data for available slots + # Initialize the dictionary to store structured data for available slots interview_data = {} - # Process each row in the DataFrame to structure data by date and time - for _, row in df.iterrows(): - # Convert Date and Start Time to string format for easier comparison - date = str(row['Date']).split(" ")[0] # Format date to YYYY-MM-DD - start_time = str(row['Start Time']) + # Process each row in the DataFrame to structure data by date and time + for _, row in df.iterrows(): + # Convert Date and Start Time to string format for easier comparison + date = str(row['Date']).split(" ")[0] # Format date to YYYY-MM-DD + start_time = str(row['Start Time']) - # Calculate the slot capacity and current number of interviewees - slot_capacity = int(row['Slot']) if not pd.isna(row['Slot']) else 0 - interviewee_names = [name.strip() for name in str(row['Interviewee Name']).split(',') if name.strip()] - interviewee_count = len(interviewee_names) if interviewee_names != ["nan"] else 0 + # Calculate the slot capacity and current number of interviewees + slot_capacity = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + interviewee_names = [name.strip() for name in str(row['Interviewee Name']).split(',') if name.strip()] + interviewee_count = len(interviewee_names) if interviewee_names != ["nan"] else 0 - # Check if there are available slots for more interviewees - if interviewee_count < slot_capacity: - # Organize data by date and time, keeping track of available slots and meeting duration - if date not in interview_data: - interview_data[date] = {} - interview_data[date][start_time] = { - 'Meeting Duration': row['Meeting Duration'], - 'Available Slots': slot_capacity - interviewee_count - } + # Check if there are available slots for more interviewees + if interviewee_count < slot_capacity: + # Organize data by date and time, keeping track of available slots and meeting duration + if date not in interview_data: + interview_data[date] = {} + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + 'Available Slots': slot_capacity - interviewee_count + } - return interview_data + return interview_data def AppendAppointment(date, start_time, interviewee_name, interviewee_email): """ @@ -84,46 +78,41 @@ def AppendAppointment(date, start_time, interviewee_name, interviewee_email): file_path = f"OR{year_donation}-L-Interview Data.xlsx" lock_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" - # checks for if the file exisits for the year otherwise it will create one - if not (os.path.isfile(file_path) or os.path.isfile(lock_path)): - NoSheet() - else: + available_slots = ReadDatabase() + + # Check if the requested slot is available in the `available_slots` structure + if date in available_slots and start_time in available_slots[date]: + with FileLock(lock_path): # Ensure process-safe access to the file + # Load workbook and select "Sheet1" for updating appointments + workbook = load_workbook(file_path) + sheet = workbook["Interview Timetable"] + df = pd.read_excel(file_path) - available_slots = ReadDatabase() - - # Check if the requested slot is available in the `available_slots` structure - if date in available_slots and start_time in available_slots[date]: - with FileLock(lock_path): # Ensure process-safe access to the file - # Load workbook and select "Sheet1" for updating appointments - workbook = load_workbook(file_path) - sheet = workbook["Interview Timetable"] - df = pd.read_excel(file_path) + # Find and update the row that matches the provided date and start time + for index, row in df.iterrows(): + row_date = str(row['Date']).split(" ")[0] + row_start_time = str(row['Start Time']) - # Find and update the row that matches the provided date and start time - for index, row in df.iterrows(): - row_date = str(row['Date']).split(" ")[0] - row_start_time = str(row['Start Time']) + if row_date == date and row_start_time == start_time: + # Current entries for names and emails, and append new data with comma and space + current_names = str(row['Interviewee Name']).strip() + current_emails = str(row['Interviewee Email']).strip() + + updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name + updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email - if row_date == date and row_start_time == start_time: - # Current entries for names and emails, and append new data with comma and space - current_names = str(row['Interviewee Name']).strip() - current_emails = str(row['Interviewee Email']).strip() - - updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name - updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email + # Update the cells with new names and emails + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) + email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) + name_cell.value = updated_names + email_cell.value = updated_emails - # Update the cells with new names and emails - name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) - email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) - name_cell.value = updated_names - email_cell.value = updated_emails + workbook.save(file_path) + send_email(interviewee_email, interviewee_name, date, start_time) + return True - workbook.save(file_path) - send_email(interviewee_email, interviewee_name, date, start_time) - return True - - # If no slots available, return that the slot is unavailable - return False + # If no slots available, return that the slot is unavailable + return False def run_tests(): diff --git a/__pycache__/ReadDB.cpython-313.pyc b/__pycache__/ReadDB.cpython-313.pyc index 6517ae0..7cf7153 100644 Binary files a/__pycache__/ReadDB.cpython-313.pyc and b/__pycache__/ReadDB.cpython-313.pyc differ diff --git a/__pycache__/WriteDB.cpython-313.pyc b/__pycache__/WriteDB.cpython-313.pyc index f2c7f95..57778a9 100644 Binary files a/__pycache__/WriteDB.cpython-313.pyc and b/__pycache__/WriteDB.cpython-313.pyc differ diff --git a/__pycache__/interviewPackagers.cpython-313.pyc b/__pycache__/interviewPackagers.cpython-313.pyc new file mode 100644 index 0000000..bb1131e Binary files /dev/null and b/__pycache__/interviewPackagers.cpython-313.pyc differ diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 84981fb..0daf245 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/__pycache__/send_email.cpython-313.pyc b/__pycache__/send_email.cpython-313.pyc index def618d..9495a50 100644 Binary files a/__pycache__/send_email.cpython-313.pyc and b/__pycache__/send_email.cpython-313.pyc differ diff --git a/interviewPackagers.py b/interviewPackagers.py index e69de29..3f08274 100644 --- a/interviewPackagers.py +++ b/interviewPackagers.py @@ -0,0 +1,62 @@ +from ReadDB import ReadDatabase + + +def getSchedulePackager(): + """ + Packages up the response for a http response + + ``REQUIRES``: None + + ``PROMISES``: ``JSON`` http response ready + + ``Develop in part by``: Brock T + + ``Contact``: darkicewolf50@gmail.ocm + + """ + return { + "interviewDates": ReadDatabase() + } + +from WriteDB import AppendAppointment +from email_validator import validate_email, EmailNotValidError + + +def SelectAppointment (appointmentJson): + """ + Packages up a response for a http request + + ``REQUIRES``: ``JSON`` with the data of interviewee name, date, starttime and interviewee email + + ``PROMISES``: ``JSON`` Returns if the booking was a success + + ``Developed in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + """ + Example of an incoming http post body + { + "intervieweeName": "Brock", + "date": "2024-09-16", + "startTime": "10:30:00", + "intervieweeEmail": "darkicewolf50@gmail.com" + } + """ + try: + validEmail = validate_email(appointmentJson["intervieweeEmail"], check_deliverability=True) + if validEmail: + status = AppendAppointment(date=appointmentJson["date"], start_time=appointmentJson["startTime"], interviewee_name=appointmentJson["intervieweeName"], interviewee_email=appointmentJson["intervieweeEmail"]) + + if status: + resBody = {"Success": True, "validEmail": "true"} + else: + resBody = {"Success": False, "validEmail": "true"} + + # resBody["message"] = appointmentJson for testing + return resBody + + except EmailNotValidError as e: + return {"Success": False, "validEmail": "false"} + diff --git a/main.py b/main.py index 307f85c..666bc9f 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,18 @@ -import json from fastapi import FastAPI from fastapi.responses import JSONResponse from pydantic import BaseModel +from NoSheet import NoSheet +import datetime +import os + +year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season +# name based off the 2025 naming system +# Define the path to the Excel file and the lock file +file_name = f"OR{year_donation}-L-Interview Data.xlsx" +if not os.path.isfile(file_name): + NoSheet() + app = FastAPI() @app.get("/")