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).
Method 1: Using NeomodelConfig (Recommended)
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
NeomodelConfigdataclass or the legacyconfig.DRIVERattribute. - The
driverfield inneomodel/config.pystores the instance and is deliberately excluded fromto_dict()serialization to prevent configuration leaks. - Use the
NeomodelConfigapproach for multi-tenant applications or when you need explicit configuration management and validation. - Use the
config.DRIVERapproach 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →