# How to Use Soft Cardinality Checks for Relationship Validation in Neomodel

> Learn how to use soft cardinality checks in Neomodel to log warnings instead of errors for relationship violations. Debug and migrate data smoothly without breaking transactions.

- Repository: [Neo4j Contrib/neomodel](https://github.com/neo4j-contrib/neomodel)
- Tags: how-to-guide
- Published: 2026-03-08

---

**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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/cardinality.py) and [`neomodel/async_/cardinality.py`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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:

```python
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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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):

```python
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:

```python
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 `AttemptedCardinalityViolation` exceptions.
- The feature is controlled by the `soft_cardinality_check` boolean flag in [`neomodel/config.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/config.py), accessed via `get_config()`.
- When enabled, the `check_cardinality()` methods in [`neomodel/sync_/cardinality.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/cardinality.py) and [`neomodel/async_/cardinality.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/async_/cardinality.py) print 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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/async_/cardinality.py) references the same global configuration object as the sync version in [`neomodel/sync_/cardinality.py`](https://github.com/neo4j-contrib/neomodel/blob/main/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.