Validation and Rollback API Reference¶
Overview¶
The noctua library provides automatic validation and rollback capabilities to ensure data integrity when making changes to GO-CAM models. This feature allows you to specify expected outcomes and automatically revert changes if those expectations aren't met.
How It Works¶
- Execute with Undo Enabled: Operations are performed with undo capability enabled
- Validate Results: After execution, the results are checked against expected conditions
- Automatic Rollback: If validation fails, changes are automatically reverted
- Response Flags: The response includes special flags indicating validation status
BaristaResponse Validation Properties¶
⚠️ CRITICAL DISTINCTION: ok
vs succeeded
¶
When validation is used, BaristaResponse
has THREE different success indicators:
class BaristaResponse:
# Properties
ok: bool # ⚠️ ONLY checks if API call worked (ignores validation!)
succeeded: bool # ✅ Checks BOTH API success AND validation pass
validation_passed: bool # Only checks validation status
# Error information (all public!)
error: str # ✅ Universal error message (API or validation)
validation_failed: bool # True if validation failed and rollback occurred
validation_reason: str # Reason for validation failure
The Confusion Problem¶
DO NOT USE ok
for validated operations! Here's why:
# WRONG - This will execute even if validation failed!
response = client.add_individual_validated(model_id, "GO:wrong", expected_type={"label": "kinase"})
if response.ok: # ⚠️ WRONG! ok=True even though validation failed!
print(f"Individual created: {response.individual_id}") # ERROR: individual was rolled back!
# CORRECT - Use succeeded for validated operations
response = client.add_individual_validated(model_id, "GO:wrong", expected_type={"label": "kinase"})
if response.succeeded: # ✅ CORRECT! This checks both API and validation
print(f"Individual created: {response.individual_id}")
else:
if response.validation_failed:
print(f"Validation failed: {response.validation_reason}")
else:
print(f"API call failed: {response.raw}")
Quick Reference Table¶
Property | What it checks | When to use |
---|---|---|
ok |
API call succeeded | Non-validated operations only |
succeeded |
API call + validation both passed | Always use for validated operations |
validation_passed |
Validation passed (or wasn't used) | Checking validation specifically |
validation_failed |
Validation was used and failed | Getting validation status |
error |
Universal error message | Getting error details for any failure |
validation_reason |
Specific validation failure reason | Getting validation error details |
Understanding Validation Results¶
Successful Validation¶
When validation passes:
- response.ok
returns True
(API succeeded)
- response.succeeded
returns True
(API + validation both passed) ✅
- response.validation_failed
is False
- The changes remain in the model
- No rollback occurs
Failed Validation¶
When validation fails:
- response.ok
returns True
(⚠️ API succeeded, but this doesn't mean success!)
- response.succeeded
returns False
(validation failed) ✅
- response.validation_failed
is True
- response.validation_reason
contains explanation
- Changes have been rolled back automatically
- The response contains the result of the undo operation
Methods¶
execute_with_validation¶
The primary method for operations with automatic validation:
execute_with_validation(
requests: List[Dict[str, Any]],
expected_individuals: Optional[List[Dict[str, str]]] = None,
expected_facts: Optional[List[Dict[str, str]]] = None,
privileged: bool = True
) -> BaristaResponse
Parameters:
- requests
: List of operations to execute
- expected_individuals
: List of expected individual types after execution
- expected_facts
: List of expected relationships (not yet implemented)
- privileged
: Whether to use privileged endpoint
Returns:
- BaristaResponse
with validation flags set
add_individual_validated¶
Convenience method for adding an individual with validation:
add_individual_validated(
model_id: str,
class_id: str,
expected_type: Optional[Dict[str, str]] = None
) -> BaristaResponse
Example:
response = client.add_individual_validated(
model_id,
"GO:0003924",
expected_type={"id": "GO:0003924", "label": "GTPase activity"}
)
if not response.succeeded: # Use succeeded, not ok!
print(f"Failed: {response.validation_reason}")
# The individual was added but then removed because validation failed
Validation Format¶
Expected Individuals Format¶
The expected_individuals
parameter is a list of dictionaries, where each dictionary can contain:
- id
: The expected GO/RO term ID
- label
: The expected term label
- Both id
and label
for strict validation
# Check only ID
expected = [{"id": "GO:0003924"}]
# Check only label
expected = [{"label": "GTPase activity"}]
# Check both ID and label must match
expected = [{"id": "GO:0003924", "label": "GTPase activity"}]
# Check multiple individuals
expected = [
{"id": "GO:0003924", "label": "GTPase activity"},
{"id": "UniProtKB:P12345"}
]
Complete Examples¶
Example 1: Add Individual with Validation¶
from noctua.barista import BaristaClient
client = BaristaClient()
# Add a kinase activity with validation
response = client.add_individual_validated(
model_id="gomodel:123",
class_id="GO:0004672",
expected_type={
"id": "GO:0004672",
"label": "protein kinase activity"
}
)
# Check the result - USE succeeded, NOT ok!
if response.succeeded:
print("Successfully added protein kinase activity")
print(f"Individual ID: {response.individual_id}")
else:
if response.validation_failed:
print(f"Validation failed: {response.validation_reason}")
print("The individual was added but then automatically removed")
print("Reason: The GO term ID doesn't match its expected label")
else:
print("API call itself failed")
Example 2: Batch Operations with Validation¶
# Create multiple requests
requests = [
client.req_add_individual(model_id, "GO:0003924"),
client.req_add_individual(model_id, "GO:0005525"),
]
# Execute with validation
response = client.execute_with_validation(
requests,
expected_individuals=[
{"id": "GO:0003924", "label": "GTPase activity"},
{"id": "GO:0005525", "label": "GTP binding"}
]
)
if response.succeeded:
print("Both individuals added successfully")
else:
print("Batch operation rolled back!")
print(f"Reason: {response.validation_reason}")
# Both individuals were added then removed
Example 3: Update with Validation¶
# Update an annotation with validation
response = client.update_individual_annotation(
model_id,
individual_id,
"enabled_by",
"UniProtKB:P12345",
validation={
"id": individual_id,
"label": "protein kinase activity"
}
)
if not response.succeeded:
print("Update rolled back - individual doesn't have expected type")
print(f"Expected: protein kinase activity")
print(f"Reason: {response.validation_reason}")
Common Validation Scenarios¶
Wrong GO Term ID¶
# Try to add "GTPase activity" but use wrong ID
response = client.add_individual_validated(
model_id,
"GO:0004674", # This is actually "protein kinase activity"
expected_type={"label": "GTPase activity"}
)
# Result: validation_failed = True
# Reason: Term GO:0004674 has label "protein kinase activity", not "GTPase activity"
Typo in Expected Label¶
response = client.add_individual_validated(
model_id,
"GO:0003924",
expected_type={"id": "GO:0003924", "label": "GTPase activty"} # Typo!
)
# Result: validation_failed = True
# Reason: Expected label "GTPase activty" not found
Successful Validation¶
response = client.add_individual_validated(
model_id,
"GO:0003924",
expected_type={"id": "GO:0003924", "label": "GTPase activity"}
)
# Result: validation_failed = False
# The individual remains in the model
Error Handling¶
When Undo Is Not Available¶
If validation fails but undo is not available (rare), a BaristaError
is raised:
try:
# If someone manually calls validate without enable_undo
response = client.m3_batch(requests, enable_undo=False)
response.validate_and_rollback([{"id": "GO:0003924"}])
except BaristaError as e:
print(f"Cannot rollback: {e}")
Checking Before Using Validation Results¶
Always check succeeded
(not just ok
!) before using the response:
response = client.add_individual_validated(model_id, class_id, expected_type)
if response.succeeded: # ✅ Use succeeded, NOT ok!
# Safe to use the results
new_id = response.individual_id
print(f"Created: {new_id}")
else:
# Don't use response.individual_id - it might not exist!
print("Operation failed or was rolled back")
if response.validation_failed:
print(f"Validation issue: {response.validation_reason}")
print(f"Created individual: {new_id}")
Best Practices¶
- Always Check Validation Status: Check
validation_failed
before using response data - Use Specific Expectations: Be as specific as possible in your validation criteria
- Handle Both Outcomes: Write code that handles both success and rollback scenarios
- Log Validation Failures: Log
validation_reason
for debugging - Test with Dev Server: Test validation logic on dev server before production
Implementation Notes¶
- Validation uses the model state after the operation completes
- Rollback is atomic - either all changes are reverted or none are
- The undo operation itself cannot be undone
- Validation currently supports individual type checking; fact validation is planned
- Performance impact is minimal as validation reuses the response data