The cfgfilter Module

There are three use cases for the ConfigFilter class:

  1. Help enforce that a given module does not access options registered by another module, without first declaring those cross-module dependencies using import_opt().

  2. Prevent private configuration opts from being visible to modules other than the one which registered it.

  3. Limit the options on a Cfg object that can be accessed.

New in version 1.4.

Cross-Module Option Dependencies

When using the global cfg.CONF object, it is quite common for a module to require the existence of configuration options registered by other modules.

For example, if module ‘foo’ registers the ‘blaa’ option and the module ‘bar’ uses the ‘blaa’ option then ‘bar’ might do:

import foo

print(CONF.blaa)

However, it’s completely non-obvious why foo is being imported (is it unused, can we remove the import) and where the ‘blaa’ option comes from.

The CONF.import_opt() method allows such a dependency to be explicitly declared:

CONF.import_opt('blaa', 'foo')
print(CONF.blaa)

However, import_opt() has a weakness - if ‘bar’ imports ‘foo’ using the import builtin and doesn’t use import_opt() to import ‘blaa’, then ‘blaa’ can still be used without problems. Similarly, where multiple options are registered a module imported via import_opt(), a lazy programmer can get away with only declaring a dependency on a single option.

The ConfigFilter class provides a way to ensure that options are not available unless they have been registered in the module or imported using import_opt() for example with:

CONF = ConfigFilter(cfg.CONF)
CONF.import_opt('blaa', 'foo')
print(CONF.blaa)

no other options other than ‘blaa’ are available via CONF.

Private Configuration Options

Libraries which register configuration options typically do not want users of the library API to access those configuration options. If API users do access private configuration options, those users will be disrupted if and when a configuration option is renamed. In other words, one does not typically wish for the name of the private config options to be part of the public API.

The ConfigFilter class provides a way for a library to register options such that they are not visible via the ConfigOpts instance which the API user supplies to the library. For example:

from __future__ import print_function

from oslo_config.cfg import *
from oslo_config.cfgfilter import *

class Widget(object):

    def __init__(self, conf):
        self.conf = conf
        self._private_conf = ConfigFilter(self.conf)
        self._private_conf.register_opt(StrOpt('foo'))

    @property
    def foo(self):
        return self._private_conf.foo

conf = ConfigOpts()
widget = Widget(conf)
print(widget.foo)
print(conf.foo)  # raises NoSuchOptError

Limited Configuration Options

It may be required that when passing a CONF object to other functions we want to filter that the receiving code is only able to access a restricted subset of the options that are available on the CONF object. This is essentially a more general case of the Private Configuration Options and Cross-Module Options whereby we expose an option that is already present on the underlying CONF object without providing any means to load it if not present.

So given a CONF object with options defined:

CONF.register_opt(StrOpt('foo'))
CONF.register_opt(StrOpt('bar'))

we can expose options such that only those options are present:

restricted_conf = CfgFilter(CONF)
restricted_conf.expose_opt('foo')

print(restricted_conf.foo)
print(restricted_conf.bar)  # raises NoSuchOptError