Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .github/workflows/extremes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
echo "$DEPS" | grep 'duckdb==1.1'
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pyarrow,polars[eager],polars[lazy],duckdb
coverage run -m pytest tests --runslow --nw-backends=pandas,pyarrow,polars[eager],polars[lazy],duckdb
coverage combine
coverage report --fail-under=50

Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
echo "$DEPS" | grep 'duckdb==1.2'
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pyarrow,polars[eager],polars[lazy],duckdb
coverage run -m pytest tests --runslow --nw-backends=pandas,pyarrow,polars[eager],polars[lazy],duckdb
coverage combine
coverage report --fail-under=50

Expand Down Expand Up @@ -127,7 +127,7 @@ jobs:
echo "$DEPS" | grep 'duckdb==1.3'
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pyarrow,polars[eager],polars[lazy],dask,duckdb
coverage run -m pytest tests --runslow --nw-backends=pandas,pyarrow,polars[eager],polars[lazy],dask,duckdb
coverage combine
coverage report --fail-under=50

Expand Down Expand Up @@ -185,6 +185,6 @@ jobs:
echo "$DEPS" | grep 'dask.*@'
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],dask,duckdb
coverage run -m pytest tests --runslow --nw-backends=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],dask,duckdb
coverage combine
coverage report --fail-under=50
2 changes: 1 addition & 1 deletion .github/workflows/pytest-ibis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ jobs:
- name: show-deps
run: uv pip freeze
- name: Run pytest
run: pytest tests --constructors ibis
run: pytest tests --nw-backends ibis
2 changes: 1 addition & 1 deletion .github/workflows/pytest-modin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ jobs:
- name: show-deps
run: uv pip freeze
- name: Run pytest
run: pytest tests --constructors modin[pyarrow]
run: pytest tests --nw-backends modin[pyarrow]
6 changes: 3 additions & 3 deletions .github/workflows/pytest-pyspark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors pyspark
coverage run -m pytest tests --runslow --nw-backends pyspark
coverage combine
coverage report --fail-under=95 --include "narwhals/_spark_like/*"

Expand Down Expand Up @@ -71,7 +71,7 @@ jobs:
- name: show-deps
run: uv pip freeze
- name: Run pytest
run: pytest tests --constructors pyspark
run: pytest tests --nw-backends pyspark

pytest-pyspark-connect-constructor:
strategy:
Expand Down Expand Up @@ -138,7 +138,7 @@ jobs:

- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors "pyspark[connect]"
coverage run -m pytest tests --runslow --nw-backends "pyspark[connect]"
coverage combine
coverage report --fail-under=95 --include="narwhals/_spark_like/*"

Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
COVERAGE_PATCH_EXECV: ${{ matrix.os == 'windows-latest' && 'subprocess' || 'execv' }}
COVERAGE_PATCH_FORK: ${{ matrix.os == 'windows-latest' && 'subprocess' || 'fork' }}
run: |
coverage run -m pytest tests --constructors=pandas,pyarrow,polars[eager],polars[lazy]
coverage run -m pytest tests --nw-backends=pandas,pyarrow,polars[eager],polars[lazy]
coverage combine
coverage report --fail-under=75
- name: install-test-plugin
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],duckdb,sqlframe --durations=30
coverage run -m pytest tests --runslow --nw-backends=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],duckdb,sqlframe --durations=30
coverage combine
coverage report --fail-under=95

Expand Down Expand Up @@ -108,7 +108,7 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],dask,duckdb,sqlframe --durations=30
coverage run -m pytest tests --runslow --nw-backends=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],dask,duckdb,sqlframe --durations=30
coverage combine
coverage report --fail-under=100
- name: Run doctests
Expand Down Expand Up @@ -139,20 +139,20 @@ jobs:
uv pip install -e ".[pandas]" --group tests
uv pip freeze
- name: Run pytest (pandas and pandas[nullable])
run: pytest tests --runslow --constructors=pandas,pandas[nullable]
run: pytest tests --runslow --nw-backends=pandas,pandas[nullable]
- name: install-more-reqs
run: |
uv pip install -U pyarrow
uv pip freeze
- name: Run pytest (pandas[pyarrow] and pyarrow)
run: pytest tests --runslow --constructors=pandas[pyarrow],pyarrow
run: pytest tests --runslow --nw-backends=pandas[pyarrow],pyarrow
- name: install-polars
run: |
uv pip uninstall pandas pyarrow
uv pip install polars
uv pip freeze
- name: Run pytest (polars)
run: pytest tests --runslow --constructors=polars[eager],polars[lazy]
run: pytest tests --runslow --nw-backends=polars[eager],polars[lazy]

