|
6 | 6 | object model in one place; the per-type tests for ``DataSource``, ``Entity``, |
7 | 7 | ``LabelView``, and ``RoleBasedPolicy`` live in their own modules. |
8 | 8 |
|
9 | | -The contrib offline sources (athena, couchbase, mssql, oracle, postgres, ray, |
| 9 | +Most contrib offline sources (athena, couchbase, mssql, oracle, postgres, ray, |
10 | 10 | trino) and the optional-dependency transformations are intentionally omitted: |
11 | 11 | the unit environment does not install their drivers, so they cannot be imported |
12 | | -here. Their ``__eq__`` follows the identical, mechanical pattern. |
| 12 | +here. Their ``__eq__`` follows the identical, mechanical pattern. SparkSource |
| 13 | +is the exception — its module imports without pyspark, and its ``__eq__`` |
| 14 | +accesses spark-only attributes after the shared ``DataSource`` base check, so |
| 15 | +it gets a dedicated cross-subclass test below. |
13 | 16 | """ |
14 | 17 |
|
15 | 18 | from datetime import timedelta |
|
18 | 21 |
|
19 | 22 | from feast import Entity, FeatureService, FeatureView, Project |
20 | 23 | from feast.aggregation import Aggregation |
21 | | -from feast.data_format import ProtoFormat |
| 24 | +from feast.data_format import ParquetFormat, ProtoFormat |
22 | 25 | from feast.data_source import KafkaSource, KinesisSource, RequestSource |
| 26 | +from feast.feature import Feature |
23 | 27 | from feast.field import Field |
24 | 28 | from feast.infra.offline_stores.bigquery_source import BigQuerySource |
25 | 29 | from feast.infra.offline_stores.file_source import FileSource |
|
33 | 37 | RoleBasedPolicy, |
34 | 38 | ) |
35 | 39 | from feast.types import Int64 |
| 40 | +from feast.value_type import ValueType |
36 | 41 |
|
37 | 42 |
|
38 | 43 | def _instances(): |
39 | 44 | """One instance of each importable type touched by the __eq__ sweep.""" |
40 | 45 | return { |
41 | 46 | "Entity": Entity(name="e"), |
| 47 | + "Feature": Feature(name="f", dtype=ValueType.INT64), |
| 48 | + "ParquetFormat": ParquetFormat(), |
| 49 | + "ProtoFormat": ProtoFormat("com.example.Msg"), |
42 | 50 | "Project": Project(name="proj"), |
43 | 51 | "Aggregation": Aggregation(column="c", function="sum"), |
44 | 52 | "Permission": Permission(name="perm"), |
@@ -92,3 +100,23 @@ def test_eq_cross_type_returns_false(name, obj): |
92 | 100 | assert (obj != object()) is True |
93 | 101 | # The isinstance guard must not break same-object equality. |
94 | 102 | assert (obj == obj) is True |
| 103 | + |
| 104 | + |
| 105 | +def test_spark_source_vs_file_source_eq(): |
| 106 | + # Reported on #6636: swapping a FeatureView's source from FileSource to |
| 107 | + # SparkSource. The two sources share the base DataSource fields, so |
| 108 | + # SparkSource.__eq__ used to pass the base check and then raise |
| 109 | + # AttributeError on FileSource's missing `table`. Both directions must |
| 110 | + # simply compare False. |
| 111 | + spark_source = pytest.importorskip( |
| 112 | + "feast.infra.offline_stores.contrib.spark_offline_store.spark_source" |
| 113 | + ) |
| 114 | + SparkSource = spark_source.SparkSource |
| 115 | + |
| 116 | + spark = SparkSource(name="src", table="t", timestamp_field="ts") |
| 117 | + file = FileSource(name="src", path="/tmp/x.parquet", timestamp_field="ts") |
| 118 | + |
| 119 | + assert (spark == file) is False |
| 120 | + assert (file == spark) is False |
| 121 | + assert (spark == object()) is False |
| 122 | + assert (spark == SparkSource(name="src", table="t", timestamp_field="ts")) is True |
0 commit comments