srctools.keyvalues

Keyvalues 1 files are very common in Source 1. VMFs, soundscripts, VMT and many other files use the syntax. A keyvalues file is a tree structure composed of keyvalue nodes. Each has a name, and either a single string value or any number of children. Duplicate names are allowed.

Core Behaviours

Keyvalues are represented with Keyvalues objects, which have three forms:

  • “Leaf” keyvalues have a name, and a single string value but no children.

  • “Block” keyvalues have a name, and a list of child keyvalues.

  • “Root” keyvalues are a special block type, with no name. When exported, its children are written directly into the file with no nesting. This is returned by parse() to allow use of the Keyvalues API on these root blocks, while preserving the structure.

class srctools.keyvalues.Keyvalues(
name: str,
value: list[Keyvalues] | str,
line_num: int | None = None,
)
class srctools.keyvalues.Keyvalues(
name: None,
value: list[Keyvalues] | str,
line_num: int | None = None,
)

Represents Valve’s Keyvalues 1 file format.

Value should be a string (for leaf Keyvalues), or a list of children Keyvalues objects. The name should be a string, or None for a root object. Root objects export each child at the topmost indent level. This is produced from Keyvalues.parse() calls.

classmethod root(*children: Keyvalues) Keyvalues

Return a new ‘root’ keyvalue. These have no name, and are returned from parse().

When serialised, their children are directly written with no indents, allowing multiple keyvalues blocks to exist on the topmost level.

name

Produces a str.casefold()-ed version of the keyvalue’s name.

Usually names are treated as case-insensitive, so this makes it convenient to do comparisons. Assigning to this automatically updates both this and real_name.

Deprecated:

‘Root’ keyvalues have a name of None. This will be replaced by a blank string, use is_root() instead.

real_name

The original, case-sensitive version of this name.

Assigning to this automatically updates both this and name.

Deprecated:

‘Root’ keyvalues have a name of None. This will be replaced by a blank string, use is_root() instead.

value

Return the value of a leaf keyvalue.

Deprecated:

Accessing the internal list for keyvalue blocks.

line_num: int | None

If parsed from a file, the line number this keyvalue starts on. This allows providing a source location for missing-value exceptions.

The following two methods allow identifying the type of a keyvalue.

has_children() bool

Does this have child keyvalues?

is_root() bool

Check if the keyvalue is a root, returned from the parse() or root() methods.

See root() for more information.

If an operation is performed on a leaf keyvalue which requires children, the following exception is raised:

class srctools.keyvalues.LeafKeyvalueError(
leaf: Keyvalues,
operation: str,
filename: str | None = None,
)

Bases: ValueError

Raised when a method requiring a keyvalue block is run on a leaf keyvalue.

Leaf keyvalues only have a string value, so this operation is not valid.

Reading and Writing

To read and write keyvalues files, use parse() and serialise():

with open('filename.txt', 'r') as read_file:
        kv = Keyvalues.parse(read_file, 'filename.txt')
with open('filename_2.txt', 'w') as write_file:
        kv.serialise(write_file)
staticmethod Keyvalues.parse(
file_contents: str | Iterable[str],
filename: StringPath = '',
*,
flags: Mapping[str, bool] = EmptyMapping,
newline_keys: bool = False,
newline_values: bool = True,
periodic_callback: Callable[[], object] | None = None,
allow_escapes: bool = True,
single_line: bool = False,
) Keyvalues
staticmethod Keyvalues.parse(
file_contents: BaseTokenizer,
filename: StringPath = '',
*,
flags: Mapping[str, bool] = EmptyMapping,
newline_keys: bool = False,
newline_values: bool = True,
single_line: bool = False,
single_block: bool = False,
) Keyvalues

Returns a Keyvalues tree parsed from text, a file, or an existing Tokenizer.

Valve’s parsers are very inconsistent, sometimes accepting escape sequences, having keyvalues all on the same line and allowing newlines in the middle of text. For this reason many options are provided to fine tune parsing, and determine how strict to treat input.

Parameters:
  • file_contents – should be an iterable of strings (like a file object) or a single string. Alternatively, file_contents may be an already created tokenizer.

  • filename – If set this should be the source of the text for debug purposes. If not supplied, the name attribute of file_contents will be used if present.

  • flags – This should be a mapping for additional [flag] suffixes to accept.

  • single_line – If this is set, allow multiple keyvalues to be on the same line. This means unterminated strings will be caught late (if at all), but it allows parsing some generated data blocks inside things like PHY files.

  • newline_keys – This specifies if newline characters are allowed in keys. Keys are prohibited by default, since this is fairly useless. If a quote character is omitted accidentally, this check is likely to quickly catch the error.

  • newline_values – This specifies if newline characters are allowed in string values.

The following parameter is only allowed if an explicit Tokenizer is passed in:

Parameters:

single_block – If set, parse a single keyvalues block, instead of a file with multiple roots. Importantly this will exit after hitting this brace, allowing it to be called in the middle of parsing a larger document.

These parameters are allowed for strings/files, to initalise the tokenizer:

Parameters:
  • periodic_callback – If set, this function will be called periodically after every few lines, to allow aborting parsing.

  • allow_escapes – This allows choosing if \t or similar escapes are parsed.

Keyvalues.serialise(
file: _SupportsWrite,
/,
*,
indent: str = '\t',
indent_braces: bool = True,
start_indent: str = '',
) None
Keyvalues.serialise(
*,
indent: str = '\t',
indent_braces: bool = True,
start_indent: str = '',
) str

Serialise the keyvalues data to a file, or return as a string if no file is provided.

This does not handle cyclic loops in the tree.

Parameters:
  • file – The file to write to. If omitted, the data is returned instead.

  • indent – The characters to use for each indentation level.

  • indent_braces – If enabled, indent the braces to match the block contents, instead of the name.

  • start_indent – The initial starting indentation. Useful to allow serialising inside an existing file.

serialise() is also available with the spelling serialize().

for line in Keyvalues.export() Iterator[str]

Generate the set of strings for a keyvalues file.

Recursively calls itself for all child keyvalues.

Deprecated:

Use serialise() instead.

class srctools.keyvalues.KeyValError(
message: str,
file: str | _os.PathLike[str] | None = None,
line: int | None = None,
)

An error that occurred when parsing a Valve KeyValues file.

See the base class for available attributes.

Searching

A number of different methods are available to search a tree and locate specific keyvalues. When searching by name, all methods are case-insensitive, and return the last matching value, not the first.

The simplest search is to find a matching key as a direct child. find_key() finds any child, while find_block() only finds blocks. Keyvalues can also be indexed to retrieve a string value:

kv['value']  # Returns the contents, or raises
kv['value', 'default'] # Returns default value if not found.
Keyvalues.find_key(key: str, *, or_blank: bool) Keyvalues
Keyvalues.find_key(key: str, def_: str = <object object>) Keyvalues

Obtain the child Keyvalue with a given name.

  • If no child is found with the given name, this will return the default value wrapped in a new Keyvalue. If or_blank is set, it will be a blank block instead. If neither default is provided this will raise NoKeyError.

  • This prefers keys located closer to the end of the value list.

Keyvalues.find_block(key: str, or_blank: bool = False) Keyvalues

Obtain the child Keyvalue block with a given name.

  • If no child is found with the given name and or_blank is true, a blank Keyvalues block will be returned. Otherwise, NoKeyError will be raised.

  • This prefers keys located closer to the end of the value list.

This exception is raised if a lookup fails. Currently direct indexing raises IndexError, but this will change. Prefer catching LookupError, which will catch both.

class srctools.keyvalues.NoKeyError(key: str, line_num: int | None = None, filename: str | None = None)

Bases: LookupError

Raised if a key is not found when searching with find_key(), find_block(), etc.

As a convenience, Keyvalues.int(), Keyvalues.float(), bool() and vec() will search for a key, parse to the specified type, returning a default if the parse fails or the key is missing.

Keyvalues.bool(key: str) bool
Keyvalues.bool(key: str, def_: T) bool | T

Return the value of a boolean key.

The value may be case-insensitively true, false, 1, 0, T, F, y, n, yes, or no. If multiple keys with the same name are present, this will use the last only.

Keyvalues.float(key: str) float
Keyvalues.float(key: str, def_: T) float | T

Return the value of an integer key.

Equivalent to float(kv[key]), but with a default value if missing or invalid. If multiple keys with the same name are present, this will use the last only.

Keyvalues.int(key: str) int
Keyvalues.int(key: str, def_: T) int | T

Return the value of an integer key.

Equivalent to int(kv[key]), but with a default value if missing or invalid. If multiple keys with the same name are present, this will use the last only.

Keyvalues.vec(
key: str,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
) Vec

Return the given keyvalue, converted to a vector.

If multiple keys with the same name are present, this will use the last only.

For searching deeper in trees, the following iterators are available:

for kv in Keyvalues.find_all(*keys: str) Iterator[Keyvalues]

Search through the tree, yielding all keyvalues that match a particular path.

for child in Keyvalues.find_children(
*keys: str,
) Iterator[Keyvalues]

Search through the tree, yielding children of keyvalues in a path.

for kv in Keyvalues.iter_tree(
blocks: bool = False,
) Iterator[Keyvalues]

Iterate through all keyvalues in this tree.

This goes through keyvalues in the same order that they will serialise into. If blocks is True, the keyvalue blocks will be returned as well as keyvalues. If false, only keyvalues will be yielded.

In many keyvalues files, arrays of data are defined as a block of leaf values, where the name of each is identical or irrelevant. The following helper function makes parsing these easier:

Keyvalues.as_array() list[str]
Keyvalues.as_array(
*,
conv: Callable[[str], T],
) list[T]

Convert a keyvalue block into a list of values.

If the keyvalue is a single keyvalue, the single value will be yielded. Otherwise, each child must be a single value and each of those will be yielded. The name is ignored.

Parameters:

conv – If provided, this will be called on every value to convert it.

Block keyvalues are also sequences, and support iteration and len() as normal.

