Skip to content

Commit 17e4309

Browse files
committed
merge main
2 parents cf2366d + 5c3db5b commit 17e4309

File tree

203 files changed

+3521
-1648
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+3521
-1648
lines changed

.github/dependabot.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ updates:
55
- package-ecosystem: "github-actions"
66
directory: "/"
77
schedule:
8-
# Check for updates to GitHub Actions every week
9-
interval: "weekly"
8+
# Check for updates to GitHub Actions every month
9+
interval: "monthly"
1010
commit-message:
1111
prefix: "skip changelog" # So this PR will not be added to release-drafter
1212
include: "scope" # List of the updated dependencies in the commit will be added

.github/workflows/downstream_tests.yml

+59-10
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
matrix:
5656
python-version: ["3.12"]
5757
os: [ubuntu-latest]
58-
dependencies: ["core", "core,optional"]
58+
dependencies: ["core,optional"]
5959

6060
runs-on: ${{ matrix.os }}
6161
steps:
@@ -73,19 +73,27 @@ jobs:
7373
run: |
7474
git clone https://github.com/marimo-team/marimo.git --depth=1
7575
cd marimo
76+
uv venv -p 3.12
7677
git log
7778
- name: install-basics
7879
run: uv pip install --upgrade tox virtualenv setuptools hatch --system
7980
- name: install-marimo-dev
8081
run: |
8182
cd marimo
82-
uv pip install -e ".[dev]" --system
83+
. .venv/bin/activate
84+
uv pip install -e ".[dev]"
85+
which python
8386
- name: install-narwhals-dev
8487
run: |
85-
uv pip uninstall narwhals --system
86-
uv pip install -e . --system
88+
cd marimo
89+
. .venv/bin/activate
90+
uv pip uninstall narwhals
91+
uv pip install -e ./..
8792
- name: show-deps
88-
run: uv pip freeze
93+
run: |
94+
cd marimo
95+
. .venv/bin/activate
96+
uv pip freeze
8997
- name: Create assets directory, copy over index.html
9098
continue-on-error: true
9199
run: |
@@ -96,12 +104,13 @@ jobs:
96104
if: ${{ matrix.dependencies == 'core,optional' }}
97105
run: |
98106
cd marimo
99-
hatch run +py=${{ matrix.python-version }} test-optional:test-narwhals
107+
. .venv/bin/activate
108+
# make sure that we use the .venv when running tests, so that
109+
# the local narwhals install is picked up
110+
sed -i '/^\[tool.hatch.envs.default\]/a path = ".venv"' pyproject.toml
111+
hatch run python -c "import narwhals; print(narwhals.__file__)"
112+
hatch run test-optional:test-narwhals
100113
timeout-minutes: 15
101-
- name: Run typechecks
102-
run: |
103-
cd marimo
104-
hatch run typecheck:check
105114

106115
scikit-lego:
107116
strategy:
@@ -181,3 +190,43 @@ jobs:
181190
run: |
182191
cd py-shiny
183192
make narwhals-test-integration
193+
194+
tubular:
195+
strategy:
196+
matrix:
197+
python-version: ["3.12"]
198+
os: [ubuntu-latest]
199+
200+
runs-on: ${{ matrix.os }}
201+
steps:
202+
- uses: actions/checkout@v4
203+
- uses: actions/setup-python@v5
204+
with:
205+
python-version: ${{ matrix.python-version }}
206+
- name: Install uv
207+
uses: astral-sh/setup-uv@v3
208+
with:
209+
enable-cache: "true"
210+
cache-suffix: ${{ matrix.python-version }}
211+
cache-dependency-glob: "**requirements*.txt"
212+
- name: clone-tubular
213+
run: |
214+
git clone https://github.com/lvgig/tubular --depth=1
215+
cd tubular
216+
git log
217+
- name: install-basics
218+
run: uv pip install --upgrade tox virtualenv setuptools pytest-env --system
219+
- name: install-tubular-dev
220+
run: |
221+
cd tubular
222+
uv pip install -e .[dev] --system
223+
- name: install-narwhals-dev
224+
run: |
225+
uv pip uninstall narwhals --system
226+
uv pip install -e . --system
227+
- name: show-deps
228+
run: uv pip freeze
229+
- name: Run pytest
230+
run: |
231+
cd tubular
232+
pytest tests --config-file=pyproject.toml

.github/workflows/extremes.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ jobs:
9090
nightlies:
9191
strategy:
9292
matrix:
93-
python-version: ["3.12"]
93+
python-version: ["3.13"]
9494
os: [ubuntu-latest]
9595
if: github.event.pull_request.head.repo.full_name == github.repository
9696
runs-on: ${{ matrix.os }}

.github/workflows/pytest.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
pytest-windows:
3535
strategy:
3636
matrix:
37-
python-version: ["3.9", "3.10", "3.11", "3.12"]
37+
python-version: ["3.10", "3.12"]
3838
os: [windows-latest]
3939

