Refactor application configuration; implement dynamic database URI setup based on environment variables for improved flexibility and maintainability

This commit is contained in:
Yaro Kasear 2025-06-20 13:48:03 -05:00
parent 86a4e4d22f
commit e67ae63eb8
10 changed files with 88 additions and 29 deletions

3
.gitignore vendored
View file

@ -1,2 +1,3 @@
__pycache__/
.venv/
.venv/
.env

View file

@ -1,37 +1,28 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from urllib.parse import quote_plus
import logging
db = SQLAlchemy()
logger = logging.getLogger('sqlalchemy.engine')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
logger.addHandler(handler)
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
logger.addHandler(handler)
def create_app():
from config import Config
app = Flask(__name__)
params = quote_plus(
"DRIVER=ODBC Driver 17 for SQL Server;"
"SERVER=NDVASQLCR01;"
"DATABASE=conradTest;"
"Trusted_Connection=yes;"
)
app.config['SQLALCHEMY_DATABASE_URI'] = f"mssql+pyodbc:///?odbc_connect={params}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config.from_object(Config)
db.init_app(app)
with app.app_context():
from . import models
db.create_all()
from .routes import main
app.register_blueprint(main)
@app.route("/")
def index():
return "Hello, you've reached the terrible but functional root of the site."
return app

60
config.py Normal file
View file

@ -0,0 +1,60 @@
import os
import urllib.parse
from dotenv import load_dotenv
load_dotenv()
def quote(value):
return urllib.parse.quote_plus(value)
class Config:
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG = False
TESTING = False
DB_BACKEND = os.getenv('DB_BACKEND', 'sqlite').lower()
DB_WINDOWS_AUTH = os.getenv('DB_WINDOWS_AUTH', 'false').strip().lower() in ['true', '1', 'yes']
DB_USER = os.getenv('DB_USER', '')
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
DB_HOST = os.getenv('DB_HOST', 'localhost')
DB_PORT = os.getenv('DB_PORT', '')
DB_NAME = os.getenv('DB_NAME', 'app.db') # default file for sqlite
SQLALCHEMY_DATABASE_URI = None # <-- initialize properly
if DB_BACKEND == 'mssql':
driver = os.getenv('DB_DRIVER', 'ODBC Driver 17 for SQL Server')
quoted_driver = quote(driver)
if DB_WINDOWS_AUTH:
SQLALCHEMY_DATABASE_URI = (
f"mssql+pyodbc://@{DB_HOST}/{DB_NAME}?driver={quoted_driver}&Trusted_Connection=yes"
)
else:
SQLALCHEMY_DATABASE_URI = (
f"mssql+pyodbc://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
f"?driver={quoted_driver}"
)
elif DB_BACKEND == 'postgres':
SQLALCHEMY_DATABASE_URI = (
f"postgresql://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT or '5432'}/{DB_NAME}"
)
elif DB_BACKEND in ['mariadb', 'mysql']:
SQLALCHEMY_DATABASE_URI = (
f"mysql+pymysql://{quote(DB_USER)}:{quote(DB_PASSWORD)}@{DB_HOST}:{DB_PORT or '3306'}/{DB_NAME}"
)
elif DB_BACKEND == 'sqlite':
if DB_NAME == ':memory:':
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
else:
SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_NAME}"
else:
raise ValueError(
f"Unsupported DB_BACKEND: {DB_BACKEND}. "
"Supported backends: mssql, postgres, mariadb, mysql, sqlite."
)

View file

@ -3,8 +3,7 @@ if TYPE_CHECKING:
from .inventory import Inventory
from .inventory import User
from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, text
from sqlalchemy.dialects.mssql import DATETIME2
from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, DateTime, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
import datetime
@ -17,8 +16,8 @@ class WorkLog(db.Model):
)
id: Mapped[int] = mapped_column("ID", Integer, Identity(start=1, increment=1), primary_key=True)
start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DATETIME2)
end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DATETIME2)
start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DateTime)
end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DateTime)
notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode())
complete: Mapped[Optional[bool]] = mapped_column("Complete", Boolean, server_default=text('((0))'))
followup: Mapped[Optional[bool]] = mapped_column('Needs Follow-Up', Boolean, server_default=text('((0))'))

View file

@ -1,4 +1,5 @@
dotenv
flask
flask_sqlalchemy
pyodbc
pandas
pyodbc

View file

@ -165,7 +165,12 @@ def index():
'Deployed','Inoperable', 'Partially Inoperable',
'Unverified', 'Working'
]
pivot = df['condition'].value_counts().reindex(expected_conditions, fill_value=0)
print(df)
if 'condition' in df.columns:
pivot = df['condition'].value_counts().reindex(expected_conditions, fill_value=0)
else:
pivot = pd.Series([0] * len(expected_conditions), index=expected_conditions)
# Convert pandas/numpy int64s to plain old Python ints
pivot = pivot.astype(int)
@ -376,7 +381,7 @@ def search():
query = request.args.get('q', '').strip()
if not query:
return redirect(url_for('index'))
return redirect(url_for('main.index'))
InventoryAlias = aliased(Inventory)
UserAlias = aliased(User)

View file

@ -5,7 +5,7 @@
<div class="col">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{{ url_for('index') }}" class="link-success link-underline-opacity-0">
<a href="{{ url_for('main.index') }}" class="link-success link-underline-opacity-0">
{{ icons.render_icon('house', 16) }}
</a>
</li>

View file

@ -25,6 +25,7 @@
</div>
</div>
{% endif %}
{% if (datasets[0]['values'] | sum) > 0 %}
<div class="col">
<div class="card">
<div class="card-body">
@ -33,6 +34,7 @@
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -30,7 +30,7 @@
<body class="bg-tertiary text-primary-emphasis">
<nav class="navbar navbar-expand bg-body-tertiary border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">
<a class="navbar-brand" href="{{ url_for('main.index') }}">
Inventory Manager
</a>
<button class="navbar-toggler">