Keyvalues.__iter__() Iterator[Keyvalues]

Iterate through the value list.

Keyvalues.__len__() int

Determine the number of child keyvalues.

Editing

Keyvalues.append(
other: Iterable[Keyvalues] | Keyvalues,
) None

Append another keyvalue to this one.

Deprecated behaviour: Accept an iterable of keyvalues or a root keyvalue which are merged into this one.

Keyvalues.extend(other: Iterable[Keyvalues]) None

Extend this keyvalue with the contents of another, or an iterable.

Keyvalues.build() _Builder

Allows appending a tree to this keyvalue in a convenient way.

Use as follows:

# doctest: +NORMALIZE_WHITESPACE
>>> kv = Keyvalues('name', [])
>>> with kv.build() as builder:
...     builder.root1('blah')
...     builder.root2('blah')
...     with builder.subprop:
...         subprop = builder.config('value')
...         builder['unusual name']('value')
Keyvalues('root1', 'blah')
Keyvalues('root2', 'blah')
Keyvalues('unusual name', 'value')
>>> print(subprop) # doctest: +NORMALIZE_WHITESPACE
"config" "value"
>>> print(kv.serialise()) # doctest: +NORMALIZE_WHITESPACE
"name"
    {
    "root1" "blah"
    "root2" "blah"
    "subprop"
        {
        "config" "value"
        "unusual name" "value"
        }
    }

The return values/results of the context manager are the keyvalues. Set names by builder.name, builder['name']. For keywords append _.

Alternatively:

>>> with Keyvalues('name', []).build() as builder:
...     builder.root1('blah')
...     builder.root2('blah')
Keyvalues('root1', 'blah')
Keyvalues('root2', 'blah')
>>> kv = builder()
>>> print(repr(kv))
Keyvalues('name', [Keyvalues('root1', 'blah'), Keyvalues('root2', 'blah')])
Keyvalues.ensure_exists(key: str) Keyvalues

Ensure a Keyvalue block exists with this name, and return it.

Keyvalues.merge_children(*names: str) None

Merge together any children of ours with the given names.

After execution, this tree will have only one sub-Keyvalue for each of the given names. Direct leaf keyvalues will be left unchanged, even if they happen to match the given names.

Keyvalues.set_key(path: tuple[str, ...] | str, value: str) None

Set the value of a key deep in the tree hierarchy.

  • If any of the hierarchy do not exist (or do not have children), blank keyvalues will be added automatically.

  • path should be a tuple of names, or a single string.

Special methods

Keyvalues.__repr__() str

Return repr(self).

Keyvalues.__str__() str

Return str(self).

Keyvalues.__ne__(other: object) bool

Not-Equal To comparison. This ignores names.

Keyvalues.__eq__(other: object) bool

Compare two items and determine if they are equal.

This ignores names.

Keyvalues.__add__(
other: Iterable[Keyvalues],
) Keyvalues

Extend this keyvalue with the contents of another, or an iterable.

This deep-copies the Keyvalue tree first. Deprecated behaviour: This also accepts a non-root keyvalue, which will be appended instead.

Keyvalues.__iadd__(
other: Iterable[Keyvalues],
) Self

Extend this keyvalue with the contents of another, or an iterable.

Deprecated behaviour: This also accepts a non-root keyvalue, which will be appended instead.

Keyvalues.__bool__() bool

Keyvalues are true if we have children, or have a value.

Keyvalues.__contains__(key: str) bool

Check to see if a name is present in the children.

Keyvalues.__getitem__(index: int) Keyvalues
Keyvalues.__getitem__(index: slice) list[Keyvalues]
Keyvalues.__getitem__(index: str) str
Keyvalues.__getitem__(
index: tuple[str, T],
) str | T

Allow indexing the children directly.

  • If given an index, it will return the keyvalues in that position.

  • If given a string, it will find the last Keyvalue with that name. (Default can be chosen by passing a 2-tuple like kv[key, default])

  • If none are found, it raises IndexError.

Keyvalues.__setitem__(
index: slice,
value: Iterable[Keyvalues],
) None
Keyvalues.__setitem__(index: int, value: Keyvalues) None
Keyvalues.__setitem__(index: str, value: str | Keyvalues) None

Allow setting the values of the children directly.

  • If given an index or slice, it will add these keyvalues in these positions.

  • If given a string, it will set the last Keyvalue with that name to a string value, or replace with the provided keyvalue.

  • If none are found, it appends the value to the tree.

Keyvalues.__delitem__(index: int | slice | str) None

Delete the given keyvalues index.

  • If given an integer, it will delete by position.

  • If given a string, it will delete the last Keyvalue with that name.

Miscellaneous

Keyvalues.as_dict() dict

Convert this keyvalue tree into a tree of dictionaries.

This keeps only the last if multiple items have the same name.

Keyvalues.clear() None

Delete the contents of a block.

Keyvalues.copy() Keyvalues

Deep copy this Keyvalue tree and return it.

Keyvalues.edit(
name: str | None = None,
value: str | list[Keyvalues] | None = None,
) Keyvalues

Simultaneously modify the name and value.