4040
runs-on: ${{ matrix.os }}
@@ -61,7 +61,7 @@ jobs:
6161
pytest-coverage:
6262
strategy:
6363
matrix:
64-
python-version: ["3.9", "3.10", "3.11", "3.12"]
64+
python-version: ["3.9", "3.11", "3.13"]
6565
os: [ubuntu-latest]
6666

6767
runs-on: ${{ matrix.os }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ coverage.xml
1717
# Documentation
1818
site/
1919
todo.md
20+
docs/this.md
2021
docs/api-completeness/*.md
2122
!docs/api-completeness/index.md
2223

.pre-commit-config.yaml

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
ci:
2+
autoupdate_schedule: monthly
13
repos:
24
- repo: https://github.com/astral-sh/ruff-pre-commit
35
# Ruff version.
4-
rev: 'v0.6.9'
6+
rev: 'v0.7.1'
57
hooks:
68
# Run the formatter.
79
- id: ruff-format
810
# Run the linter.
911
- id: ruff
1012
args: [--fix]
1113
- repo: https://github.com/pre-commit/mirrors-mypy
12-
rev: 'v1.11.2'
14+
rev: 'v1.13.0'
1315
hooks:
1416
- id: mypy
1517
additional_dependencies: ['polars==1.4.1', 'pytest==8.3.2']
@@ -40,7 +42,7 @@ repos:
4042
hooks:
4143
- id: nbstripout
4244
- repo: https://github.com/adamchainz/blacken-docs
43-
rev: "1.19.0" # replace with latest tag on GitHub
45+
rev: "1.19.1" # replace with latest tag on GitHub
4446
hooks:
4547
- id: blacken-docs
4648
args: [--skip-errors]

CONTRIBUTING.md

+4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ nox
109109

110110
Notice that nox will also require to have all the python versions that are defined in the `noxfile.py` installed in your system.
111111

112+
#### Testing cuDF
113+
114+
We can't currently test in CI against cuDF, but you can test it manually in Kaggle using GPUs. Please follow this [Kaggle notebook](https://www.kaggle.com/code/marcogorelli/testing-cudf-in-narwhals) to run the tests.
115+
112116
### 7. Building docs
113117

114118
To build the docs, run `mkdocs serve`, and then open the link provided in a browser.

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ Join the party!
4343

4444
- [Altair](https://github.com/vega/altair/)
4545
- [Hamilton](https://github.com/DAGWorks-Inc/hamilton/tree/main/examples/narwhals)
46+
- [marimo](https://github.com/marimo-team/marimo)
47+
- [pymarginaleffects](https://github.com/vincentarelbundock/pymarginaleffects)
4648
- [scikit-lego](https://github.com/koaning/scikit-lego)
4749
- [scikit-playtime](https://github.com/koaning/scikit-playtime)
4850
- [timebasedcv](https://github.com/FBruzzesi/timebasedcv)
49-
- [marimo](https://github.com/marimo-team/marimo)
51+
- [tubular](https://github.com/lvgig/tubular)
52+
- [wimsey](https://github.com/benrutter/wimsey)
5053

5154
Feel free to add your project to the list if it's missing, and/or
5255
[chat with us on Discord](https://discord.gg/V3PqtB4VA4) if you'd like any support.

docs/api-reference/dependencies.md

+6
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@
1111
- get_polars
1212
- get_pyarrow
1313
- is_cudf_dataframe
14+
- is_cudf_index
1415
- is_cudf_series
1516
- is_dask_dataframe
1617
- is_ibis_table
18+
- is_into_dataframe
19+
- is_into_series
1720
- is_modin_dataframe
21+
- is_modin_index
1822
- is_modin_series
1923
- is_numpy_array
2024
- is_pandas_dataframe
25+
- is_pandas_index
2126
- is_pandas_like_dataframe
27+
- is_pandas_like_index
2228
- is_pandas_like_series
2329
- is_pandas_series
2430
- is_polars_dataframe

docs/api-reference/expr_dt.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@
66
members:
77
- convert_time_zone
88
- date
9-
- year
10-
- month
119
- day
12-
- ordinal_day
1310
- hour
14-
- minute
15-
- second
16-
- millisecond
1711
- microsecond
12+
- millisecond
13+
- minute
14+
- month
1815
- nanosecond
16+
- ordinal_day
1917
- replace_time_zone
20-
- total_minutes
21-
- total_seconds
22-
- total_milliseconds
18+
- second
19+
- timestamp
2320
- total_microseconds
21+
- total_milliseconds
22+
- total_minutes
2423
- total_nanoseconds
24+
- total_seconds
2525
- to_string
26+
- year
2627
show_source: false
2728
show_bases: false

docs/api-reference/narwhals.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Here are the top-level functions available in Narwhals.
1515
- from_dict
1616
- from_native
1717
- from_arrow
18+
- generate_temporary_column_name
1819
- get_level
1920
- get_native_namespace
2021
- is_ordered_categorical

docs/api-reference/series_dt.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@
66
members:
77
- convert_time_zone
88
- date
9-
- year
10-
- month
119
- day
12-
- ordinal_day
1310
- hour
14-
- minute
15-
- second
16-
- millisecond
1711
- microsecond
12+
- millisecond
13+
- minute
14+
- month
1815
- nanosecond
16+
- ordinal_day
1917
- replace_time_zone
20-
- total_minutes
21-
- total_seconds
22-
- total_milliseconds
18+
- second
19+
- timestamp
2320
- total_microseconds
21+
- total_milliseconds
22+
- total_minutes
2423
- total_nanoseconds
24+
- total_seconds
2525
- to_string
26+
- year
2627
show_source: false
2728
show_bases: false

docs/basics/dataframe_conversion.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Conversion between libraries
2+
3+
Some library maintainers must apply complex dataframe operations, using methods and functions that may not (yet) be implemented in Narwhals. In such cases, Narwhals can still be highly beneficial, by allowing easy dataframe conversion.
4+
5+
## Dataframe X in, pandas out
6+
7+
Imagine that you maintain a library with a function that operates on pandas dataframes to produce automated reports. You want to allow users to supply a dataframe in any format to that function (pandas, Polars, DuckDB, cuDF, Modin, etc.) without adding all those dependencies to your own project and without special-casing each input library's variation of `to_pandas` / `toPandas` / `to_pandas_df` / `df` ...
8+
9+
One solution is to use Narwhals as a thin Dataframe ingestion layer, to convert user-supplied dataframe to the format that your library uses internally. Since Narwhals is zero-dependency, this is a much more lightweight solution than including all the dataframe libraries as dependencies,
10+
and easier to write than special casing each input library's `to_pandas` method (if it even exists!).
11+
12+
To illustrate, we create dataframes in various formats:
13+
14+
```python exec="1" source="above" session="conversion"
15+
import narwhals as nw
16+
from narwhals.typing import IntoDataFrame
17+
18+
import duckdb
19+
import polars as pl
20+
import pandas as pd
21+
22+
df_polars = pl.DataFrame(
23+
{
24+
"A": [1, 2, 3, 4, 5],
25+
"fruits": ["banana", "banana", "apple", "apple", "banana"],
26+
"B": [5, 4, 3, 2, 1],
27+
"cars": ["beetle", "audi", "beetle", "beetle", "beetle"],
28+
}
29+
)
30+
df_pandas = df_polars.to_pandas()
31+
df_duckdb = duckdb.sql("SELECT * FROM df_polars")
32+
```
33+
34+
Now, we define a function that can ingest any dataframe type supported by Narwhals, and convert it to a pandas DataFrame for internal use:
35+
36+
```python exec="1" source="above" session="conversion" result="python"
37+
def df_to_pandas(df: IntoDataFrame) -> pd.DataFrame:
38+
return nw.from_native(df).to_pandas()
39+
40+
41+
print(df_to_pandas(df_polars))
42+
```
43+
44+
## Dataframe X in, Polars out
45+
46+
### Via PyCapsule Interface
47+
48+
Similarly, if your library uses Polars internally, you can convert any user-supplied dataframe to Polars format using Narwhals.
49+
50+
```python exec="1" source="above" session="conversion" result="python"
51+
def df_to_polars(df: IntoDataFrame) -> pl.DataFrame:
52+
return nw.from_arrow(nw.from_native(df), native_namespace=pl).to_native()
53+
54+
55+
print(df_to_polars(df_duckdb)) # You can only execute this line of code once.
56+
```
57+
58+
It works to pass Polars to `native_namespace` here because Polars supports the [PyCapsule Interface](https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html) for import.
59+
60+
Note that the PyCapsule Interface makes no guarantee that you can call it repeatedly, so the approach above only works if you
61+
only expect to perform the conversion a single time on each input object.
62+
63+
### Via PyArrow
64+
65+
If you need to ingest the same dataframe multiple times, then you may want to go via PyArrow instead.
66+
This may be less efficient than the PyCapsule approach above (and always requires PyArrow!), but is more forgiving:
67+
68+
```python exec="1" source="above" session="conversion" result="python"
69+
def df_to_polars(df: IntoDataFrame) -> pl.DataFrame:
70+
return pl.DataFrame(nw.from_native(df).to_arrow())
71+
72+
73+
df_duckdb = duckdb.sql("SELECT * FROM df_polars")
74+
print(df_to_polars(df_duckdb)) # We can execute this...
75+
print(df_to_polars(df_duckdb)) # ...as many times as we like!
76+
```

0 commit comments

Comments
 (0)