Error Handling¶
Viper uses a single exception type for all errors: ViperError. This chapter explains how
to catch, interpret, and recover from errors in your Python code.
The ViperError Exception¶
All Viper operations that fail raise a ViperError exception:
from dsviper import ViperError, ValueInt8
try:
v = ValueInt8(200) # Out of range for int8
except ViperError as e:
print(str(e))
Output:
[macbook@myapp]:Viper:Value:1:value is not in the range of 'int8'
The message follows the format: [host@process]:Component:Domain:Code:Message
Parsing Error Details¶
To access individual error fields, use the Error class to parse the exception message:
from dsviper import ViperError, Error, ValueInt8
try:
v = ValueInt8(200)
except ViperError as e:
error = Error.parse(str(e))
if error:
print(error.component()) # "Viper"
print(error.domain()) # "Value"
print(error.code()) # 1
print(error.message()) # "value is not in the range of 'int8'"
print(error.hostname()) # "macbook"
print(error.process_name()) # "myapp"
Error Class Methods¶
Method |
Return |
Description |
|---|---|---|
|
|
Parse an error string into an Error object |
|
|
The subsystem (e.g., “Viper”, “Database”) |
|
|
The functional domain (e.g., “Value”, “Type”) |
|
|
Numeric error code within the domain |
|
|
Human-readable description |
|
|
Host where error originated |
|
|
Process name |
|
|
Full formatted error string |
Common Error Categories¶
Type Mismatch Errors¶
These occur when you provide a value of the wrong type:
from dsviper import Value, Type, TypeVector, ViperError
t_vec = TypeVector(Type.INT64)
try:
# Trying to create a vector with strings instead of integers
v = Value.create(t_vec, ["a", "b", "c"])
except ViperError as e:
print(str(e)) # "...expected type 'long', got 'str'..."
Recovery: Check your input types before calling Viper functions.
Range Validation Errors¶
These occur when numeric values exceed their type’s range:
from dsviper import ValueInt8, ViperError
# Int8 range: -128 to 127
try:
v = ValueInt8(200)
except ViperError as e:
print(str(e)) # "...value is not in the range of 'int8'..."
Integer type ranges:
Type |
Range |
|---|---|
|
-128 to 127 |
|
-32,768 to 32,767 |
|
-2³¹ to 2³¹-1 |
|
-2⁶³ to 2⁶³-1 |
|
0 to 255 |
|
0 to 65,535 |
|
0 to 2³²-1 |
|
0 to 2⁶⁴-1 |
Recovery: Validate numeric inputs before creating values, or use larger types.
Optional Unwrap Errors¶
These occur when you try to unwrap an empty (nil) optional:
from dsviper import Value, Type, TypeOptional, ValueOptional
t_opt = TypeOptional(Type.STRING)
v_empty = ValueOptional(t_opt) # Creates nil optional
try:
content = v_empty.unwrap()
except Exception as e:
print("Cannot unwrap nil optional")
Recovery: Always check is_nil() before calling unwrap():
if not v_empty.is_nil():
content = v_empty.unwrap()
else:
content = "default value"
Cast Errors¶
These occur when you cast a value to an incompatible type:
from dsviper import ValueString, ValueInt64, ViperError
v_str = ValueString("hello")
try:
v_int = ValueInt64.cast(v_str)
except ViperError as e:
print(str(e)) # "...expected int64, got string [cast]..."
Recovery: Use type() to check the value’s type before casting:
from dsviper import Type
if v_str.type() == Type.INT64:
v_int = ValueInt64.cast(v_str)
Key Not Found (Not an Error)¶
When accessing non-existent keys in databases, Viper does not raise an exception.
Instead, get() returns a ValueOptional that is nil:
# db.get() returns ValueOptional, not the value directly
result = db.get(attachment, unknown_key)
# Check if key exists
if result.is_nil():
print("Key not found")
else:
value = result.unwrap()
Note:
ValueOptionalis a Viper container type, unrelated to Python’styping.Optional. It wraps a value with nil/non-nil semantics and providesis_nil(),unwrap(), andwrap()methods.
Alternatives: Use has() to check key existence before get:
if db.has(attachment, key):
value = db.get(attachment, key).unwrap()
DSM Parse Errors¶
These occur when parsing invalid DSM files:
from dsviper import DSMBuilder
builder = DSMBuilder.assemble("invalid.dsm")
report, dsm_defs, defs = builder.parse()
if report.has_error():
for error in report.errors():
print(f"Line {error.line()}: {error.message()}")
Note: DSM parsing returns a report object instead of raising exceptions, allowing you to collect multiple errors at once.
Best Practices¶
1. Catch Specific Errors¶
Always catch ViperError specifically, not bare Exception:
# Good
try:
v = ValueInt8(value)
except ViperError as e:
handle_viper_error(e)
# Bad - catches too much
try:
v = ValueInt8(value)
except Exception:
pass
2. Check Before You Leap¶
For performance-critical code, validate inputs before calling Viper:
# Check range before creating value
def safe_int8(value: int) -> ValueInt8:
if not (-128 <= value <= 127):
raise ValueError(f"Value {value} out of int8 range")
return ValueInt8(value)
3. Use Optionals Correctly¶
Never assume an optional contains a value:
# Good - check before unwrap
result = db.get(attachment, key)
if not result.is_nil():
value = result.unwrap()
else:
value = default
# Bad - will raise if nil
value = db.get(attachment, key).unwrap()
4. Handle DSM Errors Gracefully¶
Always check the parse report before using definitions:
builder = DSMBuilder.assemble(dsm_path)
report, dsm_defs, defs = builder.parse()
if report.has_error():
for err in report.errors():
print(f"Error at line {err.line()}: {err.message()}")
raise RuntimeError("DSM parsing failed")
# Safe to use defs here
5. Transaction Error Handling¶
Wrap database transactions with proper error handling:
db.begin_transaction()
try:
db.set(attachment, key, value)
db.commit()
except ViperError as e:
db.rollback()
raise
Debugging Tips¶
Enable Verbose Logging¶
Use Viper’s logging system to trace operations:
from dsviper import LoggerConsole, Logging
# Create a console logger with debug level
logger = LoggerConsole(Logging.LEVEL_DEBUG)
logging = logger.logging()
# Log operations
logging.info("Starting database operation")
logging.error("Operation failed")
Available log levels: LEVEL_ALL, LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARNING,
LEVEL_ERROR, LEVEL_CRITICAL.
Inspect Error Fields¶
When debugging, parse and print all error fields:
except ViperError as e:
error = Error.parse(str(e))
if error:
print(f"Host: {error.hostname()}")
print(f"Process: {error.process_name()}")
print(f"Component: {error.component()}")
print(f"Domain: {error.domain()}")
print(f"Code: {error.code()}")
print(f"Message: {error.message()}")
Remote Errors¶
When using DatabaseRemote or ServiceRemote, errors include the remote host
information, helping you identify where the error occurred:
[remote-server@db-process]:Viper:Database:3:Connection timeout
What’s Next¶
Types and Values - Learn the type system
Collections - Work with containers