Skip to content

enh: Start narwhals.stable.v2 and enforce deprecations#2814

Merged
MarcoGorelli merged 55 commits intonarwhals-dev:mainfrom
MarcoGorelli:start-v2
Jul 28, 2025
Merged

enh: Start narwhals.stable.v2 and enforce deprecations#2814
MarcoGorelli merged 55 commits intonarwhals-dev:mainfrom
MarcoGorelli:start-v2

Conversation

@MarcoGorelli
Copy link
Member

@MarcoGorelli MarcoGorelli commented Jul 10, 2025

From the downstream tests, the only breakage is in:

  • hierarchical forecast is using native_namespace in from_dict

That's...all, from what I can tell.

I've made a PR to them to update this: Nixtla/hierarchicalforecast#381

I don't want to be disruptive towards them, so perhaps we could take a bit of a middle-ground approach:

  • for deprecated functions/params which don't show up in any downstream test, just enforce the deprecation
  • for native_namespace in from_dict, keep it around longer in the main Narwhals namespace at least until there's been a new hierarchicalforecast

todo:

  • for from_native_test, separate out some tests to just v1, and see what we need to test for v2 as well

Aiming to merge this (and make a Narwhals 2.0 release) by August

@MarcoGorelli MarcoGorelli added breaking road to v2 What takes us closer to `stable.v2` (high prio) labels Jul 10, 2025
@MarcoGorelli MarcoGorelli changed the title enh: Start v2 enh: Start narwhals.stable.v2 and enforce deprecations Jul 18, 2025
@MarcoGorelli MarcoGorelli marked this pull request as ready for review July 18, 2025 15:02
Comment on lines 375 to +376
return self._with_orderable_filtration(
lambda plx: self._to_compliant_expr(plx).gather_every(n=n, offset=offset)
lambda plx: self._to_compliant_expr(plx).gather_every(n=n, offset=offset) # type: ignore[attr-defined]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @MarcoGorelli 👋

IIUC, would the motivation for removing deprecated members from Compliant* be that extenders of narwhals aren't required to implement them?

If so, this is a tricky problem to solve 🤔

Note

TL;DR: I'd recommend keeping deprecations in the protocols

Typing

The main downside for us is that the loss of typing information makes it more likely that we unknowingly regress on the v1 promise in the future.
Despite the deprecation, we can clearly see which things are deprecated atm, and have some IDE help with:

Finding all references

image

Finding all implementations

This one probably should've had a @deprecated 🤦‍♂️

image

New [attr-defined]s make me think that this link between layers is now (statically) murky?

If the goal were just to keep a type checker happy, then the two options are:

  1. Add Compliant*V1 protocols, use them in *V1 narwhals-level and implement both in the backends that support V1
  2. Keep deprecations in Compliant* protocols, punt the issue for now

Runtime

This is the trickier one for me ...

Would a new internal backend be expected to support v1?

For example, in the daft PR, we can see the typing distinction between things that aren't yet and won't ever be implemented.

https://github.com/MarcoGorelli/narwhals/blob/f918282f14feb43e0a9687d82e16eed79b3832f7/narwhals/_daft/dataframe.py#L333-L340

    gather_every = not_implemented.deprecated(
        "`LazyFrame.gather_every` is deprecated and will be removed in a future version."
    )
    join_asof = not_implemented()
    tail = not_implemented.deprecated(
        "`LazyFrame.tail` is deprecated and will be removed in a future version."
    )
    explode = not_implemented()

At runtime we give the same message to users (I don't know why I made that decision (#2232 (comment)) 😅)

A new backend won't get a type checker yelling to add a not_implemented, which at runtime will mean we just get an AttributeError 😢

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've got a file which specifically tests all the functionality from v1 (tests/v1_test.py), if we're removing the functionality then I think it looks a bit odd to keep it but marked as deprecated

Would a new internal backend be expected to support v1?

no, we only need to care about preserving functionality for code that's already been written

Comment on lines +92 to +96
class DataFrame(NwDataFrame[IntoDataFrameT]):
@inherit_doc(NwDataFrame)
def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None:
assert df._version is Version.V2 # noqa: S101
super().__init__(df, level=level)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move level to v1-only can't we?

Or at least "interchange" I thought was on the chopping block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes true, i just think it'd require a larger refactor

it's not user-facing though, so not necessarily pressing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that seems fair

How about in this PR, try removing v2.typing.DataFrameLike and adding some tests using a mock w/ __dataframe__?

I think making sure the typing on v2 rejects interchange only should be doable 🙂

We can punt the rest to another issue

@MarcoGorelli
Copy link
Member Author

Just tried v2 out in Bokeh, and it all passes as-is

In Altair it requires some code changes because eager_or_interchange_only was used there, but with those in it seems fine

@MarcoGorelli MarcoGorelli merged commit 6cd8e3c into narwhals-dev:main Jul 28, 2025
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking road to v2 What takes us closer to `stable.v2` (high prio)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants