Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
451 commits
Select commit Hold shift + click to select a range
f85a827
feat: Impl `prepare_excluded`, return from `replace_regex`
dangotbanned Jun 8, 2025
cfdacf2
feat: Impl `expand_columns`
dangotbanned Jun 8, 2025
04427db
feat: Impl `expand_indices`, `replace_dtype_or_index_with_column`
dangotbanned Jun 8, 2025
7b3641b
feat: Impl `replace_wildcard`, `replace_wildcard_with_column`
dangotbanned Jun 8, 2025
f76c9dd
docs(DRAFT): Add more notes on selectors todo
dangotbanned Jun 8, 2025
f42e202
feat: Impl `replace_selector`
dangotbanned Jun 8, 2025
9a627a7
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 8, 2025
cfe3229
revert: Remove unplanned dtypes stuff and comments
dangotbanned Jun 8, 2025
804ac3d
chore: Remove factored-out `Inplace` 🥳
dangotbanned Jun 8, 2025
353ef59
chore: use `Version.dtypes`
dangotbanned Jun 8, 2025
1d63326
feat: Impl `ExprIR.map_ir` for most nodes
dangotbanned Jun 9, 2025
0df2be6
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 9, 2025
d3ea987
fix: typo
dangotbanned Jun 9, 2025
4604d9a
test: add `assert_expr_ir_equal`
dangotbanned Jun 9, 2025
360caec
test: Add `test_replace_selector`
dangotbanned Jun 9, 2025
2550103
feat: Impl `WindowExpr.map_ir`
dangotbanned Jun 9, 2025
7dd7092
feat: Impl `FunctionExpr.map_ir`
dangotbanned Jun 9, 2025
a3b96ee
chore: Tidy up notes
dangotbanned Jun 9, 2025
b569b57
test: Add `test_prepare_projection`
dangotbanned Jun 10, 2025
534c902
test: Add repro for horizontal alias bug
dangotbanned Jun 10, 2025
23045dc
fix: Add missing `Exclude` iterators
dangotbanned Jun 10, 2025
4e65ebc
refactor(DRAFT): Start splitting out `WindowExpr`
dangotbanned Jun 10, 2025
99cb01a
refactor: Use a single `Over` with two builder methods
dangotbanned Jun 10, 2025
422bbc7
fix: Expand exprs/selectors in `over(order_by=...)`
dangotbanned Jun 10, 2025
ee1bdb8
chore: Update comments
dangotbanned Jun 10, 2025
41f4070
refactor: Simplify `with_order_by`
dangotbanned Jun 10, 2025
7d4543e
refactor: Factor out tuple boilerplate
dangotbanned Jun 10, 2025
9303338
perf: Prepare `FrozenSchema` for caching
dangotbanned Jun 10, 2025
090330c
feat: Validate expressions with schema
dangotbanned Jun 11, 2025
d8dcfa4
revert: Remove superseded `_ColumnSelection.expand_columns`
dangotbanned Jun 11, 2025
49a6fc9
refactor: Repurpose `col`
dangotbanned Jun 11, 2025
b244b34
refactor: Make `expr` a dependency of `expr_expansion`
dangotbanned Jun 11, 2025
09c01fe
revert: Remove unused `regex` expansion stuff
dangotbanned Jun 11, 2025
bd58867
refactor: Remove/rename things inherited from `rust`
dangotbanned Jun 11, 2025
cfcbbda
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 11, 2025
00c2cc1
fix: Add some missing `is_scalar` props
dangotbanned Jun 11, 2025
48e9f25
feat: Add `functions.Log`
dangotbanned Jun 12, 2025
fb3f407
feat: Add `Expr.filter`
dangotbanned Jun 12, 2025
52f7975
ci: Update `name-tests-test` exclude pattern
dangotbanned Jun 12, 2025
ba0271e
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 12, 2025
0ff24fe
refactor: Move types from `common` to `typing`
dangotbanned Jun 12, 2025
13caf8d
feat: Utilize `IntoDType`
dangotbanned Jun 12, 2025
8c86fea
perf: Add a two-level cache for selectors expansion
dangotbanned Jun 12, 2025
6414142
refactor: Replace 3x `replace_*` functions with 1
dangotbanned Jun 12, 2025
80cc1c0
Merge branch 'main' into oh-nodes
dangotbanned Jun 12, 2025
0d0d6a2
feat: Support `*args, **kwds` in `when`
dangotbanned Jun 12, 2025
534cf16
feat: Add `expr`, `sqrt`, `kurtosis`
dangotbanned Jun 12, 2025
c9cb596
feat: Ensure mutability stays within function boundaries
dangotbanned Jun 12, 2025
03af47e
feat: more consistent index error
dangotbanned Jun 12, 2025
446c082
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 12, 2025
43a1ee2
Merge branch 'main' into oh-nodes
dangotbanned Jun 13, 2025
708d6ac
refactor: Reduce schema to columns where possible
dangotbanned Jun 13, 2025
7dbc380
typo
dangotbanned Jun 13, 2025
e7e17a7
feat: Add `_repr_html_`
dangotbanned Jun 14, 2025
579f9bd
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 15, 2025
12d7c96
chore(ruff): Update for `3.9` typing
dangotbanned Jun 15, 2025
1de65d2
fix: More consistent `__str__`
dangotbanned Jun 18, 2025
11f1e1b
refactor: Move, document `GroupByKeys`
dangotbanned Jun 22, 2025
1134e10
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 22, 2025
e17ab21
refactor: Add some `is_*_expr` guards
dangotbanned Jun 22, 2025
12ebe0c
docs: lil note on `prepare_projection`
dangotbanned Jun 22, 2025
44f7602
feat(DRAFT): Add `rewrite_elementwise_over`
dangotbanned Jun 22, 2025
67451b1
Merge branch 'main' into oh-nodes
dangotbanned Jun 23, 2025
b3af144
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 23, 2025
576afa9
feat: Add a basic rewrite composer
dangotbanned Jun 23, 2025
2ceadc4
test: `test_rewrite_elementwise_over_(simple|multiple)`
dangotbanned Jun 23, 2025
357d419
perf: Add safe caching to `meta.output_name`
dangotbanned Jun 23, 2025
2b8aea5
prep for `NamedIR`
dangotbanned Jun 23, 2025
bcc071a
refactor: Update to rewrite w/ `NamedIR`
dangotbanned Jun 23, 2025
d973b83
fix: Make sure to call `function` on result
dangotbanned Jun 23, 2025
5ae792d
refactor: Add `map_ir` function, un special-case `NamedIR`
dangotbanned Jun 23, 2025
ed9d769
docs(typing): `IntoFrozenSchema` alias
dangotbanned Jun 24, 2025
7dcdf86
test: Move `meta.output_name` doctests, add failing one
dangotbanned Jun 24, 2025
54d0781
fix: Get the right name from `FunctionExpr`
dangotbanned Jun 24, 2025
e8106c4
test: Lots of `output_name` coverage
dangotbanned Jun 24, 2025
b79181a
test: Backcompat `len`, `nth`
dangotbanned Jun 24, 2025
26716c5
refactor: Handle `SortBy`, `WindowExpr` internally
dangotbanned Jun 24, 2025
3a1c375
refactor: Simplify `FunctionExpr` version
dangotbanned Jun 24, 2025
bd5c33f
fix: Ensure `output_name` matches upstream
dangotbanned Jun 24, 2025
f0d9ddc
Merge branch 'main' into oh-nodes
dangotbanned Jun 24, 2025
a84ae05
feat: Add `NamedIR.(__repr__|_repr_html_)`
dangotbanned Jun 24, 2025
a46b3e5
test: Add `test_rewrite_elementwise_over_complex`
dangotbanned Jun 24, 2025
83e2b58
fix: Handle `*args` in `rewrite_elementwise_over`
dangotbanned Jun 24, 2025
ac2b1ff
feat: Add `int_range`
dangotbanned Jun 25, 2025
cb8234e
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jun 25, 2025
7324753
feat: Add `NamedIR.is_elementwise_top_level`
dangotbanned Jun 25, 2025
5114d32
Merge branch 'main' into oh-nodes
dangotbanned Jun 25, 2025
afd0bc7
Merge branch 'main' into oh-nodes
dangotbanned Jun 25, 2025
1f6e1da
feat: Initial `rewrite_binary_agg_over` impl
dangotbanned Jun 26, 2025
f2f6141
Merge branch 'oh-nodes' of https://github.com/narwhals-dev/narwhals i…
dangotbanned Jun 26, 2025
f2ac1c6
fix: undo `TypeIs` import
dangotbanned Jun 26, 2025
46831ea
Merge branch 'main' into oh-nodes
dangotbanned Jun 26, 2025
e726865
fix: Ensure lhs gets leaf name used
dangotbanned Jun 26, 2025
6edc52c
test: Add some `rewrite_binary_agg_over`
dangotbanned Jun 26, 2025
5c80033
Merge branch 'main' into oh-nodes
dangotbanned Jun 29, 2025
4076d4d
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 2, 2025
463d75a
feat: `FrozenSchema` repr#
dangotbanned Jul 2, 2025
08dcfca
refactor: Split out `FrozenSchema`
dangotbanned Jul 2, 2025
17822e8
planning schema projection
dangotbanned Jul 2, 2025
8243433
revert: Drop unplanned `impl_arrow` bits
dangotbanned Jul 2, 2025
984c07b
fix(typing): `*Series` generic
dangotbanned Jul 2, 2025
1468662
ci: Ignore dtypes import
dangotbanned Jul 2, 2025
2985bd5
feat: Reimpl `pyarrow`, start on `select`
dangotbanned Jul 2, 2025
a85fc7e
feat(pyarrow): Impl `Cast`, `Sort`, `Filter`, `Len`
dangotbanned Jul 2, 2025
faa91ec
feat(pyarrow): Impl `SortBy`
dangotbanned Jul 2, 2025
e6fab3f
Merge branch 'main' into oh-nodes
dangotbanned Jul 3, 2025
25ba870
Merge branch 'main' into oh-nodes
dangotbanned Jul 3, 2025
925d601
feat(pyarrow): Impl `First`, `Last`
dangotbanned Jul 3, 2025
c823d78
feat(pyarrow): Impl all aggregations
dangotbanned Jul 3, 2025
b069348
docs: Note on broacasting
dangotbanned Jul 3, 2025
a4d90d8
feat(DRAFT): Prepare new broadcasting layer
dangotbanned Jul 3, 2025
0ba05eb
refactor: Move `flatten_hash_safe`
dangotbanned Jul 4, 2025
3a4776f
more giant refactors 😅
dangotbanned Jul 4, 2025
5ca704f
ignore-banned-import
dangotbanned Jul 4, 2025
8e40fea
fix: alias removed exception types
dangotbanned Jul 4, 2025
34355dc
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 4, 2025
e45118d
refactor: Handle expansion, projection at narwhals-level
dangotbanned Jul 4, 2025
86d56ad
feat: Add `NamedIR.from_name`
dangotbanned Jul 5, 2025
a193af0
test: Add `test_lit_series_roundtrip`
dangotbanned Jul 5, 2025
8e47788
fix(typing): Propagate `NativeSeriesT`
dangotbanned Jul 5, 2025
9e2e2b9
chore(typing): Link to pyright explainer
dangotbanned Jul 5, 2025
154a3a0
test: Update tests that shouldn't broadcast
dangotbanned Jul 5, 2025
6bf86d8
keep on iterating
dangotbanned Jul 5, 2025
f2c6566
fix: Fill in missing type params
dangotbanned Jul 6, 2025
423ea9a
refactor: Move `native` out of higher protocol
dangotbanned Jul 6, 2025
f47fed2
refactor: Impl dispatch only once?
dangotbanned Jul 6, 2025
a4b1a02
chore: planning `CompliantNamespace`
dangotbanned Jul 6, 2025
1aaca2a
feat: `col`, `lit` classmethods?
dangotbanned Jul 6, 2025
1060f0e
feat(DRAFT): Dispatch take ✌
dangotbanned Jul 6, 2025
d6ebf9b
Update narwhals/_plan/dummy.py
dangotbanned Jul 6, 2025
33ddb8d
maybe `pyarrow` backcompat?
dangotbanned Jul 6, 2025
7aa7d1d
is `len` the issue?
dangotbanned Jul 6, 2025
eebef7a
plz
dangotbanned Jul 6, 2025
acdbf5e
revert: remove typing check
dangotbanned Jul 7, 2025
4437861
fix: Unwrap scalar on old pyarrow
dangotbanned Jul 7, 2025
5b6e644
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 7, 2025
bcdca6e
fix: Use new `Interval` helper in `truncate`
dangotbanned Jul 7, 2025
96b0c9c
feat(pyarrow): Impl `len`
dangotbanned Jul 7, 2025
9583383
feat(pyarrow): More impls
dangotbanned Jul 7, 2025
5457b30
test(pyarrow): Coverage for most of the current impl
dangotbanned Jul 7, 2025
c0c6b7d
refactor(pyarrow): Migrate most of `evaluate` into `ArrowExpr`
dangotbanned Jul 7, 2025
5039a09
feat(pyarrow): Add `ArrowDataFrame.sort`
dangotbanned Jul 7, 2025
c26c862
feat(pyarrow): Impl `Expr.sort_by`
dangotbanned Jul 7, 2025
c720749
fix: unused-ignore
dangotbanned Jul 7, 2025
017aa57
Merge branch 'main' into oh-nodes
dangotbanned Jul 8, 2025
8dcc57b
refactor: Move `lit`, `col`, `len` to namespace
dangotbanned Jul 8, 2025
4d54ff4
refactor: Move `len` impl to `EagerNamespace`
dangotbanned Jul 8, 2025
25a248b
revert: remove unused
dangotbanned Jul 8, 2025
d682390
feat: Add the remaining top-level nodes
dangotbanned Jul 8, 2025
6fdbd34
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 9, 2025
14033ae
refactor: Use `Protocol`s, move to `protocols`
dangotbanned Jul 9, 2025
d72b200
feat(pyarrow): Impl `BinaryExpr`
dangotbanned Jul 9, 2025
d5ccd31
🧹🧹🧹
dangotbanned Jul 9, 2025
a40b044
fix: Align all binary ops with `polars`
dangotbanned Jul 10, 2025
1880cef
fix: Fill in some `DType` holes
dangotbanned Jul 10, 2025
4b5b9ab
test: use `assert_equal_data`
dangotbanned Jul 10, 2025
2c0c067
refactor: tighten up protocols
dangotbanned Jul 10, 2025
97b84fd
Merge branch 'main' into oh-nodes
dangotbanned Jul 11, 2025
cb2e247
refactor: rename `Agg` -> `AggExpr`
dangotbanned Jul 11, 2025
4947ec4
fix: Check for scalars in `int_range`
dangotbanned Jul 11, 2025
5c33fce
feat: Identify n-ary functions
dangotbanned Jul 11, 2025
3c5663a
feat(DRAFT): Stub out namespace functions
dangotbanned Jul 11, 2025
f48d868
fix(typing): Oops they return expr
dangotbanned Jul 11, 2025
08e2bd2
feat(pyarrow): Impl `int_range`
dangotbanned Jul 11, 2025
5635be2
fix: typo in error message
dangotbanned Jul 11, 2025
98fe85a
feat(pyarrow): Impl `pow`
dangotbanned Jul 12, 2025
63b63c8
feat(pyarrow): Impl `fill_null`, `is_between`
dangotbanned Jul 12, 2025
cccc323
feat(pyarrow): Impl 6x boolean unary functions
dangotbanned Jul 12, 2025
1ad79a1
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 12, 2025
638b585
chore: remove `to_compliant`/`_to_compliant`
dangotbanned Jul 12, 2025
ccfa7da
rename `to_compliant_test` -> `compliant_test`
dangotbanned Jul 12, 2025
df52814
test: Add failing `when` tests
dangotbanned Jul 13, 2025
3140a48
feat(pyarrow): Impl complex `when-then-otherwise`
dangotbanned Jul 13, 2025
d9d8e64
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 14, 2025
b5550b4
refactor: Remove `pyarrow<13` compat
dangotbanned Jul 14, 2025
97453bb
refactor: Moving around
dangotbanned Jul 14, 2025
fbb463b
feat(pyarrow): Impl `all_horizontal`
dangotbanned Jul 14, 2025
21bf3db
feat(pyarrow): Impl `{any,sum,min,max}_horizontal`
dangotbanned Jul 14, 2025
0ae9f5f
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 15, 2025
ec06a5e
test: Shorten some test ids
dangotbanned Jul 15, 2025
031d547
test(pyarrow): Add `any_horizontal` tests
dangotbanned Jul 15, 2025
51ed0fb
test: Add equiv to `test_sumh_broadcasting`
dangotbanned Jul 15, 2025
b065573
feat(pyarrow): Impl `mean_horizontal`
dangotbanned Jul 15, 2025
3732e5a
fix: fill nulls in `sum_horizontal`
dangotbanned Jul 15, 2025
2b44d63
test: Add detail to xfail message
dangotbanned Jul 15, 2025
17634cd
refactor: Split out `functions`, `typing`
dangotbanned Jul 16, 2025
02092b4
feat(pyarrow): Impl `concat_str`
dangotbanned Jul 16, 2025
5d57676
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 16, 2025
3db1e65
prep for complex nodes
dangotbanned Jul 16, 2025
a5f110f
feat(pyarrow): Impl `map_batches`
dangotbanned Jul 16, 2025
747a5ae
test: Add `map_batches` tests
dangotbanned Jul 17, 2025
e85af02
refactor(pyarrow): Split out `int_range`
dangotbanned Jul 17, 2025
088a48a
feat(DRAFT): Add `with_columns`
dangotbanned Jul 17, 2025
168e930
Merge branch 'main' into oh-nodes
dangotbanned Jul 18, 2025
cf2f96c
fix: Extend exprs in `Schema._with_columns`
dangotbanned Jul 18, 2025
cd6ade0
wip: add `concat`
dangotbanned Jul 18, 2025
76b9b9a
refactor: Split out eager, remove `from_series`
dangotbanned Jul 18, 2025
37d7709
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 18, 2025
250922f
revert: Leave out unused `schema_projected` for now
dangotbanned Jul 23, 2025
8729ebe
refactor(typing): Relax `_concat_horizontal`
dangotbanned Jul 23, 2025
6ef9375
feat(pyarrow): Impl `over_ordered`
dangotbanned Jul 23, 2025
d3bed76
oop
dangotbanned Jul 23, 2025
cf38656
Merge branch 'main' into oh-nodes
dangotbanned Jul 23, 2025
2c85701
add `_with_columns`
dangotbanned Jul 30, 2025
4de8579
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Jul 30, 2025
94d533d
perf: Add early return path for `over_ordered`
dangotbanned Jul 30, 2025
c893b2f
Merge branch 'main' into oh-nodes
dangotbanned Aug 2, 2025
b03d31f
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 15, 2025
25549de
style(ruff): re-run updated config
dangotbanned Aug 15, 2025
3ec5f7b
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 19, 2025
a12a985
chore(ruff): re-run updated config x2
dangotbanned Aug 19, 2025
62030ae
refactor(expr-ir): Rename `Dummy*` everything (#3014)
dangotbanned Aug 19, 2025
25b744d
refactor(expr-ir): Heavily sugar `Function` defs (#3017)
dangotbanned Aug 21, 2025
fcfcabf
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 21, 2025
de884c5
refactor(typing): Align `NativeDataFrame`
dangotbanned Aug 21, 2025
dbe2a45
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 25, 2025
f5ecc6e
refactor(expr-ir): Add more builder sugar (#3040)
dangotbanned Aug 25, 2025
69eede5
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 28, 2025
c934d25
refactor(expr-ir): Clearing out the cobwebs (#3053)
dangotbanned Aug 29, 2025
dc3b9ea
Merge branch 'main' into oh-nodes
dangotbanned Aug 29, 2025
607ea5a
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Aug 30, 2025
631d3a3
refactor(expr-ir): `copy.replace` most `with_*` methods (#3063)
dangotbanned Aug 31, 2025
192de64
Merge branch 'main' into oh-nodes
dangotbanned Sep 1, 2025
215386f
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Sep 5, 2025
973824c
refactor(expr-ir): Shrinking `ExprIR` (main) (#3066)
dangotbanned Sep 10, 2025
70744e0
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Sep 10, 2025
6bc5ff5
fix(typing): Make pyright mostly happy
dangotbanned Sep 10, 2025
2aa8a2e
chore(typing): Ignore the other one for now
dangotbanned Sep 10, 2025
53d314d
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Sep 11, 2025
967a150
refactor(expr-ir): Organize `_plan` package (#3122)
dangotbanned Sep 14, 2025
318261e
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Sep 15, 2025
7599fc4
revert(ruff): ignore (`RUF043`)
dangotbanned Sep 15, 2025
e502dcc
Merge branch 'main' into oh-nodes
dangotbanned Sep 18, 2025
65f9738
Merge branch 'main' into oh-nodes
dangotbanned Sep 21, 2025
8208d32
feat(expr-ir): Support `group_by`, utilize `pyarrow.acero` (#3143)
dangotbanned Oct 1, 2025
53f4041
Merge branch 'main' into oh-nodes
dangotbanned Oct 1, 2025
2b9dbf0
refactor(expr-ir): Split up and refine `protocols.py` (#3166)
dangotbanned Oct 1, 2025
2403f1b
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Oct 6, 2025
bde22ac
feat(expr-ir): Acero `order_by`, `hashjoin` , `DataFrame.{filter,join…
dangotbanned Oct 12, 2025
cce4289
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Oct 12, 2025
1a433a9
fix: Update alias import
dangotbanned Oct 12, 2025
bf544d4
fix(expr-ir): Ensure only `__slots__`, and not `__dict__` too (#3201)
dangotbanned Oct 13, 2025
63c85c8
Merge branch 'main' into oh-nodes
dangotbanned Oct 13, 2025
fa0899a
perf(expr-ir): Optimize `ExpansionFlags.from_ir` (#3206)
dangotbanned Oct 15, 2025
1ab1599
Merge branch 'main' into oh-nodes
dangotbanned Oct 15, 2025
34a1fd0
refactor(expr-ir): Improve function dispatch (#3215)
dangotbanned Oct 18, 2025
dbca5a5
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Oct 18, 2025
9acce69
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Oct 20, 2025
46abfe6
chore: re-sync imports following (#3086)
dangotbanned Oct 20, 2025
1ba04b1
Merge remote-tracking branch 'upstream/main' into oh-nodes
dangotbanned Nov 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ repos:
hooks:
- id: codespell
files: \.(py|rst|md)$
args: [--ignore-words-list=ser]
args: [--ignore-words-list=ser, --ignore-words-list=RightT]
exclude: ^docs/api-completeness.md$
- repo: https://github.com/crate-ci/typos
rev: 'v1.39.0'
Expand Down Expand Up @@ -83,6 +83,8 @@ repos:
entry: "self: Self"
language: pygrep
files: ^narwhals/
# mypy needs `Self` for `ExprIR.dispatch`
exclude: ^narwhals/_plan/.*\.py
- id: dtypes-import
name: don't import from narwhals.dtypes (use `Version.dtypes` instead)
entry: |
Expand All @@ -101,7 +103,10 @@ repos:
narwhals/_utils\.py|
narwhals/stable/v./_?dtypes.py|
narwhals/.*__init__.py|
narwhals/.*typing\.py
narwhals/.*typing\.py|
narwhals/_plan/functions\.py|
narwhals/_plan/expressions/ranges\.py|
narwhals/_plan/schema\.py
)
- id: pull-request-target
name: don't use `pull_request_target`
Expand All @@ -123,6 +128,7 @@ repos:
(?x)
^(tests/utils\.py)
|^(tests/test_plugin/)
|^(tests/plan/utils\.py)
- id: no-commit-to-branch
- id: end-of-file-fixer
exclude: .svg$
56 changes: 56 additions & 0 deletions narwhals/_plan/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import annotations

from narwhals._plan.dataframe import DataFrame
from narwhals._plan.expr import Expr, Selector
from narwhals._plan.expressions import selectors
from narwhals._plan.functions import (
all,
all_horizontal,
any_horizontal,
col,
concat_str,
exclude,
int_range,
len,
lit,
max,
max_horizontal,
mean,
mean_horizontal,
median,
min,
min_horizontal,
nth,
sum,
sum_horizontal,
when,
)
from narwhals._plan.series import Series

__all__ = [
"DataFrame",
"Expr",
"Selector",
"Series",
"all",
"all_horizontal",
"any_horizontal",
"col",
"concat_str",
"exclude",
"int_range",
"len",
"lit",
"max",
"max_horizontal",
"mean",
"mean_horizontal",
"median",
"min",
"min_horizontal",
"nth",
"selectors",
"sum",
"sum_horizontal",
"when",
]
188 changes: 188 additions & 0 deletions narwhals/_plan/_dispatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
from __future__ import annotations

import re
from collections.abc import Callable
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Generic, Literal, Protocol, final, overload

from narwhals._plan._guards import is_function_expr
from narwhals._plan.compliant.typing import FrameT_contra, R_co
from narwhals._typing_compat import TypeVar

if TYPE_CHECKING:
from typing_extensions import Never, TypeAlias

from narwhals._plan.compliant.typing import Ctx
from narwhals._plan.expressions import ExprIR, FunctionExpr
from narwhals._plan.typing import ExprIRT, FunctionT

__all__ = ["Dispatcher", "get_dispatch_name"]


Node = TypeVar("Node", bound="ExprIR | FunctionExpr[Any]")
Node_contra = TypeVar(
"Node_contra", bound="ExprIR | FunctionExpr[Any]", contravariant=True
)
Raiser: TypeAlias = Callable[..., "Never"]


class Binder(Protocol[Node_contra]):
def __call__(
self, ctx: Ctx[FrameT_contra, R_co], /
) -> BoundMethod[Node_contra, FrameT_contra, R_co]: ...


class BoundMethod(Protocol[Node_contra, FrameT_contra, R_co]):
def __call__(self, node: Node_contra, frame: FrameT_contra, name: str, /) -> R_co: ...


@final
class Dispatcher(Generic[Node]):
"""Translate class definitions into error-wrapped method calls.

Operates over `ExprIR` and `Function` nodes.

By default, we dispatch to the compliant-level by calling a method that is the
**snake_case**-equivalent of the class name:

class BinaryExpr(ExprIR): ...

class CompliantExpr(Protocol):
def binary_expr(self, *args: Any): ...
"""

__slots__ = ("_bind", "_name")
_bind: Binder[Node]
_name: str

@property
def name(self) -> str:
return self._name

def __repr__(self) -> str:
return f"{type(self).__name__}<{self.name}>"

def bind(
self, ctx: Ctx[FrameT_contra, R_co], /
) -> BoundMethod[Node, FrameT_contra, R_co]:
"""Retrieve the implementation of this expression from `ctx`.

Binds an instance method, most commonly via:

expr: CompliantExpr
method = getattr(expr, "method_name")
"""
try:
return self._bind(ctx)
except AttributeError:
raise self._not_implemented_error(ctx, "compliant") from None

def __call__(
self,
node: Node,
ctx: Ctx[FrameT_contra, R_co],
frame: FrameT_contra,
name: str,
/,
) -> R_co:
"""Evaluate this expression in `frame`, using implementation(s) provided by `ctx`."""
method = self.bind(ctx)
if result := method(node, frame, name):
return result
raise self._not_implemented_error(ctx, "context")

@staticmethod
def from_expr_ir(tp: type[ExprIRT], /) -> Dispatcher[ExprIRT]:
if not tp.__expr_ir_config__.allow_dispatch:
return Dispatcher._no_dispatch(tp)
return Dispatcher._from_type(tp)

@staticmethod
def from_function(tp: type[FunctionT], /) -> Dispatcher[FunctionExpr[FunctionT]]:
return Dispatcher._from_type(tp)

@staticmethod
@overload
def _from_type(tp: type[ExprIRT], /) -> Dispatcher[ExprIRT]: ...
@staticmethod
@overload
def _from_type(tp: type[FunctionT], /) -> Dispatcher[FunctionExpr[FunctionT]]: ...
@staticmethod
def _from_type(tp: type[ExprIRT | FunctionT], /) -> Dispatcher[Any]:
obj = Dispatcher.__new__(Dispatcher)
obj._name = _method_name(tp)
getter = attrgetter(obj._name)
is_namespaced = tp.__expr_ir_config__.is_namespaced
obj._bind = _via_namespace(getter) if is_namespaced else getter
return obj

@staticmethod
def _no_dispatch(tp: type[ExprIRT], /) -> Dispatcher[ExprIRT]:
obj = Dispatcher.__new__(Dispatcher)
obj._name = tp.__name__
obj._bind = obj._make_no_dispatch_error()
return obj

def _make_no_dispatch_error(self) -> Callable[[Any], Raiser]:
def _no_dispatch_error(node: Node, *_: Any) -> Never:
msg = (
f"{self.name!r} should not appear at the compliant-level.\n\n"
f"Make sure to expand all expressions first, got:\n{node!r}"
)
raise TypeError(msg)

def getter(_: Any, /) -> Raiser:
return _no_dispatch_error

return getter

def _not_implemented_error(
self, ctx: object, /, missing: Literal["compliant", "context"]
) -> NotImplementedError:
if missing == "context":
msg = f"`{self.name}` is not yet implemented for {type(ctx).__name__!r}"
else:
msg = (
f"`{self.name}` has not been implemented at the compliant-level.\n"
f"Hint: Try adding `CompliantExpr.{self.name}()` or `CompliantNamespace.{self.name}()`"
)
return NotImplementedError(msg)


def _via_namespace(getter: Callable[[Any], Any], /) -> Callable[[Any], Any]:
def _(ctx: Any, /) -> Any:
return getter(ctx.__narwhals_namespace__())

return _


def _pascal_to_snake_case(s: str) -> str:
"""Convert a PascalCase string to snake_case.

Adapted from https://github.com/pydantic/pydantic/blob/f7a9b73517afecf25bf898e3b5f591dffe669778/pydantic/alias_generators.py#L43-L62
"""
# Handle the sequence of uppercase letters followed by a lowercase letter
snake = _PATTERN_UPPER_LOWER.sub(_re_repl_snake, s)
# Insert an underscore between a lowercase letter and an uppercase letter
return _PATTERN_LOWER_UPPER.sub(_re_repl_snake, snake).lower()


_PATTERN_UPPER_LOWER = re.compile(r"([A-Z]+)([A-Z][a-z])")
_PATTERN_LOWER_UPPER = re.compile(r"([a-z])([A-Z])")


def _re_repl_snake(match: re.Match[str], /) -> str:
return f"{match.group(1)}_{match.group(2)}"


def _method_name(tp: type[ExprIRT | FunctionT]) -> str:
config = tp.__expr_ir_config__
name = config.override_name or _pascal_to_snake_case(tp.__name__)
return f"{ns}.{name}" if (ns := getattr(config, "accessor_name", "")) else name


def get_dispatch_name(expr: ExprIR, /) -> str:
"""Return the synthesized method name for `expr`."""
return (
repr(expr.function) if is_function_expr(expr) else expr.__expr_ir_dispatch__.name
)
Loading
Loading