GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — On-demand feature view UDFs lose their module globals when rebuilt from the registry, so serving fails with NameError · Issue #6815 · feast-dev/feast

On-demand feature view UDFs lose their module globals when rebuilt from the registry, so serving fails with NameError #6815

New issue
Copy link
New issue
Copy link
Closed
#6816
#6891
Closed
On-demand feature view UDFs lose their module globals when rebuilt from the registry, so serving fails with NameError#6815
#6816
#6891
Copy link

Description

@Daksha1611
Daksha1611
opened on Sep 6, 2026
Issue body actions

Expected Behavior

An OnDemandFeatureView whose UDF calls a helper (or reads a constant/import) defined
at its own module level should keep working when the view is loaded back from the
registry — e.g. in a feature server, or any process that did not define the view itself.

Current Behavior

The UDF is rebuilt from its stored source text only, so every name it resolves from
its defining module is missing, and the transformation fails at call time:

NameError: name 'scaled' is not defined

PandasTransformation.from_proto / PythonTransformation.from_proto prefer
rehydrate_udf_from_source over the stored dill body. That function execs only the
function's own source
into a fresh namespace seeded with just pd/pandas/np/numpy
(sdk/python/feast/transformation/udf_rehydrate.py:102), so a module-level helper,
constant, or an import under any other alias is not present.

The exec itself still succeeds, because a function body's free variables are only
resolved when the function is called. So resolve_udf
(sdk/python/feast/transformation/udf_rehydrate.py:160) treats rehydration as
successful and never falls back to the dill body — even when that body would have
worked. The failure surfaces later, during retrieval.

Steps to reproduce

Apply an ODFV from a script/notebook, so dill serializes the UDF by value and captures
its globals:

SCALE = 100.0

def scaled(x):
    return x * SCALE

@on_demand_feature_view(
    sources=[stats, req],
    schema=[Field(name="conv_rate_scaled", dtype=Float64)],
)
def scaled_rate(inputs: pd.DataFrame) -> pd.DataFrame:
    df = pd.DataFrame()
    df["conv_rate_scaled"] = scaled(inputs["conv_rate"]) + inputs["bonus"]
    return df

fs.apply([driver, src, stats, req, scaled_rate])
fs.materialize_incremental(datetime(2026, 6, 1))

Then read it back in a separate process that only has the registry (what a feature
server does):

fs = FeatureStore(repo_path=".")
fs.get_online_features(
    features=["driver_stats:conv_rate", "scaled_rate:conv_rate_scaled"],
    entity_rows=[{"driver_id": 1001, "bonus": 1.0}],
).to_dict()
# NameError: name 'scaled' is not defined

Monkeypatching from_proto back to the previous dill-only behaviour, in the same
process and against the same registry, returns the correct result:

@classmethod
def _old_from_proto(cls, p):
    return PandasTransformation(udf=dill.loads(p.body), udf_string=p.body_text)

PandasTransformation.from_proto = _old_from_proto
# -> conv_rate_scaled=[51.0]

So the registry demonstrably holds a working body that is being ignored.

Both transformation modes are affected — a bare proto round-trip is enough to show it:

t  = PythonTransformation(udf=py_udf, udf_string=src)   # py_udf calls helper()
rt = PythonTransformation.from_proto(t.to_proto())

t.udf({"a": [1, 2]})    # {'out': [3, 6]}
rt.udf({"a": [1, 2]})   # NameError: name 'helper' is not defined

Specifications

  • Version: master @ 5ad5592390febfca60c9d88edf7daccbdd156fd6 (source-first rehydration added in fix: UDF/ODFV source rehydrate (+ Postgres / online cache) #6655)
  • Platform: Linux x86_64, Python 3.11.15
  • Subsystem: on-demand feature views / transformation serialization

Possible Solution

Make the "is this rehydrated callable usable?" check non-vacuous before accepting it.
Walk the compiled function's co_names (recursively through nested code objects) and
confirm each is resolvable in the exec namespace or in builtins; if any name is missing,
return None so resolve_udf falls back to the dill body as it did before.

A NameError-catching wrapper that retries via dill on first call would also work, but
validating up front keeps the failure out of the serving path entirely.

Worth noting this is distinct from #5620, which covers the dill-by-reference case where
the defining module genuinely is not importable. The problem here is that source-first
rehydration is chosen even when the stored dill body would succeed, so a setup that
previously served correctly now raises.

I'm happy to put up a PR for this if the validate-then-fall-back approach looks right.

Activity

  1. shuchu
    closed this as completedin #6816on Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.