Skip to content

Commit 1579b32

Browse files
committed
test: FrozenSchema cov
1 parent f954e05 commit 1579b32

File tree

2 files changed

+60
-9
lines changed

2 files changed

+60
-9
lines changed

narwhals/_plan/schema.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
from functools import lru_cache
55
from itertools import chain
66
from types import MappingProxyType
7-
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload
7+
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, final, overload
88

99
from narwhals._plan._expr_ir import NamedIR
1010
from narwhals._plan._immutable import Immutable
1111
from narwhals._utils import _hasattr_static
1212
from narwhals.dtypes import Unknown
1313

1414
if TYPE_CHECKING:
15-
from collections.abc import ItemsView, Iterator, KeysView, ValuesView
15+
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
1616

1717
from typing_extensions import Never, TypeAlias, TypeIs
1818

@@ -22,7 +22,7 @@
2222

2323

2424
IntoFrozenSchema: TypeAlias = (
25-
"IntoSchema | Iterator[tuple[str, DType]] | FrozenSchema | HasSchema"
25+
"IntoSchema | Iterable[tuple[str, DType]] | FrozenSchema | HasSchema"
2626
)
2727
"""A schema to freeze, or an already frozen one.
2828
@@ -35,14 +35,15 @@
3535
_T2 = TypeVar("_T2")
3636

3737

38+
@final
3839
class FrozenSchema(Immutable):
3940
"""Use `freeze_schema(...)` constructor to trigger caching!"""
4041

4142
__slots__ = ("_mapping",)
4243
_mapping: MappingProxyType[str, DType]
4344

44-
def __init_subclass__(cls, *_: Never, **__: Never) -> Never: # pragma: no cover
45-
msg = f"Cannot subclass {cls.__name__!r}"
45+
def __init_subclass__(cls, *_: Never, **__: Never) -> Never:
46+
msg = f"Cannot subclass {FrozenSchema.__name__!r}"
4647
raise TypeError(msg)
4748

4849
def merge(self, other: FrozenSchema, /) -> FrozenSchema:
@@ -111,7 +112,7 @@ def items(self) -> ItemsView[str, DType]:
111112
def keys(self) -> KeysView[str]:
112113
return self._mapping.keys()
113114

114-
def values(self) -> ValuesView[DType]: # pragma: no cover
115+
def values(self) -> ValuesView[DType]:
115116
return self._mapping.values()
116117

117118
@overload
@@ -121,15 +122,15 @@ def get(self, key: str, default: DType | _T2, /) -> DType | _T2: ...
121122
def get(self, key: str, default: DType | _T2 | None = None, /) -> DType | _T2 | None:
122123
if default is not None:
123124
return self._mapping.get(key, default)
124-
return self._mapping.get(key) # pragma: no cover
125+
return self._mapping.get(key)
125126

126127
def __iter__(self) -> Iterator[str]:
127128
yield from self._mapping
128129

129-
def __contains__(self, key: object) -> bool: # pragma: no cover
130+
def __contains__(self, key: object) -> bool:
130131
return self._mapping.__contains__(key)
131132

132-
def __getitem__(self, key: str, /) -> DType: # pragma: no cover
133+
def __getitem__(self, key: str, /) -> DType:
133134
return self._mapping.__getitem__(key)
134135

135136
def __len__(self) -> int:

tests/plan/schema_test.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
import narwhals as nw
6+
from narwhals._plan.schema import FrozenSchema, freeze_schema
7+
from tests.plan.utils import dataframe
8+
9+
10+
def test_schema() -> None:
11+
mapping = {"a": nw.Int64(), "b": nw.String()}
12+
schema = nw.Schema(mapping)
13+
frozen_schema = freeze_schema(mapping)
14+
15+
assert frozen_schema.keys() == schema.keys()
16+
assert tuple(frozen_schema.values()) == tuple(schema.values())
17+
18+
# NOTE: Would type-check if `Schema.__init__` didn't make liskov unhappy
19+
assert schema == nw.Schema(frozen_schema) # type: ignore[arg-type]
20+
assert mapping == dict(frozen_schema)
21+
22+
assert frozen_schema == freeze_schema(mapping)
23+
assert frozen_schema == freeze_schema(**mapping)
24+
assert frozen_schema == freeze_schema(a=nw.Int64(), b=nw.String())
25+
assert frozen_schema == freeze_schema(schema)
26+
assert frozen_schema == freeze_schema(frozen_schema)
27+
assert frozen_schema == freeze_schema(frozen_schema.items())
28+
29+
# NOTE: Using `**` unpacking, despite not inheriting from `Mapping` or `dict`
30+
assert frozen_schema == freeze_schema(**frozen_schema)
31+
32+
# NOTE: Using `HasSchema`
33+
df = dataframe({"a": [1, 2, 3], "b": ["c", "d", "e"]})
34+
assert frozen_schema == freeze_schema(df)
35+
36+
# NOTE: In case this all looks *too good* to be true
37+
assert frozen_schema != freeze_schema(**mapping, c=nw.Float64())
38+
39+
assert frozen_schema["a"] == schema["a"]
40+
41+
assert frozen_schema.get("c") is None
42+
assert frozen_schema.get("c", nw.Unknown) is nw.Unknown
43+
assert frozen_schema.get("c", nw.Unknown()) == nw.Unknown()
44+
45+
assert "b" in frozen_schema
46+
assert "e" not in frozen_schema
47+
48+
with pytest.raises(TypeError, match="Cannot subclass 'FrozenSchema'"):
49+
50+
class MutableSchema(FrozenSchema): ... # type: ignore[misc]

0 commit comments

Comments
 (0)