55from flask import Flask
66from pathlib import Path
77from dotenv import load_dotenv
8- from flask_login import LoginManager
9- from flask_sqlalchemy import SQLAlchemy
10- from sqlalchemy import MetaData
118from logging .config import dictConfig
129from flask .logging import default_handler
1310from flask_swagger_ui import get_swaggerui_blueprint
14- from flask_migrate import Migrate
15- from flask_caching import Cache
11+
12+ # Import extensions
13+ from .extensions import db , login_manager , migrate , cache
1614
1715# Prevent creation of __pycache__. Pycache messes up auth.
1816sys .dont_write_bytecode = True
1917
2018DB_NAME = "database.db"
2119
22- # Naming conventions for Flask-Migrate.
23- convention = {
24- "ix" : "ix_%(column_0_label)s" ,
25- "uq" : "uq_%(table_name)s_%(column_0_name)s" ,
26- "ck" : "ck_%(table_name)s_%(constraint_name)s" ,
27- "fk" : "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s" ,
28- "pk" : "pk_%(table_name)s" ,
29- }
30- metadata = MetaData (naming_convention = convention )
31- db = SQLAlchemy (metadata = metadata )
32-
3320env_path = Path ("." ) / ".secret"
3421load_dotenv (dotenv_path = env_path )
3522SECRET_KEY = os .environ ["SECRET_KEY" ]
3623SWAGGER_URL = "/docs"
3724API_URL = "/api/spec"
38- cache = Cache (config = {'CACHE_TYPE' : 'SimpleCache' })
3925
40- def main ():
41- # Setup logging.
26+ def setup_logging ():
27+ """Configure logging settings"""
4228 log_level_map = {
43- "info" : logging .INFO , # General operational info.
44- "warning" : logging .WARNING , # Warnings and above.
45- "debug" : logging .DEBUG , # Most verbose, debug info.
29+ "info" : logging .INFO ,
30+ "warning" : logging .WARNING ,
31+ "debug" : logging .DEBUG ,
4632 }
4733
48- if "DEBUG" in os .environ :
49- # Get log_level from env var, default to info if none set.
50- log_level_str = os .getenv ("LOG_LEVEL" , "info" ).lower ()
51- log_level = log_level_map .get (log_level_str , logging .INFO )
52- dictConfig (
53- {
54- "version" : 1 ,
55- "formatters" : {
56- "default" : {
57- "format" : "[%(asctime)s] %(levelname)s in %(module)s: %(message)s" ,
58- },
59- "audit" : {
60- "format" : "[%(asctime)s] AUDIT: %(message)s" ,
61- }
34+ # Get log_level from env var, default to info if none set.
35+ log_level_str = os .getenv ("LOG_LEVEL" , "info" ).lower ()
36+ log_level = log_level_map .get (log_level_str , logging .INFO )
37+
38+ dictConfig (
39+ {
40+ "version" : 1 ,
41+ 'disable_existing_loggers' : False ,
42+ "formatters" : {
43+ "default" : {
44+ "format" : "[%(asctime)s] %(levelname)s in %(module)s: %(message)s" ,
6245 },
63- "handlers" : {
64- "wsgi" : {
65- "class" : "logging.StreamHandler" ,
66- "stream" : "ext://flask.logging.wsgi_errors_stream" ,
67- "formatter" : "default" ,
68- },
69- "audit_file" : {
70- "class" : "logging.FileHandler" ,
71- "filename" : "logs/audit.log" ,
72- "formatter" : "audit" ,
73- }
74- },
75- "loggers" : {
76- "audit" : {
77- "level" : "INFO" ,
78- "handlers" : ["audit_file" ],
79- "propagate" : False ,
80- }
46+ "audit" : {
47+ "format" : "[%(asctime)s] AUDIT: %(message)s" ,
48+ }
49+ },
50+ "handlers" : {
51+ "wsgi" : {
52+ "class" : "logging.StreamHandler" ,
53+ "stream" : "ext://flask.logging.wsgi_errors_stream" ,
54+ "formatter" : "default" ,
8155 },
82- "root" : {
83- "level" : log_level ,
84- "handlers" : ["wsgi" ]
56+ "audit_file" : {
57+ "class" : "logging.FileHandler" ,
58+ "filename" : "logs/audit.log" ,
59+ "formatter" : "audit" ,
60+ }
61+ },
62+ "loggers" : {
63+ "audit" : {
64+ "level" : "INFO" ,
65+ "handlers" : ["audit_file" ],
66+ "propagate" : False ,
8567 },
86- }
87- )
68+ },
69+ "root" : {
70+ "level" : log_level ,
71+ "handlers" : ["wsgi" ]
72+ },
73+ }
74+ )
8875
8976 current_log_level = logging .getLogger ().getEffectiveLevel ()
90-
91- # Print the human-readable log level name
9277 print (f"Root logger level: { logging .getLevelName (current_log_level )} " )
9378
94- # Initialize app.
95- app = Flask (__name__ )
96- app .config ["SECRET_KEY" ] = SECRET_KEY
97- app .config ["SQLALCHEMY_DATABASE_URI" ] = f"sqlite:///{ app .root_path } /{ DB_NAME } "
98- app .config ["SQLALCHEMY_TRACK_MODIFICATIONS" ] = False
99- app .config ["SESSION_COOKIE_SAMESITE" ] = "Lax"
100- app .config ["REMEMBER_COOKIE_SAMESITE" ] = "Lax"
101- app .logger .removeHandler (default_handler )
102- app .audit_logger = logging .getLogger ('audit' )
103- migrate = Migrate (app , db , render_as_batch = True )
104- cache .init_app (app )
105- app .jinja_env .add_extension ('jinja2.ext.loopcontrols' )
106-
107- # Initialize DB.
79+ def register_extensions (app ):
80+ """Register Flask extensions with the app"""
10881 db .init_app (app )
109- migrate .init_app (app , db )
110-
111- # Load models.
112- from .models import User
113-
114- # Pull in our views route(s).
115- from .views import views
116-
117- app .register_blueprint (views , url_prefix = "/" )
118-
119- # Pull in our auth route(s).
120- from .auth import auth
82+ migrate .init_app (app , db , render_as_batch = True )
83+ cache .init_app (app )
12184
122- app .register_blueprint (auth , url_prefix = "/" )
85+ # Setup LoginManager
86+ login_manager .login_view = "auth.login"
87+ login_manager .login_message = None
88+ login_manager .init_app (app )
12389
124- # Pull in our api route(s).
125- from .api import api_bp
90+ def register_blueprints (app ):
91+ """Register blueprints with the app"""
92+ from .blueprints .main import main_bp
93+ from .blueprints .auth import auth_bp
94+ from .blueprints .api import api_bp
12695
96+ app .register_blueprint (main_bp , url_prefix = "/" )
97+ app .register_blueprint (auth_bp , url_prefix = "/" )
12798 app .register_blueprint (api_bp , url_prefix = "/api" )
12899
129- # Create Swagger UI blueprint.
100+ # Register Swagger UI blueprint
130101 swagger_ui = get_swaggerui_blueprint (
131102 SWAGGER_URL ,
132103 API_URL ,
@@ -147,27 +118,46 @@ def main():
147118 },
148119 },
149120 )
150-
151- # Register Swagger UI blueprint
152121 app .register_blueprint (swagger_ui )
153122
154- # Setup LoginManager.
155- login_manager = LoginManager ()
123+ def register_template_filters (app ):
124+ """Register custom template filters"""
125+ @app .template_filter ("from_json" )
126+ def from_json_filter (s ):
127+ return json .loads (s )
156128
157- # Redirect to auth.login if not already logged in.
158- login_manager .login_view = "auth.login"
159- login_manager .login_message = None
160- login_manager .init_app (app )
129+ def register_user_loader ():
130+ """Register the user loader for Flask-Login"""
131+ from .models import User
161132
162- # Decorator to set up login session.
163133 @login_manager .user_loader
164134 def load_user (id ):
165135 return db .session .get (User , int (id ))
166136
167- # Filter for jinja2 json parsing for user permissions.
168- @ app . template_filter ( "from_json" )
169- def from_json_filter ( s ):
170- return json . loads ( s )
137+ def create_app ():
138+ """Application factory function"""
139+ # Setup logging first
140+ setup_logging ( )
171141
172- return app
142+ # Initialize app
143+ app = Flask (__name__ )
144+ app .config ["SECRET_KEY" ] = SECRET_KEY
145+ app .config ["SQLALCHEMY_DATABASE_URI" ] = f"sqlite:///{ app .root_path } /{ DB_NAME } "
146+ app .config ["SQLALCHEMY_TRACK_MODIFICATIONS" ] = False
147+ app .config ["SESSION_COOKIE_SAMESITE" ] = "Lax"
148+ app .config ["REMEMBER_COOKIE_SAMESITE" ] = "Lax"
173149
150+ # Remove default handler and add audit logger
151+ app .logger .removeHandler (default_handler )
152+ app .audit_logger = logging .getLogger ('audit' )
153+
154+ # Add Jinja2 extension
155+ app .jinja_env .add_extension ('jinja2.ext.loopcontrols' )
156+
157+ # Register everything
158+ register_extensions (app )
159+ register_blueprints (app )
160+ register_template_filters (app )
161+ register_user_loader ()
162+
163+ return app
0 commit comments