start project
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""Concrete, checkable translation of the cPanel account ceiling (Chapter 03).
|
||||
|
||||
Single source of truth for the account's literal resource limits. Other
|
||||
chapters should import BUDGET rather than re-hardcoding these numbers.
|
||||
validate_budget_config() is called from create_app() so an out-of-budget
|
||||
deployment fails loudly at startup instead of silently degrading under load
|
||||
— this is the concrete mechanism behind Chapter 03's "flag, don't silently
|
||||
accept" requirement.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AccountBudget:
|
||||
"""The hosting account's literal ceiling (Chapter 03).
|
||||
|
||||
iops/io_throughput are informational only here — Python config can't
|
||||
enforce them directly; they constrain how Ch06/07 batch reads/writes.
|
||||
"""
|
||||
|
||||
cpu_cores: int = 4
|
||||
max_entry_processes: int = 60
|
||||
memory_mb: int = 2048
|
||||
iops: int = 1024
|
||||
io_throughput_mb_s: int = 16
|
||||
max_total_processes: int = 150
|
||||
max_db_connections: int = 150
|
||||
|
||||
# Derived engineering targets from Chapter 03's budget table.
|
||||
db_pool_size_min: int = 2
|
||||
db_pool_size_max: int = 5
|
||||
|
||||
|
||||
BUDGET = AccountBudget()
|
||||
|
||||
_DISALLOWED_CACHE_BACKENDS = {"redis", "rediscache", "memcached", "memcachedcache"}
|
||||
|
||||
|
||||
def validate_budget_config(app: Flask) -> None:
|
||||
"""Raise at startup if config violates a Chapter 03 rule.
|
||||
|
||||
Cheap checks only (string/int comparisons) since this runs on every
|
||||
process boot — Passenger may recycle processes frequently (factor 9).
|
||||
"""
|
||||
# Validated as its own config key, not read out of
|
||||
# SQLALCHEMY_ENGINE_OPTIONS — that dict is empty for SQLite (its pool
|
||||
# classes reject pool_size/pool_recycle outright; see app/config.py's
|
||||
# _engine_options_for), so the *intended* setting must be checked
|
||||
# independently of whether the active engine actually consumes it.
|
||||
pool_size = app.config.get("DB_POOL_SIZE")
|
||||
if pool_size is None or not (BUDGET.db_pool_size_min <= pool_size <= BUDGET.db_pool_size_max):
|
||||
raise RuntimeError(
|
||||
f"DB_POOL_SIZE={pool_size} is outside the Chapter 03 budget "
|
||||
f"({BUDGET.db_pool_size_min}-{BUDGET.db_pool_size_max} per process; "
|
||||
f"{BUDGET.max_db_connections} total connections are shared across "
|
||||
f"up to {BUDGET.max_entry_processes} entry processes)."
|
||||
)
|
||||
|
||||
cache_type = str(app.config.get("CACHE_TYPE", "")).lower()
|
||||
if any(name in cache_type for name in _DISALLOWED_CACHE_BACKENDS):
|
||||
raise RuntimeError(
|
||||
f"CACHE_TYPE={app.config.get('CACHE_TYPE')!r} assumes a backend "
|
||||
"(Redis/Memcached) Chapter 03 says not to assume is available. "
|
||||
"Use FileSystemCache or a dashboard_cache DB table (Ch06)."
|
||||
)
|
||||
|
||||
batch_size = app.config.get("PARSE_BATCH_SIZE")
|
||||
if not batch_size or batch_size <= 0:
|
||||
raise RuntimeError(
|
||||
"PARSE_BATCH_SIZE must be a positive integer — unbounded/whole-file "
|
||||
"parsing per invocation violates the Chapter 03 memory budget."
|
||||
)
|
||||
|
||||
max_upload_mb = app.config.get("UPLOAD_MAX_SIZE_MB")
|
||||
if not max_upload_mb or max_upload_mb <= 0:
|
||||
raise RuntimeError(
|
||||
"UPLOAD_MAX_SIZE_MB must be a positive integer to bound disk/IOPS per upload."
|
||||
)
|
||||
Reference in New Issue
Block a user