pythonpython-3.xflaskpython-unittestpython-config

Is it safe to overwrite app config after 1st config assignment is made (and doesn't it negate the benefit of facotry pattern)?


Hello so we have couple of methods to setup app configurations in Flask.

  1. ENV var directly in cmd before running flask

  2. app.config['xxx']

  3. app.config.from_object (module or class)

  4. app.config.from_pyfile (file)

  5. app.config.from_envvar (FILEPATH_ENVVAR)

I have read that for example when unit testing, it is best to implement factory pattern as some configs don't work when overwritten.

So I am wondering if it is safe to use multiple methods from above?

For example if i follow below steps together then is it safe to assume config will be applied correctly?

Step 1: Use method 1 before running application (for example to set ENV var secret key or to setup an ENV var that can be checked in the code at step 2 to decide whether to apply dev/prod config settings class or to set ENV var PATH_TO_SOMECONFIGFILE)

Step 2: Immediately after initializing the app object, use method 3 (to set default production settings or check ENV var set in above step to invoke appropriate dev/prod class).

Step 3: Immediately after above step, use method 4 or 5 to update the config settings

So will the settings from step 3 overwrite all previous (previously set) settings correctly? And is this good practice? And doesn't it negate the benefit of using factory pattern since I have read that not using factory pattern (for example while unit testing) could result in certain config if updated will not apply correctly. Hence create factory pattern to get fresh object with the needed config applied.


Solution

  • I'll start by answering your questions and then I'll continue with a few explanations regarding configuration best practices.

    So I am wondering if it is safe to use multiple methods from above?

    Yes. And it's in fact recommended that you use multiple ways of loading your configuration (see below why).

    So will the settings from step 3 overwrite all previous (previously set) settings correctly? And is this good practice?

    Yes. And usually the best way to learn things is to try things out. So I'll give you an example below, using classes and inheritance just to prove how overriding works (you can paste this in a Python module and run it, assuming you have Flask installed), but you should go ahead and experiment with all of the above methods that you've read about and mix and match them to solidify your knowledge.

    from flask import Flask
    
    
    class Config:
        LOGGING_CONFIGURATION = None
        DATABASE = None
    
    class DevelopmentConfig(Config):
        LOGGING_CONFIGURATION = 'Development'
        DATABASE = 'development.db'
    
    class TestConfig(Config):
        LOGGING_CONFIGURATION = 'Test'
        DATABASE = 'test.db'
    
    
    def create_app(config):
        app = Flask(__name__)
        app.config.from_object(Config)  # we load the default configuration
        print(app.config['LOGGING_CONFIGURATION'], app.config['DATABASE'])
        app.config.from_object(config)  # we override with the parameter config
        print(app.config['LOGGING_CONFIGURATION'], app.config['DATABASE'])
        return app
    
    
    if __name__ == '__main__':
        app = create_app(DevelopmentConfig)
        test_app = create_app(TestConfig)  # you do this during tests
    

    outputs

    None None
    Development development.db
    None None
    Test test.db
    

    And Doesn't it negate the benefit of using factory pattern since I have read that not using factory pattern (for example while unit testing) could result in certain config if updated will not apply correctly. Hence create factory pattern to get fresh object with the needed config applied.

    No. You're confusing things here, loading configurations and using application factories are NOT mutually exclusive. In fact, they work together.

    You are correct that messing with some config values, such as ENV and DEBUG, which are special config values may cause the application to behave inconsistently. More details on those special vales here. So try not to alter those once the application has finished setting up. See more in the word of advice section below.


    Further clarifications

    The way Flask is designed usually requires the configuration to be available when the application starts up, and you usually need SOME KIND of configuration because, depending on your environment you might want to change different settings like:

    Now you see that, because you need different settings for different configurations, you'll need some kind of mechanism to toggle between configurations, which is why using all those methods above combined is useful and recommended, because generally you'll load some kind of DEFAULT configuration, and override accordingly based on the environment (PRODUCTION, DEVELOPMENT, etc).

    The way you would achieve all the above is described very meticulously with enough examples here, which is beyond the scope of this thread, but I'd encourage you to go through each example and try it out.

    I also have an open source app, built with Flask, where I've used classes and inheritance to load different configurations based on different environments and went even further to customise those as well. So not only do I load, say, the DEVELOPMENT configuration, but I even customise it further should I want to. Examples are here, and I load them from here. Do try to understand the mechanism by which I load the configuration, it will help you.

    Edit:

    The config object is just a subclass of a dictionary that has additional methods that help you load configurations from different places (the ones you've seen above):

      # override a setting if it exists or add it if it doesn't
      # just like you would do in a normal dictionary
      app.config[key] = value
    
      # this will either override all the values that are loaded
      # from that object or it will add them
      app.config.from_pyobject(object)
    
      # same as above, and the same goes for any method you use to add/override
      app.config.from_pyfile(filename)
    

    The same goes for any other method not listed here that might load/override settings. They all have the same precedence, just be careful of the order in which you override, the configuration you load last is the one that the app will use.


    Word of advice

    Try to load your configuration, along with all its core settings, regardless of your environment, as soon as your application starts up, and don't alter them from that point forward. If you find you are altering a lot of settings from different parts of the application, after the application is running (i.e you're doing a lot of app[value] = another_value), try to rethink your design, as that is an unstable implementation. What if a part of an application is expecting a setting that hasn't been set up by another part of the application ? Don't do that.