angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask application settings changed on-the-fly by an administrator

Application config must be static, application settings must be dynamic

26 July 2019 Updated 13 August 2019
In Flask
post main image
Original photo unsplash.com/@ssvveennn

In Flask we have the config object which can be used to specify database parameters, email parameters, etc. When we run a Flask program it first creates the application. Once the application has been created subsequent requests skip the app creation and are redirected to the blueprint views.

When Flask starts, the config variables are loaded and used. Important to understand is that every visitor gets its own instance. Once the Flask application is running we can change the config variables but what is the use of this? If we change a config variable in one instance then this is not visible in other instances.

I also believe we should distinct between configuration variables like database, email, session setup variables and settings variables that we want to change on-the-fly like the program name text displayed in templates or a allow/block registrations boolean. We should be able to manage the settings variables using an administrator and trigger other running instances to use them without reloading these other instances.

In short, to implement application settings we must do the following:

  • Define the application settings classes
  • Load the current version of the application settings when the Flask app is created
  • Make an admin function to edit the application settings and create a new version of the application settings
  • Make an admin function to activate a new version of the application settings
  • Trigger other instances to use a newly activated version of the application settings
  • Make the application settings accessible in our Jinja templates

Define the application setting classes

A requirement is that it must be possible to create different versions of the application settings. This is more work but it makes it possible to switch without problem to a new version and switch back to a previous version. I use a class attribute/database table field seqno to identify a version.

The application settings, Settings, are grouped into sections, SettingSections, and have a value_type that tells whether it is a integer, string or boolean. Every time the application settings are saved, a new version of the application settings is created. This version can be inspected and if all is well it can be activated. I also added a from_seqno variable that indicates the version we modified, I use this to show the changes between versions.

Finaly there is the application settings seqno or version, SettingSeqno, which holds the version number of the active version of the application settings. Note that I left out any back populates stuff, I do not think it adds anything, see also a previous post.

class SettingSeqno(Base):

    __tablename__ = 'setting_seqno'

    id = Column(Integer, primary_key=True)
    ...
    # only one can be active at a time
    active_seqno = Column(Integer, server_default='0', index=True)


class Setting(Base):

    __tablename__ = 'setting'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)
    value = Column(String(60), server_default='')
    value_type = Column(Integer, server_default='0')
    setting_section_id = Column(Integer, ForeignKey('setting_section.id'))


class SettingSection(Base):

    __tablename__ = 'setting_section'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)

Load the current version of the application settings when the Flask app is created

I did this by creating an AppSettings object, a bit like when you create a Flask extension. In the __init__ function the application settings are loaded from the database. In the create_app function I just added the line:

app.app_settings = AppSettings(app)

Note that I prefix app_settings with 'app.' to make app_settings global. In the create_app function I can refer to it as app.app_settings and in the blueprint functions I can refer to it as current_app.app_settings. So far so good, we now have application settings we can use in our application e.g. by calling:

allow_registration = current_app.app_settings.get_value('SECTION_AUTH', 'ALLOW_REGISTRATION')

Make an admin function to edit the application settings and create a new version of the application settings

This is about designing templates and coding. I have a list page that contains a list of all versions and an edit page that is used to edit the application settings. Every time changes are saved, they are saved to a new application settings version. This new version is not automatically activated. The page with the versions also shows the differences between the application setting settings using Python's difflib.

Make an admin function to activate a new version of the application settings

The list page with the versions also has a radio button group with a radio button for each version, the activated has the radio button checked. To move to a new version, you just select the version and in the code update the value of SettingSeqno.active_seqno and reload the application settings versions from the database. This works fine but only in the instance of the admin, the other instances (website visitors) are unaware of this.

Trigger other instances to use a newly activated version of the application settings

Because we do not want to reload the Flask application for website visitors we must look for another way. What I did is use the Flask before_request handler. I use a session variable 'app_settings_current_seqno' and database value 'active_seqno' to check if a new version of the application settings must be loaded.

Unfortunately we must add a database request in Flask's before_request handler to get the current version of the application settings. This could be avoided using a shared file, etc. but as in future I also may want to put some other values here as well I decided to add an extra object/table BeforeRequestData that contains a copy of the active_seqno variable. To make a long story short, if the session variable differs from the database value, the application settings are reloaded from the database. After that, the session variable is updated to the active_seqno value preventing that this happens on subsequent requests.

    @app.before_request
    def before_request():
        with app.app_context():
            ...
            # reload application settings if session var not equal to database var
            if session_app_settings_current_seqno != database_app_settings_current_seqno:
                app.app_settings.load_from_database(app)
                session['app_settings_current_seqno'] = database_app_settings_current_seqno

Make the application settings accessible in our Jinja templates

Almost there. Flask config variables are passed by default by flask, which allows you to access the config variables in templates. To pass our application settings to the templates we use a context processor. I already have a context processor injecting things so I just added the application settings.

I also added the current application settings version so I could easily see in the web page which version of the application settings is used:

    @app.context_processor
    def inject_base_template_parameters():

        base_template_parameters = {}

        # app_settings
        base_template_parameters['app_settings'] = app.app_settings.get_setting_section_name2setting_name2settings()

        # app_settings seqno
        app_settings_current_seqno = ''
        if session.get('app_settings_current_seqno'):
            app_settings_current_seqno = str( session.get('app_settings_current_seqno') )
        base_template_parameters['app_settings_current_seqno'] = app_settings_current_seqno
        ...
        return base_template_parameters

And in the base template:

	<p class="copyright text-muted small">
		{{ app_settings['SECTION_GENERAL']['WEBSITE_COPYRIGHT']['value'] }} [{{ app_settings_current_seqno }}] [72ms]
	</p>

Summary

It was not an easy task to implement this but it gave more understanding of Flask. Creating a Flask app is different from running subsequent requests. It is confusing that during initialization we must use the Flask app object and after that the Flask current_app object. I have read about this somewhere, will have to read about it again, and again, and again.

Links / credits

Configuration Handling
https://flask.palletsprojects.com/en/1.1.x/config/

Context Processors
https://flask.palletsprojects.com/en/1.1.x/templating/#context-processors

Dynaconf - Easy and Powerful Settings Configuration for Python
https://dynaconf.readthedocs.io/en/latest/index.html

How to reload a configuration file on each request for Flask?
https://stackoverflow.com/questions/39456672/how-to-reload-a-configuration-file-on-each-request-for-flask

Leave a comment

Comment anonymously or log in to comment.

Comments

Leave a reply

Reply anonymously or log in to reply.