Files
shopdb-flask/shopdb/config.py
cproudlock 0436e8b0af Asset-side printer save, status CRUD, real printer types, network fix
- Printers save through the asset blueprint (PUT /printers) instead of the
  legacy machines API; restrict supply-model picker to printer models.
- Asset statuses get full CRUD (PUT/DELETE with in-use guard); canonical set.
- Printer types reseeded to a real classification set + list filter.
- Equipment accepts gauge/maintenance references.
- Fix network list emitting network_device instead of networkdevice (View 404).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:35:32 -04:00

151 lines
4.7 KiB
Python

"""Flask application configuration."""
import os
from datetime import timedelta
from sqlalchemy.pool import StaticPool
class ConfigError(Exception):
"""Raised when required configuration is missing or unsafe."""
def _required_env(varname):
"""Read an env var; raise ConfigError if missing or empty."""
value = os.environ.get(varname)
if not value:
raise ConfigError(
f'{varname} is required in production. Set it in the environment '
f'before starting the app. Insecure defaults are not permitted in '
f'ProductionConfig.'
)
return value
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
SQLALCHEMY_DATABASE_URI = os.environ.get(
'DATABASE_URL',
'mysql+pymysql://root:password@localhost:3306/shopdb_flask',
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_pre_ping': True,
'pool_recycle': 300,
}
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'jwt-secret-key-change-in-production')
JWT_ACCESS_TOKEN_EXPIRES = timedelta(
seconds=int(os.environ.get('JWT_ACCESS_TOKEN_EXPIRES', 3600))
)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(
seconds=int(os.environ.get('JWT_REFRESH_TOKEN_EXPIRES', 2592000))
)
CORS_ORIGINS = [
origin.strip()
for origin in os.environ.get('CORS_ORIGINS', 'http://localhost:5173').split(',')
if origin.strip()
]
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
ZABBIX_ENABLED = os.environ.get('ZABBIX_ENABLED', 'false').lower() == 'true'
ZABBIX_URL = os.environ.get('ZABBIX_URL', '')
ZABBIX_TOKEN = os.environ.get('ZABBIX_TOKEN', '')
CACHE_TYPE = 'SimpleCache'
CACHE_DEFAULT_TIMEOUT = 600
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_ECHO = True
SQLALCHEMY_DATABASE_URI = os.environ.get(
'DATABASE_URL',
'mysql+pymysql://root:rootpassword@127.0.0.1:3306/shopdb_flask',
)
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
SQLALCHEMY_ENGINE_OPTIONS = {
'connect_args': {'check_same_thread': False},
'poolclass': StaticPool,
}
JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=5)
class ProductionConfig(Config):
"""Production configuration.
Validation is deferred to validate() so that importing this class in a
non-production environment (tests, dev, tooling) does not raise.
create_app() invokes validate() when config_name == 'production' so
a misconfigured production deploy still fails loud at boot.
"""
DEBUG = False
SQLALCHEMY_ECHO = False
JWT_COOKIE_SECURE = True
JWT_COOKIE_CSRF_PROTECT = True
@classmethod
def validate(cls):
"""Verify production config is safe. Called from create_app."""
secret_key = os.environ.get('SECRET_KEY', '')
jwt_secret = os.environ.get('JWT_SECRET_KEY', '')
database_url = os.environ.get('DATABASE_URL', '')
cors_raw = os.environ.get('CORS_ORIGINS', '').strip()
insecure_defaults = {
'dev-secret-key-change-in-production',
'jwt-secret-key-change-in-production',
}
if not secret_key or secret_key in insecure_defaults:
raise ConfigError(
'SECRET_KEY is required in production and must not be the '
'development default. Set a strong random value in the '
'environment before starting the app.'
)
if not jwt_secret or jwt_secret in insecure_defaults:
raise ConfigError(
'JWT_SECRET_KEY is required in production and must not be '
'the development default. Set a strong random value in the '
'environment before starting the app.'
)
if not database_url:
raise ConfigError(
'DATABASE_URL is required in production. No fallback to a '
'development localhost URL is permitted.'
)
if not cors_raw or cors_raw == '*':
raise ConfigError(
'CORS_ORIGINS must be a comma-separated allowlist of '
'explicit origins in production. Wildcard "*" is not '
'permitted. Example: '
'CORS_ORIGINS=https://shopdb.example.com,https://shopdb-mirror.example.com'
)
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig,
}