Skip to content

Commit

Permalink
wip (#410)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoGorelli authored Jul 7, 2024
1 parent cf95776 commit 23ec907
Show file tree
Hide file tree
Showing 81 changed files with 1,695 additions and 75 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ Seamlessly support all, without depending on any!

-**Just use** a subset of **the Polars API**, no need to learn anything new
-**Zero dependencies**, Narwhals only uses what
the user passes in, so you can keep your library lightweight
the user passes in so your library can stay lightweight
- ✅ Separate **lazy** and eager APIs, use **expressions**
- ✅ Support pandas' complicated type system and index, without
either getting in the way
-**100% branch coverage**, tested against pandas and Polars nightly builds
-**Negligible overhead**, see [overhead](https://narwhals-dev.github.io/narwhals/overhead/)
- ✅ Let your IDE help you thanks to **full static typing**, see [typing](https://narwhals-dev.github.io/narwhals/typing/)
-**Perfect backwards compatibility policy**,
see [stable api](https://narwhals-dev.github.io/narwhals/backcompat/) for how to opt-in

## Used by / integrates with

Expand Down
92 changes: 92 additions & 0 deletions docs/backcompat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Perfect backwards compatibility policy

Narwhals is primarily aimed at library maintainers rather than end users. As such,
we need to take stability and backwards compatibility extra-seriously. Our policy is:

- If you write code using `import narwhals.stable.v1 as nw`, then we promise to
never change or remove any public function you're using.
- If we need to make a backwards-incompatible change, it will be pushed into
`narwhals.stable.v2`, leaving `narwhals.stable.v1` unaffected.
- We will maintain `narwhals.stable.v1` indefinitely, even as `narwhals.stable.v2` and other
stable APIs come out. For example, Narwhals version 1.0.0 will offer
`narwhals.stable.v1`, whereas Narwhals 2.0.0 will offer both `narwhals.stable.v1` and
`narwhals.stable.v2`.

Like this, we enable different packages to be on different Narwhals stable APIs, and for
end-users to use all of them in the same project without conflicts nor
incompatibilities.

## Background

Ever upgraded a package, only to find that it breaks all your tests because of an intentional
API change? Did you end up having to litter your code with statements such as the following?

```python
if parse_version(pdx.__version__) < parse_version('1.3.0'):
df = df.brewbeer()
elif parse_version('1.3.0') <= parse_version(pdx.__version__) < parse_version('1.5.0'):
df = df.brew_beer()
else:
df = df.brew_drink('beer')
```

Now imagine multiplying that complexity over all the dataframe libraries you want to support...

Narwhals offers a simple solution, inspired by Rust editions.

## Narwhals' Stable API

Narwhals implements a subset of the Polars API. What will Narwhals do if/when Polars makes
a backwards-incompatible change? Would you need to update your Narwhals code?

To understand the solution, let's go through an example. Suppose that, hypothetically, in Polars 2.0,
`polars.Expr.cum_sum` was renamed to `polars.Expr.cumulative_sum`. In Narwhals, we
have `narwhals.Expr.cum_sum`. Does this mean that Narwhals will also rename its method,
and deprecate the old one? The answer is...no!

Narwhals offers a `stable` namespace, which allows you to write your code once and forget about
it. That is to say, if you write your code like this:

```python
import narwhals.stable.v1 as nw
from narwhals.typing import FrameT

@nw.narwhalify
def func(df: FrameT) -> FrameT:
return df.with_columns(nw.col('a').cum_sum())
```

then we, in Narwhals, promise that your code will keep working, even in newer versions of Polars
after they have renamed their method.

Concretely, we would do the following:

- `narwhals.stable.v1`: you can keep using `Expr.cum_sum`
- `narwhals.stable.v2`: you can only use `Expr.cumulative_sum`, `Expr.cum_sum` will have been removed
- `narwhals`: you can only use `Expr.cumulative_sum`, `Expr.cum_sum` will have been removed

So, although Narwhals' main API (and `narwhals.stable.v2`) will have introduced a breaking change,
users of `narwhals.stable.v1` will have their code unaffected.

## `import narwhals as nw` or `import narwhals.stable.v1 as nw`?

Which should you use? In general we recommend:

- When prototyping, use `import narwhals as nw`, so you can iterate quickly.
- Once you're happy with what you've got and what to release something production-ready and stable,
when switch out your `import narwhals as nw` usage for `import narwhals.stable.v1 as nw`.

## Exceptions

Are we really promising perfect backwards compatibility in all cases, without exceptions? Not quite.
There are some exceptions, which we'll now list. But we'll never intentionally break your code.
Anything currently in `narwhals.stable.v1` will not be changed or removed in future Narwhals versions.

Here are exceptions to our backwards compatibility policy:

- unambiguous bugs. If a function contains what is unambiguously a bug, then we'll fix it, without
considering that to be a breaking change.
- radical changes in backends. Suppose that Polars was to remove
expressions, or pandas were to remove support for categorical data. At that point, we might
need to rethink Narwhals. However, we expect such radical changes to be exceedingly unlikely.
- we may consider making some type hints more precise.
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ Seamlessly support both, without depending on either!

-**Just use** a subset of **the Polars API**, no need to learn anything new
-**Zero dependencies**, Narwhals only uses what
the user passes in, so you can keep your library lightweight
the user passes in so your library can stay lightweight
- ✅ Separate **lazy** and eager APIs, use **expressions**
- ✅ Support pandas' complicated type system and index, without
either getting in the way
-**100% branch coverage**, tested against pandas and Polars nightly builds
-**Negligible overhead**, see [overhead](https://narwhals-dev.github.io/narwhals/overhead/)
- ✅ Let your IDE help you thanks to **full static typing**, see [typing](https://narwhals-dev.github.io/narwhals/typing/)
-**Perfect backwards compatibility policy**,
see [stable api](https://narwhals-dev.github.io/narwhals/backcompat/) for how to opt-in

## Who's this for?

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ nav:
- Other concepts:
- other/pandas_index.md
- overhead.md
- backcompat.md
- extending.md
- how_it_works.md
- Roadmap: roadmap.md
Expand Down
2 changes: 2 additions & 0 deletions narwhals/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from narwhals import selectors
from narwhals import stable
from narwhals.dataframe import DataFrame
from narwhals.dataframe import LazyFrame
from narwhals.dtypes import Boolean
Expand Down Expand Up @@ -88,4 +89,5 @@
"Date",
"narwhalify",
"show_versions",
"stable",
]
5 changes: 5 additions & 0 deletions narwhals/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def __init__(self, call: Callable[[Any], Any]) -> None:
# callable from namespace to expr
self._call = call

def _taxicab_norm(self) -> Self:
# This is just used to test out the stable api feature in a realistic-ish way.
# It's not intended to be used.
return self.__class__(lambda plx: self._call(plx).abs().sum())

# --- convert ---
def alias(self, name: str) -> Self:
"""
Expand Down
3 changes: 3 additions & 0 deletions narwhals/stable/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from narwhals.stable import v1

__all__ = ["v1"]
Loading

0 comments on commit 23ec907

Please sign in to comment.