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
valuebut 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( )
- class srctools.keyvalues.Keyvalues( )
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.
- real_name
The original, case-sensitive version of this name.
Assigning to this automatically updates both this and
name.
- 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.
If an operation is performed on a leaf keyvalue which requires children, the following exception is raised:
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: str | PathLike[str] = '',
- *,
- flags: Mapping[str, bool] = EmptyMapping,
- newline_keys: bool = False,
- newline_values: bool = True,
- single_line: bool = False,
- periodic_callback: Callable[[], object] | None = None,
- allow_escapes: bool = True,
- staticmethod Keyvalues.parse(
- file_contents: BaseTokenizer,
- filename: str | PathLike[str] = '',
- *,
- flags: Mapping[str, bool] = EmptyMapping,
- newline_keys: bool = False,
- newline_values: bool = True,
- single_line: bool = False,
- single_block: bool = False,
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_contentsmay be an already created tokenizer.filename – If set this should be the source of the text for debug purposes. If not supplied, the
nameattribute offile_contentswill 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
.PHYfiles.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
Tokenizeris 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
\tor similar escapes are parsed.
- Keyvalues.serialise( ) None
- Keyvalues.serialise( ) 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. Normally spaces/tabs, but any character is accepted.
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, or to add a prefix like comment delimiters.
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.
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 raiseNoKeyError.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_blankis true, a blankKeyvaluesblock will be returned. Otherwise,NoKeyErrorwill 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:
LookupErrorRaised 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, orno. 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( ) 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,
Search through the tree, yielding children of keyvalues in a path.
- for kv in Keyvalues.iter_tree(
- blocks: bool = False,
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( ) 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.
Editing
- Keyvalues.append( ) 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.
Special methods
- Keyvalues.__eq__(other: object) bool
Compare two items and determine if they are equal.
This ignores names.
- Keyvalues.__add__( ) 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__( ) 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.__getitem__(index: int) Keyvalues
- Keyvalues.__getitem__(index: slice) list[Keyvalues]
- Keyvalues.__getitem__(index: str) str
- Keyvalues.__getitem__( ) 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__( ) 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.