Skip to content

Commit c61fe33

Browse files
committed
fix: address import overhead review feedback
1 parent 9f0f9f6 commit c61fe33

2 files changed

Lines changed: 40 additions & 31 deletions

File tree

hamilton/base.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
import abc
2626
import collections
27+
import importlib
2728
import logging
29+
from functools import cache
2830
from typing import TYPE_CHECKING, Any
2931

3032
if TYPE_CHECKING:
@@ -46,22 +48,11 @@
4648
logger = logging.getLogger(__name__)
4749

4850

49-
def _get_pandas():
50-
import pandas as pd
51-
52-
return pd
53-
54-
55-
def _get_pandas_extension():
56-
from pandas.core.indexes import extension as pd_extension
57-
58-
return pd_extension
59-
60-
61-
def _get_numpy():
62-
import numpy as np
63-
64-
return np
51+
@cache
52+
def _lazy_import(module: str, attr: str | None = None) -> Any:
53+
"""Lazy import a module. Cached after 1st call."""
54+
mod = importlib.import_module(module)
55+
return mod if attr is None else getattr(mod, attr)
6556

6657

6758
class ResultMixin(lifecycle_api.LegacyResultMixin):
@@ -148,8 +139,8 @@ def pandas_index_types(
148139
all_index_types = collections.defaultdict(list)
149140
time_indexes = collections.defaultdict(list)
150141
no_indexes = collections.defaultdict(list)
151-
pd = _get_pandas()
152-
pd_extension = _get_pandas_extension()
142+
pd = _lazy_import("pandas")
143+
pd_extension = _lazy_import("pandas.core.indexes.extension")
153144

154145
def index_key_name(pd_object: pd.DataFrame | pd.Series) -> str:
155146
"""Creates a string helping identify the index and it's type.
@@ -248,7 +239,7 @@ def build_result(**outputs: dict[str, Any]) -> pd.DataFrame:
248239
:param outputs: the outputs to build a dataframe from.
249240
"""
250241
# TODO check inputs are pd.Series, arrays, or scalars -- else error
251-
pd = _get_pandas()
242+
pd = _lazy_import("pandas")
252243
output_index_type_tuple = PandasDataFrameResult.pandas_index_types(outputs)
253244
# this next line just log warnings
254245
# we don't actually care about the result since this is the current default behavior.
@@ -283,7 +274,7 @@ def build_dataframe_with_dataframes(outputs: dict[str, Any]) -> pd.DataFrame:
283274
:param outputs: The outputs to build the dataframe from.
284275
:return: A dataframe with the outputs.
285276
"""
286-
pd = _get_pandas()
277+
pd = _lazy_import("pandas")
287278

288279
def get_output_name(output_name: str, column_name: str) -> str:
289280
"""Add function prefix to columns.
@@ -329,7 +320,7 @@ def input_types(self) -> list[type[type]]:
329320
return [Any]
330321

331322
def output_type(self) -> type:
332-
return _get_pandas().DataFrame
323+
return _lazy_import("pandas", "DataFrame")
333324

334325

335326
class StrictIndexTypePandasDataFrameResult(PandasDataFrameResult):
@@ -395,7 +386,7 @@ def build_result(**outputs: dict[str, Any]) -> np.matrix:
395386
:return: numpy matrix
396387
"""
397388
# TODO check inputs are all numpy arrays/array like things -- else error
398-
np = _get_numpy()
389+
np = _lazy_import("numpy")
399390
num_rows = -1
400391
columns_with_lengths = collections.OrderedDict()
401392
for col, val in outputs.items(): # assumption is fixed order
@@ -432,7 +423,7 @@ def input_types(self) -> list[type[type]]:
432423
return [Any] # Typing
433424

434425
def output_type(self) -> type:
435-
return _get_pandas().DataFrame
426+
return _lazy_import("pandas", "DataFrame")
436427

437428

438429
class HamiltonGraphAdapter(lifecycle_api.GraphAdapter, abc.ABC):

tests/test_imports.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,31 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
import subprocess
19+
import sys
20+
import textwrap
1821

19-
def test_driver_base_public_imports():
20-
from hamilton import base, driver
21-
from hamilton.driver import Driver
2222

23-
assert driver.Driver is Driver
24-
assert base.DictResult().build_result(foo=1) == {"foo": 1}
25-
assert base.DefaultAdapter().build_result(foo=1) == {"foo": 1}
26-
assert base.PandasDataFrameResult is not None
27-
assert base.NumpyMatrixResult is not None
23+
def test_driver_base_public_imports_do_not_load_heavy_dependencies():
24+
code = textwrap.dedent(
25+
"""
26+
import sys
27+
28+
heavy_dependencies = {"pandas", "numpy"}
29+
assert heavy_dependencies.isdisjoint(sys.modules)
30+
31+
from hamilton import base, driver
32+
from hamilton.driver import Driver
33+
34+
assert driver.Driver is Driver
35+
assert base.DictResult().build_result(foo=1) == {"foo": 1}
36+
assert base.DefaultAdapter().build_result(foo=1) == {"foo": 1}
37+
assert base.PandasDataFrameResult is not None
38+
assert base.NumpyMatrixResult is not None
39+
40+
loaded_heavy_dependencies = heavy_dependencies.intersection(sys.modules)
41+
assert not loaded_heavy_dependencies, sorted(loaded_heavy_dependencies)
42+
"""
43+
)
44+
45+
subprocess.run([sys.executable, "-c", code], check=True)

0 commit comments

Comments
 (0)