Streamlined application runtime configuration, validation and access
Project description
Configures: Simplifies Configuration Access & Validation
The Configures library provides a streamlined interface for application configuration, validation and access.
The library provides support for specifying expected configuration options, often termed secrets, for an application, allowing for secrets to be marked as required or optional, along with the ability to define the "shape" of the value for each secret, either according to a regular expression or a list of acceptable values, as well as allowing a default fallback value to be specified that will be used if the secret is absent at runtime.
The Configures library also provides a streamlined and consistent interface to access secrets at runtime, including convenience functionality such as to dynamically cast values to different data types. Furthermore, secrets can be added and modified at runtime if needed.
Requirements
The Configures library has been tested with Python 3.10, 3.11, 3.12, 3.13 and 3.14. The library has not been tested with, nor is it likely compatible with Python 3.9 and earlier.
Installation
The Configures library is available from PyPI, so may be added to a project's dependencies
via its requirements.txt file or similar by referencing the Configures library's name,
configures, or the library may be installed directly into the local runtime environment
using pip install by running the following command:
$ pip install configures
Usage Example
To use the Configures library, simply import the library into your project and create an
instance of the Secrets class. By default the Secrets class will import all of the
secrets defined in the current runtime environment, and if a configuration specification
file has been defined for the application, and if it exists either in one of the standard
locations, or if the file's location has been provided to the Secrets class, it will
load the configuration specification, and validate the secrets that are referenced in
the specification against the configuration.
The Secrets class supports several ways of providing a configuration specification, and
offers a range of methods for getting and temporarily setting or modifying secrets within
an application at runtime.
See the Classes & Methods section for more information about the classes, methods and properties provided by the library.
from configures import Secrets
secrets = Secrets()
print(secrets.get("MY_SECRET", default="<default fallback value>"))
Methodology
The Configures library supports verifying that any required secrets exist, and optionally that their values are of the expected type and format as specified in the provided configuration specification file. Secrets can also be marked as optional and will only be validated if they are available.
The configuration specification file consists of a list of named secrets that should or must exist as well as optional regular expressions or lists of option values that define the acceptable format and values for each variable.
A configuration specification file may be provided in one of three supported formats:
- a text file that provides a list of secrets and their corresponding specifications
- a JSON file that provides a dictionary of secrets and their corresponding specifications
- a YAML file that provides a dictionary of secrets and their corresponding specifications
By being able to provide a listing of the secrets used within an application, to noting which secrets are optional, as well as being able to specify the acceptable values in a concise way, runtime configuration can be validated to ensure it is within the expected range of the software during the very first steps of its initialisation, allowing for configuration issues to be highlighted at startup before any issues related to misconfiguration can occur.
Classes & Methods
The Configures library provides several classes which are documented below along with their available methods and properties.
Secrets Class Methods & Properties
The Secrets class offers the following methods:
-
get(name: str, default: object = None)(object) – Theget()method provides support for obtaining a named secret, if a matching secret exists, as well as providing support for returning a default fallback value if the named secret does not exist. -
set(name: str, value: object)– Theset()method provides support for setting a named secret on theSecretsclass. The method expects the secret to have a name defined as a string along with the secret's value. If a matching secret already exists, its value will be overwritten with the provided value. The method returns a reference toselfso that calls toset()may be chained with calls to other methods on theSecretsclass. -
require(name: str)(object) – Therequire()method provides support for obtaining a named secret value, ensuring that a matching secret exists. If no matching secret is found, then aConfigurationErrorexception will be raised. -
empty(name: str, default: object = None)(bool) – Theempty()method supports determining if the named secret has an empty string value or not, returningTrueif it does orFalseotherwise. If the secret has not been specified in the application's configuration, thedefaultvalue, if specified, will be checked for being an empty string instead. -
nonempty(name: str, default: object = None)(bool) – Thenonempty()method supports determining if the named secret has an non-empty string value or not, returningTrueif it does orFalseotherwise. If the secret has not been specified in the application's configuration, thedefaultvalue, if specified, will be checked for being a non-empty string instead. -
null(name: str, default: object = None)(bool) – Thenull()method provides support for determining if the named secret has an "null" value or not; this is achieved by comparing the secret value against the configured class-levelsentinelproperty value. If a match is found then this method returnsTrueorFalseotherwise. If the secret has not been specified in the application's configuration,, thedefaultvalue, if specified, will be compared against thesentinelproperty value instead. -
true(name: str, default: object = None)(bool) – Thetrue()method provides support for determining if the named secret value has a truthy value or not; the truthy values are configured at class-level and if a match is found between the current configuration value and one of the truthy values, the method will returnTrue, orFalseotherwise; if the secret has not been specified in the application's configuration, thedefaultvalue, if specified, will be compared against the truthy values instead. -
false(name: str, default: object = None)(bool) – Thefalse()method supports determining if the named configuration value has a falsey value or not; the falsey values are configured at class-level and if a match is found between the current configuration value and one of the falsey values, the method will return True, or false otherwise; if the secret has not been specified in the application's configuration, thedefaultvalue, if specified, will be compared against the falsey values instead. -
int(name: str, default: object = None)(int) – Theint()method provides support for returning the named secret cast to anintvalue, if the secret has been specified, otherwise thedefaultvalue, if specified, will be returned instead. -
float(name: str, default: object = None)(float) – Thefloat()method provides support for returning the named secret cast to afloatvalue, if the secret has been specified, otherwise thedefaultvalue, if specified, will be returned instead. -
combine(variables: list[str], separator: str = None, strip: bool = False)(str) – Thecombine()method provides support for combining the string values from multiple secrets, joining parts with an optional separator character, and optionally stripping the separator character from the beginning and end of each secret value. -
keys()(list[str]) – Thekeys()method supports obtaining the keys for the secrets currently held by theSecretsinstance. -
values()(list[object]) – Thevalues()method supports obtaining the values for the secrets currently held by theSecretsinstance. -
items()(list[tuple[str, object]]) – Theitems()method supports obtaining the items for the secrets currently held by theSecretsinstance. -
update(**secrets: dict[str, object])(Secrets) – Theupdate()method supports updating the secrets currently held by theSecretsinstance. The method returns a reference toselfso calls to theupdate()method can be chained with calls to other methods and or access to properties on theSecretsclass.
The Secrets class offers the following properties:
-
configuration(Configuration) – Theconfigurationproperty provides access to theSecretclass' associatedConfigurationclass instance. -
sentinel(object) – Thesentinelproperty provides access to the sentinel value, if any, that was optionally set when theSecretsclass was instantiated. This value can only be specified when theSecretsclass is instantiated via thesentinelkeyword argument, and is used when determining if any given secret has a "null" value by comparing the secret's value against the sentinel value.
The Secrets class also offers dictionary-style access to the secrets using standard
dictionary patterns for getting, setting, deleting, counting and checking existence:
from configures import Secrets
secrets = Secrets(secrets=dict(TZ="Europe/Rome"))
assert len(secrets) == 1
# Checking if a secret exists or not:
assert "TZ" in secrets
# Accessing an existing secret (if the secret does not exist, a KeyError will be raised):
timezone = secrets["TZ"]
assert timezone == "Europe/Rome"
# Setting a secret (either a new secret, or to overwrite an existing secret's value):
secrets["TZ"] = "Europe/London"
assert secrets["TZ"] == "Europe/London"
# Deleting a secret (if the secret does not exist, a KeyError will be raised):
del secrets["TZ"]
# Getting a count of the current secrets:
assert len(secrets) == 0
# Checking if a secret exists or not:
assert not "TZ" in secrets
⚠️ Note: The same access caveats apply as with dictionary access in that if you reference
a key (a secret name) that does not exist, a KeyError will be raised both when trying
to get or delete a non-existent key.
Configuration Class Methods & Properties
The Configuration class offers the following methods:
validate(secrets: Secrets)(generator) – Thevalidate()method provides support for validating the provided secrets with the provided configuration specification. The validation step ensures that each secret that has been referenced in the configuration specification passes the validation rules, including ensuring that required secrets are present, and that values conform to any defined regular expression or options list validation.
The Configuration class offers the following properties:
specification(Specification) – Thespecificationproperty returns a reference to theSpecificationclass instance that holds the parsed specification provided when theConfigurationclass was instantiated.
Specification Class Methods & Properties
The Specification class offers the following methods:
-
update(**specifications: dict[str, Variable])– Theupdate()method supports updating the secrets specifications held by the Specification instance. -
copy()(dict[str, Variable]) – Thecopy()method supports copying the secrets specifications held by the Specification instance. -
keys()(list[str]) – Thekeys()method supports obtaining the keys for the secrets specification held by the Specification instance. -
values()(list[str]) – Thevalues()method supports obtaining the values for the secrets specification held by the Specification instance. -
items()(generator) – Theitems()method supports obtaining the items for the secrets specification held by the Specification instance via a generator.
The Specification class offers the following properties:
variables(dict[str, Variable]) – Thevariablesproperty returns a reference to a copy of theSpecificationclass' internal dictionary of variables, keyed by name.
The Specification class also offers dictionary-style access to the secrets specifications
using standard dictionary patterns for getting, counting and checking for existence. The
class does not allow secrets specifications to be modified, so dictionary-style setting,
deleting or clearing of secrets specifications are not supported and will raise an error.
from configures import Secrets
secrets = Secrets()
# Accessing a secret specification (`None` will be returned for a non-existent spec):
specification = secrets.configuration.specification["TZ"]
# Checking if a secret specification exists:
assert not "OTHER" in secrets.configuration.specification
Validator Class Methods & Properties
The Validator class offers the following methods:
valid(variable: Variable, value: object = None)– Thevalid()method determines if the provided variable has a valid value according to the specified secrets specification for the variable, if one has been provided; the method accepts an instance of aVariableclass, and optionally avalueoverride. If provided, thevalueargument will override the value held within theVariableinstance.
The Validator class offers the following properties:
required(bool) – Therequiredproperty returns therequiredproperty value.nullable(bool) – Thenullableproperty returns thenullableproperty value.
ValidatorOption Class Methods & Properties
The ValidatorOption class offers the following methods:
-
cast(source: object, target: type = None)– Thecast()method supports casting a source value to a target data type, where such type casting is possible and makes sense; when no semantically sensible type casting exists between the provided source value and the specified target data type, aTypeErrorexception will be raised. -
valid(variable: Variable, value: object = None)– Thevalid()method determines if the provided variable has a valid value according to the specified secrets specification for the variable, if one has been provided; the method accepts an instance of aVariableclass, and optionally avalueoverride. If provided, thevalueargument will override the value held within theVariableinstance.
The ValidatorOption class offers the following properties:
-
options(list[str]) – Theoptionsproperty returns the accepted secret options, as defined in the secrets specification for the associated secret. -
typecast(bool) – Thetypecastproperty returns whether the secret value can be type cast or not. -
typed(type) – Thetypedproperty returns the data type of the options values.
ValidatorRegex Class Methods & Properties
The ValidatorRegex class offers the following methods:
valid(variable: Variable, value: object = None)– Thevalid()method determines if the provided variable has a valid value according to the specified secrets specification for the variable, if one has been provided; the method accepts an instance of aVariableclass, and optionally avalueoverride. If provided, thevalueargument will override the value held within theVariableinstance.
The ValidatorRegex class offers the following properties:
pattern(re.Pattern) – Thepatternproperty provides access to the regular expression pattern as defined in the secrets specification for the associated secret.
Variable Class Methods & Properties
The Variable class offers the following methods:
validate(value: object = None)(bool) – Thevalidate()method validates theVariableclass's value via the associatedValidatorinstance, returningTrueif the variable passes the validation defined in the secrets specification, orFalseif it does not.
The Variable class offers the following properties:
-
name(str) – Thenameproperty returns the name of the variable. -
validator(Validator) – Thevalidatorproperty returns theValidatorclass instance that is associated with the variable. -
value(object) – Thevalueproperty returns the value, if any, of the variable. -
default(object) – Thedefaultproperty returns the default, if any, of the variable.
Configuration Specifications
The configuration specification files consists of a list of named secret that should or must exist as well as optional regular expressions that define the acceptable format and values for each configuration option.
A configuration specification file may be provided in one of three supported formats: an
.env style file that lists one secret per line and its corresponding specification with
a .spec file name extension; a JSON file that provides a dictionary of secrets and their
corresponding specifications, with a .json file name extension, or a YAML file that
provides a dictionary of secrets and their corresponding specifications, with a .yaml or
.yml file name extension. These are summarized in the table below:
| Specification Format | File Extensions | Summary |
|---|---|---|
| SPEC File | .spec |
Defines variables and their specification in an .env style way |
| JSON File | .json 💡 |
Defines variables and their specification in a dictionary format |
| YAML File | .yaml or .yml |
Defines variables and their specification in a dictionary format |
💡 Note: For JSON Specification Files, the Configures library also supports extended JSON files with single-line and multi-line comments as well as trailing commas; unquoted keys are not currently supported. These specification files may be specified with either the standard .json file name extension, the .jsonc file name extension made popular by Microsoft®, or a .jsonx (JSON-extended) file name extension.
Configuration Specification: Environment Variable-Style File
Configuration specifications provided using an .env style file must use the format
noted below where each line represents a named secret and its corresponding specification.
Each line must adhere to the format noted below. Lines starting with a # character are considered to be comments and are ignored:
[<optional?>]<secret-name>=(<regex>)[<optional-default-value>]
Where <secret-name> is the name of the secret as set in the environment and as specified in the application or package software; the names of secrets are usually capitalised, but this is a convention rather than a requirement, and will depend upon the style guidelines of the application or package that is making use the configuration options in question.
The [<optional?>] flag notes if a secret is optional for the application; this is noted by prefixing the secret's name with a question mark (?) character – required secrets must be listed in the file without the optional flag.
Following the secret's name, a regular expression can be provided which will be used to validate the format and optionally the accepted values of the secret; if no regular expression is provided, only the presence of the secret will be checked, and unless it is marked as being optional, a ConfigurationError will be raised if it is not available in the current runtime environment.
Finally the [<optional-default-value>] can be used to specify an optional default for
the secret if the secret has not been defined in the current runtime environment.
Below is an example highlighting the structure of a possible rule:
?TZ=([A-Za-z]+(_[A-Za-z]+)?/[A-Za-z_]+(_[A-Za-z])?)[America/Los_Angeles]
The rule specifies that the TZ secret is optional as the line starts with a ? character, but if it is specified, its value must consist of at least one of characters A-Z or a-z, optionally followed by one or more additional characters from the range A-Z, a-z and _, then followed by a required / separator character which must then be followed by one or more of the characters A-Z, a-z, optionally followed by one or more of the characters A-Z, a-z and _, such that a value like America/New_York would be considered valid, whereas a value like 123 or America would not.
As this rule also specifies an optional default value of America/Los_Angeles, if the TZ secret has not been defined in the current runtime environment, the default value will be applied to the current environment's configuration instead.
The regular expression rules can also be used to specify a range of fixed values such as YES and NO with a rule similar to the following:
?SOME_FEATURE_FLAG=(YES|NO)[NO]
Configuration Specification: JSON File
JSON configuration validation specification files must conform to the following format with one or more nested JSON dictionaries expressed according to the pattern below:
{
"<secret-name>": {
"optional": "<optional>",
"nullable": "<nullable>",
"validate": {
"pattern": "<pattern>"
},
"default": "<default>"
},
"<secret-name>": {
"optional": "<optional>",
"nullable": "<nullable>",
"validate": {
"options": [
"RED",
"GREEN",
"BLUE"
]
},
"default": "<default>"
}
}
Each block must note the configuration variable name to which it applies, and each secret should only be named once, otherwise the last instance will be used. The <secret-name> expressed as a string must match the name of the secret as held in the secrets and as used in the software.
The validation specification for the secret is then detailed within the block.
The <optional> (boolean) flag notes if a secret is optional or not; this is specified by providing the optional key with a boolean value of true if the associated secret is optional, and thus does not need specifying in the secrets. For required secrets, the optional key can either be omitted from the specification for the secret, or it must have a false value.
The <nullable> (boolean) flag notes if a secret's value can hold a null (None) value or not; this is specified by providing the nullable key with a boolean value of true. The nullable key only needs specifying for secrets that are nullable; for non-nullable secrets, the nullable key can either be omitted from the specification for the secret, or must have a false value.
In order to validate the value of a secret, the library currently provides the following validation mechanisms:
* regular expression matching
* basic options list matching
To validate the value of a secret, at least one of the validation mechanisms must be configured via the validate key for the relevant secret.
To use regular expression matching, a regular expression must be provided under the
validate.pattern key-path for the relevant variable. The regular expression must be
written so that it matches against the valid options for the secret.
To use basic options list matching, a list of one or more accepted option values,
must be provided via the validate.options key-path for the relevant secret.
Each secret will be validated through the validation mechanisms that have been defined for it in the specification. One should ensure the validations are compatible with each other – that is that a secret value will either match or fail to match through all of the validation mechanisms defined for a given secret. A validation regular expression for example should not result in a match when the corresponding options list match would fail or vice-versa.
If no validation mechanism are defined for a given secret, only the presence of the configuration variable will be checked, and unless it is marked as being optional, a ConfigurationError will be raised if it has not been defined.
An example JSON-serialised secrets specification may look something like the following:
{
"TIMEZONE": {
"optional": false,
"nullable": false,
"validate": {
"pattern": "[A-Z]{1}[A-Za-z]+/[A-Za-z_]+(_[A-Za-z])?"
},
"default": "America/Los_Angeles"
},
"UI_COLOR_THEME": {
"optional": true,
"nullable": false,
"validate": {
"pattern": "[A-Z]{1}([a-z]{1,})?/[A-Z]{1}([a-z]{1,})?",
"options": [
"Grey/Blue",
"Grey/Orange",
"Grey/Red"
]
},
"default": "Grey/Blue"
},
}
Configuration Specification: YAML File
YAML configuration validation specification files must conform to the following format with one or more nested YAML dictionaries expressed according to the pattern below:
<secret-name>:
optional: <optional>
nullable: <nullable>
validate:
pattern: <pattern>
default: <default>
<secret-name>:
optional: <optional>
nullable: <nullable>
validate:
options:
- '1'
- '2'
- '3'
default: <default>
Each block must note the secret's name to which it applies, and each configuration variable should only be named once, otherwise the last instance will be used. The <secret-name expressed as a string must match the name of the secret as held in the secrets and as used in the software.
The validation specification for the secret is then detailed within the block.
The <optional> (boolean) flag notes if a secret is optional or not; this is specified by providing the optional key with a boolean value of true if the associated secret is optional, and thus does not need specifying in the secrets. For required secrets, the optional key can either be omitted from the specification for the variable, or it must have a false value.
The <nullable> (boolean) flag notes if a secret's value can hold a null (None) value or not; this is specified by providing the nullable key with a boolean value of true. The nullable key only needs specifying for secrets that are nullable; for non-nullable variables, the nullable key can either be omitted from the specification for the secret, or must have a false value.
In order to validate the value of a configuration variable, the library currently provided the following validation mechanisms:
* regular expression matching
* basic options list matching
To validate the value of a secret, at least one of the validation mechanisms must be configured via the validate key for the relevant secret.
To use regular expression matching, a regular expression must be provided under the
validate.pattern key-path for the relevant variable. The regular expression must be
written so that it matches against the valid options for the secret.
To use basic options list matching, a list of one or more accepted option values,
must be provided via the validate.options key-path for the relevant secret.
Each secret will be validated through the validation mechanisms that have been defined for it in the specification. One should ensure the validations are compatible with each other – that is that a configuration variable value will either match or fail to match through all of the validation mechanisms defined for a given variable. A validation regular expression for example should not result in a match when the corresponding options list match would fail or vice-versa.
If no validation mechanism are defined for a given secret, only the presence of the secret will be checked, and unless it is marked as being optional, a ConfigurationError will be raised if it has not been defined.
An example configuration variable specification may look something like the following:
TIMEZONE:
optional: false
nullable: false
validate:
pattern: '[A-Z]{1}[A-Za-z]+/[A-Za-z_]+(_[A-Za-z])?'
default: America/Los_Angeles
UI_COLOR_THEME:
optional: true
nullable: false
validate:
pattern: '[A-Z]{1}([a-z]{1,})?/[A-Z]{1}([a-z]{1,})?'
options:
- Grey/Blue
- Grey/Orange
- Grey/Red
default: Grey/Blue
Unit Tests
The Configures library includes a suite of comprehensive unit tests which ensure that
the library functionality operates as expected. The unit tests were developed with and
are run via pytest.
To ensure that the unit tests are run within a predictable runtime environment where all
of the necessary dependencies are available, a Docker image is
created within which the tests are run. To run the unit tests, ensure Docker and Docker
Compose is installed, and perform the
following commands, which will build the Docker image via docker compose build and
then run the tests via docker compose run – the output the tests will be displayed:
$ docker compose build
$ docker compose run tests
To run the unit tests with optional command line arguments being passed to pytest,
append the relevant arguments to the docker compose run tests command, as follows, for
example passing -v to enable verbose output and -s to print standard output:
$ docker compose run tests -v -s
See the documentation for PyTest regarding available optional command line arguments.
Copyright & License Information
Copyright © 2023–2026 Daniel Sissman; licensed under the MIT License.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file configures-0.9.3.tar.gz.
File metadata
- Download URL: configures-0.9.3.tar.gz
- Upload date:
- Size: 31.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f9f639e6912eda9084829fa4602ec38fcee11f73132ca41b3019712d4eadc0c
|
|
| MD5 |
0cabcf45ff07c426ed04d309b27f3130
|
|
| BLAKE2b-256 |
f035d99f088a3623a452721ecde38b8e2d3e03679e33f50c4ae2b7ecaeb61e22
|
Provenance
The following attestation bundles were made for configures-0.9.3.tar.gz:
Publisher:
python-publish.yml on bluebinary/configures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
configures-0.9.3.tar.gz -
Subject digest:
1f9f639e6912eda9084829fa4602ec38fcee11f73132ca41b3019712d4eadc0c - Sigstore transparency entry: 942540463
- Sigstore integration time:
-
Permalink:
bluebinary/configures@aaafee1f1a284ace53babac999700e7b93909238 -
Branch / Tag:
refs/tags/v0.9.3 - Owner: https://github.com/bluebinary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@aaafee1f1a284ace53babac999700e7b93909238 -
Trigger Event:
release
-
Statement type:
File details
Details for the file configures-0.9.3-py3-none-any.whl.
File metadata
- Download URL: configures-0.9.3-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
148f8e6ae8980fd082a4bd0c1da2211443f75fb2495ede9f2cff378b35b1c1e1
|
|
| MD5 |
a4d88cec3e7c2f4a1e556a7a3effd45e
|
|
| BLAKE2b-256 |
7224d00bcc6ca0daad626789774fd4dd5927e35a66a23ab45158893b40799a94
|
Provenance
The following attestation bundles were made for configures-0.9.3-py3-none-any.whl:
Publisher:
python-publish.yml on bluebinary/configures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
configures-0.9.3-py3-none-any.whl -
Subject digest:
148f8e6ae8980fd082a4bd0c1da2211443f75fb2495ede9f2cff378b35b1c1e1 - Sigstore transparency entry: 942540472
- Sigstore integration time:
-
Permalink:
bluebinary/configures@aaafee1f1a284ace53babac999700e7b93909238 -
Branch / Tag:
refs/tags/v0.9.3 - Owner: https://github.com/bluebinary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@aaafee1f1a284ace53babac999700e7b93909238 -
Trigger Event:
release
-
Statement type: