Skip to content

API reference

The public API is re-exported from the top-level nmlform package.

Files and groups

nmlform.NamelistFile dataclass

NamelistFile(items=list(), filename=None)

A parsed namelist file: an ordered list of groups and raw text chunks.

Attributes

nmlform.NamelistFile.namelists_by_name property
namelists_by_name

Namelist groups keyed by (lowercased) name; a name may repeat.

Methods:

nmlform.NamelistFile.get_namelist
get_namelist(name, index=0)

The index-th namelist named name (case-insensitive), or None.

Source code in nmlform/namelist.py
1316
1317
1318
1319
1320
1321
def get_namelist(self, name: str, index: int = 0) -> Namelist | None:
    """The ``index``-th namelist named ``name`` (case-insensitive), or ``None``."""
    matches = self.namelists_by_name.get(name.lower(), [])
    if -len(matches) <= index < len(matches):
        return matches[index]
    return None
nmlform.NamelistFile.render
render(options=None)

Render the whole Namelist file.

Without options the source is reproduced verbatim. When specified, the output Namelist file will be formatted according to the provided options.

Source code in nmlform/namelist.py
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
def render(self, options: NamelistFormatOptions | None = None) -> str:
    """
    Render the whole Namelist file.

    Without ``options`` the source is reproduced verbatim. When specified,
    the output Namelist file will be formatted according to the provided
    options.
    """
    if options is None:
        parts: list[str] = []
        for item in self.items:
            if parts and not (isinstance(item, Namelist) and item.continues_line):
                parts.append("\n")
            parts.append(item.render() if isinstance(item, Namelist) else item)
        return "".join(parts)
    return self._render_with_options(options)
nmlform.NamelistFile.update_namelist
update_namelist(name, assignments, *, index=0)

Add/update a namelist section.

Parameters:

Name Type Description Default
name str

The namelist group name.

required
assignments dict[str, str]

key -> raw value entries to set. Existing keys are updated in place; missing keys are appended before the terminator.

required
index int

When several groups share name, which one to update. Ignored when creating a new group. Defaults to the first.

0

Returns:

Type Description
Namelist

The updated or newly created group.

Source code in nmlform/namelist.py
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
def update_namelist(
    self,
    name: str,
    assignments: dict[str, str],
    *,
    index: int = 0,
) -> Namelist:
    """
    Add/update a namelist section.

    Parameters
    ----------
    name : str
        The namelist group name.
    assignments : dict[str, str]
        ``key -> raw value`` entries to set. Existing keys are updated in
        place; missing keys are appended before the terminator.
    index : int, optional
        When several groups share ``name``, which one to update. Ignored
        when creating a new group. Defaults to the first.

    Returns
    -------
    Namelist
        The updated or newly created group.
    """
    name = name.removeprefix("&").removeprefix("$")
    target = self.get_namelist(name, index)
    if target is None:
        target = Namelist(name=name, lines=[f"&{name}", "/"])

        if self.items:
            last_item = self.items[-1]
            if isinstance(last_item, Namelist) or last_item.strip():
                self.items.append("")

        self.items.append(target)
    for key, value in assignments.items():
        target.set(key, value)
    return target

nmlform.Namelist dataclass

Namelist(name, lines=list(), assignments=list(), filename=None, start_line=0, continues_line=False)

One &name ... / namelist group from a .nml file like "tao.init".

Attributes:

Name Type Description
lines list[str]

The verbatim source lines of the block, including the &name opener and / terminator; the source of truth for rendering.

assignments list[Assignment]

Derived from lines and refreshed after every edit.

filename Path or None

The source filename, if applicable.

start_line int

The absolute, 0-indexed line of the &name opener in the source file.

continues_line bool

The group opened mid-line: lines[0] is the source line from the opener onward, and rendering the file glues it onto the previous item's last line instead of starting a new one. Column locations on that first line are relative to the slice.

Attributes

nmlform.Namelist.loc property
loc

Source location spanning the whole &name ... / block.

Methods:

nmlform.Namelist.get
get(key)

Return the assignment matching key (case/space-insensitive).

Source code in nmlform/namelist.py
1058
1059
1060
1061
1062
1063
1064
1065
def get(self, key: str) -> Assignment | None:
    """Return the assignment matching ``key`` (case/space-insensitive)."""
    target = _normalize_key(key)
    for assignment in self.assignments:
        # Assignment keys are whitespace-normalized at parse time.
        if assignment.key.lower() == target:
            return assignment
    return None
nmlform.Namelist.remove
remove(key)

Remove key's assignment, including continuation lines (no-op if absent).

