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__/ __pycache__/
.venv/ .venv/
.env

View file

@ -1,37 +1,28 @@
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from urllib.parse import quote_plus
import logging import logging
db = SQLAlchemy() db = SQLAlchemy()
logger = logging.getLogger('sqlalchemy.engine') logger = logging.getLogger('sqlalchemy.engine')
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
handler = logging.StreamHandler() if not logger.handlers:
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) handler = logging.StreamHandler()
logger.addHandler(handler) handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
logger.addHandler(handler)
def create_app(): def create_app():
from config import Config
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(Config)
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
db.init_app(app) db.init_app(app)
with app.app_context():
from . import models
db.create_all()
from .routes import main from .routes import main
app.register_blueprint(main) app.register_blueprint(main)
@app.route("/")
def index():
return "Hello, you've reached the terrible but functional root of the site."
return app 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 Inventory
from .inventory import User from .inventory import User
from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, text from sqlalchemy import Boolean, ForeignKeyConstraint, Identity, Integer, ForeignKey, Unicode, DateTime, text
from sqlalchemy.dialects.mssql import DATETIME2
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
import datetime 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) 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) start_time: Mapped[Optional[datetime.datetime]] = mapped_column('Start Timestamp', DateTime)
end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DATETIME2) end_time: Mapped[Optional[datetime.datetime]] = mapped_column('End Timestamp', DateTime)
notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode()) notes: Mapped[Optional[str]] = mapped_column('Description & Notes', Unicode())
complete: Mapped[Optional[bool]] = mapped_column("Complete", Boolean, server_default=text('((0))')) 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))')) followup: Mapped[Optional[bool]] = mapped_column('Needs Follow-Up', Boolean, server_default=text('((0))'))

View file

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

View file

@ -165,7 +165,12 @@ def index():
'Deployed','Inoperable', 'Partially Inoperable', 'Deployed','Inoperable', 'Partially Inoperable',
'Unverified', 'Working' '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 # Convert pandas/numpy int64s to plain old Python ints
pivot = pivot.astype(int) pivot = pivot.astype(int)
@ -376,7 +381,7 @@ def search():
query = request.args.get('q', '').strip() query = request.args.get('q', '').strip()
if not query: if not query:
return redirect(url_for('index')) return redirect(url_for('main.index'))
InventoryAlias = aliased(Inventory) InventoryAlias = aliased(Inventory)
UserAlias = aliased(User) UserAlias = aliased(User)

View file

@ -5,7 +5,7 @@
<div class="col"> <div class="col">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <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) }} {{ icons.render_icon('house', 16) }}
</a> </a>
</li> </li>

View file

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

View file

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