Error Handling
We designed the error handling for the API to allow you developing tools as quickly as possible. For this reason, all runtime errors raised by the API are derived from Error
. This allows you to catch all errors raised by the API in a single place, making the implementation of your tools easier.
Also, each error message provides as much details about the source of the error as possibe. Ist most cases, the original error message is enoug for direct display to the user.
Have a look at the following example code:
import erbsland.conf as elcl
def parse_configuration(file_name: str):
doc = elcl.load(file_name)
server_name = doc.get_text("main.server.name")
port = doc.get_int("main.server.port", default=8080)
clients = []
for client_value in doc["client"]:
name = client_value.get_text("name")
ip = client_value.get_text("ip")
port = client_value.get_int("port", default=9000)
if filter_value := client_value.get("filter", default=None):
keywords = filter_value.get_list("keywords", str)
else:
keywords = []
clients.append((name, ip, port, keywords))
print(f"Server name: {server_name}")
print(f"Port: {port}")
for index, client in enumerate(clients):
print(f"Client {index}: {client}")
def main():
try:
parse_configuration("error_handling_4.elcl")
print("Success!")
exit(0)
except elcl.Error as e:
print(f"Error reading the configuration.")
# print(e)
print(e.to_text(elcl.ErrorOutput.FILENAME_ONLY | elcl.ErrorOutput.USE_LINES))
exit(1)
if __name__ == "__main__":
main()
Successful Run
If you run this code with a valid configuration file, you will see the following output:
--[ Main ]-------------------------------------------------------------------
Listener : Enabled
Polling Interval : 1000 ms
--[ Main . Server ]----------------------------------------------------------
Name : "host01.example.com"
Port : 8080
-*[ Client ]*----------------------------------------------------------------
Name : "client01"
IP : "192.168.1.10"
-*[ Client ]*----------------------------------------------------------------
Name : "client02"
IP : "192.168.1.11"
Port : 9100
----[ .Filter ]--------------------------------------------------------------
Keywords :
* "tree"
* "leaf"
* "branch"
* "flower"
-*[ Client ]*----------------------------------------------------------------
Name : "client03"
IP : "192.168.1.12"
Port : 9170
Server name: host01.example.com
Port: 9170
Client 0: ('client01', '192.168.1.10', 9000, [])
Client 1: ('client02', '192.168.1.11', 9100, ['tree', 'leaf', 'branch', 'flower'])
Client 2: ('client03', '192.168.1.12', 9170, [])
Success!
IO Errors
In case the configuration file is not found or cannot be read, the following error message is displayed:
Error reading the configuration.
Failed to open file, path=/examples/error_handling_X.elcl, system error=[Errno 2] No such file or directory: '/examples/error_handling_X.elcl'
As you can see, the error message provides the source of the error including the error message from the underlying system.
Syntax, Encoding and Character Errors
If there is a problem while parsing the configuration file, for example, a user forgets to put the double quotes around the IP address.
-*[ Client ]*----------------------------------------------------------------
Name : "client02"
IP : 192.168.1.11
Port : 9100
You will see an error message like this:
Error reading the configuration.
Expected a valid value but got something else, source=file:/examples/error_handling_3.elcl:[15:22], name-path=client[1].ip
You not only see the exact location of the error in the source field, but also the name-path of the element that caused the error. This makes it easy, especially in large configurations, to find the exact location of the error.
If you have long and complex paths to the configuration files, you can also customize the error output:
print(e.to_text(elcl.ErrorOutput.FILENAME_ONLY | elcl.ErrorOutput.USE_LINES))
This will split the error message into multiple lines and only show the filename, without the full path:
Error reading the configuration.
Expected a valid value but got something else
source: file:error_handling_3.elcl:[15:22]
name-path: client[1].ip
Errors while Interpreting the Parsed Result
Error handling is not limited to the parsing of the configuration file. The API is designed to provide useful error messages if you access and convert the values. For example, in our demo code, the server name (at main.server.name
) is required and must be a string.
If a user forgets to provide a server name, the following error message is displayed:
Error reading the configuration.
Value not found
name-path: main.server.name
This is enough information for many use cases and extra line of code is required.
We also use the get_text()
method to convert the value to a string. If the value is not a string, the following error message is displayed:
Error reading the configuration.
Expected value of type Text, got Integer
source: file:error_handling_4.elcl:[7:22]
name-path: main.server.name
This is, again, sufficient for most use cases. A user gets what’s expected and the exact location of the error.
Generating Error Messages
Generating error messages is part of the public API. This is useful, if you already have a catch all error handler in place and do custom type checking in the configuration.
The error classes have a uniform interface, where you have one positional and required parameter with the error message itself, and many keyword parameters to provide additional details.
# ...
value_x = doc["main.x"]
if x := value_x.as_int(default=None):
print(f"x is int: {x}")
elif x := value_x.as_text(default=None):
print(f"x is text: {x}")
else:
raise elcl.ConfTypeMismatch(
"Expected integer or text value",
source=value_x.location,
name_path=value_x.name_path
)
Here we raise a ConfTypeMismatch
error if the value is neither an integer nor a text. To provide additional details, we pass the location of the value and the name path of the value.