diff --git a/narwhals/functions.py b/narwhals/functions.py index e3d457adf..45f273df8 100644 --- a/narwhals/functions.py +++ b/narwhals/functions.py @@ -52,7 +52,7 @@ def from_dict( data: dict[str, Any], schema: dict[str, DType] | Schema | None = None, *, - native_namespace: ModuleType, + native_namespace: ModuleType | None = None, ) -> DataFrame[Any]: """ Instantiate DataFrame from dictionary. @@ -64,7 +64,8 @@ def from_dict( Arguments: data: Dictionary to create DataFrame from. schema: The DataFrame schema as Schema or dict of {name: type}. - native_namespace: The native library to use for DataFrame creation. + native_namespace: The native library to use for DataFrame creation. Only + necessary if inputs are not Narwhals Series. Examples: >>> import pandas as pd @@ -97,6 +98,21 @@ def from_dict( │ 2 ┆ 4 │ └─────┴─────┘ """ + from narwhals.series import Series + from narwhals.translate import to_native + + if not data: + msg = "from_dict cannot be called with empty dictionary" + raise ValueError(msg) + if native_namespace is None: + for val in data.values(): + if isinstance(val, Series): + native_namespace = val.__native_namespace__() + break + else: + msg = "Calling `from_dict` without `native_namespace` is only supported if all input values are already Narwhals Series" + raise TypeError(msg) + data = {key: to_native(value, strict=False) for key, value in data.items()} implementation = Implementation.from_native_namespace(native_namespace) if implementation is Implementation.POLARS: diff --git a/narwhals/stable/v1.py b/narwhals/stable/v1.py index 5c067bf45..1c1c91711 100644 --- a/narwhals/stable/v1.py +++ b/narwhals/stable/v1.py @@ -1473,7 +1473,7 @@ def from_dict( data: dict[str, Any], schema: dict[str, DType] | Schema | None = None, *, - native_namespace: ModuleType, + native_namespace: ModuleType | None = None, ) -> DataFrame[Any]: """ Instantiate DataFrame from dictionary. @@ -1485,7 +1485,8 @@ def from_dict( Arguments: data: Dictionary to create DataFrame from. schema: The DataFrame schema as Schema or dict of {name: type}. - native_namespace: The native library to use for DataFrame creation. + native_namespace: The native library to use for DataFrame creation. Only + necessary if inputs are not Narwhals Series. Examples: >>> import pandas as pd diff --git a/tests/from_dict_test.py b/tests/from_dict_test.py index ad6248f7c..cfaf99a7b 100644 --- a/tests/from_dict_test.py +++ b/tests/from_dict_test.py @@ -29,3 +29,27 @@ def test_from_dict_schema(constructor: Any, request: Any) -> None: schema=schema, # type: ignore[arg-type] ) assert result.collect_schema() == schema + + +def test_from_dict_without_namespace(constructor: Any) -> None: + df = nw.from_native(constructor({"a": [1, 2, 3], "b": [4, 5, 6]})).lazy().collect() + result = nw.from_dict({"c": df["a"], "d": df["b"]}) + compare_dicts(result, {"c": [1, 2, 3], "d": [4, 5, 6]}) + + +def test_from_dict_without_namespace_invalid(constructor: Any) -> None: + df = nw.from_native(constructor({"a": [1, 2, 3], "b": [4, 5, 6]})).lazy().collect() + with pytest.raises(TypeError, match="namespace"): + nw.from_dict({"c": nw.to_native(df["a"]), "d": nw.to_native(df["b"])}) + + +def test_from_dict_one_native_one_narwhals(constructor: Any) -> None: + df = nw.from_native(constructor({"a": [1, 2, 3], "b": [4, 5, 6]})).lazy().collect() + result = nw.from_dict({"c": nw.to_native(df["a"]), "d": df["b"]}) + expected = {"c": [1, 2, 3], "d": [4, 5, 6]} + compare_dicts(result, expected) + + +def test_from_dict_empty() -> None: + with pytest.raises(ValueError, match="empty"): + nw.from_dict({})