Edgewall Software

Configuration (trac.ini) API

Most of Trac's configuration is stored in the trac.ini file. Trac provides an API to retrieve and set the settings in this configuration file.

For the sake of this article, here's a quick reminder of the structure of trac.ini:

[wiki]   ; <= section
ignore_missing_pages = false  ; <= option with option value
max_size = 262144
render_unsafe_content = false

The file consists of multiple sections (written as [sectionname]). Each section consists of multiple options with their option values (like ignore_missing_pages = false in the example above). All options that come after the beginning of a section belong to this section - until a new section begins.

Note: The following examples will use env.config to access the configuration API. From within a component method you can use self.config to access the configuration API as well.

Retrieving arbitrary option values

The easiest way to retrieve the value of a certain option is to use:

value = env.config.get('wiki', 'ignore_missing_pages')

The method get() will return the option value as string (type unicode). Of course, there are also methods to retrieve the option value in several other data formats:

env.config.get()      # as string
env.config.getbool()  # as bool
env.config.getint()   # as integer
env.config.getfloat() # as float; since Trac 0.12
env.config.getlist()  # as list
env.config.getpath()  # as absolute path

Note: Most options have some meta data (data type, description) associated with them. For getting those meta data, see Listing Options below.

Setting arbitrary option values

Setting an option value is almost as easy as retrieving one. For this purpose, you need to use the method set():

# the last parameter is the new option value ("False" in this case)
env.config.set('wiki', 'ignore_missing_pages', False)
env.config.save()

You also need to call save() to store the changes you've made to trac.ini.

There's just one thing you need to be aware of:

The option value must be a string!

This is not a problem for most data types - except for lists. When you want to save a list, write your code like this:

my_list = [ 'test1', 'test2' ]
env.config.set('my_section', 'my_option', ', '.join(my_list))
env.config.save()

Defining options

While you can use config.set() to store values for arbitrary options, there's also a way to tell Trac which options are available. To do this, create a component and specify the option like this:

from trac.core import *
from trac.config import Option

class MyComponent(Component):
    my_option = Option('my-section', 'my-option', 'my default value',
        doc="Here goes the description of this option.")

Note: This only works in components, not in "regular" classes.

This defines the option "my-option" in the section "my-section" with the default value "my default value" and a description. Defining an option like this (rather than just setting the value) has several advantages:

The most important advantage is that you can access the option's value more easily (see also the next section):

class MyComponent(Component):
    my_option = ...

    def my_method(self):
      # "self.my_option" is equivalent to
      # "self.config.get('my-section', 'my-option')"
      print "My option value: ", self.my_option

Secondly, you can define a default value for the option. If no value has been defined in trac.ini for this specific option, the default value will used as value (no matter which of the two previously mentioned method is used).

Last, this allows plugins like IniAdminPlugin or TracIniAdminPanelPlugin to work. These plugins allow the user to edit trac.ini via the webadmin interface. For this purpose they need to know which options exist which is done like described in this section.

Retrieving the value of previously defined options

As describes in the previous section, defining an option (in a component) allows you to retrieve its value more easily. Furthermore, this definition also allows for automatic type conversion. For this you need to use one of the child classes of Option (instead of Option itself).

For example, let's assume the option my_option defined in the next example has the value 1.234. Now consider calling my_method in the following code:

class MyComponent(Component):
    # using "FloatOption" instead of "Option"
    my_option = FloatOption(...)  

    def my_method(self):
      # will print: 
      # <type 'float'> : 1.234
      print type(self.my_option), ':', self.my_option

The option value has automatically been converted to float. If you had simply used Option (instead of FloatOption), the value would have been a string (instead of a float).

Beside these "simple" types (BoolOption, IntOption, FloatOption) there are also options with a little bit more "magic":

  • PathOption simply describes a path that can be absolute or relative. The option always returns an absolute path. Relative paths are resolved relative to the conf directory of the environment.
  • ChoiceOption is simply an option that only has a certain set of valid values.
  • ListOption converts the option value into a list of strings. So a list option allows for iteration like this:
    # with my_option_list = ListOption(...)
    for item in self.my_option_list:
        print item
    
  • ExtensionOption describes exactly one enabled component implementing a specific extension point interface.
  • OrderedExtensionsOption describes an ordered list of enabled components implementing a specific extension point interface. (Components that also implement the same interface but are not listed in the option can automatically be appended to the list.) This list can be iterated like with ListOption.

Note: Of course, you can also access the defined options of another component by using:

OtherComponent(env).other_option

Note: Currently (as with Trac 0.12.1) you can't set an option's value this way (e.g. with self.my_option = new_option_value). This will raise an AttributeError, but there's ticket #9967 that aims to fix this problem.

Why does this work?

So, how can one define an option as ListOption but end up with a list? This works because there are two ways to access the option: as class variable or as instance variable.

class MyComponent(Component):
    my_option = ListOption(...)  

    def my_method(self):
      # 1) as instance variable
      # <type 'list'>
      print type(self.my_option)

      # 2) as class variable
      # <class 'trac.config.ListOption'>
      print type(MyComponent.my_option)

The first way uses the instance attribute which is the actual list, while the second way uses the class attribute which is the actual ListOption object.

When you try to access another component's defined options the difference between these two ways is less obvious, so be careful:

# 1) as instance variable
# <type 'list'>
print type(OtherComponent(env).my_option)

# 2) as class variable
# <class 'trac.config.ListOption'>
print type(OtherComponent.my_option)

For a more deeper understanding of how this implementation works, see the official Descriptor HowTo Guide - since the Option class is a data descriptor.

Listing known options

Beside retrieving the value of a certain option, there are also methods for listing all known options.

  • config.sections(): Returns a list of the names of all known sections.
  • config.options(section_name): Returns a list of (name, value) tuples for all known options in the specified section.
  • config.defaults(): Returns a dict with the default values of all known options (that have one) in all known sections.

A "known option" in this context is an option that

  • is defined like described in the section Defining Options above
  • and/or has value assigned to it.

Furthermore, there is a way to list all defined options. This is done by using Option.registry which is a dict with (section_name, option_name) keys. The value for each key is the Option object that defines the option (not its current value). The following example will list the descriptions of all defined options:

for (section_name, option_name), option in Option.registry.items():
    print option.__doc__
Last modified 3 years ago Last modified on Jan 8, 2011 2:01:52 PM