Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 4 additions & 2 deletions .github/workflows/stage_unit_tests.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
name: Unit Tests - Stage Branch

on:
push:
pull_request:
types: [opened , reopene, edited]
branches:
- stage
- 'stage'

workflow_dispatch: # Allows manual triggering from the UI

jobs:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ pythonpath = ["."]
testspaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"

18 changes: 9 additions & 9 deletions src/config.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import os
import yaml

class VerityConfig:

class VerityConfig:
def __init__(self):
self.SECRET_KEY = os.environ.get('SECRET_KEY') or 'super_secure_secret_key'
self.DATABASE = 'Verity.db'
self.CONFIG_FILE_DIRECTORY = 'config_files'
self.LOGGING_CONFIG = self.load_config_file('logging_config.yaml')
self.DATABASE_SCHEMA = self.load_config_file('verity_schema.yaml')
self.SECRET_KEY = os.environ.get("SECRET_KEY") or "super_secret_key"
self.DATABASE = "Verity.db"
self.CONFIG_FILE_DIRECTORY = "config_files"
self.LOGGING_CONFIG = self.load_config_file("logging_config.yaml")
self.DATABASE_SCHEMA = self.load_config_file("verity_schema.yaml")

def load_config_file(self, file):
config = ''
filepath = os.path.join(self.CONFIG_FILE_DIRECTORY,file)
config = ""
filepath = os.path.join(self.CONFIG_FILE_DIRECTORY, file)
try:
with open(filepath, 'r') as f:
with open(filepath, "r") as f:
try:
config = yaml.safe_load(f)
except yaml.YAMLError as e:
Expand Down
2 changes: 1 addition & 1 deletion src/config_files/logging_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ formatters:
handlers:
stderr:
class: logging.StreamHandler
level: INFO
level: DEBUG
formatter: nodate
stream: ext://sys.stdout
file:
Expand Down
141 changes: 107 additions & 34 deletions src/data_handler.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,76 @@
import sqlite3
import logging
from config import VerityConfig
from datetime import datetime

logger = logging.getLogger(__name__)

class database():
'basic Database class to start some development'
def __init__(self, config: VerityConfig ) -> None:
self.database = config.DATABASE
self.schema = config.DATABASE_SCHEMA

class database:
"basic Database class to start some development"

def __init__(self) -> None:
self.verity_config = VerityConfig()
self.database = self.verity_config.DATABASE
self.schema = self.verity_config.DATABASE_SCHEMA

def execute_sql(self, sql_statement: str, return_id: bool = False) -> (bool, int):
"send the query here, returns true if successful, false if fail"
logging.debug(f"received request to execute {sql_statement}")
new_id: int = 0
is_success: bool = False
try:
connection = sqlite3.connect(
database=self.database,
timeout=10, # seconds i hope
)
logging.debug("opened connection to database")
cursor = connection.cursor()
cursor.execute(sql=sql_statement)
connection.commit()
if return_id:
new_id = cursor.lastrowid
is_success = True
except Exception as e: # TODO: better Exception handling
logging.error(e)
is_success = False
finally:
connection.close()
return (is_success, new_id)

@staticmethod
def _build_column(column:dict) -> str:
name = column['column_name']
is_pk = column['is_pk']
datatype = column['datatype']
nullable = column['nullable']
column_string = f'{name} {datatype}'
def _build_column(column: dict) -> str:
name = column["column_name"]
is_pk = column["is_pk"]
datatype = column["datatype"]
nullable = column["nullable"]
column_string = f"{name} {datatype}"
if is_pk:
column_string += ' PRIMARY KEY AUTOINCREMENT'
column_string += " PRIMARY KEY AUTOINCREMENT"
if not nullable:
column_string += ' NOT NULL'
column_string += " NOT NULL"
return column_string

@staticmethod
def _build_foreign_key(key:dict) -> str:
column = key['column']
reference_table = key['references']
reference_column = key['reference_column']
return f'FOREIGN KEY ({column}) REFERENCES {reference_table} ({reference_column})'

def _add_table_to_db(self, table:dict) -> bool:
'Creates the table in the verity database, based on the schema yaml'
sql = f'''CREATE TABLE IF NOT EXISTS {table['table_name']} (
'''
def _build_foreign_key(key: dict) -> str:
column = key["column"]
ref_table = key["references"]
ref_column = key["reference_column"]
return f"FOREIGN KEY ({column}) REFERENCES {ref_table} ({ref_column})"

def _add_table_to_db(self, table: dict) -> bool:
"Creates the table in the verity database, based on the schema yaml"
sql = f"""CREATE TABLE IF NOT EXISTS {table["table_name"]} (
"""
columns = []
for column in table['table_columns']:
for column in table["table_columns"]:
columns.append(self._build_column(column))
sql += ',\n'.join(columns)
if table.get('table_foreign_keys', None):
sql += ',\n'
for key in table['table_foreign_keys']:
sql += f'{self._build_foreign_key(key)}'
sql += '\n);'
sql += ",\n".join(columns)
if table.get("table_foreign_keys", None):
sql += ",\n"
for key in table["table_foreign_keys"]:
sql += f"{self._build_foreign_key(key)}"
sql += "\n);"
success_status = False
try:
connection = sqlite3.connect(self.database)
Expand All @@ -64,14 +92,15 @@ def _add_table_to_db(self, table:dict) -> bool:
return success_status

def build_database(self):
for table in self.schema['tables']:
for table in self.schema["tables"]:
logger.info(f"Checking {table['table_name']}")
# Add true/false handling here to gracefully handle errors
self._add_table_to_db(table)

