The Configuration Inheritance Model
The configuration system in etchblok-test-api is built on a hierarchical model using Python's @dataclass decorator. This approach provides a type-safe, structured way to manage environment-specific settings while maintaining a single source of truth for shared defaults. By leveraging class inheritance, the project ensures that production safety is enforced while local development and testing remain flexible.
The Configuration Hierarchy
All configuration classes in etchblok-test-api reside in app/config.py and inherit from a common base. This structure allows the application to override specific parameters based on the execution context (development, testing, or production).
Base Foundation
The BaseConfig class defines the global defaults used across the entire application. It establishes the baseline for security, debugging, and pagination.
@dataclass
class BaseConfig:
"""Base configuration shared across all environments."""
SECRET_KEY: str = field(default_factory=lambda: os.environ.get("SECRET_KEY", "change-me"))
DEBUG: bool = False
TESTING: bool = False
PAGE_SIZE: int = DEFAULT_PAGE_SIZE
def get_cache_config(self) -> Dict[str, Any]:
"""Return cache settings for this environment."""
return _build_cache_config()
A key feature of BaseConfig is the _validate method, which checks internal invariants, such as ensuring the PAGE_SIZE does not exceed the MAX_PAGE_SIZE constant (set to 100 in app/config.py).
Environment Specialization
Each environment-specific class inherits from BaseConfig and modifies only the necessary attributes:
- DevelopmentConfig: Optimized for local iteration. It sets
DEBUG = Trueto enable Flask's debugger and reduces thePAGE_SIZEto 10. It also uses a smaller cache footprint (30s TTL, 128 entries) via an override ofget_cache_config. - TestingConfig: Tailored for automated test suites. It sets
TESTING = Trueand uses a very smallPAGE_SIZEof 5. This low limit is intentional; it forces pagination logic to trigger even with small test datasets, ensuring that edge cases in list endpoints are exercised during CI/CD. - ProductionConfig: Prioritizes security and performance. Unlike other configurations, it enforces that
SECRET_KEYmust be provided via an environment variable by usingos.environ["SECRET_KEY"]without a default value. This prevents the application from starting in production with the insecure "change-me" default. It also scales up the cache to 4096 entries with a 600s TTL.
Loading Configuration
The etchblok-test-api application factory in app/__init__.py uses Flask's from_object method to load these classes. This allows the entire configuration to be swapped by passing a different class to the factory.
def create_app(config_class=DevelopmentConfig) -> Flask:
app = Flask(__name__)
app.config.from_object(config_class)
# ... registration of blueprints ...
return app
By defaulting to DevelopmentConfig, the project ensures that a developer can run the application immediately after cloning without setting up environment variables.
Design Tradeoffs and Implementation Status
While the configuration model is robust, there are specific areas in the current etchblok-test-api implementation where the configuration is defined but not yet fully integrated:
Cache Configuration Decoupling
The get_cache_config method is implemented in all configuration classes to provide environment-aware caching parameters (TTL and max size). However, the current LRUCache implementation in the service layer does not yet consume these values, instead relying on internal defaults.
Pagination Hardcoding
Although PAGE_SIZE is defined in the configuration hierarchy, some route handlers currently use hardcoded defaults. For example, in app/routes/bookmarks.py, the list_bookmarks endpoint defaults to 25:
@bookmarks_bp.route("/", methods=["GET"])
def list_bookmarks():
page = request.args.get("page", 1, type=int)
per_page = request.args.get("per_page", 25, type=int) # Hardcoded default
# ...
This means that while TestingConfig specifies a PAGE_SIZE of 5, the route will still default to 25 unless the configuration is explicitly accessed via current_app.config['PAGE_SIZE'] within the route logic.
Production Safety
The choice to use os.environ["SECRET_KEY"] in ProductionConfig is a deliberate "fail-fast" design. If the environment variable is missing, the application will raise a KeyError immediately upon instantiation of the config class, preventing the deployment of an insecurely configured instance.