Lines the assignment shares with the opener, the terminator, or another assignment are spliced rather than deleted.

Source code in nmlform/namelist.py
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
def remove(self, key: str) -> None:
    """
    Remove ``key``'s assignment, including continuation lines (no-op if absent).

    Lines the assignment shares with the opener, the terminator, or another
    assignment are spliced rather than deleted.
    """
    existing = self.get(key)
    if existing is None:
        return
    span = existing.span
    first = span.line - self.start_line
    last = span.end_line - self.start_line
    others = [a for a in self.assignments if a is not existing]

    def shared(index: int) -> bool:
        absolute = index + self.start_line
        return any(a.span.line <= absolute <= a.span.end_line for a in others)

    for index in range(last, first - 1, -1):
        line = self.lines[index]
        protected = (
            index == 0
            or (self._terminator is not None and self._terminator[0] == index)
            or shared(index)
        )
        if not protected:
            del self.lines[index]
            continue
        start = span.column if index == first else 0
        end = span.end_column if index == last else len(line)
        while end < len(line) and line[end] in " \t,":
            end += 1
        remainder = (line[:start] + line[end:]).rstrip()
        if remainder:
            self.lines[index] = remainder
        else:
            del self.lines[index]
    self._reparse()
nmlform.Namelist.render
render(options=None)

Render the group's source lines.

With options given, fields are re-indented, cased, and aligned per those options; otherwise the verbatim source lines are returned.

Source code in nmlform/namelist.py
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
def render(self, options: NamelistFormatOptions | None = None) -> str:
    """
    Render the group's source lines.

    With ``options`` given, fields are re-indented, cased, and aligned per
    those options; otherwise the verbatim source lines are returned.
    """
    if options is None:
        return "\n".join(self.lines)
    return "\n".join(_format_group_lines(self.lines, options))
nmlform.Namelist.set
set(key, value, *, comment='')

Update key's value in place, or append it before the terminator.

A value that continues across several lines is collapsed onto its first line (interior comments on those lines are dropped with them). When appending a new key, embedded newlines in value insert the continuation lines verbatim (e.g. a follow-on comment-only line).

Source code in nmlform/namelist.py
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
def set(self, key: str, value: str, *, comment: str = "") -> Assignment:
    """
    Update ``key``'s value in place, or append it before the terminator.

    A value that continues across several lines is collapsed onto its
    first line (interior comments on those lines are dropped with them).
    When appending a new key, embedded newlines in ``value`` insert the
    continuation lines verbatim (e.g. a follow-on comment-only line).
    """
    existing = self.get(key)
    if existing is not None:
        loc = existing.value.loc
        first = loc.line - self.start_line
        last = loc.end_line - self.start_line
        spliced = self.lines[first][: loc.column] + value + self.lines[last][loc.end_column :]
        self.lines[first : last + 1] = spliced.split("\n")
    else:
        new_line = f"{self._indent()}{key} = {value}"
        if comment:
            if not comment.lstrip().startswith("!"):
                comment = f"!{comment}"
            new_line = f"{new_line} {comment}"
        for line in reversed(new_line.split("\n")):
            self._insert_before_terminator(line)

    self._reparse()
    return cast(Assignment, self.get(key))

nmlform.Assignment dataclass

Assignment(key, value, comment='', key_loc=None, field_tokens=(), base_line=0)

A single key = value assignment within a namelist group.

Values are free-form: they may continue across lines and several assignments may share one line.

Attributes:

Name Type Description
key str

The whitespace-normalized key (e.g. datum(1)%ele_name).

value Token

The value text. Its loc spans from the first character of the first value field through the last character of the last one (multi-line when the value continues across lines). Per physical line, str(value) is the raw source slice from that line's first field to its last, joined with newlines — so for single-line values str(value) == value.loc.get_string(source) exactly.

values list[Token]

The individual value fields with per-field source locations; Fortran repeat counts (3*0) are expanded. Derived lazily from the scanned field tokens.

comment str

Trailing ! comments on the lines the assignment spans.

key_loc Location or None

Source location of the key.

Attributes

nmlform.Assignment.loc property
loc

Source location of the value (value.loc).

nmlform.Assignment.path property
path

The key decomposed into KeyComponent parts (any nesting depth).

nmlform.Assignment.span property
span

Source span from the start of the key through the last value character.

nmlform.Assignment.values cached property
values

The value fields, repeat counts expanded, with per-field locations.

A quoted string continued across lines is one token, its per-line texts joined with newlines. A Fortran null value -- ,,, a comma directly after =, or a bare repeat r* -- is an empty token per skipped element, so positional slots stay honest.

Keys and locations

nmlform.KeyPath dataclass

