Skip to content

Commit 9f07071

Browse files
committed
feat: Allow empty mapping
Aligns with (#2874)
1 parent b204f45 commit 9f07071

File tree

2 files changed

+31
-21
lines changed

2 files changed

+31
-21
lines changed

narwhals/schema.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -109,53 +109,54 @@ def from_arrow(cls, schema: IntoArrowSchema, /) -> Self:
109109
"""Construct a Schema from a pyarrow Schema.
110110
111111
Arguments:
112-
schema: A pyarrow Schema or non-empty mapping of column names to
113-
pyarrow data types.
112+
schema: A pyarrow Schema or mapping of column names to pyarrow data types.
114113
115114
Returns:
116115
A Narwhals Schema.
117116
"""
118-
import pyarrow as pa # ignore-banned-import
117+
if isinstance(schema, Mapping):
118+
if not schema:
119+
return cls()
120+
import pyarrow as pa # ignore-banned-import
119121

122+
schema = pa.schema(schema)
120123
from narwhals._arrow.utils import native_to_narwhals_dtype
121124

122-
if isinstance(schema, Mapping):
123-
schema = pa.schema(schema)
124125
return cls(
125126
(field.name, native_to_narwhals_dtype(field.type, cls._version))
126127
for field in schema
127128
)
128129

129130
@classmethod
130-
def from_cudf(cls, schema: IntoPandasSchema, /) -> Self: # pragma: no cover
131+
def from_cudf(cls, schema: IntoPandasSchema, /) -> Self:
131132
"""Construct a Schema from a cudf schema representation.
132133
133134
Arguments:
134-
schema: A non-empty mapping of column names to cudf data types.
135+
schema: A mapping of column names to cudf data types.
135136
136137
Returns:
137138
A Narwhals Schema.
138139
"""
139-
return cls._from_pandas_like(schema, Implementation.CUDF)
140+
return cls._from_pandas_like(schema, Implementation.CUDF) if schema else cls()
140141

141142
@classmethod
142143
def from_pandas(cls, schema: IntoPandasSchema, /) -> Self:
143144
"""Construct a Schema from a pandas schema representation.
144145
145146
Arguments:
146-
schema: A non-empty mapping of column names to pandas data types.
147+
schema: A mapping of column names to pandas data types.
147148
148149
Returns:
149150
A Narwhals Schema.
150151
"""
151-
return cls._from_pandas_like(schema, Implementation.PANDAS)
152+
return cls._from_pandas_like(schema, Implementation.PANDAS) if schema else cls()
152153

153154
@classmethod
154155
def from_pandas_like(cls, schema: IntoPandasSchema, /) -> Self:
155156
"""Construct a Schema from a pandas-like schema representation.
156157
157158
Arguments:
158-
schema: A non-empty mapping of column names to pandas-like data types.
159+
schema: A mapping of column names to pandas-like data types.
159160
160161
Returns:
161162
A Narwhals Schema.
@@ -178,7 +179,7 @@ def from_native(
178179
"""Construct a Schema from a native schema representation.
179180
180181
Arguments:
181-
schema: A native schema object, or non-empty mapping of column names to
182+
schema: A native schema object, or mapping of column names to
182183
*instantiated* native data types.
183184
184185
Returns:
@@ -188,9 +189,8 @@ def from_native(
188189
return cls.from_arrow(schema)
189190
if is_polars_schema(schema):
190191
return cls.from_polars(schema)
191-
# avoid the empty case as well, since we have no dtypes to sample
192-
if isinstance(schema, Mapping) and schema:
193-
return cls._from_native_mapping(schema)
192+
if isinstance(schema, Mapping):
193+
return cls._from_native_mapping(schema) if schema else cls()
194194
msg = (
195195
f"Expected an arrow, polars, or pandas schema, but got "
196196
f"{qualified_type_name(schema)!r}\n\n{schema!r}"
@@ -202,12 +202,14 @@ def from_polars(cls, schema: IntoPolarsSchema, /) -> Self:
202202
"""Construct a Schema from a polars Schema.
203203
204204
Arguments:
205-
schema: A polars Schema or non-empty mapping of column names to
206-
*instantiated* polars data types.
205+
schema: A polars Schema or mapping of column names to *instantiated*
206+
polars data types.
207207
208208
Returns:
209209
A Narwhals Schema.
210210
"""
211+
if not schema:
212+
return cls()
211213
from narwhals._polars.utils import native_to_narwhals_dtype
212214

213215
return cls(

tests/frame/schema_test.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,10 +623,6 @@ def test_schema_from_pandas_like_pyarrow(
623623
def test_schema_from_invalid() -> None:
624624
flags = re.DOTALL | re.IGNORECASE
625625

626-
with pytest.raises(
627-
TypeError, match=re.compile(r"expected.+schema.+got.+dict.+\{\}", flags)
628-
):
629-
nw.Schema.from_native({})
630626
with pytest.raises(
631627
TypeError, match=re.compile(r"expected.+schema.+got.+list.+a.+string", flags)
632628
):
@@ -642,6 +638,18 @@ def test_schema_from_invalid() -> None:
642638
nw.Schema.from_native(nw.Schema({"a": nw.Int64()})) # type: ignore[arg-type]
643639

644640

641+
def test_schema_from_empty_mapping() -> None:
642+
# NOTE: Should never require importing a native package
643+
expected = nw.Schema()
644+
assert nw.Schema.from_native({}) == expected
645+
assert nw.Schema.from_arrow({}) == expected
646+
assert nw.Schema.from_cudf({}) == expected
647+
assert nw.Schema.from_modin({}) == expected
648+
assert nw.Schema.from_pandas({}) == expected
649+
assert nw.Schema.from_pandas_like({}) == expected
650+
assert nw.Schema.from_polars({}) == expected
651+
652+
645653
@pytest.mark.skipif(
646654
POLARS_VERSION < (1, 6, 0), reason="https://github.com/pola-rs/polars/pull/18308"
647655
)

0 commit comments

Comments
 (0)