Repository: narwhals-dev/narwhals
Pull request #1150 (2024-10-07 18:10:16)

docs: apply further changes to api reference
<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [x] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue https://github.com/narwhals-dev/narwhals/pull/1144#pullrequestreview-2351890173

## Checklist

- [x] Code follows style guide (ruff)
- [ ] Tests added 
- [x] Documented the changes

## If you have comments or can explain your changes, please do so below.

Iterates over the referenced PR. I pushed the suggested addition after the merge 😇.


Patch:
diff --git a/docs/api-reference/expr_str.md b/docs/api-reference/expr_str.md
index 15d66ac9..01966a87 100644
--- a/docs/api-reference/expr_str.md
+++ b/docs/api-reference/expr_str.md
@@ -8,9 +8,9 @@
         - ends_with
         - head
         - len_chars
-        - slice
         - replace
         - replace_all
+        - slice
         - starts_with
         - strip_chars
         - tail
diff --git a/docs/api-reference/series_str.md b/docs/api-reference/series_str.md
index 9ae01890..d4ca3602 100644
--- a/docs/api-reference/series_str.md
+++ b/docs/api-reference/series_str.md
@@ -13,9 +13,9 @@
         - slice
         - starts_with
         - strip_chars
+        - tail
         - to_datetime
         - to_lowercase
         - to_uppercase
-        - tail
       show_source: false
       show_bases: false
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 6d1001c1..c70425ef 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -540,7 +540,7 @@ class ArrowExprStringNamespace:
             self._expr, "str", "slice", offset, length
         )
 
-    def to_datetime(self, format: str | None = None) -> ArrowExpr:  # noqa: A002
+    def to_datetime(self: Self, format: str | None) -> ArrowExpr:  # noqa: A002
         return reuse_series_namespace_implementation(
             self._expr,
             "str",
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 183cf37b..50702349 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -1033,9 +1033,13 @@ class ArrowSeriesStringNamespace:
             ),
         )
 
-    def to_datetime(self: Self, format: str | None = None) -> ArrowSeries:  # noqa: A002
+    def to_datetime(self: Self, format: str | None) -> ArrowSeries:  # noqa: A002
         import pyarrow.compute as pc  # ignore-banned-import()
 
+        if format is None:
+            msg = "`format` is required for pyarrow backend."
+            raise ValueError(msg)
+
         return self._arrow_series._from_native_series(
             pc.strptime(self._arrow_series._native_series, format=format, unit="us")
         )
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index d8d86692..10b95bc8 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -811,7 +811,7 @@ class DaskExprStringNamespace:
             returns_scalar=False,
         )
 
-    def to_datetime(self, format: str | None = None) -> DaskExpr:  # noqa: A002
+    def to_datetime(self: Self, format: str | None) -> DaskExpr:  # noqa: A002
         import dask.dataframe as dd  # ignore-banned-import()
 
         return self._expr._from_call(
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 4e301144..2ebadbe1 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -486,7 +486,7 @@ class PandasLikeExprStringNamespace:
             self._expr, "str", "slice", offset, length
         )
 
-    def to_datetime(self, format: str | None = None) -> PandasLikeExpr:  # noqa: A002
+    def to_datetime(self: Self, format: str | None) -> PandasLikeExpr:  # noqa: A002
         return reuse_series_namespace_implementation(
             self._expr,
             "str",
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 74e1c492..9cca6640 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -757,7 +757,7 @@ class PandasLikeSeriesStringNamespace:
             self._pandas_series._native_series.str.slice(start=offset, stop=stop),
         )
 
