Repository: narwhals-dev/narwhals

Issue:
feat: add dtypes.Unknown
currently this fails:

```python
data = {'a': [1,2,3]}
df = pd.DataFrame({'a': pd.period_range('2000', periods=3, freq='M'), 'b': [1, 2, 3]})

print(nw.from_native(df).select(nw.selectors.numeric()))
```

we should let unknown dtypes just pass through

Discussion:

Patch:
diff --git a/docs/api-reference/dtypes.md b/docs/api-reference/dtypes.md
index c89b7f49..848e9e58 100644
--- a/docs/api-reference/dtypes.md
+++ b/docs/api-reference/dtypes.md
@@ -20,6 +20,7 @@
         - Datetime
         - Duration
         - Object
+        - Unknown
       show_root_heading: false
       show_source: false
       show_bases: false
diff --git a/narwhals/__init__.py b/narwhals/__init__.py
index 86cbfaf8..5aa5d72a 100644
--- a/narwhals/__init__.py
+++ b/narwhals/__init__.py
@@ -18,6 +18,7 @@ from narwhals.dtypes import UInt8
 from narwhals.dtypes import UInt16
 from narwhals.dtypes import UInt32
 from narwhals.dtypes import UInt64
+from narwhals.dtypes import Unknown
 from narwhals.expression import Expr
 from narwhals.expression import all
 from narwhals.expression import col
@@ -75,6 +76,7 @@ __all__ = [
     "Float32",
     "Boolean",
     "Object",
+    "Unknown",
     "Categorical",
     "String",
     "Datetime",
diff --git a/narwhals/_arrow/namespace.py b/narwhals/_arrow/namespace.py
index 31af558e..4f3256d6 100644
--- a/narwhals/_arrow/namespace.py
+++ b/narwhals/_arrow/namespace.py
@@ -28,6 +28,7 @@ class ArrowNamespace:
     Float32 = dtypes.Float32
     Boolean = dtypes.Boolean
     Object = dtypes.Object
+    Unknown = dtypes.Unknown
     Categorical = dtypes.Categorical
     String = dtypes.String
     Datetime = dtypes.Datetime
diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py
index c8e4f101..2d6d154f 100644
--- a/narwhals/_pandas_like/namespace.py
+++ b/narwhals/_pandas_like/namespace.py
@@ -33,6 +33,7 @@ class PandasNamespace:
     Float32 = dtypes.Float32
     Boolean = dtypes.Boolean
     Object = dtypes.Object
+    Unknown = dtypes.Unknown
     Categorical = dtypes.Categorical
     String = dtypes.String
     Datetime = dtypes.Datetime
diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py
index d679058b..8862d091 100644
--- a/narwhals/_pandas_like/utils.py
+++ b/narwhals/_pandas_like/utils.py
@@ -447,8 +447,7 @@ def translate_dtype(column: Any) -> DType:
             # which is inferred by default.
             return dtypes.String()
         return dtypes.Object()
-    msg = f"Unknown dtype: {dtype}"  # pragma: no cover
-    raise AssertionError(msg)
+    return dtypes.Unknown()
 
 
 def get_dtype_backend(dtype: Any, implementation: str) -> str:
diff --git a/narwhals/dtypes.py b/narwhals/dtypes.py
index aa98f2c5..36e10f24 100644
--- a/narwhals/dtypes.py
+++ b/narwhals/dtypes.py
@@ -67,6 +67,9 @@ class Boolean(DType): ...
 class Object(DType): ...
 
 
+class Unknown(DType): ...
+
+
 class Datetime(TemporalType): ...
 
 
@@ -155,6 +158,8 @@ def to_narwhals_dtype(dtype: Any, *, is_polars: bool) -> DType:
         return Boolean()
     if dtype == pl.Object:
         return Object()
+    if dtype == pl.Unknown:  # pragma: no cover
+        return Unknown()
     if dtype == pl.Categorical:
         return Categorical()
     if dtype == pl.Datetime:


Test patch:
diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py
index 288db181..932bc269 100644
--- a/tests/frame/schema_test.py
+++ b/tests/frame/schema_test.py
@@ -121,3 +121,8 @@ def test_dtypes() -> None:
     result_pa = df.schema
     assert result_pa == expected
     assert {name: df[name].dtype for name in df.columns} == expected
+
+
+def test_unknown_dtype() -> None:
+    df = pd.DataFrame({"a": pd.period_range("2000", periods=3, freq="M")})
+    assert nw.from_native(df).schema == {"a": nw.Unknown}
diff --git a/tests/series/cast_test.py b/tests/series/cast_test.py
index cb2d176b..0192cefe 100644
--- a/tests/series/cast_test.py
+++ b/tests/series/cast_test.py
@@ -84,3 +84,8 @@ def test_cast_date_datetime_invalid() -> None:
     df = nw.from_native(dfpd)
     with pytest.raises(NotImplementedError, match="pyarrow"):
         df.select(nw.col("a").cast(nw.Date))
+
+
+def test_unknown_to_int() -> None:
+    df = pd.DataFrame({"a": pd.period_range("2000", periods=3, freq="M")})
+    assert nw.from_native(df).select(nw.col("a").cast(nw.Int64)).schema == {"a": nw.Int64}