Skip to content

__eq__ raises TypeError on cross-type comparison instead of returning NotImplemented #6636

Description

@larrysingleton007

Expected Behavior

Comparing two Feast objects of different concrete types (e.g. FileSource vs SnowflakeSource)
returns False, per Python's data model. In particular, changing a feature view's data source
type and re-running feast apply over an existing registry succeeds.

Current Behavior

__eq__ raises TypeError on a cross-type comparison instead of returning False. Because the
apply/diff path compares registered objects against incoming ones, changing a source type and
re-applying crashes:

TypeError: Comparisons should only involve FileSource class objects.

Minimal repro:

from feast import FileSource
from feast.infra.offline_stores.snowflake_source import SnowflakeSource
a = FileSource(name="x", path="/tmp/x.parquet", timestamp_field="ts")
b = SnowflakeSource(name="x", database="D", schema="S", table="T", timestamp_field="ts")
a == b   # TypeError — should be False

Root cause: the affected __eq__ methods do
if not isinstance(other, X): raise TypeError("Comparisons should only involve X class objects.").
Python's data model requires __eq__ to return NotImplemented for an unrecognized operand (so
the interpreter can try the reflected op and fall back to identity → False), never raise.

This is systemic — the pattern appears in ~35 __eq__ methods across the object model:
DataSource and its Kafka/Request/Kinesis subclasses; all offline sources (file,
snowflake, bigquery, redshift, and the ray/couchbase/trino/oracle/athena/mssql/
postgres contrib sources); plus Entity, FeatureView, BaseFeatureView, OnDemandFeatureView,
StreamFeatureView, FeatureService, SavedDataset, Project, ProjectMetadata, the four
transformation/* classes, Aggregation, LabelView, Permission, and the RoleBasedPolicy /
GroupBasedPolicy / NamespaceBasedPolicy / CombinedGroupNamespacePolicy policies.
PushSource.__eq__ already does the right thing (return False), so the correct pattern is
already in-tree.

Steps to reproduce

Run the snippet above, or: define a feature view on a FileSource and feast apply; change that
feature view's source to a SnowflakeSource (same name) and feast apply again → crash.

Specifications

  • Version: 0.64.0 and 0.65.0 / master (verified; pattern unchanged)
  • Subsystem: core object model (sdk/python/feast)

Possible Solution

Replace each raise TypeError("Comparisons should only involve …") in these __eq__ methods with
return False, matching PushSource.__eq__, which already handles a cross-type comparison this
way. PushSource is the one __eq__ that does this correctly today, and there is no
return NotImplemented anywhere in the codebase, so return False keeps a single consistent
convention rather than introducing a new one. (NotImplemented would be the stricter data-model
answer and would also let a reflected/matcher __eq__ participate, but the codebase already
forgoes that everywhere.)

Add a unit test asserting cross-type equality returns False (not a raise). This can be one sweep
across all 35 sites (uniform and mechanical) or scoped to the DataSource hierarchy (where the
crash reproduces) with a follow-up for the rest — happy to do whichever the maintainers prefer.
PR incoming.

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