Refactor application configuration; implement dynamic database URI setup based on environment variables for improved flexibility and maintainability
This commit is contained in:
parent
86a4e4d22f
commit
e67ae63eb8
10 changed files with 88 additions and 29 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
__pycache__/
|
||||
.venv/
|
||||
.env
|
29
__init__.py
29
__init__.py
|
@ -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
60
config.py
Normal 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."
|
||||
)
|
Binary file not shown.
|
@ -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))'))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
dotenv
|
||||
flask
|
||||
flask_sqlalchemy
|
||||
pyodbc
|
||||
pandas
|
||||
pyodbc
|
|
@ -165,7 +165,12 @@ def index():
|
|||
'Deployed','Inoperable', 'Partially Inoperable',
|
||||
'Unverified', 'Working'
|
||||
]
|
||||
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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue