Roi Jacob Olfindo
10/30/2025, 4:25 AMclass hamilton.driver.Driver or class hamilton.driver.Builder. What’s the difference and when to use each?
Perhaps this was already answered, having the links would be appreciated. 🙂Elijah Ben Izzy
10/30/2025, 4:26 AMThierry Jean
10/30/2025, 5:35 PMDriver object is a core object of Hamilton that you will be using all the time. Notably, .execute() executes your DAG.
Now, what is the Builder / why does it exist? In early versions, you would create the driver directly
dr = Driver(...)
But as the library became more complex and we added features, we added the Builder , which has the method .build() to return a driver.
builder: hamilton.driver.Driver = Builder().with_modules(...).with_adapters(...)
driver: hamilton.driver.Builder = builder().build()
The distinction might not be obvious from the examples because they are typically combined in one step
driver = (
Builder()
.with_modules(...)
.with_adapters(...)
.build()
)Roi Jacob Olfindo
10/31/2025, 12:01 AMGavin Kistner
10/31/2025, 8:33 PMdr = driver.Builder().with_modules(my_dataflow).build()) is clunky/cumbersome. It makes doing one-off calcs in a notebook feel harder than it needs to be. I'd prefer to see an option like:
import hamilton
driver = hamilton.Driver(modules=[my_dataflow])
result = driver.execute(final_vars=['foo'])
or even even a helper for a one-off:
result = hamilton.execute(modules=[my_dataflow], final_vars=['foo'])
Sure, I can add (have added) simple wrappers for myself and my users. But I propose (for discussion) that simplifying the common-case is one way to lower the barrier to entry and increase adoption.Gavin Kistner
10/31/2025, 8:40 PMThierry Jean
10/31/2025, 8:46 PMhamilton.driver.Driver.__init__. The Builder was introduced to make sure we could expand the driver object (as in add more features) without breaking things each release
Currently, it has
def __init__(
self,
config: Dict[str, Any],
*modules: ModuleType,
adapter: Optional[
Union[lifecycle_base.LifecycleAdapter, List[lifecycle_base.LifecycleAdapter]]
] = None,
allow_module_overrides: bool = False,
_materializers: typing.Sequence[Union[ExtractorFactory, MaterializerFactory]] = None,
_graph_executor: GraphExecutor = None,
_use_legacy_adapter: bool = True,
):
Which means it must have a config arg THEN a list of modules and modules=... is not possible (early API mistakes are costly 😅)
You need to call it
hamilton.driver.Driver({}, module_foo, module_bar)
## Potential solutions
### Change Driver.__init__()
Can make a big breaking change with a more solid Driver.__init__() API. I think it's worth doing and we can have good error message for migration / upgrading. This would require a major release Hamilton 2.0
### Add a new top-level entrypoint
We discussed having something like hamilton.execute(). It's a good idea and definitely useful in quick scripts, notebooks, etc.
Open questions:
• I don't know if .execute() is the best method name if its signature differ from Driver.execute()
• Should it have feature parity with Driver and Builder? (i.e., support adapters, materializers, config)
Challenges:
• hamilton/__init__.py imports no module because hamilton/registry.py needs to be imported first. This is to register plugins and more. To add execute() inside hamilton/__init__.py, we can't add module-level imports and we'll need to import things from within the functionGavin Kistner
10/31/2025, 11:00 PMGavin Kistner
10/31/2025, 11:05 PMhamilton.run() - nice and short
• hamilton.evaluate() - maybe too close to execute()
• hamilton.calculate() - kinda long; could be a little gross and just hamilton.calc(), with the abbreviation sort of implying the hack nature?
• hamilton.results_for() - if the signature focuses on the output name(s)
IMHO it should NOT have full feature parity; only the minimum needed to support common notebook-style investigations.Thierry Jean
11/01/2025, 5:09 PMhamilton.run() to automatically capture the current frame / scope to build the dataflow. For example, this would automatically build the DAG defined in the module
def foo() -> int: ...
def bar(foo: int) -> float: ...
if __name__ == "__main__:
import hamilton
results = hamilton.run(["bar"])