Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/configobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,17 @@ have an explicit configspec.
See `Repeated Sections`_ for examples.


Mentioning Optional Sections
############################

It is possible to mark sections as optional. This is done by prepending
``__optional__`` to the section name. If the optional section is present,
it can be accessed normally (without the ``__optional__`` prefix).
If it is not present, it won't show up in the validated ``ConfigObj``.

See `Optional Sections`_ for examples.


Mentioning SimpleVal
####################

Expand Down Expand Up @@ -1815,6 +1826,30 @@ If you want to use repeated values alongside repeated sections you can call one
other ``___many___`` (with three underscores).


Optional Sections
-----------------

Optional sections are sections that have to either be fully present,
or not be in the file at all. This is useful for components that
require configuration but aren't required. As opposed to single
values, these can't just have a default of ``None``.

An optional section is created by prepending ``__optional__`` to the
section name in the configspec. When an optional section is present,
it is validated normally. If it it not present, validation ignores the
part of the configspec describing the section.

For example, we might be configuring a planet. It has some attributes and
*might* have a human settlement. If it has one, we want it to be validated
according to a spec. To do this, we make the ``settlement`` section optional ::

[planet]
mass = integer
radius = integer
[[__optional__settlement]]
inhabitants = integer
name = string

Copy Mode
---------

Expand Down
9 changes: 8 additions & 1 deletion src/configobj/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,13 @@ def _set_configspec(self, section, copy):
for entry in configspec.sections:
if entry == '__many__':
continue
if entry.startswith('__optional__'):
configspec_entry = configspec[entry]
entry = entry[len('__optional__'):]
if entry not in section:
continue
else:
configspec_entry = configspec[entry]
if entry not in section:
section[entry] = {}
section[entry]._created = True
Expand All @@ -1941,7 +1948,7 @@ def _set_configspec(self, section, copy):

# Could be a scalar when we expect a section
if isinstance(section[entry], Section):
section[entry].configspec = configspec[entry]
section[entry].configspec = configspec_entry


def _write_line(self, indent_string, entry, this_entry, comment):
Expand Down
13 changes: 13 additions & 0 deletions src/tests/test_configobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -1412,3 +1412,16 @@ def test_writing_out_dict_value_with_unrepr(self):
c = ConfigObj(cfg, unrepr=True)
assert repr(c) == "ConfigObj({'thing': {'a': 1}})"
assert c.write() == ["thing = {'a': 1}"]


def test_optional_sections():
c = ConfigObj({'sec': {'name': 'value'}}, configspec={'__optional__sec': {'name': 'string'}})
assert c.validate(Validator()) == True
c = ConfigObj({'unrelated sec': {}},
configspec={'unrelated sec': {},
'__optional__sec': {'some name': 'string',
'subsec': {},
'something else': 'integer'}})
assert c.validate(Validator()) == True
c = ConfigObj({'sec': {'name': 'a string'}}, configspec={'__optional__sec': {'name': 'integer'}})
assert c.validate(Validator()) != True