Skip to content

Data Validation Expectations

skyulf.profiling.expect is a lightweight, dependency-free data-validation helper — a tiny subset of what Great Expectations offers, but with zero extra dependencies. Each expect_* function checks a single condition and raises ExpectationError with a precise message when the condition is violated.

It is engine-agnostic: Pandas frames are used directly; raw and wrapped Polars frames stay native for simple predicates. Boolean range checks and other frames exposing to_pandas() use Pandas conversion.

When to use it

These are manual assertions — they are not wired into profiling or CI automatically. You call them yourself in two main places:

  1. In tests / CI — guard a dataset contract so a bad upstream change fails the build.
  2. In a pipeline — assert preconditions before an expensive step, so you get a clear error instead of a deep traceback later.

Available expectations

Function Checks
expect_columns_exist(df, columns) Every name in columns is present.
expect_no_nulls(df, columns=None, *, allow_empty=False) Given columns (default: all) have no nulls.
expect_value_range(df, column, *, minimum=None, maximum=None, inclusive=True, allow_empty=False) All non-null values fall within the optional bounds.
expect_unique(df, columns, *, allow_empty=False) The combination of columns has no duplicate rows.

Empty datasets

The null, range and uniqueness checks require at least one row by default. An ingestion that unexpectedly returns zero rows therefore raises ExpectationError, even if the expected columns are present.

If empty partitions are valid in your workflow, explicitly opt in on each check:

import pandas as pd
from skyulf import expect_no_nulls, expect_unique, expect_value_range

empty = pd.DataFrame({"age": pd.Series(dtype="float64")})
expect_no_nulls(empty, ["age"], allow_empty=True)
expect_value_range(empty, "age", minimum=0, maximum=120, allow_empty=True)
expect_unique(empty, ["age"], allow_empty=True)

Previously these checks accepted empty frames automatically; existing callers that rely on that behavior must add allow_empty=True. Requested columns must still exist. expect_columns_exist only checks the schema and accepts zero rows. Range checks continue to ignore null values, including an all-null column in a frame that has rows; combine them with expect_no_nulls when nulls are invalid.

Example: a dataset contract in CI

import pandas as pd
from skyulf import (
    ExpectationError,
    expect_columns_exist,
    expect_no_nulls,
    expect_unique,
    expect_value_range,
)


def validate_customers(df: pd.DataFrame) -> None:
    """Raises ExpectationError if the customers frame breaks its contract."""
    expect_columns_exist(df, ["customer_id", "age", "signup_date"])
    expect_unique(df, ["customer_id"])
    expect_no_nulls(df, ["customer_id", "signup_date"])
    expect_value_range(df, "age", minimum=0, maximum=120)

Wire it into a test so CI enforces it:

def test_customers_contract():
    df = pd.read_parquet("data/customers.parquet")
    validate_customers(df)  # raises ExpectationError on violation → test fails

Example: a pipeline guard

from skyulf import expect_no_nulls

def run(df):
    # Fail fast with a clear message before an expensive fit.
    expect_no_nulls(df, ["target"])
    ...

API reference

skyulf.profiling.expect

Lightweight data-validation expectations (no Great Expectations dependency).

Each expect_* function checks a single condition on a DataFrame and raises :class:ExpectationError with a precise message when the condition is violated. Pure-Python and engine-agnostic: Pandas frames are used directly; raw and wrapped Polars frames stay native for simple predicates; other frames exposing to_pandas() fall back to Pandas conversion.

Example

import pandas as pd from skyulf.profiling.expect import expect_no_nulls, expect_value_range df = pd.DataFrame({"age": [21, 35, 40]}) expect_no_nulls(df) expect_value_range(df, "age", minimum=0, maximum=120)

ExpectationError

Bases: ValueError

Raised when a data-validation expectation is not met.

Source code in skyulf-core/skyulf/profiling/expect.py
34
35
class ExpectationError(ValueError):
    """Raised when a data-validation expectation is not met."""

expect_columns_exist(df, columns)

Assert that every name in columns is present in df.

Source code in skyulf-core/skyulf/profiling/expect.py
92
93
94
95
96
97
98
def expect_columns_exist(df: Any, columns: Sequence[str]) -> None:
    """Assert that every name in ``columns`` is present in ``df``."""
    frame = _as_polars(df)
    columns_in_frame = list(frame.columns) if frame is not None else list(_as_pandas(df).columns)
    missing = [c for c in columns if c not in columns_in_frame]
    if missing:
        raise ExpectationError(f"Expected columns are missing: {missing}")

