Ellis
Ellis

Reputation: 3088

Access Flask config as attributes rather than dict keys

When handling application config in Flask, it's usually recommended to use the config dictionary available on the flask.Flask object, e.g. flask.current_app.config["TESTING"].

One recommendation for populating this is to use classes and inheritance to separate config per environment:

class Config:
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite:///:memory:'

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DEBUG = True

# Example usage:
app = flask.Flask(__name__)
app.config.from_object(ProductionConfig)

db_uri = app.config["DATABASE_URI"]

The disadvantage to this is that despite having defined our keys using class attributes, we access them using string keys, which end up being referenced around the code. If I mistype a key, I won't get a warning in my IDE and will get a KeyError at runtime. I also don't get type inference for the objects in the config.

It would be nice to be able to populate the Flask config in a similar way, but have better access to these values, like I might access ordinary attributes on an object.

I've thought of a few approaches, but none of them quite seem ideal:

  1. Wrap every config key in a function e.g. get_database_uri() - as long as this is the only way the config is accessed, the available keys and the types of their values are known. Requires writing a function for every new config key.
  2. Store a single Config object within the Flask config e.g. app.config["config"] = ProductionConfig(). With a type hint or wrapper function, we get typing, but it's odd to store a config object inside a config, and we lose the automatic setting of Flack-specific values such as TESTING.
  3. Forget about using app.config, and use any other solution.

Has anyone found any cleaner solutions which also integrate with Flask's config?

Upvotes: 4

Views: 1361

Answers (1)

davidism
davidism

Reputation: 127220

The class used for app.config can be configured by overriding Flask.config_class. Write a subclass of flask.config.Config that defines __getattr__ to look up keys, and assign it to the config_class attribute.

from flask.app import Flask
from flask.config import Config

class AttrConfig(Config):
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(key)

    def __dir__(self):
        out = set(self.keys())
        out.update(super().__dir__())
        return sorted(out)

class CustomFlask(Flask):
    config_class = AttrConfig

app = CustomFlask(__name__)

There's no reliable way to make IDEs understand these attributes though, given that they are loaded dynamically. Implementing __dir__ will make them show up in tab completion from the Python or IPython shell, but IDEs generally don't execute module code to provide introspection, so this won't affect them.

Upvotes: 5

Related Questions