python-314:
strategy:
Expand All @@ -177,7 +177,7 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --constructors=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],duckdb,sqlframe --durations=30
coverage run -m pytest tests --runslow --nw-backends=pandas,pandas[nullable],pandas[pyarrow],pyarrow,polars[eager],polars[lazy],duckdb,sqlframe --durations=30
coverage combine
coverage report --fail-under=50

Expand Down Expand Up @@ -206,6 +206,6 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --runslow --durations=30 --constructors=pandas,pandas[nullable],pandas[pyarrow],pyarrow
coverage run -m pytest tests --runslow --durations=30 --nw-backends=pandas,pandas[nullable],pandas[pyarrow],pyarrow
coverage combine
coverage report --fail-under=50
2 changes: 1 addition & 1 deletion .github/workflows/random_ci_pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ jobs:
run: uv pip freeze
- name: Run pytest
run: |
coverage run -m pytest tests --constructors=pandas,pyarrow,polars[eager],polars[lazy]
coverage run -m pytest tests --nw-backends=pandas,pyarrow,polars[eager],polars[lazy]
coverage combine
coverage report --fail-under=75
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ test: ## Run unittest
--editable .[ibis,modin,pyspark] \
--group core \
--group tests
$(VENV_BIN)/uv run --no-sync coverage run -m pytest tests --all-cpu-constructors --numprocesses=logical
$(VENV_BIN)/uv run --no-sync coverage run -m pytest tests --all-nw-backends --numprocesses=logical
$(VENV_BIN)/uv run --no-sync coverage combine
$(VENV_BIN)/uv run --no-sync coverage report --fail-under=95
43 changes: 25 additions & 18 deletions docs/api-reference/testing.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# `narwhals.testing`

## Asserts
## Assertions

::: narwhals.testing
handler: python
options:
show_root_heading: false
heading_level: 3
members:
- assert_frame_equal
- assert_series_equal
Expand All @@ -18,19 +20,22 @@ to build native frames from a column-oriented python `dict`.

| Fixture | Backends |
|---|---|
| `constructor` | every selected backend (eager + lazy) |
| `constructor_eager` | only eager backends |
| `nw_frame_constructor` | every selected backend (eager + lazy) |
| `nw_eager_constructor` | only eager backends |
| `nw_pandas_like_constructor` | pandas-like backends |

The selection is controlled by two CLI options:
### Pytest options

* `--constructors=pandas,polars[lazy],duckdb`: comma-separated list.
Defaults to [`DEFAULT_CONSTRUCTORS`][narwhals.testing.constructors.DEFAULT_CONSTRUCTORS]
The backend selection is controlled by the following CLI options:

* `--nw-backends=pandas,polars[lazy],duckdb`: comma-separated list.
Defaults to [`DEFAULT_BACKENDS`][narwhals.testing.constructors.DEFAULT_BACKENDS]
intersected with the backends installed in the current environment.
* `--all-cpu-constructors`: shortcut for "every CPU backend that is installed".
* `--use-external-constructor`: Skip narwhals.testing's parametrisation and let
* `--nw-all-backends`: shortcut for "every **CPU** backend that is installed".
* `--use-nw-external-constructor`: Skip narwhals.testing's parametrisation and let
another plugin provide the `constructor*` fixtures.

Set the `NARWHALS_DEFAULT_CONSTRUCTORS` environment variable to override the default
Set the `NARWHALS_DEFAULT_BACKENDS` environment variable to override the default
list (useful e.g. when running under `cudf.pandas`).

### Quick start
Expand All @@ -43,30 +48,32 @@ from typing import TYPE_CHECKING
import narwhals as nw

if TYPE_CHECKING:
from narwhals.testing.typing import ConstructorEager, Data
from narwhals.testing.typing import EagerFrameConstructor, Data


