3 Grundlagen der Anwendungsstruktur

3.1 Application Factory Pattern

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.

3.1.1 Traditionelle Anwendungsstruktur

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()

3.1.2 Application Factory Implementation

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 app

3.1.3 Vorteile des Factory Patterns

Konfigurationsflexibilitä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.

3.1.4 Anwendungsstart mit Factory

# run.py
from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

3.1.5 Testing mit Factory Pattern

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)

3.2 Projektorganisation und Ordnerstruktur

Eine durchdachte Projektstruktur ist entscheidend für die Wartbarkeit und Skalierbarkeit von Flask-Anwendungen. Die Organisation sollte logische Trennung von Verantwortlichkeiten widerspiegeln.

3.2.1 Kleine bis mittlere Anwendungen

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

3.2.2 Große Anwendungen mit Blueprints

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

3.2.3 Dateiorganisation nach Funktionalität

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.

3.2.4 Blueprint-Struktur

# 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')

3.2.5 Datenbankmodell-Organisation

# 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)

3.3 Konfigurationsmanagement

Professionelle Flask-Anwendungen erfordern strukturiertes Konfigurationsmanagement für verschiedene Umgebungen. Konfigurationsklassen bieten typisierte und organisierte Verwaltung von Anwendungseinstellungen.

3.3.1 Basis-Konfigurationsklasse

# 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')

3.3.2 Umgebungsspezifische Konfigurationen

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
}

3.3.3 MS-SQL Server Konfiguration

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
        }
    }

3.3.4 Konfiguration in der Application Factory

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 app

3.4 Umgebungsvariablen

Umgebungsvariablen ermöglichen die sichere Konfiguration von Anwendungen ohne Speicherung sensibler Daten im Quellcode. Sie sind essentiell für die Bereitstellung in verschiedenen Umgebungen.

3.4.1 .env-Dateien für Entwicklung

# .env
FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=entwicklungsschluessel-hier
DATABASE_URL=sqlite:///app.db
MAIL_SERVER=localhost
MAIL_PORT=8025

3.4.2 Environment-Loader

# 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 app

3.4.3 Windows-spezifische Umgebungsvariablen

REM 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

3.4.4 IIS-Integration mit Umgebungsvariablen

<!-- 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>

3.4.5 Umgebungsvariablen-Validierung

# 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')

3.4.6 Sicherheitsaspekte

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.