Skip to content

Commit aac8b0c

Browse files
committed
Add more documentation and make test involving datetime reproducible
1 parent 03600f6 commit aac8b0c

4 files changed

Lines changed: 52 additions & 39 deletions

File tree

sdk/python/feast/sdk/client.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,23 @@ def create_dataset(
269269
return DatasetInfo(resp.datasetInfo.name, resp.datasetInfo.tableUrl)
270270

271271
def _ensure_valid_timestamp_in_dataframe(self, dataframe, timestamp_column=None):
272+
"""
273+
Helper method to ensure the DataFrame has valid column containing feature timestamps.
274+
275+
If timestamp_column is set to "None", a new column "_event_timestamp" will be
276+
created with value equal to the current time. All the features in the DataFrame
277+
will then have the same timestamp value of current time.
278+
279+
Args:
280+
dataframe (pandas.DataFrame): DataFrame containing features
281+
timestamp_column (:obj:`str`, optional): Column in the DataFrame representing feature timestamp
282+
283+
Returns:
284+
The "timestamp_column" passed in as argument or the newly created
285+
"timestamp_column" if the argument is "None". The return value can be used
286+
by the caller to know the "timestamp_column" created by this method, if any.
287+
288+
"""
272289
if timestamp_column is None:
273290
self.logger.info(
274291
'No "timestamp_column" is specified, Feast will assign current '
@@ -314,7 +331,7 @@ def load_features_from_dataframe(
314331
required to modify the DataFrame metadata (e.g. removing/renaming the
315332
columns) before calling this method.
316333
317-
This method will publish all features to the message and wait until
334+
This method will publish all features to the message broker and wait until
318335
it receives confirmation from the broker that the features are received
319336
successfully. If timeout is set to "None" and the broker gets
320337
disconnected during transfer, the process may seem to hang. It is therefore

sdk/python/feast/sdk/utils/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
def dtype_to_feast_value_attr(dtype):
7070
return DTYPE_TO_FEAST_VALUE_ATTR_NAME[dtype.__str__()]
7171

72+
7273
def dtype_to_value_type(dtype):
7374
"""Returns the equivalent feast valueType for the given dtype
7475
@@ -79,3 +80,8 @@ def dtype_to_value_type(dtype):
7980
feast.types.ValueType2.ValueType: equivalent feast valuetype
8081
"""
8182
return DTYPE_TO_VALUE_TYPE_MAPPING[dtype.__str__()]
83+
84+
85+
# TODO: to pass test_importer
86+
def dtype_to_feast_value_type(dtype):
87+
pass

sdk/python/requirements-test.txt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
google-api-core>=1.7.0
2-
google-auth>=1.6.0
3-
google-cloud-bigquery>=1.8.0
4-
google-cloud-bigquery-storage>=0.5.0
5-
google-cloud-storage>=1.13.0
1+
google-api-core==1.*
2+
google-auth==1.*
3+
google-cloud-bigquery==1.*
4+
google-cloud-bigquery-storage==0.*
5+
google-cloud-storage==1.*
66
google-resumable-media==0.3.1
7-
googleapis-common-protos>=1.5.5
8-
grpcio>=1.16.1
7+
googleapis-common-protos==1.*
8+
grpcio==1.*
99
numpy
10-
pandas>=0.24.0
11-
protobuf>=3.0.0
10+
pandas==0.*
11+
protobuf==3.*
1212
pytest
1313
pytest-mock
1414
PyYAML
15-
fastavro>=0.21.23
15+
fastavro==0.21.*
16+
grpcio-testing==1.*

sdk/python/tests/sdk/test_client.py

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
# limitations under the License.
1414

1515
from datetime import datetime
16+
from unittest import mock
1617
from unittest.mock import MagicMock
1718

1819
import grpc
1920
import grpc_testing
2021
import numpy as np
2122
import pandas as pd
2223
import pytest
24+
from freezegun import freeze_time
2325
from google.protobuf.timestamp_pb2 import Timestamp
2426
from pandas.util.testing import assert_frame_equal
2527

@@ -548,11 +550,16 @@ def _create_bq_spec(self, id, project, dataset):
548550
def test_ensure_valid_timestamp_in_dataframe_with_no_timestamp(
549551
self, mock_feast_client, dataframe, expected
550552
):
551-
timestamp_column = mock_feast_client._ensure_valid_timestamp_in_dataframe(
553+
timestamp_column_created = mock_feast_client._ensure_valid_timestamp_in_dataframe(
552554
dataframe
553555
)
554-
pd.testing.assert_frame_equal(dataframe, expected, check_less_precise=0)
555-
assert timestamp_column == "_event_timestamp"
556+
assert dataframe.columns.values.tolist() == expected.columns.values.tolist()
557+
assert timestamp_column_created == "_event_timestamp"
558+
timestamp_second_difference = int(
559+
(expected["_event_timestamp"] - dataframe["_event_timestamp"]).mean()
560+
/ np.timedelta64(1, "s")
561+
)
562+
assert timestamp_second_difference < 3
556563

557564
@pytest.mark.parametrize(
558565
"dataframe, expected",
@@ -674,9 +681,9 @@ def test_ensure_valid_timestamp_in_dataframe_with_invalid_timestamp_value(
674681
pd.DataFrame({"entity_id": [1, 3, 4, 1], "feature_1": [1, 2, 5, 9]}),
675682
pd.DataFrame(
676683
{
677-
"entity_id": [1, 3],
678-
"feature_1": [1, 2],
679-
"feature_2": ["text", np.NaN],
684+
"entity_id": [1, 3, 4],
685+
"feature_1": [1, 7, np.NaN],
686+
"feature_2": ["text", np.NaN, "text"],
680687
}
681688
),
682689
],
@@ -692,29 +699,11 @@ def test_load_features_from_dataframe(self, mock_feast_client, dataframe):
692699

693700
@pytest.mark.parametrize(
694701
"dataframe",
695-
[
696-
pd.DataFrame({"entity_id": [1, 3, 4, 1], "feature_1": [1, 2, 5, 9]}),
697-
pd.DataFrame(
698-
{
699-
"entity_id": [1, 3, 4],
700-
"feature_1": [1, 7, np.NaN],
701-
"feature_2": ["text", np.NaN, "text"],
702-
}
703-
),
704-
],
702+
[pd.DataFrame({"entity_id": [1, 3, 4, 1], "feature_1": [1, 2, 5, 9]})],
705703
)
706-
def test_load_features_from_dataframe_with_inconsiste(self, mock_feast_client, dataframe):
707-
mock_feast_client.load_features_from_dataframe(
708-
dataframe=dataframe, entity_name="entity", entity_key_column="entity_id"
709-
)
710-
mock_feast_client._core_service_stub.ApplyEntity.assert_called_with(
711-
EntitySpec(name="entity", description="", tags=[])
712-
)
713-
mock_feast_client._core_service_stub.ApplyFeatures.assert_called()
714-
715-
def test_load_features_from_dataframe_with_non_existent_entity_key_column(self):
716-
dataframe = pd.DataFrame({"entity_id": [1, 3, 4, 1], "feature_1": [1, 2, 5, 9]})
717-
mock_feast_client = Client()
704+
def test_load_features_from_dataframe_with_non_existent_entity_key_column(
705+
self, mock_feast_client, dataframe
706+
):
718707
with pytest.raises(ValueError):
719708
mock_feast_client.load_features_from_dataframe(
720709
dataframe=dataframe,

0 commit comments

Comments
 (0)