KeyPath(components)

A decomposed derived-type assignment key, e.g. foo(3)%bar(2)%val.

Attributes

nmlform.KeyPath.names property
names

The component names in order, without indices.

nmlform.KeyComponent dataclass

KeyComponent(name, index_text=None)

One name or name(index) segment of a derived-type key path.

Attributes

nmlform.KeyComponent.index property
index

The integer index, or None for no index or a non-integer (e.g. a 1:8 range).

nmlform.KeyComponent.indices property
indices

The explicit integer indices this component designates.

[i] for a single subscript name(i); the expanded, stop- inclusive range for an array section name(a:b) or name(a:b:step). A missing a defaults to a lower bound of 1; a negative step descends (5:1:-2 gives [5, 3, 1]). The list is empty when the section designates no elements (e.g. 5:3, which gfortran itself rejects as a bad range). None when there is no subscript or it cannot be enumerated: a non-integer or named bound, a zero step, an open-ended section (see open_slice), or a multi-dimensional subscript.

nmlform.KeyComponent.open_slice property
open_slice

(start, step) of an open-ended array section, or None.

Open-ended means no stop bound: name(a:), name(:), or name(a::step); a missing a defaults to a lower bound of 1. None for a closed section, a non-section subscript, or a zero step.

nmlform.KeyComponent.slice_start property
slice_start

The start of an open-ended array section (see open_slice), or None.

nmlform.Location

Bases: NamedTuple

nmlform.Token

Token(content, loc=None, comments=None)

Bases: str

String with source loc information.

Comparisons are case-insensitive.

Source code in nmlform/token.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    content: str,
    loc: Location | None = None,
    comments: Comments | None = None,
):
    self.loc = loc or Location(end_column=len(content))
    self.comments = comments or Comments()
    self._upper = str.upper(self)

    # internal error
    if not isinstance(self.loc, Location):
        raise ValueError(type(self.loc))
    if not isinstance(self.comments, Comments):
        raise ValueError(type(self.comments))

Arrays of derived types

nmlform.NamelistArrayGroup dataclass

NamelistArrayGroup(namelist)

Base for a namelist group that carries an indexed array of derived types.

Subclasses wrap a single latform._namelist.Namelist (e.g. a &tao_d1_data or &tao_var block), exposing its scalar settings and the parsed array entries.

nmlform.NamelistArrayEntry dataclass

NamelistArrayEntry(index, positional=list(), components=dict(), comment='')

One indexed entry of a namelist array-of-derived-type.

Tao packs an array such as datum or var two ways, and both are merged here:

  • anonymously/positionally --- datum(1) = 'orbit.x' '' '' 'END\2' ..., whose values fill :data:FIELDS in declaration order;
  • by component --- datum(1)%ele_name = 'END\2'.

An explicit component takes precedence over the same positional slot.

Attributes:

Name Type Description
index int or None

The i in name(i). 0 is the conventional slot Tao reads for SEARCH:/SAME: element specifications; None for a non-integer subscript (e.g. a range).

positional list[Token]

Values from an anonymous name(i) = ... assignment, in field order.

components dict[str, Token]

Values from name(i)%field = ... assignments, keyed by field name.

comment str

Trailing comment on the anonymous assignment, if any.

Methods:

nmlform.NamelistArrayEntry.get
get(name)

The raw (quoted, if a string) value token for name, or None.

Source code in nmlform/namelist.py
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
def get(self, name: str) -> Token | None:
    """The raw (quoted, if a string) value token for ``name``, or ``None``."""
    name = name.lower()
    if name in self.components:
        return self.components[name]
    if name in self.FIELDS:
        position = self.FIELDS.index(name)
        if position < len(self.positional):
            token = self.positional[position]
            # A null value (``,,`` or ``r*``) leaves the field unset;
            # an explicitly blank string is the distinct token ``''``.
            return token if str(token) else None
    return None
nmlform.NamelistArrayEntry.value
value(name)

The value token for name, unquoted.

None if unset; an empty ('') token if explicitly blank. The returned token keeps its source Location.

Source code in nmlform/namelist.py
1415
1416
1417
1418
1419
1420
1421
1422
1423
def value(self, name: str) -> Token | None:
    """
    The value token for ``name``, unquoted.

    ``None`` if unset; an empty (``''``) token if explicitly blank. The
    returned token keeps its source `Location`.
    """
    token = self.get(name)
    return unquote_value(token.strip()) if token is not None else None

Formatting

nmlform.NamelistFormatOptions dataclass

NamelistFormatOptions(indent_size=2, indent_char=' ', blank_line_after_group=True, field_case='lower', align_equals=True, align_comments=True)

