How to Configure neomodel with a Custom Neo4j Driver Instance

You can configure neomodel with a custom Neo4j driver instance by either passing it to a NeomodelConfig dataclass and calling set_config(), or by assigning it directly to neomodel.config.DRIVER for backward compatibility.

The neomodel library provides an Object Graph Mapper (OGM) for Neo4j graph databases in Python. While it typically constructs a driver automatically from a DATABASE_URL environment variable, production applications often require fine-grained control over TLS settings, authentication mechanisms, or connection pooling. This guide explains how to configure neomodel with a custom Neo4j driver instance using both the modern configuration API and the legacy module-level attribute.

Why Use a Custom Neo4j Driver with neomodel?

Injecting a custom driver instance is essential when you need capabilities beyond the standard URL-based configuration:

  • Advanced TLS configuration – Custom CA certificates, client certificates, or specific trust policies
  • Custom connection pooling – Fine-tuned max connection limits, timeout settings, or resolver functions
  • Multi-tenant architectures – Maintain separate driver instances for different Neo4j databases within the same process
  • Testing and mocking – Inject mock drivers for unit tests without requiring a live database
  • Dynamic authentication – Custom auth tokens or token refresh logic not supported by simple URL strings

Two Methods to Configure a Custom Driver

The library exposes two distinct approaches to inject a custom driver, both storing the instance in the NeomodelConfig.driver attribute defined in neomodel/config.py (lines 34-41).

The modern approach uses the NeomodelConfig dataclass. This method provides explicit configuration management and is ideal for applications requiring multiple configuration states or clean separation of concerns.

from neomodel import NeomodelConfig, set_config
from neo4j import GraphDatabase

# Create your custom driver

custom_driver = GraphDatabase.driver(
    "neo4j://localhost:7687",
    auth=("neo4j", "securepassword"),
    encrypted=True
)

# Create configuration object and set it globally

cfg = NeomodelConfig(driver=custom_driver)
set_config(cfg)

Method 2: Using the Legacy config.DRIVER Attribute

For backward compatibility, neomodel exposes a module-level DRIVER property via the _ConfigModule class in neomodel/config.py (lines 49-57). Setting config.DRIVER updates the global configuration in place, making it suitable for quick scripts or existing codebases.

from neomodel import config
from neo4j import GraphDatabase

# Create your custom driver

custom_driver = GraphDatabase.driver(
    "neo4j://localhost:7687",
    auth=("neo4j", "securepassword")
)

# Assign directly to the module attribute

config.DRIVER = custom_driver

Step-by-Step Implementation Guide

Follow these steps to successfully configure neomodel with a custom Neo4j driver instance in your application.

Step 1: Create the Custom Driver

Instantiate the official Neo4j Python driver with your specific connection requirements.

from neo4j import GraphDatabase, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES

# Example with custom TLS and connection settings

custom_driver = GraphDatabase.driver(
    "neo4j://my-secure-host:7687",
    auth=("neo4j", "supersecret"),
    encrypted=True,
    trust=TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
    max_connection_pool_size=50
)

Step 2: Inject the Driver into neomodel

Choose your preferred configuration method.

Using NeomodelConfig:

from neomodel import NeomodelConfig, set_config

cfg = NeomodelConfig(driver=custom_driver)
set_config(cfg)

Using the legacy attribute:

from neomodel import config

config.DRIVER = custom_driver

Step 3: Verify the Configuration

Confirm that the global configuration holds your custom instance.

from neomodel import get_config

assert get_config().driver is custom_driver
print("Custom driver successfully registered with neomodel")

Step 4: Use neomodel Models Normally

All subsequent database operations will utilize your custom driver.

from neomodel import StructuredNode, StringProperty

class Person(StructuredNode):
    name = StringProperty()

# This save operation uses the custom driver instance

alice = Person(name="Alice").save()

Step 5: Clean Up on Shutdown

Always close the driver when your application terminates to prevent connection leaks.

custom_driver.close()

Technical Implementation Details

The custom driver configuration is implemented in neomodel/config.py. The driver field is defined at lines 34-41 as a dataclass field that accepts a Driver instance or None:

driver: Driver | None = field(
    default=None,
    metadata={"env_var": None, "description": "Custom database driver instance"},
)

The module-level DRIVER property (lines 49-57) provides backward compatibility through the _ConfigModule class, which forwards attribute access to the underlying global configuration using _get_attr and _set_attr helpers.

A critical implementation detail is that the driver is deliberately excluded from serialization. The to_dict() method omits the driver field to prevent serialization errors and ensure driver objects never leak into configuration dumps or logs. The validation logic in the setter guarantees that any change is validated and automatically rolled back if it fails, maintaining global configuration consistency.

Testing with a Mock Driver

The test suite in test/test_config_modernization.py (specifically the test_custom_driver_configuration test at lines 91-118) demonstrates how to inject a mock driver for unit testing.

from unittest.mock import Mock
from neomodel import config, get_config, NeomodelConfig

# Create a mock driver for testing

mock_driver = Mock()
mock_driver.close = Mock()

# Method 1: Via NeomodelConfig

cfg_obj = NeomodelConfig(driver=mock_driver)
assert cfg_obj.driver is mock_driver

# Method 2: Via legacy attribute

config.DRIVER = mock_driver
assert get_config().driver is mock_driver

# Verify exclusion from serialization

assert "driver" not in cfg_obj.to_dict()

# Restore original state

config.DRIVER = None

This pattern allows you to verify that your application code interacts with the driver correctly without requiring a live Neo4j database instance.

Summary

  • Configure neomodel with a custom Neo4j driver instance by using either the NeomodelConfig dataclass or the legacy config.DRIVER attribute.
  • The driver field in neomodel/config.py stores the instance and is deliberately excluded from to_dict() serialization to prevent configuration leaks.
  • Use the NeomodelConfig approach for multi-tenant applications or when you need explicit configuration management and validation.
  • Use the config.DRIVER approach for quick scripts or when maintaining backward compatibility with existing neomodel codebases.
  • Always close the custom driver during application shutdown to prevent connection pool exhaustion.

Frequently Asked Questions

How do I switch drivers at runtime after neomodel has already connected?

You can update the driver at runtime by calling set_config() with a new NeomodelConfig instance containing the new driver, or by assigning a new driver to config.DRIVER. However, existing connections from the previous driver will remain active until explicitly closed. Always ensure proper cleanup of the old driver using driver.close() before switching to prevent connection pool exhaustion and resource leaks.

Why is the driver excluded from the to_dict() method in NeomodelConfig?

The driver is deliberately excluded from serialization because driver instances contain open network sockets, thread pools, and other non-serializable resources that cannot be represented in JSON or YAML. Including the driver in to_dict() would raise serialization errors when dumping configuration for logging or debugging. The implementation in neomodel/config.py explicitly omits this field to ensure configuration dumps remain safe, portable, and free from sensitive connection details.

How do I configure neomodel with a custom driver for testing purposes?

For unit testing, create a Mock object from Python's unittest.mock module and assign it to config.DRIVER or pass it to NeomodelConfig. The test suite in test/test_config_modernization.py demonstrates this pattern, allowing you to verify that your application code calls the correct driver methods without requiring a live Neo4j instance. Remember to mock the close() method if your teardown code invokes it, and restore the original driver state after each test to prevent cross-test contamination.

What happens if I set both DATABASE_URL and a custom driver?

When you explicitly set a custom driver via NeomodelConfig or config.DRIVER, it takes precedence over the DATABASE_URL environment variable. The driver field is checked first during connection initialization, and if present, neomodel uses that instance rather than creating a new one from the URL. This allows you to override environment-based configuration programmatically when you need specific connection parameters not supported by the URL string format.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →