Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
some minor updates
  • Loading branch information
brucewzj99 committed Mar 15, 2024
commit 2a461facbb9176d43afb8405347c971e03243990
14 changes: 14 additions & 0 deletions bot/google_sheet_service/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
"""
auth.py

This file contains a function that returns the credentials for the Google Sheets API.
The credentials are obtained from the GOOGLE_JSON environment variable, which is set in the .env file.
The get_credentials function uses the google.oauth2.service_account module to create a credentials
object from the service account info in the GOOGLE_JSON environment variable.
The credentials object is then returned to the caller.

"""

import os
import json
from google.oauth2 import service_account


def get_credentials():
"""
Get credentials for the Google Sheets API.
"""
SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
google_json = os.getenv("GOOGLE_JSON")
google_service = json.loads(google_json)
Expand Down
58 changes: 58 additions & 0 deletions bot/google_sheet_service/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from googleapiclient.errors import HttpError
from functools import wraps

# Custom exception classes defined here


def google_sheets_exception_handler(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except HttpError as e:
if e.resp.status == 401:
raise AuthenticationError(
"Authentication failed: Check your credentials."
)
elif e.resp.status == 404:
raise SpreadsheetNotFoundError(
"Spreadsheet not found: Check your spreadsheet ID."
)
else:
raise APIRequestError(
f"API request failed with status {e.resp.status}: {e.error_details}"
)
except Exception as e:
raise SheetsServiceError(f"An unexpected error occurred: {str(e)}")

return wrapper


class SheetsServiceError(Exception):
"""Base class for exceptions in this module."""

pass


class AuthenticationError(SheetsServiceError):
"""Raised when there's an issue with authentication or credentials."""

pass


class SpreadsheetNotFoundError(SheetsServiceError):
"""Raised when a specified spreadsheet cannot be found."""

pass


class APIRequestError(SheetsServiceError):
"""Raised for errors related to making API requests to Google Sheets."""

pass


class InvalidEntryTypeError(SheetsServiceError):
"""Raised when an invalid entry type is specified."""

pass
9 changes: 0 additions & 9 deletions bot/google_sheet_service/sheets_api.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
"""
sheets_range.py

This file contains the ranges for the Google Sheets Services for consistency.
The ranges are used to access specific cells in the Google Sheets.

"""

from bot.google_sheet_service.utils import create_range, create_complex_range

# Sheet names
DD_SHEET = "Dropdown" # DD for Dropdown
TR_SHEET = "Tracker" # TR for Tracker
Expand Down Expand Up @@ -40,28 +50,6 @@
# Months
OVERALL_RANGE = "!M13:O25"


# Helper functions
def create_range(sheet, start_col, start_row, end_col=None, end_row=None):
"""
Create a standard range string.
Sample output: "Dropdown!A2:A9"
"""
end_part = f":{end_col}{end_row}" if end_col and end_row else ""
return f"{sheet}!{start_col}{start_row}{end_part}"


def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end):
"""
Create a range string for complex cases.
Sample output: ["Dropdown!B2:B9", "Dropdown!C2:C9", ...]
"""
return [
f"{sheet}!{chr(i)}{row_start}:{chr(i)}{row_end}"
for i in range(start_col_ord, end_col_ord)
]


# Transport ranges
TRANSPORT_RANGE = create_range(
DD_SHEET, DD_TRANSPORT_COL, DD_SUBCAT_START, DD_TRANSPORT_COL, DD_SUBCAT_END
Expand All @@ -71,6 +59,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end):
OTHERS_MAIN_RANGE = create_range(
DD_SHEET, DD_OTHERS_COL_START, DD_MAIN_CAT_ROW, DD_OTHERS_COL_END, DD_MAIN_CAT_ROW
)

OTHERS_SUB_RANGE = create_complex_range(
DD_SHEET,
ord(DD_OTHERS_COL_START),
Expand All @@ -83,6 +72,7 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end):
PAYMENT_MAIN_RANGE = create_range(
DD_SHEET, DD_PAYMENT_COL_START, DD_MAIN_PAY_ROW, DD_PAYMENT_COL_END, DD_MAIN_PAY_ROW
)

PAYMENT_SUB_RANGE = create_complex_range(
DD_SHEET,
ord(DD_PAYMENT_COL_START),
Expand All @@ -107,13 +97,15 @@ def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end):
TR_QUICKADD_OT_TYPE,
TR_QUICKADD_ROW_START,
)

QUICK_OTHERS_RANGE = create_range(
TR_SHEET,
TR_QUICKADD_OT_PAY,
TR_QUICKADD_ROW_START,
TR_QUICKADD_OT_TYPE,
TR_QUICKADD_ROW_END,
)

QUICK_TRANSPORT_RANGE = create_range(
TR_SHEET,
TR_QUICKADD_TP_PAY,
Expand Down
122 changes: 122 additions & 0 deletions bot/google_sheet_service/sheets_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
sheet_api.py


"""

# sheets_api.py
from googleapiclient.discovery import build
from bot.common import EntryType
from bot.google_sheet_service.auth import get_credentials
from bot.google_sheet_service.sheets_range import *


class GoogleSheetsClient:
_instance = None

@classmethod
def get_instance(cls):
if cls._instance is None:
creds = get_credentials()
cls._instance = build("sheets", "v4", credentials=creds)
return cls._instance


class DropdownManager:
"""
This class is responsible for managing the Dropdown sheet in the google sheet.
"""

def __init__(self):
self.sheets_api = GoogleSheetsClient.get_instance()

# to be rename as get_header_values
def get_main_dropdown_value(self, spreadsheet_id, entry_type) -> list[str]:
"""
This method gets the header values for the CATEGORY/PAYMENT of the transaction.
"""
range = []
if entry_type == EntryType.TRANSPORT:
# actually if entry_type is transport shouldnt be calling this
# but instead call get_sub_dropdown_value instead
# will do something to make this change after refactoring
range = TRANSPORT_RANGE
elif entry_type == EntryType.OTHERS:
range = OTHERS_MAIN_RANGE
else:
range = PAYMENT_MAIN_RANGE

results = (
self.sheets_api.spreadsheets()
.values()
.get(spreadsheetId=spreadsheet_id, range=range)
.execute()
)

values_results = results.get("values", [])

# to remove this and probably move it to get_sub_dropdown_value
if entry_type == EntryType.TRANSPORT:
results_list = []
for sublist in values_results:
for item in sublist:
results_list.append(item)
return results_list

return values_results[0]

# to be rename as get_sub_values
def get_sub_dropdown_value(
self, spreadsheet_id, header_value, entry_type
) -> list[str]:
range = []
# if entry_type is transport, should be calling this instead of get_main_dropdown_value
if entry_type == EntryType.OTHERS:
range = OTHERS_SUB_RANGE
else:
range = PAYMENT_SUB_RANGE
results = (
self.sheets_api.spreadsheets()
.values()
.batchGet(spreadsheetId=spreadsheet_id, ranges=range)
.execute()
)

value_results = results.get("valueRanges", [])

dropdown = []
for value in value_results:
if value.get("values", []):
if header_value == value.get("values", [])[0][0]:
dropdown.append(value.get("values", []))
pass

result_list = [item for sublist in dropdown[0] for item in sublist]
return result_list


class TrackerManager:
"""
This class is responsible for managing the Tracker sheet in the google sheet.
"""

def __init__(self):
self.sheets_api = GoogleSheetsClient.get_instance()


class EntryManager:
"""
This class is responsible for logging of transactions in the google sheet.
"""

def __init__(self):
self.sheets_api = GoogleSheetsClient.get_instance()


class SheetManager:
"""
This class is responsible for retrieving/moving the google sheet.
"""

def __init__(self):
self.sheets_api = GoogleSheetsClient.get_instance()
26 changes: 26 additions & 0 deletions bot/google_sheet_service/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
utils.py

This file contains utility functions for the Google Sheet Services.

"""


def create_range(sheet, start_col, start_row, end_col=None, end_row=None):
"""
Create a standard range string.
Sample output: "Dropdown!A2:A9"
"""
end_part = f":{end_col}{end_row}" if end_col and end_row else ""
return f"{sheet}!{start_col}{start_row}{end_part}"


def create_complex_range(sheet, start_col_ord, end_col_ord, row_start, row_end):
"""
Create a range string for complex cases.
Sample output: ["Dropdown!B2:B9", "Dropdown!C2:C9", ...]
"""
return [
f"{sheet}!{chr(i)}{row_start}:{chr(i)}{row_end}"
for i in range(start_col_ord, end_col_ord + 1)
]