def print_table_schema(self, table_name):
"""
Connects to the sqlite3 database, retrieves the schema of a specified table,
Connects to the sqlite3 database,
retrieves the schema of a specified table,
and prints it to the console.

Args:
Expand All @@ -87,7 +116,9 @@ def print_table_schema(self, table_name):
# Print the schema
logger.debug(f"Schema for table: {table_name}")
for row in cursor.fetchall():
logger.debug(f" Column Name: {row[1]}, Data Type: {row[2]}, Not Null: {row[3]}")
logger.debug(
f"Column Name: {row[1]}, Data Type: {row[2]}, Not Null: {row[3]}"
)

except Exception as e:
logger.error(f"An error occurred: {e}")
Expand All @@ -96,4 +127,46 @@ def print_table_schema(self, table_name):
try:
connection.close()
except Exception as e:
logger.error(e)
logger.error(e)

def add_budget_name(self, budget_name: str) -> int:
"takes budget name string, returns budget id"
now = datetime.now()
formatted_datetime = now.strftime("%Y-%m-%d %H:%M:%S")
logger.debug(
f"attempting to insert values into budget table {budget_name} | {
formatted_datetime
}"
)
try:
connection = sqlite3.connect(self.database)
logger.debug("connection to db open")
cursor = connection.cursor()
logger.debug("cursor activated")
cursor.execute(
"""INSERT INTO budget (
name,
created_date
)
VALUES (?,?)
""",
(budget_name, formatted_datetime),
)
logger.debug("cursor executed")
connection.commit()
budget_id = cursor.lastrowid
logger.debug(f"insert attempt seems successful, budget id is {budget_id}")

if budget_id is None:
budget_id = 0
except Exception as e:
logger.error(f"Failed to insert budget name, error: {e}")
budget_id = 0
finally:
try:
connection.close()
logger.debug("connection to db closed")
return budget_id
except Exception as e:
logger.error(f"failed to close connection message: {e}")
return 0
45 changes: 33 additions & 12 deletions src/front/home.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
from flask import Blueprint, render_template, flash, redirect, url_for, session
import os
from flask import Blueprint, render_template
from flask import flash, redirect, url_for, session, request
import logging

home_bp = Blueprint('home',__name__, template_folder='templates')
import data_handler

@home_bp.route('/')
logger = logging.getLogger(__name__)

home_bp = Blueprint("home", __name__, template_folder="templates")


@home_bp.route("/")
def home_page():
cwd = os.getcwd()
return render_template('home.html',cwd=cwd)

@home_bp.route('/clear_session')
def clear_session():
session.clear()
flash('All Clear!',category='success')
return redirect(url_for('home.home'))
logger.info("home page hit")
return render_template("home.html")


@home_bp.route("/submit", methods=["POST"])
def submit_budget_name():
budget_name = request.form.get("budgetName")
logger.debug(request.form)
if budget_name:
logger.info(f"User submitted new budget name: {budget_name}")
session["budget_name"] = budget_name
db_call = data_handler.database()
budget_id = db_call.add_budget_name(budget_name)
if budget_id == 0:
# db entry failed, throw error message
flash("Budget Name not saved, please check the logs", "error")
return redirect(url_for("home.home_page"))
session["budget_id"] = budget_id
flash("Budget name saved!", "success")
return redirect(url_for("home.home_page"))
else:
flash("Please enter a budget name.", "error")
return redirect(url_for("home.home_page"))
6 changes: 5 additions & 1 deletion src/front/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
crossorigin="anonymous"
/>
<!-- Custom CSS -->
<link rel="stylesheet" href="/../static/style.css" />
<link
rel="stylesheet"
type="text/css"
href="{{ url_for('static', filename='verity_style.css') }}"
/>
</head>

<body>
Expand Down
16 changes: 15 additions & 1 deletion src/front/templates/home.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
{% extends "base.html" %} {% block title %} Home {% endblock %} {% block content
%}
<h1>Home of Verity</h1>
<p>Current Directory: {{ cwd }}</p>
<div>
<form action="{{url_for('home.submit_budget_name')}}" method="POST">
<p>New Budget Name:</p>
<input
type="text"
id="budgetName"
name="budgetName"
rows="4"
cols="50"
placeholder="Enter Budget Name Here"
/>
<input type="submit" value="Submit" />
</form>
</div>

{% endblock %}
32 changes: 20 additions & 12 deletions src/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,54 @@
import os
import yaml


def test_verity_config_default_secret_key(monkeypatch):
"""Test that the default secret key is used if the environment variable is not set."""
"""Test that the default secret key is used
if the environment variable is not set."""
testing_config = config.VerityConfig()
assert testing_config.SECRET_KEY == 'super_secure_secret_key'
assert testing_config.SECRET_KEY == "super_secret_key"


def test_verity_config_secret_key_from_env(monkeypatch):
"""Test that the secret key is loaded from the environment variable."""
os.environ['SECRET_KEY'] = 'a_real_secret'
os.environ["SECRET_KEY"] = "a_real_secret"
testing_config = config.VerityConfig()
assert testing_config.SECRET_KEY == 'a_real_secret'
del os.environ['SECRET_KEY'] # Clean up the environment
assert testing_config.SECRET_KEY == "a_real_secret"
del os.environ["SECRET_KEY"] # Clean up the environment


def test_verity_config_database_name(monkeypatch):
"""Test that the database name is correctly set."""
testing_config = config.VerityConfig()
assert testing_config.DATABASE == 'Verity.db'
assert testing_config.DATABASE == "Verity.db"


def test_verity_config_config_file_directory(monkeypatch):
"""Test that the config file directory is set."""
testing_config = config.VerityConfig()
assert testing_config.CONFIG_FILE_DIRECTORY == 'config_files'
assert testing_config.CONFIG_FILE_DIRECTORY == "config_files"


def test_verity_config_load_config_file(monkeypatch):
"""Test that load_config_file handles successful YAML loading."""
# Mock the YAML file content
mock_config = {'logging_level': 'INFO'}
mock_config = {"logging_level": "INFO"}

# Create a temporary file
import tempfile

temp_dir = tempfile.mkdtemp()
mock_filepath = os.path.join(temp_dir, 'logging_config.yaml')
with open(mock_filepath, 'w') as f:
mock_filepath = os.path.join(temp_dir, "logging_config.yaml")
with open(mock_filepath, "w") as f:
yaml.safe_dump(mock_config, f)

# Configure monkeypatch to simulate file loading
testing_config = config.VerityConfig()
monkeypatch.setattr(testing_config, 'CONFIG_FILE_DIRECTORY', temp_dir)
loaded_config = testing_config.load_config_file('logging_config.yaml')
monkeypatch.setattr(testing_config, "CONFIG_FILE_DIRECTORY", temp_dir)
loaded_config = testing_config.load_config_file("logging_config.yaml")
assert loaded_config == mock_config

# Clean up the temporary file
import shutil

shutil.rmtree(temp_dir)
Loading