def test_shape(constructor_eager: ConstructorEager) -> None:
def test_shape(nw_eager_constructor: EagerFrameConstructor) -> None:
data: Data = {"x": [1, 2, 3]}
df = nw.from_native(constructor_eager(data), eager_only=True)
df = nw.from_native(nw_eager_constructor(data), eager_only=True)
assert df.shape == (3, 1)
```

The fixtures are parametrised against every supported backend that is installed
in the current environment. Filter the matrix on the command line:

```bash
pytest --constructors="pandas,polars[lazy]"
pytest --all-cpu-constructors
pytest --nw-backends="pandas,polars[lazy]"
pytest --all-nw-backends
```

### Type aliases
## Type aliases

::: narwhals.testing.typing
handler: python
options:
show_root_heading: false
heading_level: 3
members:
- Constructor
- ConstructorEager
- ConstructorLazy
- Data
- FrameConstructor
- EagerFrameConstructor
- LazyFrameConstructor
50 changes: 25 additions & 25 deletions narwhals/testing/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ def my_backend_lazy_constructor(obj: Data, /, **kwds: Any) -> IntoLazyFrame:


__all__ = (
"available_constructors",
"available_cpu_constructors",
"available_backends",
"available_cpu_backends",
"frame_constructor",
"get_constructor",
"get_backend_constructor",
"is_backend_available",
"prepare_constructors",
"prepare_backends",
"pyspark_session",
"sqlframe_session",
)
Expand Down Expand Up @@ -464,7 +464,7 @@ def ibis_lazy_constructor(obj: Data, /, **kwds: Any) -> ibis.Table: # pragma: n
return _ibis_backend().create_table(table_name, table, **kwds)


DEFAULT_CONSTRUCTORS: frozenset[str] = frozenset(
DEFAULT_BACKENDS: frozenset[str] = frozenset(
{
"pandas",
"pandas[pyarrow]",
Expand All @@ -475,30 +475,30 @@ def ibis_lazy_constructor(obj: Data, /, **kwds: Any) -> ibis.Table: # pragma: n
"ibis",
}
)
"""Subset of constructors enabled by default for parametrised tests when the
user does not pass `--constructors` (mirrors the historical Narwhals defaults).
"""Subset of backends enabled by default for parametrised tests when the
user does not pass `--nw-backends` (mirrors the historical Narwhals defaults).
"""


def available_constructors() -> frozenset[str]:
def available_backends() -> frozenset[str]:
"""Return the names of every constructor whose backend is importable.

Examples:
>>> from narwhals.testing.constructors import available_constructors
>>> "pandas" in available_constructors()
>>> from narwhals.testing.constructors import available_backends
>>> "pandas" in available_backends()
True
"""
return frozenset(
name for name, c in frame_constructor._registry.items() if c.is_available
)


def available_cpu_constructors() -> frozenset[str]: # pragma: no cover
def available_cpu_backends() -> frozenset[str]: # pragma: no cover
"""Return the names of every CPU constructor whose backend is importable.

Examples:
>>> from narwhals.testing.constructors import available_cpu_constructors
>>> "pandas" in available_cpu_constructors()
>>> from narwhals.testing.constructors import available_cpu_backends
>>> "pandas" in available_cpu_backends()
True
"""
return frozenset(
Expand All @@ -524,14 +524,14 @@ def available_cpu_constructors() -> frozenset[str]: # pragma: no cover


@overload
def get_constructor(name: EagerName) -> frame_constructor[IntoDataFrame]: ...
def get_backend_constructor(name: EagerName) -> frame_constructor[IntoDataFrame]: ...
@overload
def get_constructor(name: LazyName) -> frame_constructor[IntoLazyFrame]: ...
def get_backend_constructor(name: LazyName) -> frame_constructor[IntoLazyFrame]: ...
@overload
def get_constructor(name: str) -> frame_constructor[IntoFrame]: ...
def get_backend_constructor(name: str) -> frame_constructor[IntoFrame]: ...


def get_constructor(name: str) -> frame_constructor[IntoFrame]:
def get_backend_constructor(name: str) -> frame_constructor[IntoFrame]:
"""Return the registered constructor for `name`.

Arguments:
Expand All @@ -542,8 +542,8 @@ def get_constructor(name: str) -> frame_constructor[IntoFrame]:
ValueError: If `name` is not a registered constructor identifier.

Examples:
>>> from narwhals.testing.constructors import get_constructor
>>> get_constructor("pandas")
>>> from narwhals.testing.constructors import get_backend_constructor
>>> get_backend_constructor("pandas")
frame_constructor(name='pandas')
"""
try:
Expand All @@ -554,7 +554,7 @@ def get_constructor(name: str) -> frame_constructor[IntoFrame]:
raise ValueError(msg) from exc


def prepare_constructors(
def prepare_backends(
*, include: Iterable[str] | None = None, exclude: Iterable[str] | None = None
) -> list[frame_constructor[IntoFrame]]:
"""Return available constructors, optionally filtered.
Expand All @@ -563,14 +563,14 @@ def prepare_constructors(
`exclude` is given precedence in the selection.

Arguments:
include: If given, only return constructors whose name is in this set.
exclude: If given, remove constructors whose name is in this set.
include: If given, only return backends whose name is in this set.
exclude: If given, remove backends whose name is in this set.

Examples:
>>> from narwhals.testing.constructors import prepare_constructors
>>> constructors = prepare_constructors(include=["pandas", "polars[eager]"])
>>> from narwhals.testing.constructors import prepare_backends
>>> backends = prepare_backends(include=["pandas", "polars[eager]"])
"""
available = available_constructors()
available = available_backends()
candidates: list[frame_constructor[Any]] = [
c for name, c in frame_constructor._registry.items() if name in available
]
Expand Down
Loading
Loading