Initial Commit
This commit is contained in:
84
sigl/database/__init__.py
Normal file
84
sigl/database/__init__.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Sigl Database Package.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from alembic.script import ScriptDirectory
|
||||
from flask import current_app
|
||||
from sqlalchemy import inspect
|
||||
|
||||
from sigl.exc import Error
|
||||
|
||||
from .globals import db, migrate
|
||||
from .util import make_uri
|
||||
|
||||
__all__ = ('db', 'migrate', 'init_db')
|
||||
|
||||
|
||||
def check_database():
|
||||
"""Check that the database schema is initialized."""
|
||||
if current_app.config.get('_SIGL_DB_NEEDS_INIT'):
|
||||
raise Error('Database is not initialized')
|
||||
|
||||
|
||||
def init_db(app):
|
||||
"""Initialize the Global Database Object for the Application."""
|
||||
# Set SQLalchemy Defaults (suppresses warning)
|
||||
app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', False)
|
||||
|
||||
# Load SQLalchemy URI
|
||||
if 'SQLALCHEMY_DATABASE_URI' not in app.config:
|
||||
if 'DB_URI' in app.config:
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = app.config['DB_URI']
|
||||
else:
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = make_uri(app)
|
||||
|
||||
# Initialize Object Relational Mapping
|
||||
from .orm import init_orm
|
||||
init_orm()
|
||||
|
||||
# Log the URI (masked)
|
||||
app.logger.debug(
|
||||
'Starting SQLalchemy with URI: "%s"', re.sub(
|
||||
r':([.+]|[0-9a-z\'_-~]|%[0-9A-F]{2})+@',
|
||||
':(masked)@',
|
||||
app.config['SQLALCHEMY_DATABASE_URI'],
|
||||
flags=re.I
|
||||
)
|
||||
)
|
||||
|
||||
# Initialize Database
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
# Check the Database Revision
|
||||
app.config['_SIGL_DB_NEEDS_INIT'] = True
|
||||
app.config['_SIGL_DB_NEEDS_UPGRADE'] = False
|
||||
app.config['_SIGL_DB_HEAD_REV'] = None
|
||||
app.config['_SIGL_DB_CUR_REV'] = None
|
||||
with app.app_context():
|
||||
if inspect(db.engine).has_table('alembic_version'):
|
||||
app.config['_SIGL_DB_NEEDS_INIT'] = False
|
||||
|
||||
db_version_sql = db.text('select version_num from alembic_version')
|
||||
db_version = db.engine.execute(db_version_sql).scalar()
|
||||
app.config['_SIGL_DB_CUR_REV'] = db_version
|
||||
|
||||
script_dir = ScriptDirectory.from_config(migrate.get_config())
|
||||
for rev in script_dir.walk_revisions():
|
||||
if rev.is_head:
|
||||
app.config['_SIGL_DB_HEAD_REV'] = rev.revision
|
||||
if rev.revision == db_version:
|
||||
app.config['_SIGL_DB_NEEDS_UPGRADE'] = not rev.is_head
|
||||
|
||||
# Register Database Setup Hook
|
||||
app.before_request(check_database)
|
||||
|
||||
# Notify Initialization
|
||||
app.logger.debug('Database Initialized')
|
||||
|
||||
# Return Success
|
||||
return True
|
||||
14
sigl/database/globals.py
Normal file
14
sigl/database/globals.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Sigl Database Global Objects.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
#: Global Database Object for Models
|
||||
db = SQLAlchemy()
|
||||
|
||||
#: Global Alembic Migration Object
|
||||
migrate = Migrate()
|
||||
34
sigl/database/orm.py
Normal file
34
sigl/database/orm.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Sigl Database ORM Setup.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from sigl.domain.models import (
|
||||
Product,
|
||||
ProductLocation,
|
||||
)
|
||||
|
||||
from .globals import db
|
||||
from .tables import (
|
||||
product_locations,
|
||||
products,
|
||||
)
|
||||
|
||||
__all__ = ('init_orm', )
|
||||
|
||||
|
||||
def init_orm():
|
||||
"""Initialize the Sigl ORM."""
|
||||
|
||||
# Products
|
||||
db.mapper(Product, products, properties={
|
||||
'locations': db.relationship(
|
||||
ProductLocation,
|
||||
backref='product',
|
||||
cascade='all, delete-orphan',
|
||||
),
|
||||
})
|
||||
|
||||
# Product Locations
|
||||
db.mapper(ProductLocation, product_locations)
|
||||
56
sigl/database/tables.py
Normal file
56
sigl/database/tables.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Sigl Database Tables.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
from .globals import db
|
||||
|
||||
#: Sigl Server Configuration Table
|
||||
sigl_config = db.Table(
|
||||
'sigl_config',
|
||||
|
||||
# Key/Value Store
|
||||
db.Column('key', db.String, primary_key=True),
|
||||
db.Column('value', db.String(128)),
|
||||
)
|
||||
|
||||
#: Product Table
|
||||
products = db.Table(
|
||||
'products',
|
||||
|
||||
# Primary Key
|
||||
db.Column('id', db.Integer, primary_key=True),
|
||||
|
||||
# Product Attributes
|
||||
db.Column('name', db.String(128), nullable=False),
|
||||
db.Column('category', db.String(128), nullable=False, index=True),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('created_at', db.DateTime(), default=None),
|
||||
db.Column('modified_at', db.DateTime(), default=None),
|
||||
)
|
||||
|
||||
#: Product Location Table
|
||||
product_locations = db.Table(
|
||||
'product_locations',
|
||||
|
||||
# Primary Keys
|
||||
db.Column(
|
||||
'product_id',
|
||||
db.ForeignKey('products.id'),
|
||||
nullable=False,
|
||||
primary_key=True,
|
||||
),
|
||||
db.Column('store', db.String(128), nullable=False, primary_key=True),
|
||||
|
||||
# Location Attributes
|
||||
db.Column('aisle', db.String(64), nullable=False),
|
||||
db.Column('bin', db.String(64), default=None),
|
||||
|
||||
# Mixin Columns
|
||||
db.Column('notes', db.String(), default=None),
|
||||
db.Column('created_at', db.DateTime(), default=None),
|
||||
db.Column('modified_at', db.DateTime(), default=None),
|
||||
)
|
||||
78
sigl/database/util.py
Normal file
78
sigl/database/util.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Sigl Database Utilities.
|
||||
|
||||
Simple Grocery List (Sigl) | sigl.app
|
||||
Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
|
||||
"""
|
||||
|
||||
import os
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from sigl.exc import ConfigError
|
||||
|
||||
__all__ = ('make_uri', )
|
||||
|
||||
|
||||
def make_uri(app):
|
||||
'''
|
||||
Assembles the Database URI from Application Configuration values.
|
||||
|
||||
:param app: :class:`Flask` application object
|
||||
'''
|
||||
if 'DB_DRIVER' not in app.config:
|
||||
raise ConfigError(
|
||||
'DB_DRIVER must be defined in application configuration',
|
||||
config_key='DB_DRIVER'
|
||||
)
|
||||
|
||||
# SQLite
|
||||
if app.config['DB_DRIVER'] == 'sqlite':
|
||||
if 'DB_FILE' not in app.config:
|
||||
raise ConfigError(
|
||||
'DB_FILE must be defined in application configuration for '
|
||||
'SQLite Database',
|
||||
config_key='DB_FILE'
|
||||
)
|
||||
return 'sqlite:///{db_file}'.format(
|
||||
db_file=os.path.abspath(app.config['DB_FILE']),
|
||||
)
|
||||
|
||||
# Database Servers (note only MySQL and PostgreSQL are tested)
|
||||
uri_auth = ''
|
||||
uri_port = ''
|
||||
|
||||
for k in ('DB_HOST', 'DB_NAME'):
|
||||
if k not in app.config:
|
||||
raise ConfigError(
|
||||
f'{k} must be defined in application configuration',
|
||||
config_key=k
|
||||
)
|
||||
|
||||
# Assemble Port Suffix
|
||||
if 'DB_PORT' in app.config:
|
||||
uri_port = ':{}'.format(app.config['DB_PORT'])
|
||||
|
||||
# Assemble Authentication Portion
|
||||
if 'DB_USERNAME' in app.config:
|
||||
if 'DB_PASSWORD' in app.config:
|
||||
uri_auth = '{username}:{password}@'.format(
|
||||
username=quote_plus(app.config['DB_USERNAME']),
|
||||
password=quote_plus(app.config['DB_PASSWORD']),
|
||||
)
|
||||
else:
|
||||
uri_auth = '{username}@'.format(
|
||||
username=quote_plus(app.config['DB_USERNAME']),
|
||||
)
|
||||
elif 'DB_PASSWORD' in app.config:
|
||||
raise ConfigError(
|
||||
'DB_USERNAME must be defined if DB_PASSWORD is set in '
|
||||
'application configuration',
|
||||
config_key='DB_USERNAME'
|
||||
)
|
||||
|
||||
return '{driver}://{auth}{host}{port}/{name}'.format(
|
||||
driver=app.config['DB_DRIVER'],
|
||||
auth=uri_auth,
|
||||
host=app.config['DB_HOST'],
|
||||
port=uri_port,
|
||||
name=app.config['DB_NAME'],
|
||||
)
|
||||
Reference in New Issue
Block a user