Formatting options for Fortran-namelist (*.init/*.nml) files.

These control only the field section between a &name opener and its / terminator; the opener and terminator are always emitted at column zero and values are never modified.

Helper functions

nmlform.quote_value

quote_value(text, quote="'")

Quote text as a Fortran-namelist string literal.

Embedded quote characters are escaped by doubling (it's becomes 'it''s'); the other quote character passes through untouched.

Parameters:

Name Type Description Default
text str

The string content to quote.

required
quote ("'", '"')

The delimiter to use. Defaults to a single quote.

"'"
Source code in nmlform/namelist.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def quote_value(text: str, quote: str = "'") -> str:
    """
    Quote ``text`` as a Fortran-namelist string literal.

    Embedded ``quote`` characters are escaped by doubling (``it's`` becomes
    ``'it''s'``); the other quote character passes through untouched.

    Parameters
    ----------
    text : str
        The string content to quote.
    quote : {"'", '"'}, optional
        The delimiter to use. Defaults to a single quote.
    """
    if quote not in {"'", '"'}:
        raise ValueError(f"quote must be a single or double quote character, got {quote!r}")
    escaped = quote * 2
    return "".join((quote, text.replace(quote, escaped), quote))

nmlform.unquote_value

unquote_value(token)

The string content of a namelist value token.

Strips the outer quotes and undoubles the escaped delimiter quote character ('it''s' becomes it's); occurrences of the other quote character are literal and left untouched. Bare (unquoted) and unterminated tokens are returned unchanged.

Source code in nmlform/namelist.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def unquote_value(token: Token) -> Token:
    """
    The string content of a namelist value token.

    Strips the outer quotes and undoubles the escaped delimiter quote
    character (``'it''s'`` becomes ``it's``); occurrences of the other quote
    character are literal and left untouched. Bare (unquoted) and unterminated
    tokens are returned unchanged.
    """
    if not token.is_quoted_string:
        return token
    text = str(token)
    quote = text[0]
    escaped = quote * 2
    return Token(text[1:-1].replace(escaped, quote), loc=token.loc, comments=token.comments)

nmlform.is_namelist_file

is_namelist_file(path)

Whether path names a Fortran-namelist file (*.init or *.nml).

Source code in nmlform/namelist.py
45
46
47
def is_namelist_file(path: pathlib.Path | str) -> bool:
    """Whether ``path`` names a Fortran-namelist file (``*.init`` or ``*.nml``)."""
    return pathlib.Path(path).suffix.lower() in _NAMELIST_SUFFIXES

nmlform.split_namelist_key

split_namelist_key(key)

Split a namelists override key into (name, index).

A trailing #N (1-based) targets the N-th group of a repeated name, e.g. tao_d1_data#2 -> ("tao_d1_data", 1). A bare name targets the first.

Source code in nmlform/apply.py
41
42
43
44
45
46
47
48
49
50
51
def split_namelist_key(key: str) -> tuple[str, int]:
    """
    Split a ``namelists`` override key into ``(name, index)``.

    A trailing ``#N`` (1-based) targets the N-th group of a repeated name, e.g.
    ``tao_d1_data#2`` -> ``("tao_d1_data", 1)``. A bare name targets the first.
    """
    if "#" in key:
        name, _, suffix = key.rpartition("#")
        return name, int(suffix) - 1
    return key, 0

nmlform.apply_namelist_values

apply_namelist_values(nml_file, values)

Apply a values mapping to a parsed namelist file in place.

Parameters:

Name Type Description Default
nml_file NamelistFile

A parsed namelist file.

required
values dict

Keys are namelist group names, optionally with a name#N suffix (1-based) to target the N-th of a repeated group. Each value is a {key: value} mapping of raw assignment values; existing keys are updated in place and missing keys are appended. A value of None removes that key. A group named only for removals that does not exist is left uncreated.

required
Source code in nmlform/apply.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def apply_namelist_values(nml_file: NamelistFile, values: dict) -> None:
    """
    Apply a values mapping to a parsed namelist file in place.

    Parameters
    ----------
    nml_file : NamelistFile
        A parsed namelist file.
    values : dict
        Keys are namelist group names, optionally with a ``name#N`` suffix (1-based)
        to target the N-th of a repeated group.
        Each value is a ``{key: value}`` mapping of raw assignment values;
        existing keys are updated in place and
        missing keys are appended.
        A value of ``None`` removes that key.
        A group named only for removals that does not exist is left uncreated.
    """
    for name_key, assignments in values.items():
        name, index = split_namelist_key(name_key)
        removals = [key for key, value in assignments.items() if value is None]
        settings = {key: str(value) for key, value in assignments.items() if value is not None}
        if settings:
            target = nml_file.update_namelist(name, settings, index=index)
        else:
            target = nml_file.get_namelist(name, index)
        if target is not None:
            for key in removals:
                target.remove(key)

nmlform.interpolate_namelist

interpolate_namelist(contents, *, values=None, filename='tao.init', options=None)

Interpolate a single namelist (*.init/*.nml) template file.

Parameters:

Name Type Description Default
contents str

The namelist file contents.

required
values dict

Namelist overrides. See apply_namelist_values.

None
filename str

Virtual filename used for source locations.

'tao.init'
options NamelistFormatOptions

When given, re-format the output (field indentation, case, and alignment). When None (default), the source layout is preserved verbatim aside from the applied value edits.

None

Returns:

Type Description
str

The interpolated namelist file.

Source code in nmlform/apply.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def interpolate_namelist(
    contents: str,
    *,
    values: dict | None = None,
    filename: str = "tao.init",
    options: NamelistFormatOptions | None = None,
) -> str:
    """
    Interpolate a single namelist (``*.init``/``*.nml``) template file.

    Parameters
    ----------
    contents : str
        The namelist file contents.
    values : dict, optional
        Namelist overrides. See `apply_namelist_values`.
    filename : str, optional
        Virtual filename used for source locations.
    options : NamelistFormatOptions, optional
        When given, re-format the output (field indentation, case, and
        alignment). When ``None`` (default), the source layout is preserved
        verbatim aside from the applied value edits.

    Returns
    -------
    str
        The interpolated namelist file.
    """
    nml_file = NamelistFile.parse(contents, filename)
    if values:
        apply_namelist_values(nml_file, values)
    return nml_file.render(options)

nmlform.load_values_file

load_values_file(source, fmt=None)

Load a values mapping from source for apply_namelist_values.

Parameters:

Name Type Description Default
source str

A path, or - to read from standard input.

required
fmt ('json', 'yaml', 'toml', 'nml')

The file format. When None (default), it is inferred from the file extension (- defaults to JSON). yaml and toml need the optional parsers (pip install nmlform[all]); on Python 3.11+ the stdlib tomllib is used for TOML.

"json"

Returns:

Type Description
dict

A {group: {key: value_or_None}} mapping of raw namelist values, suitable for apply_namelist_values.

Source code in nmlform/apply.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def load_values_file(source: str, fmt: str | None = None) -> dict[str, dict[str, str | None]]:
    """
    Load a values mapping from ``source`` for `apply_namelist_values`.

    Parameters
    ----------
    source : str
        A path, or ``-`` to read from standard input.
    fmt : {"json", "yaml", "toml", "nml"}, optional
        The file format. When ``None`` (default), it is inferred from the file
        extension (``-`` defaults to JSON). ``yaml`` and ``toml`` need the
        optional parsers (``pip install nmlform[all]``); on Python 3.11+ the
        stdlib ``tomllib`` is used for TOML.

    Returns
    -------
    dict
        A ``{group: {key: value_or_None}}`` mapping of raw namelist values,
        suitable for `apply_namelist_values`.
    """
    fmt = fmt or _detect_values_format(source)
    text = sys.stdin.read() if source == "-" else pathlib.Path(source).read_text(encoding="utf-8")

    if fmt == "json":
        import json

        return _normalize_structured(json.loads(text))
    if fmt == "yaml":
        try:
            import yaml
        except ImportError as ex:  # optional dependency
            raise ImportError(
                "Reading YAML values requires 'pyyaml' (pip install nmlform[all])."
            ) from ex
        return _normalize_structured(yaml.safe_load(text))
    if fmt == "toml":
        try:
            import tomllib
        except ImportError:  # Python 3.10: tomllib was released as tomli
            try:
                import tomli as tomllib
            except ImportError as ex:  # optional dependency
                raise ImportError(
                    "Reading TOML values on Python 3.10 requires 'tomli' "
                    "(pip install nmlform[all])."
                ) from ex
        return _normalize_structured(tomllib.loads(text))
    if fmt == "nml":
        parse_name = "namelist" if source == "-" else source
        return _values_from_nml(text, parse_name)
    raise ValueError(f"Unknown values format: {fmt!r}")

nmlform.cli_main_set

cli_main_set(argv=None)

CLI entrypoint for nmlform-set (set/remove values in one file).

Source code in nmlform/apply.py
450
451
452
def cli_main_set(argv: list[str] | None = None) -> int:
    """CLI entrypoint for ``nmlform-set`` (set/remove values in one file)."""
    return main_set(argv)