Portable Customer Transformation¶
Status: Available since ETLantic 0.17 (Polars + PySpark + Pandas + SQL relational); current docs target 0.21.0.
@Transformation.portable authoring shipped in 0.11. This guide runs a
kernel or relational plan on Polars/PySpark/Pandas without a native
@implementation(...) for the advertised claim set. Safe SQL portable
lowering for that claim set also shipped in 0.15 (etlantic-sql).
Runnable companion (checkout required — not in the PyPI wheel):
examples/portable_polars_kernel.py.
pip install 'etlantic==0.21.0' 'etlantic-polars==0.21.0'
# from a checkout:
uv run python examples/portable_polars_kernel.py
To exercise advanced profile families on Polars (string-advanced +
window/1), use
examples/portable_wave17.py
(Polars-only companion):
Author once¶
from etlantic import (
Data,
Input,
Output,
Parameter,
Pipeline,
PipelineRuntime,
Profile,
Load,
Extract,
Transformation,
)
from etlantic.plan import explain_plan, plan_pipeline
from etlantic.registry import PlanningContext
from etlantic.transform import functions as F
from etlantic_polars import create_plugin
class RawCustomer(Data):
customer_id: int
email: str
age: int
class Customer(Data):
customer_id: int
email: str
age: int
class NormalizeCustomers(Transformation):
customers: Input[RawCustomer]
minimum_age: Parameter[int] = 18
result: Output[Customer]
@NormalizeCustomers.portable
def normalize(customers, minimum_age):
return (
customers.filter(F.col("age") >= minimum_age)
.withColumn("email", F.lower(F.col("email")))
.select("customer_id", "email", "age")
)
class PortablePolarsPipeline(Pipeline):
raw: Extract[RawCustomer] = Extract(asset="customers")
normalized = NormalizeCustomers.step(customers=raw)
curated: Load[Customer] = Load(input=normalized.result, asset="curated")
Inspect the emitted plan (no engine required):
plan = NormalizeCustomers.to_transform_plan()
assert plan["planIdentity"] == "dtcs.transform-plan/2"
print(NormalizeCustomers.portable_fingerprint())
Run on Polars without a native callable¶
profile = Profile(
name="polars-portable",
dataframe_engine="polars",
portable_transform_policy="require", # fail closed if unsupported
)
runtime = PipelineRuntime()
runtime.register_dataframe_plugin("polars", create_plugin())
runtime.memory.seed(
"customers",
[
RawCustomer(customer_id=1, email="A@X.COM", age=30),
RawCustomer(customer_id=2, email="b@y.com", age=10),
],
)
context = PlanningContext.create(profile=profile, registry=runtime.registry)
planned = plan_pipeline(PortablePolarsPipeline, context=context)
assert planned.implementations["normalized"].kind == "portable_compiled"
report = PortablePolarsPipeline.run(
profile=profile, runtime=runtime, context=context
)
print(runtime.memory.get("curated"))
Default policy is prefer (portable when covered; diagnosed native fallback
otherwise). Use native to ignore compilers.
Expected plan evidence¶
{
"node": "normalized",
"implementation_kind": "portable_compiled",
"compiler": {"name": "etlantic-polars"},
"ir_fingerprint": "<64-char sha256>"
}
Use explain_plan(planned) or etlantic plan … --format json.
Expected outputs¶
| customer_id | age | |
|---|---|---|
| 1 | a@x.com | 30 |
Age 10 is filtered out. Email is lowercased.
Unsupported operations fail closed¶
All four official compilers ship kernel + portable-relational/1. In 0.17,
Polars and PySpark additionally claim string-advanced, conversion, statistics,
window /1, complex-types, complex-values, and reshape /1. Pandas and SQL
remain baseline-only. A plan requiring a profile the selected compiler does not
claim raises PipelineValidationError during planning under
portable_transform_policy="require".
Explicit window frames remain unsupported; use unframed row_number, rank,
dense_rank, lag, lead, first_value, or last_value on Polars or
PySpark. Conversion and reshape are likewise available only on those two
engines in 0.17.
What remains post-0.17¶
portable-relational-extended/1, portable-temporal-iana/1,
portable-nondeterministic/1, and portable-window/2 remain unclaimed. Keep
native callables for those profiles and for any backend/profile combination
outside the compiler matrix.