Repository: feast-dev/feast

Issue:
`Feature.proto` should have a description field
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

I would like to attach a human-readable description to each field in a feature view. Since data scientists shop for individual features and not feature-views, it's useful to have documentation associated with each feature individually (rather than for the entire feature view). Persisting it here will allow us to surface them in various UIs.

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

Similar to `Entity.proto`, we should add a description field to features and store them in the registry.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

For now, we're just using a `description` tag on each feature. This works, but it's clunky. It also means that in our UI, we have to remove certain tags before showing the raw tags to the users.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion:
Yep, this seems like it would be pretty helpful

Resolving patch:
diff --git a/sdk/python/feast/feature.py b/sdk/python/feast/feature.py
--- a/sdk/python/feast/feature.py
+++ b/sdk/python/feast/feature.py
@@ -33,6 +33,7 @@ def __init__(
         self,
         name: str,
         dtype: ValueType,
+        description: str = "",
         labels: Optional[Dict[str, str]] = None,
     ):
         """Creates a Feature object."""
@@ -42,6 +43,7 @@ def __init__(
         if dtype is ValueType.UNKNOWN:
             raise ValueError(f"dtype cannot be {dtype}")
         self._dtype = dtype
+        self._description = description
         if labels is None:
             self._labels = dict()
         else:
@@ -77,6 +79,13 @@ def dtype(self) -> ValueType:
         """
         return self._dtype
 
+    @property
+    def description(self) -> str:
+        """
+        Gets the description of the feature
+        """
+        return self._description
+
     @property
     def labels(self) -> Dict[str, str]:
         """
@@ -96,6 +105,7 @@ def to_proto(self) -> FeatureSpecProto:
         return FeatureSpecProto(
             name=self.name,
             value_type=value_type,
+            description=self.description,
             tags=self.labels,
         )
 
@@ -111,6 +121,7 @@ def from_proto(cls, feature_proto: FeatureSpecProto):
         feature = cls(
             name=feature_proto.name,
             dtype=ValueType(feature_proto.value_type),
+            description=feature_proto.description,
             labels=dict(feature_proto.tags),
         )
 
diff --git a/sdk/python/feast/field.py b/sdk/python/feast/field.py
--- a/sdk/python/feast/field.py
+++ b/sdk/python/feast/field.py
@@ -30,7 +30,7 @@ class Field:
     Attributes:
         name: The name of the field.
         dtype: The type of the field, such as string or float.
-        tags: User-defined metadata in dictionary form.
+        tags (optional): User-defined metadata in dictionary form.
     """
 
     name: str
@@ -42,6 +42,7 @@ def __init__(
         *,
         name: str,
         dtype: FeastType,
+        description: str = "",
         tags: Optional[Dict[str, str]] = None,
     ):
         """
@@ -54,6 +55,7 @@ def __init__(
         """
         self.name = name
         self.dtype = dtype
+        self.description = description
         self.tags = tags or {}
 
     def __eq__(self, other):
@@ -83,7 +85,12 @@ def __str__(self):
     def to_proto(self) -> FieldProto:
         """Converts a Field object to its protobuf representation."""
         value_type = self.dtype.to_value_type()
-        return FieldProto(name=self.name, value_type=value_type.value, tags=self.tags)
+        return FieldProto(
+            name=self.name,
+            value_type=value_type.value,
+            description=self.description,
+            tags=self.tags,
+        )
 
     @classmethod
     def from_proto(cls, field_proto: FieldProto):
@@ -109,5 +116,8 @@ def from_feature(cls, feature: Feature):
             feature: Feature object to convert.
         """
         return cls(
-            name=feature.name, dtype=from_value_type(feature.dtype), tags=feature.labels
+            name=feature.name,
+            dtype=from_value_type(feature.dtype),
+            description=feature.description,
+            tags=feature.labels,
         )
diff --git a/sdk/python/feast/templates/local/feature_repo/example_repo.py b/sdk/python/feast/templates/local/feature_repo/example_repo.py
--- a/sdk/python/feast/templates/local/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/local/feature_repo/example_repo.py
@@ -45,7 +45,7 @@
     schema=[
         Field(name="conv_rate", dtype=Float32),
         Field(name="acc_rate", dtype=Float32),
-        Field(name="avg_daily_trips", dtype=Int64),
+        Field(name="avg_daily_trips", dtype=Int64, description="Average daily trips"),
     ],
     online=True,
     source=driver_stats_source,


Test patch:
diff --git a/sdk/python/tests/unit/test_feature.py b/sdk/python/tests/unit/test_feature.py
new file mode 100644
--- /dev/null
+++ b/sdk/python/tests/unit/test_feature.py
@@ -0,0 +1,29 @@
+from feast.field import Feature, Field
+from feast.types import Float32
+from feast.value_type import ValueType
+
+
+def test_feature_serialization_with_description():
+    expected_description = "Average daily trips"
+    feature = Feature(
+        name="avg_daily_trips", dtype=ValueType.FLOAT, description=expected_description
+    )
+    serialized_feature = feature.to_proto()
+
+    assert serialized_feature.description == expected_description
+
+
+def test_field_serialization_with_description():
+    expected_description = "Average daily trips"
+    field = Field(
+        name="avg_daily_trips", dtype=Float32, description=expected_description
+    )
+    feature = Feature(
+        name="avg_daily_trips", dtype=ValueType.FLOAT, description=expected_description
+    )
+
+    serialized_field = field.to_proto()
+    field_from_feature = Field.from_feature(feature)
+
+    assert serialized_field.description == expected_description
+    assert field_from_feature.description == expected_description