Das Application Factory Pattern ist ein bewährtes Designmuster für Flask-Anwendungen, das die Flexibilität und Testbarkeit erheblich verbessert. Statt eine globale Flask-Instanz zu erstellen, wird eine Funktion definiert, die bei Bedarf eine konfigurierte Anwendungsinstanz zurückgibt.
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'geheimer-schluessel'
@app.route('/')
def index():
return 'Hallo Welt'
if __name__ == '__main__':
app.run()from flask import Flask
from config import Config
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
# Extensions initialisieren
from extensions import db, login_manager
db.init_app(app)
login_manager.init_app(app)
# Blueprints registrieren
from blueprints.main import main_bp
from blueprints.auth import auth_bp
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp, url_prefix='/auth')
return appKonfigurationsflexibilität: Verschiedene Konfigurationen können für Development, Testing und Production verwendet werden.
Testbarkeit: Für Tests können isolierte Anwendungsinstanzen mit spezifischen Testkonfigurationen erstellt werden.
Extension-Management: Flask-Extensions werden erst bei der Anwendungserstellung initialisiert, was zirkuläre Imports vermeidet.
Blueprint-Integration: Modularisierung durch Blueprints wird vereinfacht und strukturierter.
# run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)import unittest
from app import create_app
from config import TestConfig
class BasicTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
self.client = self.app.test_client()
def tearDown(self):
self.app_context.pop()
def test_index(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)Eine durchdachte Projektstruktur ist entscheidend für die Wartbarkeit und Skalierbarkeit von Flask-Anwendungen. Die Organisation sollte logische Trennung von Verantwortlichkeiten widerspiegeln.
projekt/
├── app/
│ ├── __init__.py
│ ├── routes.py
│ ├── models.py
│ ├── forms.py
│ └── templates/
│ ├── base.html
│ └── index.html
├── static/
│ ├── css/
│ ├── js/
│ └── images/
├── config.py
├── requirements.txt
└── run.py
projekt/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── extensions.py
│ ├── main/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── forms.py
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── forms.py
│ │ └── models.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── serializers.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── main/
│ │ └── auth/
│ └── static/
├── tests/
│ ├── __init__.py
│ ├── test_basic.py
│ └── test_auth.py
├── migrations/
├── config.py
├── requirements.txt
└── run.py
app/init.py: Enthält die Application Factory und zentrale Anwendungslogik.
extensions.py: Initialisierung von Flask-Extensions wie SQLAlchemy, Flask-Login, etc.
models.py: Datenbankmodelle und zugehörige Geschäftslogik.
blueprints/: Modulare Anwendungsteile mit eigenständiger Funktionalität.
templates/: Jinja2-Templates, organisiert nach Blueprints oder Funktionsbereichen.
static/: Statische Dateien wie CSS, JavaScript, Bilder.
# app/main/__init__.py
from flask import Blueprint
main_bp = Blueprint('main', __name__)
from app.main import routes# app/main/routes.py
from flask import render_template, current_app
from app.main import main_bp
@main_bp.route('/')
def index():
current_app.logger.info('Index-Seite aufgerufen')
return render_template('main/index.html')# app/models.py
from app.extensions import db
from datetime import datetime
class BaseModel(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class User(BaseModel):
__tablename__ = 'users'
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)Professionelle Flask-Anwendungen erfordern strukturiertes Konfigurationsmanagement für verschiedene Umgebungen. Konfigurationsklassen bieten typisierte und organisierte Verwaltung von Anwendungseinstellungen.
# config.py
import os
from datetime import timedelta
class Config:
# Grundkonfiguration
SECRET_KEY = os.environ.get('SECRET_KEY') or 'fallback-schluessel-nur-entwicklung'
# Datenbank
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_recycle': 300,
'pool_pre_ping': True
}
# Session-Konfiguration
PERMANENT_SESSION_LIFETIME = timedelta(hours=2)
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
# Mail-Konfiguration
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
# Upload-Konfiguration
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads')class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(os.path.dirname(__file__), 'app-dev.db')
SESSION_COOKIE_SECURE = False
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
WTF_CSRF_ENABLED = False
SESSION_COOKIE_SECURE = False
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(os.path.dirname(__file__), 'app.db')
# Logging-Konfiguration
LOG_TO_STDOUT = os.environ.get('LOG_TO_STDOUT')
# Konfigurationswörterbuch für einfache Auswahl
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}class ProductionConfig(Config):
# MS-SQL Server Verbindungsstring
SQLALCHEMY_DATABASE_URI = (
f"mssql+pyodbc://{os.environ.get('DB_USER')}:"
f"{os.environ.get('DB_PASSWORD')}@"
f"{os.environ.get('DB_SERVER')}/"
f"{os.environ.get('DB_NAME')}"
f"?driver=ODBC+Driver+17+for+SQL+Server"
)
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_recycle': 300,
'pool_pre_ping': True,
'connect_args': {
'timeout': 20,
'autocommit': True
}
}def create_app(config_name=None):
app = Flask(__name__)
# Konfiguration laden
config_name = config_name or os.environ.get('FLASK_CONFIG') or 'default'
app.config.from_object(config[config_name])
# Instance-spezifische Konfiguration
app.config.from_pyfile('config.py', silent=True)
return appUmgebungsvariablen ermöglichen die sichere Konfiguration von Anwendungen ohne Speicherung sensibler Daten im Quellcode. Sie sind essentiell für die Bereitstellung in verschiedenen Umgebungen.
# .env
FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=entwicklungsschluessel-hier
DATABASE_URL=sqlite:///app.db
MAIL_SERVER=localhost
MAIL_PORT=8025# app/__init__.py
import os
from dotenv import load_dotenv
# .env-Datei laden (nur in Entwicklung)
dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
if os.path.exists(dotenv_path):
load_dotenv(dotenv_path)
def create_app(config_name=None):
app = Flask(__name__)
# Konfiguration basierend auf Umgebungsvariable
config_name = config_name or os.environ.get('FLASK_CONFIG') or 'default'
app.config.from_object(config[config_name])
return appREM set_env.bat
set FLASK_APP=run.py
set FLASK_ENV=production
set SECRET_KEY=produktionsschluessel
set DATABASE_URL=mssql+pyodbc://user:pass@server/database?driver=ODBC+Driver+17+for+SQL+Server
set MAIL_SERVER=mail.unternehmen.de
set MAIL_PORT=587
set MAIL_USERNAME=app@unternehmen.de
set MAIL_PASSWORD=mailpasswort
<!-- web.config -->
<configuration>
<system.webServer>
<handlers>
<add name="PythonHandler" path="*" verb="*"
modules="FastCgiModule"
scriptProcessor="C:\Python\python.exe|C:\Python\wfastcgi.py"
resourceType="Unspecified" />
</handlers>
</system.webServer>
<appSettings>
<add key="FLASK_CONFIG" value="production" />
<add key="SECRET_KEY" value="produktionsschluessel" />
<add key="DATABASE_URL" value="mssql+pyodbc://..." />
</appSettings>
</configuration># config.py
import os
import sys
class Config:
@staticmethod
def init_app(app):
# Validierung kritischer Umgebungsvariablen
required_vars = ['SECRET_KEY', 'DATABASE_URL']
missing_vars = [var for var in required_vars if not os.environ.get(var)]
if missing_vars:
app.logger.error(f'Fehlende Umgebungsvariablen: {", ".join(missing_vars)}')
sys.exit(1)
class ProductionConfig(Config):
@staticmethod
def init_app(app):
Config.init_app(app)
# Produktions-spezifische Validierungen
if not os.environ.get('MAIL_SERVER'):
app.logger.warning('MAIL_SERVER nicht konfiguriert')Keine sensiblen Daten im Code: Passwörter, API-Schlüssel und Verbindungsstrings gehören nicht in den Quellcode.
Environment-spezifische Dateien: Verwenden Sie verschiedene .env-Dateien für verschiedene Umgebungen.
Produktionsgeheimnisse: In Produktionsumgebungen sollten Geheimnisse über sichere Verwaltungssysteme bereitgestellt werden.
Logging-Sicherheit: Stellen Sie sicher, dass sensible Umgebungsvariablen nicht in Logs erscheinen.
Diese strukturierte Herangehensweise an die Anwendungsarchitektur bildet die Basis für skalierbare und wartbare Flask-Anwendungen in Unternehmensumgebungen.