expect_no_nulls(df, columns=None, *, allow_empty=False)

Assert that the given columns (default: all) contain no null values.

Zero-row frames fail unless allow_empty=True is explicitly requested.

Source code in skyulf-core/skyulf/profiling/expect.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def expect_no_nulls(
    df: Any, columns: Sequence[str] | None = None, *, allow_empty: bool = False
) -> None:
    """Assert that the given columns (default: all) contain no null values.

    Zero-row frames fail unless ``allow_empty=True`` is explicitly requested.
    """
    frame = _as_polars(df)
    if frame is not None:
        cols = _resolve_columns(frame, columns)
        _check_row_count(frame, allow_empty=allow_empty)
        offenders = {
            column: count
            for column in cols
            if (count := _polars_null_count(frame.get_column(column))) > 0
        }
    else:
        pandas_frame = _as_pandas(df)
        cols = _resolve_columns(pandas_frame, columns)
        _check_row_count(pandas_frame, allow_empty=allow_empty)
        null_counts = {c: int(pandas_frame[c].isnull().sum()) for c in cols}
        offenders = {c: n for c, n in null_counts.items() if n > 0}
    if offenders:
        raise ExpectationError(f"Null values found in columns: {offenders}")

expect_unique(df, columns, *, allow_empty=False)

Assert that the combination of columns has no duplicate rows.

Zero-row frames fail unless allow_empty=True is explicitly requested.

Source code in skyulf-core/skyulf/profiling/expect.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def expect_unique(df: Any, columns: Sequence[str], *, allow_empty: bool = False) -> None:
    """Assert that the combination of ``columns`` has no duplicate rows.

    Zero-row frames fail unless ``allow_empty=True`` is explicitly requested.
    """
    frame = _as_polars(df)
    if frame is not None:
        expect_columns_exist(frame, columns)
        _check_row_count(frame, allow_empty=allow_empty)
        dup_count = int(_polars_duplicate_subset(frame, columns).is_duplicated().sum())
    else:
        pandas_frame = _as_pandas(df)
        expect_columns_exist(pandas_frame, columns)
        _check_row_count(pandas_frame, allow_empty=allow_empty)
        duplicated = pandas_frame.duplicated(subset=list(columns), keep=False)
        dup_count = int(duplicated.sum())
    if dup_count:
        raise ExpectationError(
            f"Expected unique values for {list(columns)} but found {dup_count} duplicate rows"
        )

expect_value_range(df, column, *, minimum=None, maximum=None, inclusive=True, allow_empty=False)

Assert that all values in column fall within [minimum, maximum].

minimum / maximum are optional (open-ended on the unset side). Null values are ignored. Set inclusive=False for a strict comparison. Zero-row frames fail unless allow_empty=True is explicitly requested; a frame containing only null values still has rows and passes this check.

Source code in skyulf-core/skyulf/profiling/expect.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def expect_value_range(
    df: Any,
    column: str,
    *,
    minimum: float | None = None,
    maximum: float | None = None,
    inclusive: bool = True,
    allow_empty: bool = False,
) -> None:
    """Assert that all values in ``column`` fall within ``[minimum, maximum]``.

    ``minimum`` / ``maximum`` are optional (open-ended on the unset side).
    Null values are ignored. Set ``inclusive=False`` for a strict comparison.
    Zero-row frames fail unless ``allow_empty=True`` is explicitly requested;
    a frame containing only null values still has rows and passes this check.
    """
    frame = _as_polars(df)
    if frame is not None:
        expect_columns_exist(frame, [column])
        _check_row_count(frame, allow_empty=allow_empty)
        series = frame.get_column(column)
        if series.dtype == pl.Boolean:
            pandas_frame = _as_pandas(df)
            series = pandas_frame[column].dropna()
            observed_as_float = False
        else:
            observed_as_float = series.dtype.is_integer() and series.null_count() > 0
            if series.dtype.is_float():
                series = series.fill_nan(None)
            series = series.drop_nulls()
    else:
        pandas_frame = _as_pandas(df)
        expect_columns_exist(pandas_frame, [column])
        _check_row_count(pandas_frame, allow_empty=allow_empty)
        series = pandas_frame[column].dropna()
        observed_as_float = False
    if len(series) == 0:
        return
    _check_lower_bound(series, column, minimum, inclusive, observed_as_float=observed_as_float)
    _check_upper_bound(series, column, maximum, inclusive, observed_as_float=observed_as_float)