-    def to_datetime(self, format: str | None = None) -> PandasLikeSeries:  # noqa: A002
+    def to_datetime(self: Self, format: str | None) -> PandasLikeSeries:  # noqa: A002
         return self._pandas_series._from_native_series(
             to_datetime(self._pandas_series._implementation)(
                 self._pandas_series._native_series, format=format
diff --git a/narwhals/expr.py b/narwhals/expr.py
index 59e1ff76..8446d81c 100644
--- a/narwhals/expr.py
+++ b/narwhals/expr.py
@@ -2498,7 +2498,7 @@ class ExprStringNamespace:
         """
         return self._expr.__class__(lambda plx: self._expr._call(plx).str.slice(-n))
 
-    def to_datetime(self, format: str) -> Expr:  # noqa: A002
+    def to_datetime(self: Self, format: str | None = None) -> Expr:  # noqa: A002
         """
         Convert to Datetime dtype.
 
@@ -2508,10 +2508,13 @@ class ExprStringNamespace:
             in pandas, with no ability to set any other one. The ability to
             set the time unit in pandas, if the version permits, will arrive.
 
+        Warning:
+            As different backends auto-infer format in different ways, if `format=None`
+            there is no guarantee that the result will be equal.
+
         Arguments:
-            format: Format to parse strings with. Must be passed, as different
-                    dataframe libraries have different ways of auto-inferring
-                    formats.
+            format: Format to use for conversion. If set to None (default), the format is
+                inferred from the data.
 
         Examples:
             >>> import pandas as pd
diff --git a/narwhals/series.py b/narwhals/series.py
index bb970906..1753598c 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -3083,7 +3083,7 @@ class SeriesStringNamespace:
             self._narwhals_series._compliant_series.str.to_lowercase()
         )
 
-    def to_datetime(self, format: str) -> Series:  # noqa: A002
+    def to_datetime(self: Self, format: str | None = None) -> Series:  # noqa: A002
         """
         Parse Series with strings to a Series with Datetime dtype.
 
@@ -3093,10 +3093,13 @@ class SeriesStringNamespace:
             in pandas, with no ability to set any other one. The ability to
             set the time unit in pandas, if the version permits, will arrive.
 
+        Warning:
+            As different backends auto-infer format in different ways, if `format=None`
+            there is no guarantee that the result will be equal.
+
         Arguments:
-            format: Format to parse strings with. Must be passed, as different
-                    dataframe libraries have different ways of auto-inferring
-                    formats.
+            format: Format to use for conversion. If set to None (default), the format is
+                inferred from the data.
 
         Examples:
             >>> import pandas as pd
diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py
index 7bc59042..69c31043 100644
--- a/utils/check_api_reference.py
+++ b/utils/check_api_reference.py
@@ -216,14 +216,36 @@ series = [
     for i in nw.from_native(pl.Series(), series_only=True).__dir__()
     if not i[0].isupper() and i[0] != "_"
 ]
-
 if missing := set(expr).difference(series).difference(EXPR_ONLY_METHODS):
-    print("In expr but not in series")  # noqa: T201
+    print("In Expr but not in Series")  # noqa: T201
     print(missing)  # noqa: T201
     ret = 1
 if extra := set(series).difference(expr).difference(SERIES_ONLY_METHODS):
-    print("in series but not in expr")  # noqa: T201
+    print("In Series but not in Expr")  # noqa: T201
     print(extra)  # noqa: T201
     ret = 1
 
+# Check Expr vs Series internal methods
+for namespace in NAMESPACES.difference({"name"}):
+    expr_internal = [
+        i
+        for i in getattr(nw.Expr(lambda: 0), namespace).__dir__()
+        if not i[0].isupper() and i[0] != "_"
+    ]
+    series_internal = [
+        i
+        for i in getattr(
+            nw.from_native(pl.Series(), series_only=True), namespace
+        ).__dir__()
+        if not i[0].isupper() and i[0] != "_"
+    ]
+    if missing := set(expr_internal).difference(series_internal):
+        print(f"In Expr.{namespace} but not in Series.{namespace}")  # noqa: T201
+        print(missing)  # noqa: T201
+        ret = 1
+    if extra := set(series_internal).difference(expr_internal):
+        print(f"In Series.{namespace} but not in Expr.{namespace}")  # noqa: T201
+        print(extra)  # noqa: T201
+        ret = 1
+
 sys.exit(ret)


Test patch:
diff --git a/tests/expr_and_series/str/to_datetime_test.py b/tests/expr_and_series/str/to_datetime_test.py
index a64a3c58..8474357e 100644
--- a/tests/expr_and_series/str/to_datetime_test.py
+++ b/tests/expr_and_series/str/to_datetime_test.py
@@ -1,5 +1,7 @@
 from typing import Any
 
+import pytest
+
 import narwhals.stable.v1 as nw
 from tests.utils import Constructor
 
@@ -34,3 +36,41 @@ def test_to_datetime_series(constructor_eager: Any) -> None:
         )
     ).item(0)
     assert str(result) == expected
+
+
+def test_to_datetime_infer_fmt(
+    request: pytest.FixtureRequest, constructor: Constructor
+) -> None:
+    if "pyarrow_table" in str(constructor):
+        request.applymarker(pytest.mark.xfail)
+
+    if "cudf" in str(constructor):  # pragma: no cover
+        expected = "2020-01-01T12:34:56.000000000"
+    else:
+        expected = "2020-01-01 12:34:56"
+
+    result = (
+        nw.from_native(constructor(data))
+        .lazy()
+        .select(b=nw.col("a").str.to_datetime())
+        .collect()
+        .item(row=0, column="b")
+    )
+    assert str(result) == expected
+
+
+def test_to_datetime_series_infer_fmt(
+    request: pytest.FixtureRequest, constructor_eager: Any
+) -> None:
+    if "pyarrow_table" in str(constructor_eager):
+        request.applymarker(pytest.mark.xfail)
+
+    if "cudf" in str(constructor_eager):  # pragma: no cover
+        expected = "2020-01-01T12:34:56.000000000"
+    else:
+        expected = "2020-01-01 12:34:56"
+
+    result = (
+        nw.from_native(constructor_eager(data), eager_only=True)["a"].str.to_datetime()
+    ).item(0)
+    assert str(result) == expected