How to Use Soft Cardinality Checks for Relationship Validation in Neomodel
Enabling soft_cardinality_check in the global Neomodel configuration allows relationship cardinality violations to log warnings instead of raising AttemptedCardinalityViolation exceptions, letting you debug or migrate data without breaking transactions.
When working with the neo4j-contrib/neomodel Object Graph Mapper (OGM), you define relationship cardinalities such as One or ZeroOrOne to enforce structural constraints on your graph. By default, violating these constraints aborts the operation and raises an exception. However, Neomodel provides a soft cardinality check mode that prints a warning while still creating the relationship, which is invaluable for data migration scripts, legacy imports, or debugging complex relationship logic.
Understanding Relationship Cardinality Enforcement
Neomodel validates cardinality constraints in the relationship manager classes located in neomodel/sync_/cardinality.py and neomodel/async_/cardinality.py. When you attempt to connect a node to a relationship that already exceeds its defined cardinality (for example, connecting a second pet to an owner when the relationship is defined as One), the library normally raises AttemptedCardinalityViolation and rolls back the transaction.
Enabling Soft Cardinality Checks
Configuration Flag Location
The soft check behavior is controlled by the soft_cardinality_check boolean flag defined in neomodel/config.py (lines 30-35). By default, this value is set to False, meaning the library enforces hard cardinality constraints.
Runtime Activation
To enable soft checks at runtime, import the global configuration object and toggle the flag before executing your relationship logic:
from neomodel import get_config
config = get_config()
config.soft_cardinality_check = True
Once enabled, this setting applies globally to both synchronous and asynchronous relationship managers.
How Soft Checks Work Under the Hood
Synchronous Implementation
In neomodel/sync_/cardinality.py, the check_cardinality() method (lines 22-28) inspects the soft_cardinality_check flag before raising an exception. When the flag is True, the method prints a warning message to stdout and returns without raising AttemptedCardinalityViolation, allowing the relationship connection to proceed.
Asynchronous Implementation
The identical logic exists in neomodel/async_/cardinality.py (lines 22-28). The async relationship managers reference the same global configuration object, ensuring consistent behavior whether you are using await node.connect() or synchronous node.connect() calls.
Warning Message Format
When a violation occurs in soft mode, Neomodel outputs a message in this format:
Cardinality violation detected : Node already has <description>, should not connect more. Soft check is enabled so the relationship will be created.
You can capture this output using io.StringIO or redirect it to a logging handler for audit trails.
Practical Implementation Examples
The following example demonstrates both hard and soft cardinality enforcement using a simple Owner and Pet model where an owner should only have one pet (One cardinality):
from neomodel import StructuredNode, StringProperty, RelationshipFrom, get_config
from neomodel.exceptions import AttemptedCardinalityViolation
import io
from unittest.mock import patch
# ----------------------------------------------------------------------
# Model definitions
# ----------------------------------------------------------------------
class Owner(StructuredNode):
name = StringProperty()
# One-to-one relationship: an owner should have only one pet
pets = RelationshipFrom('Pet', 'OWNED_BY', cardinality=One)
class Pet(StructuredNode):
name = StringProperty()
# ----------------------------------------------------------------------
# 1. Hard cardinality enforcement (default behavior)
# ----------------------------------------------------------------------
owner_a = Owner(name="Alice").save()
owner_b = Owner(name="Bob").save()
fluffy = Pet(name="Fluffy").save()
# First connection succeeds
owner_a.pets.connect(fluffy)
# Second connection attempt raises exception
try:
owner_b.pets.connect(fluffy) # Violates One cardinality
except AttemptedCardinalityViolation as e:
print(f"Hard check blocked connection: {e}")
# ----------------------------------------------------------------------
# 2. Soft cardinality check - logs warning but creates relationship
# ----------------------------------------------------------------------
config = get_config()
config.soft_cardinality_check = True # Enable soft mode
# Capture the warning output
stream = io.StringIO()
with patch("sys.stdout", new=stream):
# This would normally raise an exception, but now succeeds
owner_b.pets.connect(fluffy)
warning_output = stream.getvalue()
print(f"Soft check warning: {warning_output.strip()}")
# Relationship now exists despite cardinality violation
For asynchronous models, the configuration works identically:
from neomodel import AsyncStructuredNode, AsyncRelationshipFrom, get_config
config = get_config()
config.soft_cardinality_check = True
# Async usage
await owner.pets.connect(pet) # Will print warning instead of raising
When to Use Soft Cardinality Checks
Enable soft cardinality validation when you need to:
- Migrate legacy data that contains historical violations you plan to clean up later
- Debug complex relationship logic without breaking execution flow
- Import bulk datasets where you want to log violations for manual review rather than aborting the entire transaction
- Test boundary conditions in development environments
Remember that soft checks only affect the Python validation layer; they do not disable any database-level constraints you may have created separately in Neo4j.
Summary
- Soft cardinality checks in Neomodel allow relationship violations to log warnings instead of raising
AttemptedCardinalityViolationexceptions. - The feature is controlled by the
soft_cardinality_checkboolean flag inneomodel/config.py, accessed viaget_config(). - When enabled, the
check_cardinality()methods inneomodel/sync_/cardinality.pyandneomodel/async_/cardinality.pyprint a warning message and permit the relationship creation. - This mode is useful for data migration, debugging, and legacy imports where you need to identify violations without breaking workflow continuity.
Frequently Asked Questions
What is the default value of soft_cardinality_check in Neomodel?
By default, soft_cardinality_check is set to False in neomodel/config.py (lines 30-35). This means Neomodel enforces hard cardinality constraints and raises AttemptedCardinalityViolation immediately when a relationship limit is exceeded.
Does enabling soft cardinality checks affect database constraints?
No, the soft cardinality check is purely an application-level feature within the Neomodel OGM. It only changes how the Python code handles validation errors. If you have created unique constraints or relationship limits directly in Neo4j using Cypher, those database-level rules remain active and will still enforce hard limits regardless of this configuration flag.
Can I use soft checks with async Neomodel models?
Yes, the soft_cardinality_check configuration applies to both synchronous and asynchronous relationship managers. The async implementation in neomodel/async_/cardinality.py references the same global configuration object as the sync version in neomodel/sync_/cardinality.py, ensuring consistent behavior across both APIs.
How do I capture soft cardinality warnings for logging?
When soft checks are enabled, Neomodel prints the warning to standard output using a print statement. To capture these warnings programmatically, redirect sys.stdout to a io.StringIO buffer during the connect operation, or patch the stdout stream in your test suite. You can then parse the captured string to extract violation details for logging frameworks like Python's logging module.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →