From 68a87379c42567f338d86cb2be90520cc6d4bfb6 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Thu, 7 Sep 2023 02:09:31 -0700 Subject: [PATCH 01/41] fix: Fix warnings from deprecated paths and update default log level (#3757) --- sdk/python/feast/cli.py | 6 ++-- .../feast/infra/offline_stores/file_source.py | 2 +- sdk/python/feast/proto_json.py | 8 ++--- sdk/python/feast/ui_server.py | 31 ++++++++++--------- .../requirements/py3.10-ci-requirements.txt | 3 ++ .../requirements/py3.10-requirements.txt | 6 +++- .../requirements/py3.8-ci-requirements.txt | 4 ++- .../requirements/py3.8-requirements.txt | 7 +++-- .../requirements/py3.9-ci-requirements.txt | 7 ++++- .../requirements/py3.9-requirements.txt | 10 ++++-- setup.py | 2 ++ 11 files changed, 56 insertions(+), 30 deletions(-) diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 8c2c326b59..53c346b6eb 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -18,10 +18,10 @@ from typing import List, Optional import click -import pkg_resources import yaml from colorama import Fore, Style from dateutil import parser +from importlib_metadata import version as importlib_version from pygments import formatters, highlight, lexers from feast import utils @@ -68,7 +68,7 @@ def format_options(self, ctx: click.Context, formatter: click.HelpFormatter): ) @click.option( "--log-level", - default="info", + default="warning", help="The logging level. One of DEBUG, INFO, WARNING, ERROR, and CRITICAL (case-insensitive).", ) @click.option( @@ -122,7 +122,7 @@ def version(): """ Display Feast SDK version """ - print(f'Feast SDK Version: "{pkg_resources.get_distribution("feast")}"') + print(f'Feast SDK Version: "{importlib_version("feast")}"') @cli.command() diff --git a/sdk/python/feast/infra/offline_stores/file_source.py b/sdk/python/feast/infra/offline_stores/file_source.py index d8522fb445..ac824b359f 100644 --- a/sdk/python/feast/infra/offline_stores/file_source.py +++ b/sdk/python/feast/infra/offline_stores/file_source.py @@ -158,7 +158,7 @@ def get_table_column_names_and_types( # Adding support for different file format path # based on S3 filesystem if filesystem is None: - schema = ParquetDataset(path).schema + schema = ParquetDataset(path, use_legacy_dataset=False).schema if hasattr(schema, "names") and hasattr(schema, "types"): # Newer versions of pyarrow doesn't have this method, # but this field is good enough. diff --git a/sdk/python/feast/proto_json.py b/sdk/python/feast/proto_json.py index a0a4dce86b..41d2afa55a 100644 --- a/sdk/python/feast/proto_json.py +++ b/sdk/python/feast/proto_json.py @@ -1,13 +1,13 @@ import uuid from typing import Any, Callable, Type -import pkg_resources from google.protobuf.json_format import ( # type: ignore _WKTJSONMETHODS, ParseError, _Parser, _Printer, ) +from importlib_metadata import version as importlib_version from packaging import version from feast.protos.feast.serving.ServingService_pb2 import FeatureList @@ -118,7 +118,7 @@ def from_json_object_updated( # https://github.com/feast-dev/feast/issues/2484 Certain feast users need a higher version of protobuf but the # parameters of `from_json_object` changes in feast 3.20.1. This change gives users flexibility to use earlier versions. - current_version = pkg_resources.get_distribution("protobuf").version + current_version = importlib_version("protobuf") if version.parse(current_version) < version.parse("3.20"): _patch_proto_json_encoding(Value, to_json_object, from_json_object) else: @@ -168,7 +168,7 @@ def from_json_object( # https://github.com/feast-dev/feast/issues/2484 Certain feast users need a higher version of protobuf but the # parameters of `from_json_object` changes in feast 3.20.1. This change gives users flexibility to use earlier versions. - current_version = pkg_resources.get_distribution("protobuf").version + current_version = importlib_version("protobuf") if version.parse(current_version) < version.parse("3.20"): _patch_proto_json_encoding(RepeatedValue, to_json_object, from_json_object) else: @@ -221,7 +221,7 @@ def from_json_object_updated( # https://github.com/feast-dev/feast/issues/2484 Certain feast users need a higher version of protobuf but the # parameters of `from_json_object` changes in feast 3.20.1. This change gives users flexibility to use earlier versions. - current_version = pkg_resources.get_distribution("protobuf").version + current_version = importlib_version("protobuf") if version.parse(current_version) < version.parse("3.20"): _patch_proto_json_encoding(FeatureList, to_json_object, from_json_object) else: diff --git a/sdk/python/feast/ui_server.py b/sdk/python/feast/ui_server.py index e750f280ad..9950eea070 100644 --- a/sdk/python/feast/ui_server.py +++ b/sdk/python/feast/ui_server.py @@ -2,7 +2,7 @@ import threading from typing import Callable, Optional -import pkg_resources +import importlib_resources import uvicorn from fastapi import FastAPI, Response from fastapi.middleware.cors import CORSMiddleware @@ -51,20 +51,21 @@ def shutdown_event(): async_refresh() - ui_dir = pkg_resources.resource_filename(__name__, "ui/build/") - # Initialize with the projects-list.json file - with open(ui_dir + "projects-list.json", mode="w") as f: - projects_dict = { - "projects": [ - { - "name": "Project", - "description": "Test project", - "id": project_id, - "registryPath": f"{root_path}/registry", - } - ] - } - f.write(json.dumps(projects_dict)) + ui_dir_ref = importlib_resources.files(__name__) / "ui/build/" + with importlib_resources.as_file(ui_dir_ref) as ui_dir: + # Initialize with the projects-list.json file + with ui_dir.joinpath("projects-list.json").open(mode="w") as f: + projects_dict = { + "projects": [ + { + "name": "Project", + "description": "Test project", + "id": project_id, + "registryPath": f"{root_path}/registry", + } + ] + } + f.write(json.dumps(projects_dict)) @app.get("/registry") def read_registry(): diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index a59553b4ac..3192a15472 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -366,7 +366,10 @@ imagesize==1.4.1 importlib-metadata==6.8.0 # via # dask + # feast (setup.py) # great-expectations +importlib-resources==6.0.1 + # via feast (setup.py) iniconfig==2.0.0 # via pytest ipykernel==6.25.2 diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 4140bea9d0..0ebc485d6d 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -82,7 +82,11 @@ idna==3.4 # httpx # requests importlib-metadata==6.8.0 - # via dask + # via + # dask + # feast (setup.py) +importlib-resources==6.0.1 + # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) jsonschema==4.19.0 diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index b24172e890..a5acaf55d4 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -370,6 +370,7 @@ importlib-metadata==6.8.0 # via # build # dask + # feast (setup.py) # great-expectations # jupyter-client # jupyter-lsp @@ -379,6 +380,7 @@ importlib-metadata==6.8.0 # sphinx importlib-resources==6.0.1 # via + # feast (setup.py) # jsonschema # jsonschema-specifications # jupyterlab @@ -533,7 +535,7 @@ mypy-extensions==1.0.0 # via # black # mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) mysqlclient==2.2.0 # via feast (setup.py) diff --git a/sdk/python/requirements/py3.8-requirements.txt b/sdk/python/requirements/py3.8-requirements.txt index 636f886133..cccff07b7d 100644 --- a/sdk/python/requirements/py3.8-requirements.txt +++ b/sdk/python/requirements/py3.8-requirements.txt @@ -82,9 +82,12 @@ idna==3.4 # httpx # requests importlib-metadata==6.8.0 - # via dask + # via + # dask + # feast (setup.py) importlib-resources==6.0.1 # via + # feast (setup.py) # jsonschema # jsonschema-specifications jinja2==3.1.2 @@ -105,7 +108,7 @@ mypy==1.5.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) numpy==1.24.4 # via diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index ad19f9e8bd..121a768abc 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -367,6 +367,7 @@ importlib-metadata==6.8.0 # via # build # dask + # feast (setup.py) # great-expectations # jupyter-client # jupyter-lsp @@ -374,6 +375,8 @@ importlib-metadata==6.8.0 # jupyterlab-server # nbconvert # sphinx +importlib-resources==6.0.1 + # via feast (setup.py) iniconfig==2.0.0 # via pytest ipykernel==6.25.2 @@ -1080,7 +1083,9 @@ xmltodict==0.13.0 yarl==1.9.2 # via aiohttp zipp==3.16.2 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 7d30fd3452..a95bcd3524 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -82,7 +82,11 @@ idna==3.4 # httpx # requests importlib-metadata==6.8.0 - # via dask + # via + # dask + # feast (setup.py) +importlib-resources==6.0.1 + # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) jsonschema==4.19.0 @@ -214,7 +218,9 @@ watchfiles==0.20.0 websockets==11.0.3 # via uvicorn zipp==3.16.2 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/setup.py b/setup.py index 573ab54d51..cd202c9c06 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,8 @@ "bowler", # Needed for automatic repo upgrades # FastAPI does not correctly pull starlette dependency on httpx see thread(https://github.com/tiangolo/fastapi/issues/5656). "httpx>=0.23.3", + "importlib-resources>=6.0.0,<7", + "importlib_metadata>=6.8.0,<7" ] GCP_REQUIRED = [ From 8717e9bf0fd253454982b9c9e9527c4d41906e9c Mon Sep 17 00:00:00 2001 From: Nikita Demashov Date: Thu, 7 Sep 2023 21:24:55 +0300 Subject: [PATCH 02/41] fix: Set keepalives_idle None by default (#3756) set keepalives_idle None by default Co-authored-by: Nikita Demashov --- sdk/python/feast/infra/utils/postgres/postgres_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/utils/postgres/postgres_config.py b/sdk/python/feast/infra/utils/postgres/postgres_config.py index 9fbaed474d..a4ebb456ef 100644 --- a/sdk/python/feast/infra/utils/postgres/postgres_config.py +++ b/sdk/python/feast/infra/utils/postgres/postgres_config.py @@ -25,4 +25,4 @@ class PostgreSQLConfig(FeastConfigBaseModel): sslkey_path: Optional[StrictStr] = None sslcert_path: Optional[StrictStr] = None sslrootcert_path: Optional[StrictStr] = None - keepalives_idle: int = 0 + keepalives_idle: Optional[int] = None From 774ed33a067bf9bf087520325b72f4f4d194106a Mon Sep 17 00:00:00 2001 From: Danny C Date: Thu, 7 Sep 2023 13:26:00 -0700 Subject: [PATCH 03/41] fix: Pin numpy version to avoid spammy deprecation messages Signed-off-by: Danny C --- docs/SUMMARY.md | 1 + docs/reference/online-stores/README.md | 4 +++ .../requirements/py3.10-ci-requirements.txt | 26 +++++++++---------- .../requirements/py3.10-requirements.txt | 12 ++++----- .../requirements/py3.8-requirements.txt | 10 +++---- .../requirements/py3.9-ci-requirements.txt | 26 +++++++++---------- .../requirements/py3.9-requirements.txt | 12 ++++----- setup.py | 2 +- 8 files changed, 49 insertions(+), 44 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 9b22d1e286..c80ded2adf 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -90,6 +90,7 @@ * [SQLite](reference/online-stores/sqlite.md) * [Snowflake](reference/online-stores/snowflake.md) * [Redis](reference/online-stores/redis.md) + * [Dragonfly](reference/online-stores/dragonfly.md) * [Datastore](reference/online-stores/datastore.md) * [DynamoDB](reference/online-stores/dynamodb.md) * [Bigtable](reference/online-stores/bigtable.md) diff --git a/docs/reference/online-stores/README.md b/docs/reference/online-stores/README.md index 2fdfd50f7c..f86e6f6a1d 100644 --- a/docs/reference/online-stores/README.md +++ b/docs/reference/online-stores/README.md @@ -18,6 +18,10 @@ Please see [Online Store](../../getting-started/architecture-and-components/onli [redis.md](redis.md) {% endcontent-ref %} +{% content-ref url="dragonfly.md" %} +[dragonfly.md](dragonfly.md) +{% endcontent-ref %} + {% content-ref url="datastore.md" %} [datastore.md](datastore.md) {% endcontent-ref %} diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 3192a15472..cf1f4e938e 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -58,7 +58,7 @@ attrs==23.1.0 # referencing avro==1.10.0 # via feast (setup.py) -azure-core==1.29.3 +azure-core==1.29.4 # via # adlfs # azure-identity @@ -86,11 +86,11 @@ black==22.12.0 # via feast (setup.py) bleach==6.0.0 # via nbconvert -boto3==1.28.42 +boto3==1.28.43 # via # feast (setup.py) # moto -botocore==1.31.42 +botocore==1.31.43 # via # boto3 # moto @@ -172,7 +172,7 @@ dask==2023.9.1 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery -debugpy==1.6.7.post1 +debugpy==1.7.0 # via ipykernel decorator==5.1.1 # via @@ -208,7 +208,7 @@ executing==1.2.0 # via stack-data fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.8.3 # via # feast (setup.py) # pandavro @@ -307,7 +307,7 @@ great-expectations==0.15.50 # via feast (setup.py) grpc-google-iam-v1==0.12.6 # via google-cloud-bigtable -grpcio==1.57.0 +grpcio==1.58.0 # via # feast (setup.py) # google-api-core @@ -319,15 +319,15 @@ grpcio==1.57.0 # grpcio-status # grpcio-testing # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.58.0 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.58.0 # via feast (setup.py) -grpcio-status==1.57.0 +grpcio-status==1.58.0 # via google-api-core -grpcio-testing==1.57.0 +grpcio-testing==1.58.0 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.58.0 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -545,7 +545,7 @@ notebook-shim==0.2.3 # via # jupyterlab # notebook -numpy==1.25.2 +numpy==1.24.4 # via # altair # db-dtypes @@ -723,7 +723,7 @@ pyproject-hooks==1.0.0 # via build pyspark==3.4.1 # via feast (setup.py) -pytest==7.4.1 +pytest==7.4.2 # via # feast (setup.py) # pytest-benchmark diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 0ebc485d6d..9ee910bf00 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -44,7 +44,7 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.8.3 # via # feast (setup.py) # pandavro @@ -52,17 +52,17 @@ fissix==21.11.13 # via bowler fsspec==2023.9.0 # via dask -grpcio==1.57.0 +grpcio==1.58.0 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.58.0 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.58.0 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.58.0 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -107,7 +107,7 @@ mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.1 # via feast (setup.py) -numpy==1.25.2 +numpy==1.24.4 # via # feast (setup.py) # pandas diff --git a/sdk/python/requirements/py3.8-requirements.txt b/sdk/python/requirements/py3.8-requirements.txt index cccff07b7d..4011ce804f 100644 --- a/sdk/python/requirements/py3.8-requirements.txt +++ b/sdk/python/requirements/py3.8-requirements.txt @@ -44,7 +44,7 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.8.3 # via # feast (setup.py) # pandavro @@ -52,17 +52,17 @@ fissix==21.11.13 # via bowler fsspec==2023.9.0 # via dask -grpcio==1.57.0 +grpcio==1.58.0 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.58.0 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.58.0 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.58.0 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 121a768abc..3a453153e5 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -58,7 +58,7 @@ attrs==23.1.0 # referencing avro==1.10.0 # via feast (setup.py) -azure-core==1.29.3 +azure-core==1.29.4 # via # adlfs # azure-identity @@ -86,11 +86,11 @@ black==22.12.0 # via feast (setup.py) bleach==6.0.0 # via nbconvert -boto3==1.28.42 +boto3==1.28.43 # via # feast (setup.py) # moto -botocore==1.31.42 +botocore==1.31.43 # via # boto3 # moto @@ -172,7 +172,7 @@ dask==2023.9.1 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery -debugpy==1.6.7.post1 +debugpy==1.7.0 # via ipykernel decorator==5.1.1 # via @@ -208,7 +208,7 @@ executing==1.2.0 # via stack-data fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.8.3 # via # feast (setup.py) # pandavro @@ -307,7 +307,7 @@ great-expectations==0.15.50 # via feast (setup.py) grpc-google-iam-v1==0.12.6 # via google-cloud-bigtable -grpcio==1.57.0 +grpcio==1.58.0 # via # feast (setup.py) # google-api-core @@ -319,15 +319,15 @@ grpcio==1.57.0 # grpcio-status # grpcio-testing # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.58.0 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.58.0 # via feast (setup.py) -grpcio-status==1.57.0 +grpcio-status==1.58.0 # via google-api-core -grpcio-testing==1.57.0 +grpcio-testing==1.58.0 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.58.0 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -552,7 +552,7 @@ notebook-shim==0.2.3 # via # jupyterlab # notebook -numpy==1.25.2 +numpy==1.24.4 # via # altair # db-dtypes @@ -730,7 +730,7 @@ pyproject-hooks==1.0.0 # via build pyspark==3.4.1 # via feast (setup.py) -pytest==7.4.1 +pytest==7.4.2 # via # feast (setup.py) # pytest-benchmark diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index a95bcd3524..432ac4cbe6 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -44,7 +44,7 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.8.3 # via # feast (setup.py) # pandavro @@ -52,17 +52,17 @@ fissix==21.11.13 # via bowler fsspec==2023.9.0 # via dask -grpcio==1.57.0 +grpcio==1.58.0 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.58.0 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.58.0 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.58.0 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -107,7 +107,7 @@ mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.1.0 # via feast (setup.py) -numpy==1.25.2 +numpy==1.24.4 # via # feast (setup.py) # pandas diff --git a/setup.py b/setup.py index cd202c9c06..f7b1ff0417 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ "Jinja2>=2,<4", "jsonschema", "mmh3", - "numpy>=1.22,<3", + "numpy>=1.22,<1.25", "pandas>=1.4.3,<2", # For some reason pandavro higher than 1.5.* only support pandas less than 1.3. "pandavro~=1.5.0", From 709c7098dc28a35dd488f5079d3787cf1f74ec03 Mon Sep 17 00:00:00 2001 From: Jiwon Park Date: Wed, 13 Sep 2023 03:29:16 +0900 Subject: [PATCH 04/41] feat: Apply cache to load proto registry for performance (#3702) * fix: Remove unused parameter Signed-off-by: Jiwon Park * feat: Apply cache to load proto registry for performance Signed-off-by: Jiwon Park * fix: Fix update key when cache missed Signed-off-by: phil-park --------- Signed-off-by: Jiwon Park Signed-off-by: phil-park --- .../infra/registry/proto_registry_utils.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/registry/proto_registry_utils.py b/sdk/python/feast/infra/registry/proto_registry_utils.py index c7eeea0f82..e93f513b69 100644 --- a/sdk/python/feast/infra/registry/proto_registry_utils.py +++ b/sdk/python/feast/infra/registry/proto_registry_utils.py @@ -1,4 +1,5 @@ import uuid +from functools import wraps from typing import List, Optional from feast import usage @@ -23,6 +24,26 @@ from feast.stream_feature_view import StreamFeatureView +def registry_proto_cache(func): + cache_key = None + cache_value = None + + @wraps(func) + def wrapper(registry_proto: RegistryProto, project: str): + nonlocal cache_key, cache_value + + key = tuple([id(registry_proto), registry_proto.version_id, project]) + + if key == cache_key: + return cache_value + else: + cache_value = func(registry_proto, project) + cache_key = key + return cache_value + + return wrapper + + def init_project_metadata(cached_registry_proto: RegistryProto, project: str): new_project_uuid = f"{uuid.uuid4()}" usage.set_current_project_uuid(new_project_uuid) @@ -137,8 +158,9 @@ def get_validation_reference( raise ValidationReferenceNotFound(name, project=project) +@registry_proto_cache def list_feature_services( - registry_proto: RegistryProto, project: str, allow_cache: bool = False + registry_proto: RegistryProto, project: str ) -> List[FeatureService]: feature_services = [] for feature_service_proto in registry_proto.feature_services: @@ -147,6 +169,7 @@ def list_feature_services( return feature_services +@registry_proto_cache def list_feature_views( registry_proto: RegistryProto, project: str ) -> List[FeatureView]: @@ -157,6 +180,7 @@ def list_feature_views( return feature_views +@registry_proto_cache def list_request_feature_views( registry_proto: RegistryProto, project: str ) -> List[RequestFeatureView]: @@ -169,6 +193,7 @@ def list_request_feature_views( return feature_views +@registry_proto_cache def list_stream_feature_views( registry_proto: RegistryProto, project: str ) -> List[StreamFeatureView]: @@ -181,6 +206,7 @@ def list_stream_feature_views( return stream_feature_views +@registry_proto_cache def list_on_demand_feature_views( registry_proto: RegistryProto, project: str ) -> List[OnDemandFeatureView]: @@ -193,6 +219,7 @@ def list_on_demand_feature_views( return on_demand_feature_views +@registry_proto_cache def list_entities(registry_proto: RegistryProto, project: str) -> List[Entity]: entities = [] for entity_proto in registry_proto.entities: @@ -201,6 +228,7 @@ def list_entities(registry_proto: RegistryProto, project: str) -> List[Entity]: return entities +@registry_proto_cache def list_data_sources(registry_proto: RegistryProto, project: str) -> List[DataSource]: data_sources = [] for data_source_proto in registry_proto.data_sources: @@ -209,6 +237,7 @@ def list_data_sources(registry_proto: RegistryProto, project: str) -> List[DataS return data_sources +@registry_proto_cache def list_saved_datasets( registry_proto: RegistryProto, project: str ) -> List[SavedDataset]: @@ -219,6 +248,7 @@ def list_saved_datasets( return saved_datasets +@registry_proto_cache def list_validation_references( registry_proto: RegistryProto, project: str ) -> List[ValidationReference]: @@ -231,6 +261,7 @@ def list_validation_references( return validation_references +@registry_proto_cache def list_project_metadata( registry_proto: RegistryProto, project: str ) -> List[ProjectMetadata]: From fa600fe3c4b1d5fdd383a9367511ac5616ee7a32 Mon Sep 17 00:00:00 2001 From: snowron <35639836+snowron@users.noreply.github.com> Date: Tue, 12 Sep 2023 22:13:30 +0300 Subject: [PATCH 05/41] feat: Add materialize and materialize-incremental rest endpoints (#3761) * resolve #3760 Signed-off-by: snowron * format feature_server.py Signed-off-by: snowron --------- Signed-off-by: snowron --- sdk/python/feast/feature_server.py | 43 +++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/feature_server.py b/sdk/python/feast/feature_server.py index 3abca1d6e8..7c638dd248 100644 --- a/sdk/python/feast/feature_server.py +++ b/sdk/python/feast/feature_server.py @@ -1,9 +1,11 @@ import json import traceback import warnings +from typing import List, Optional import gunicorn.app.base import pandas as pd +from dateutil import parser from fastapi import FastAPI, HTTPException, Request, Response, status from fastapi.logger import logger from fastapi.params import Depends @@ -11,7 +13,7 @@ from pydantic import BaseModel import feast -from feast import proto_json +from feast import proto_json, utils from feast.data_source import PushMode from feast.errors import PushSourceNotFoundException from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest @@ -31,6 +33,17 @@ class PushFeaturesRequest(BaseModel): to: str = "online" +class MaterializeRequest(BaseModel): + start_ts: str + end_ts: str + feature_views: Optional[List[str]] = None + + +class MaterializeIncrementalRequest(BaseModel): + end_ts: str + feature_views: Optional[List[str]] = None + + def get_app(store: "feast.FeatureStore"): proto_json.patch() @@ -134,6 +147,34 @@ def write_to_online_store(body=Depends(get_body)): def health(): return Response(status_code=status.HTTP_200_OK) + @app.post("/materialize") + def materialize(body=Depends(get_body)): + try: + request = MaterializeRequest(**json.loads(body)) + store.materialize( + utils.make_tzaware(parser.parse(request.start_ts)), + utils.make_tzaware(parser.parse(request.end_ts)), + request.feature_views, + ) + except Exception as e: + # Print the original exception on the server side + logger.exception(traceback.format_exc()) + # Raise HTTPException to return the error message to the client + raise HTTPException(status_code=500, detail=str(e)) + + @app.post("/materialize-incremental") + def materialize_incremental(body=Depends(get_body)): + try: + request = MaterializeIncrementalRequest(**json.loads(body)) + store.materialize_incremental( + utils.make_tzaware(parser.parse(request.end_ts)), request.feature_views + ) + except Exception as e: + # Print the original exception on the server side + logger.exception(traceback.format_exc()) + # Raise HTTPException to return the error message to the client + raise HTTPException(status_code=500, detail=str(e)) + return app From 6a728fe66db0286ea10301d1fe693d6dcba4e4f4 Mon Sep 17 00:00:00 2001 From: Nicholas Zeolla Date: Wed, 13 Sep 2023 20:17:40 +0100 Subject: [PATCH 06/41] feat: Add support for `table_create_disposition` in bigquery job for offline store (#3762) * Add bigquery table create disposition to offline store Signed-off-by: Nick Zeolla * linting Signed-off-by: Nick Zeolla --------- Signed-off-by: Nick Zeolla --- sdk/python/feast/infra/offline_stores/bigquery.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 5913b60f62..86c587c7fd 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -19,7 +19,7 @@ import pandas as pd import pyarrow import pyarrow.parquet -from pydantic import StrictStr, validator +from pydantic import ConstrainedStr, StrictStr, validator from pydantic.typing import Literal from tenacity import Retrying, retry_if_exception_type, stop_after_delay, wait_fixed @@ -72,6 +72,13 @@ def get_http_client_info(): return http_client_info.ClientInfo(user_agent=get_user_agent()) +class BigQueryTableCreateDisposition(ConstrainedStr): + """Custom constraint for table_create_disposition. To understand more, see: + https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.create_disposition""" + + values = {"CREATE_NEVER", "CREATE_IF_NEEDED"} + + class BigQueryOfflineStoreConfig(FeastConfigBaseModel): """Offline store config for GCP BigQuery""" @@ -95,6 +102,9 @@ class BigQueryOfflineStoreConfig(FeastConfigBaseModel): gcs_staging_location: Optional[str] = None """ (optional) GCS location used for offloading BigQuery results as parquet files.""" + table_create_disposition: Optional[BigQueryTableCreateDisposition] = None + """ (optional) Specifies whether the job is allowed to create new tables. The default value is CREATE_IF_NEEDED.""" + @validator("billing_project_id") def project_id_exists(cls, v, values, **kwargs): if v and not values["project_id"]: @@ -324,6 +334,7 @@ def write_logged_features( job_config = bigquery.LoadJobConfig( source_format=bigquery.SourceFormat.PARQUET, schema=arrow_schema_to_bq_schema(source.get_schema(registry)), + create_disposition=config.offline_store.table_create_disposition, time_partitioning=bigquery.TimePartitioning( type_=bigquery.TimePartitioningType.DAY, field=source.get_log_timestamp_column(), @@ -384,6 +395,7 @@ def offline_write_batch( job_config = bigquery.LoadJobConfig( source_format=bigquery.SourceFormat.PARQUET, schema=arrow_schema_to_bq_schema(pa_schema), + create_disposition=config.offline_store.table_create_disposition, write_disposition="WRITE_APPEND", # Default but included for clarity ) From 04804a0cc32caafc583a0754898b257780b80c45 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Thu, 21 Sep 2023 12:48:52 -0700 Subject: [PATCH 07/41] chore: Updating docs to fix some outdated assets (e.g. release process) Signed-off-by: Danny Chiao --- .github/auto_assign.yml | 1 - CODEOWNERS | 11 +- README.md | 4 +- community/governance.excalidraw | 913 --------------------------- community/governance.md | 33 +- community/governance.png | Bin 258405 -> 0 bytes community/maintainers.md | 37 +- docs/README.md | 2 +- docs/community.md | 39 -- docs/getting-started/faq.md | 2 +- docs/getting-started/quickstart.md | 2 - docs/project/contributing.md | 14 +- docs/project/development-guide.md | 7 - docs/project/release-process.md | 18 +- docs/reference/feast-cli-commands.md | 2 - docs/roadmap.md | 1 - examples/quickstart/quickstart.ipynb | 1 - infra/templates/README.md.jinja2 | 3 +- sdk/python/feast/cli.py | 2 - ui/CONTRIBUTING.md | 2 +- 20 files changed, 35 insertions(+), 1059 deletions(-) delete mode 100644 community/governance.excalidraw delete mode 100644 community/governance.png diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml index 18151f7454..2a6cff517c 100644 --- a/.github/auto_assign.yml +++ b/.github/auto_assign.yml @@ -9,7 +9,6 @@ assignees: - woop - tsotnet - achals - - adchia - felixwang9817 # A number of assignees to add to the pull request diff --git a/CODEOWNERS b/CODEOWNERS index 259c13ea3f..4eae6d3524 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,16 +2,16 @@ # for more info about CODEOWNERS file # Core Interfaces -/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers @chhabrakadabra @mavysavydav @sfc-gh-madkins +/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers @sfc-gh-madkins /sdk/python/feast/infra/online_stores/online_store.py @feast-dev/maintainers @DvirDukhan /sdk/python/feast/infra/materialization_engine/batch_materialization_engine.py @feast-dev/maintainers @whoahbot @sfc-gh-madkins # ==== Offline Stores ==== # Core utils -/sdk/python/feast/infra/offline_stores/offline_utils.py @feast-dev/maintainers @chhabrakadabra @mavysavydav @sfc-gh-madkins +/sdk/python/feast/infra/offline_stores/offline_utils.py @feast-dev/maintainers @sfc-gh-madkins # BigQuery -/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers @chhabrakadabra @mavysavydav +/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers # Snowflake /sdk/python/feast/infra/offline_stores/snowflake* @sfc-gh-madkins @@ -47,8 +47,3 @@ # AWS Lambda /sdk/python/feast/infra/materialization/contrib/aws_lambda/ @achals - -# ==== Web UI ==== -/ui/ @adchia -/sdk/python/feast/ui/ @adchia -/sdk/python/feast/ui_server.py @adchia diff --git a/README.md b/README.md index f2c9348b1c..6a851d0d41 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Feast allows ML platform teams to: * **Avoid data leakage** by generating point-in-time correct feature sets so data scientists can focus on feature engineering rather than debugging error-prone dataset joining logic. This ensure that future feature values do not leak to models during training. * **Decouple ML from data infrastructure** by providing a single data access layer that abstracts feature storage from feature retrieval, ensuring models remain portable as you move from training models to serving models, from batch models to realtime models, and from one data infra system to another. -Please see our [documentation](https://docs.feast.dev/) for more information about the project, or sign up for an [email newsletter](https://feast.dev/). +Please see our [documentation](https://docs.feast.dev/) for more information about the project. ## 📐 Architecture ![](docs/assets/feast_marchitecture.png) @@ -145,7 +145,6 @@ pprint(feature_vector) The list below contains the functionality that contributors are planning to develop for Feast. * We welcome contribution to all items in the roadmap! -* Have questions about the roadmap? Go to the Slack channel to ask on #feast-development. * **Data Sources** * [x] [Snowflake source](https://docs.feast.dev/reference/data-sources/snowflake) @@ -214,7 +213,6 @@ Please refer to the official documentation at [Documentation](https://docs.feast * [Tutorials](https://docs.feast.dev/tutorials/tutorials-overview) * [Running Feast with Snowflake/GCP/AWS](https://docs.feast.dev/how-to-guides/feast-snowflake-gcp-aws) * [Change Log](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md) - * [Slack (#Feast)](https://slack.feast.dev/) ## 👋 Contributing Feast is a community project and is still under active development. Please have a look at our contributing and development guides if you want to contribute to the project: diff --git a/community/governance.excalidraw b/community/governance.excalidraw deleted file mode 100644 index f4c8dad9a4..0000000000 --- a/community/governance.excalidraw +++ /dev/null @@ -1,913 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "rectangle", - "version": 620, - "versionNonce": 853777363, - "isDeleted": false, - "id": "pr0yIJcUDXb4nFgowH9_r", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 409.5, - "y": 620.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 194, - "height": 83, - "seed": 1695250557, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [ - { - "id": "YfmPferxgVKoP70zGfYDK", - "type": "text" - }, - { - "id": "YfmPferxgVKoP70zGfYDK", - "type": "text" - }, - { - "type": "text", - "id": "YfmPferxgVKoP70zGfYDK" - }, - { - "id": "IsihlXUGDSklv2RsxX6wO", - "type": "arrow" - }, - { - "id": "G5s2AUFJ730fyPsIbA8xP", - "type": "arrow" - }, - { - "id": "j9ZVC3ZgHTsAGe3hJQecp", - "type": "arrow" - } - ], - "updated": 1662582134601, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 623, - "versionNonce": 328400605, - "isDeleted": false, - "id": "YfmPferxgVKoP70zGfYDK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 414.5, - "y": 649.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 184, - "height": 25, - "seed": 1575229907, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582134601, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "CODEOWNERS", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "pr0yIJcUDXb4nFgowH9_r", - "originalText": "CODEOWNERS" - }, - { - "type": "rectangle", - "version": 756, - "versionNonce": 1648798067, - "isDeleted": false, - "id": "XDy4VWWtJ9sd6hzPJDdFe", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 409.5, - "y": 779.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 194, - "height": 83, - "seed": 1925179667, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [ - { - "id": "gUz4p_oPytb5-ejbYb81N", - "type": "text" - }, - { - "id": "gUz4p_oPytb5-ejbYb81N", - "type": "text" - }, - { - "id": "gUz4p_oPytb5-ejbYb81N", - "type": "text" - }, - { - "type": "text", - "id": "gUz4p_oPytb5-ejbYb81N" - }, - { - "id": "G5s2AUFJ730fyPsIbA8xP", - "type": "arrow" - } - ], - "updated": 1662582134601, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 781, - "versionNonce": 1240013629, - "isDeleted": false, - "id": "gUz4p_oPytb5-ejbYb81N", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 414.5, - "y": 808.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 184, - "height": 25, - "seed": 140322205, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582134601, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Contributors", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "XDy4VWWtJ9sd6hzPJDdFe", - "originalText": "Contributors" - }, - { - "type": "text", - "version": 463, - "versionNonce": 2109720179, - "isDeleted": false, - "id": "AYJKq2RGJrSIpbfiJOf_4", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 526, - "y": 517.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 274, - "height": 75, - "seed": 1616513981, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582134602, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "1. organize contributors\n2. influence roadmap\n3. own direction of an area", - "baseline": 68, - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "1. organize contributors\n2. influence roadmap\n3. own direction of an area" - }, - { - "type": "rectangle", - "version": 776, - "versionNonce": 519656573, - "isDeleted": false, - "id": "z5LT5d710gSTA9DjwiL3O", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 70, - "angle": 0, - "x": 1013.7117834394903, - "y": 187.5000000000001, - "strokeColor": "#000000", - "backgroundColor": "#4c6ef5", - "width": 132, - "height": 682, - "seed": 1424710877, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [ - { - "id": "RscqyQXicYOkFsE_zvran", - "type": "text" - }, - { - "id": "J7IG4T5j15pB3b_K0Cpd9", - "type": "arrow" - }, - { - "id": "XEohLLmfFl0L9Wi2ew5AU", - "type": "arrow" - }, - { - "id": "o3Pp-94PORhEiEauRcZW_", - "type": "arrow" - }, - { - "type": "text", - "id": "RscqyQXicYOkFsE_zvran" - }, - { - "id": "j9ZVC3ZgHTsAGe3hJQecp", - "type": "arrow" - }, - { - "id": "Klq-VJGZiolZnGuaNJ8k9", - "type": "arrow" - } - ], - "updated": 1662582138112, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 896, - "versionNonce": 1733426643, - "isDeleted": false, - "id": "RscqyQXicYOkFsE_zvran", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1018.7117834394903, - "y": 476.0000000000001, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 122, - "height": 105, - "seed": 1202400115, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582138113, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Feast \nGitHub \nproject", - "baseline": 95, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "z5LT5d710gSTA9DjwiL3O", - "originalText": "Feast GitHub project" - }, - { - "id": "IsihlXUGDSklv2RsxX6wO", - "type": "arrow", - "x": 506.997671158975, - "y": 619, - "width": 0, - "height": 132, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 345290749, - "version": 680, - "versionNonce": 787007421, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0, - -132 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "pr0yIJcUDXb4nFgowH9_r", - "focus": 0.005130630504896713, - "gap": 1.5 - }, - "endBinding": { - "elementId": "TBYpmrW2OsKEqbpZfEeJg", - "focus": 0.461338833375829, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "G5s2AUFJ730fyPsIbA8xP", - "type": "arrow", - "x": 506.9985864097345, - "y": 776, - "width": 0.9914493467237548, - "height": 71, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 241364467, - "version": 241, - "versionNonce": 649485971, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -0.9914493467237548, - -71 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "XDy4VWWtJ9sd6hzPJDdFe", - "focus": 0.011569796958606356, - "gap": 3.5 - }, - "endBinding": { - "elementId": "pr0yIJcUDXb4nFgowH9_r", - "focus": 0.011204382815075232, - "gap": 1.5 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "TBYpmrW2OsKEqbpZfEeJg", - "type": "rectangle", - "x": 409.5, - "y": 188, - "width": 361.99999999999994, - "height": 298, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 30, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 1515491581, - "version": 231, - "versionNonce": 593345661, - "isDeleted": false, - "boundElements": [ - { - "id": "IsihlXUGDSklv2RsxX6wO", - "type": "arrow" - }, - { - "id": "Klq-VJGZiolZnGuaNJ8k9", - "type": "arrow" - } - ], - "updated": 1662582134602, - "link": null, - "locked": false - }, - { - "id": "YEEHpa4RXaR8G9YW55v25", - "type": "rectangle", - "x": 428.5, - "y": 398, - "width": 163.61445783132532, - "height": 70.00000000000001, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 932648787, - "version": 319, - "versionNonce": 398988755, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "8iyUZwSph5yMVrXehf6vg" - }, - { - "id": "o3Pp-94PORhEiEauRcZW_", - "type": "arrow" - }, - { - "id": "IsihlXUGDSklv2RsxX6wO", - "type": "arrow" - } - ], - "updated": 1662582134602, - "link": null, - "locked": false - }, - { - "id": "8iyUZwSph5yMVrXehf6vg", - "type": "text", - "x": 433.5, - "y": 422.5, - "width": 153.61445783132532, - "height": 21, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 1803538003, - "version": 365, - "versionNonce": 952837341, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "text": "Area maintainers", - "fontSize": 16.697223677317968, - "fontFamily": 1, - "textAlign": "center", - "verticalAlign": "middle", - "baseline": 15, - "containerId": "YEEHpa4RXaR8G9YW55v25", - "originalText": "Area maintainers" - }, - { - "type": "rectangle", - "version": 355, - "versionNonce": 1753998195, - "isDeleted": false, - "id": "Wh-PpzmGy1bWJko0akD-a", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 429.8072289156627, - "y": 257.1185567010309, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 161, - "height": 68.88144329896907, - "seed": 1844448573, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "boundElements": [ - { - "id": "OJCS1hAx71BD6u1jesJzR", - "type": "text" - }, - { - "type": "text", - "id": "OJCS1hAx71BD6u1jesJzR" - }, - { - "id": "o3Pp-94PORhEiEauRcZW_", - "type": "arrow" - } - ], - "updated": 1662582134602, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 409, - "versionNonce": 556564797, - "isDeleted": false, - "id": "OJCS1hAx71BD6u1jesJzR", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 434.8072289156627, - "y": 271.55927835051546, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 151, - "height": 40, - "seed": 852504851, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582134602, - "link": null, - "locked": false, - "fontSize": 16.413043478260864, - "fontFamily": 1, - "text": "Project \nmaintainers", - "baseline": 34, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "Wh-PpzmGy1bWJko0akD-a", - "originalText": "Project maintainers" - }, - { - "id": "o3Pp-94PORhEiEauRcZW_", - "type": "arrow", - "x": 510.1952932956257, - "y": 396.60017389144207, - "width": 0.34508644012566947, - "height": 69.20034778288408, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 1889236627, - "version": 572, - "versionNonce": 918879507, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0.34508644012566947, - -69.20034778288408 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "YEEHpa4RXaR8G9YW55v25", - "focus": -0.0035794947090358044, - "gap": 1.3998261085579315 - }, - "endBinding": { - "elementId": "Wh-PpzmGy1bWJko0akD-a", - "focus": -0.0051056226396315905, - "gap": 1.3998261085579884 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "4CHi-UfB3oI1PAfcFm2o_", - "type": "text", - "x": 528.5, - "y": 354.5, - "width": 218, - "height": 20, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 2054408115, - "version": 238, - "versionNonce": 1105416605, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "text": "break ties by majority vote", - "fontSize": 16, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 14, - "containerId": null, - "originalText": "break ties by majority vote" - }, - { - "id": "gHvMhIQl4S1SxPE8kzHLx", - "type": "text", - "x": 431.8072289156627, - "y": 201.5, - "width": 157, - "height": 35, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [ - "mcHoJ-dlfU3T8l_C93UPa" - ], - "strokeSharpness": "sharp", - "seed": 1597289651, - "version": 154, - "versionNonce": 1038738099, - "isDeleted": false, - "boundElements": null, - "updated": 1662582134602, - "link": null, - "locked": false, - "text": "Maintainers", - "fontSize": 28, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 25, - "containerId": null, - "originalText": "Maintainers" - }, - { - "type": "text", - "version": 545, - "versionNonce": 1478563325, - "isDeleted": false, - "id": "_qJ5MtLgnvmF1-EDKX6qg", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 533, - "y": 732.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 106, - "height": 25, - "seed": 1870614973, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582134602, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "review PRs", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "review PRs" - }, - { - "id": "j9ZVC3ZgHTsAGe3hJQecp", - "type": "arrow", - "x": 610.590909090909, - "y": 673.6931323855418, - "width": 394.7818338530517, - "height": 1.1368683772161603e-13, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 1115132605, - "version": 594, - "versionNonce": 1612334739, - "isDeleted": false, - "boundElements": null, - "updated": 1662582138112, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 394.7818338530517, - 1.1368683772161603e-13 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "pr0yIJcUDXb4nFgowH9_r", - "gap": 7.0909090909090455, - "focus": 0.2817622261576348 - }, - "endBinding": { - "elementId": "z5LT5d710gSTA9DjwiL3O", - "gap": 9.339040495529549, - "focus": -0.4257863119810608 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "Klq-VJGZiolZnGuaNJ8k9", - "type": "arrow", - "x": 775.7385321100917, - "y": 334, - "width": 233.2672383568049, - "height": 0, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "#868e96", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 632787667, - "version": 198, - "versionNonce": 1893334067, - "isDeleted": false, - "boundElements": null, - "updated": 1662582138112, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 233.2672383568049, - 0 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "TBYpmrW2OsKEqbpZfEeJg", - "gap": 4.238532110091741, - "focus": -0.020134228187919462 - }, - "endBinding": { - "elementId": "z5LT5d710gSTA9DjwiL3O", - "gap": 5.7060129725937765, - "focus": 0.5703812316715546 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "type": "text", - "version": 651, - "versionNonce": 1375877757, - "isDeleted": false, - "id": "diazwl57WWW_7gfm7wMea", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 678, - "y": 637.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 262, - "height": 25, - "seed": 2077675699, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582152851, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "merge PRs if no objections", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "merge PRs if no objections" - }, - { - "type": "text", - "version": 658, - "versionNonce": 1558756051, - "isDeleted": false, - "id": "_T1wMHFNqfA6a8Ku2OLDl", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 840, - "y": 296.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 102, - "height": 25, - "seed": 1987233139, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElements": [], - "updated": 1662582148465, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "merge PRs", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "merge PRs" - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/community/governance.md b/community/governance.md index 89cf800bc8..087b3599db 100644 --- a/community/governance.md +++ b/community/governance.md @@ -42,11 +42,7 @@ A formal governance structure helps us to On a high level, the key moving parts of the community are: - **GitHub activity** (issues + pull requests) -- **Slack community** ([slack.feast.dev](slack.feast.dev)) - - `#feast-development` is where design discussions happen amongst contributors - - Other Slack channels exist for users to ask and answer questions. - **RFCs** ([drive folder](https://drive.google.com/drive/u/0/folders/1msUsgmDbVBaysmhBlg9lklYLLTMk4bC3)) for detailed discussions -- **Community calls** (biweekly) to discuss best practices, contributions, and announce changes - **Maintainer syncs** (monthly) for [maintainers](maintainers.md) to discuss project direction and health With this structure, users and contributors largely self-organize and contribute changes as per [lazy consensus](#lazy-consensus). If there is active opposition and unresolvable conflict, then maintainers step in to break ties or make decisions. @@ -61,10 +57,6 @@ Anyone interested in the project can join the community to: - contribute to the project design - participate in the decision-making process. -The general decision making workflow is as follows: - - - > **Note**: There may not always a corresponding CODEOWNER for the affected code, in which case the responsibility falls on other maintainers or contributors with write access to review + merge the PR # Roles And Responsibilities @@ -96,7 +88,7 @@ In addition to their actions as users, contributors may also find themselves doi * Writing, editing, translating or reviewing the documentation * Organizing events or evangelizing the project -Contributors engage with the project through the issue tracker and slack community, or by writing or editing documentation. They submit changes to the project itself via Pull Requests (PRs), which will be considered for inclusion in the project by existing maintainers (see next section). +Contributors engage with the project through the issue tracker or by writing or editing documentation. They submit changes to the project itself via Pull Requests (PRs), which will be considered for inclusion in the project by existing maintainers (see next section). Contributors should follow the following guides when creating PRs: - [Contribution Process](https://docs.feast.dev/project/contributing) @@ -116,27 +108,14 @@ Maintainers are community members who have shown that they are committed to Feas > **Note**: maintainers, like other contributors, must make changes to Feast via pull requests (with code review). This applies to all changes to documentation, code, configuration, governance, etc. -### Types of maintainers - -There are two kinds of maintainers - -1. **Project maintainers** control overall project organization and resolving disputes. They also - - Attend a regular maintainers sync - - Participate in strategic planning, approve changes to the governance model, and manage the copyrights within the project outputs. - - (optional) Attend community calls - - (optional) Planning project roadmaps and articulating vision - - (optional) Guide design decisions to reinforce key project values (e.g. simplicity) -2. **Area maintainers** own a specific technical area (which may span code modules), often specifically targeting a user journey or tech stack. They - - are generally point people in GitHub or Slack on discussions in that area (e.g. tagged in `#feast-development`) - - (optional) help drive roadmap decisions - -> **Note:** project maintainers may also be area maintainers, but this does not give their ideas increased weight over other area maintainers. - -Decisions that need tie breakers may require intervention via project maintainers majority consensus. +Maintainers control overall project organization and resolving disputes. They also +- Attend a regular maintainers sync +- Participate in strategic planning, approve changes to the governance model, and manage the copyrights within the project outputs. +- (optional) Planning project roadmaps and articulating vision +- (optional) Guide design decisions to reinforce key project values (e.g. simplicity) ### Optional maintainer responsibilities Other optional activites a maintainer (project or area maintainer) may participate in: - * Monitor email aliases and our Slack (#feast-general, #feast-development, #feast-beginners). * Perform code reviews for other maintainers and the community. The areas of specialization listed in [OWNERS.md](OWNERS.md) can be used to help with routing an issue/question to the right person. * Triage GitHub issues, applying [labels]([https://github.com/feast-dev/feast/labels](https://github.com/feast-dev/feast/labels)) to each new item. Labels are extremely useful for future issue follow ups. Adding labels is somewhat subjective, so please use your best judgment. * Triage build issues, filing issues for known flaky builds or bugs, fixing or finding someone to fix any master build breakages. diff --git a/community/governance.png b/community/governance.png deleted file mode 100644 index c2b00930e3a3d4e4676ab80817e0cb364ff84777..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258405 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

V08LI?>v4AIPGz_w5sVZ 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�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Ð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 L8tYf+IsNf(^|&9| diff --git a/community/maintainers.md b/community/maintainers.md index cdf78b150c..e66dbeb762 100644 --- a/community/maintainers.md +++ b/community/maintainers.md @@ -7,36 +7,25 @@ See [Governance](governance.md) for what each maintainer type is In alphabetical order | Name | GitHub Username | Email | Organization | -| -------------- | ---------------- | --------------------------- | ------------------ | -| Abhin Chhabra | `chhabrakadabra` | chhabra.abhin@gmail.com | Shopify | +| -------------- | ---------------- |-----------------------------| ------------------ | | Achal Shah | `achals` | achals@gmail.com | Tecton | -| Danny Chiao | `adchia` | d.chiao@gmail.com | Tecton | -| David Liu | `mavysavydav` | davidyliuliu@gmail.com | Twitter | | Felix Wang | `felixwang9817` | wangfelix98@gmail.com | Tecton | | Kevin Zhang | `kevjumba` | kevin.zhang.13499@gmail.com | Tecton | -| Matt Delacour | `MattDelac` | mdelacour@hey.com | (formerly) Shopify | | Miles Adkins | `sfc-gh-madkins` | miles.adkins@snowflake.com | Snowflake | | Willem Pienaar | `woop` | will.pienaar@gmail.com | Tecton | | Zhiling Chen | `zhilingc` | chnzhlng@gmail.com | GetGround | -## Area maintainers - -Generally, with contribution questions here, default to `#feast-development` in the [slack.feast.dev](slack.feast.dev) Slack channel, but these may be folks for you to tag in messages - -| Area | Description | Name | -| -------------------- | -------------------------------------------------------------------------- | --------------------------------------------- | -| Data ingestion | ingesting batch + stream data into the online store (materialization) | Achal Shah,
Felix Wang,
Kevin Zhang | -| Developer experience | tooling, testing, documentation, tutorials | Achal Shah | -| Feature serving | optimization, caching, deployment patterns, batch retrieval, range queries | Dvir Dukhan | -| Ops | general deployment concerns, CI/CD, versioning | Keith Adler,
Danny Chiao,
Felix Wang | -| Web UI | i.e. `feast ui` output | Danny Chiao,
David Liu | - ## Emeritus Maintainers -| Name | GitHub Username | Email | Organization | -| ------------------- | --------------- | --------------------------- | ------------ | -| Oleg Avdeev | oavdeev | oleg.v.avdeev@gmail.com | Tecton | -| Oleksii Moskalenko | pyalex | moskalenko.alexey@gmail.com | Tecton | -| Jay Parthasarthy | jparthasarthy | jparthasarthy@gmail.com | Tecton | -| Pradithya Aria Pura | pradithya | pradithya.aria@gmail.com | Gojek | -| Tsotne Tabidze | tsotnet | tsotnet@gmail.com | Tecton | \ No newline at end of file +| Name | GitHub Username | Email | Organization | +|---------------------|-----------------|-----------------------------|-------------------| +| Oleg Avdeev | oavdeev | oleg.v.avdeev@gmail.com | Tecton | +| Oleksii Moskalenko | pyalex | moskalenko.alexey@gmail.com | Tecton | +| Jay Parthasarthy | jparthasarthy | jparthasarthy@gmail.com | Tecton | +| Danny Chiao | adchia | danny@tecton.ai | Tecton | +| Pradithya Aria Pura | pradithya | pradithya.aria@gmail.com | Gojek | +| Tsotne Tabidze | tsotnet | tsotnet@gmail.com | Tecton | +| Abhin Chhabra | chhabrakadabra | chhabra.abhin@gmail.com | Shopify | +| Danny Chiao | adchia | danny@tecton.ai | Tecton | +| David Liu | mavysavydav | davidyliuliu@gmail.com | Twitter | +| Matt Delacour | MattDelac | mdelacour@hey.com | Shopify | diff --git a/docs/README.md b/docs/README.md index a305c4aecd..66c7548440 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ Many companies have used Feast to power real-world ML use cases such as: ## How can I get started? {% hint style="info" %} -The best way to learn Feast is to use it. Join our [Slack channel](http://slack.feast.dev) and head over to our [Quickstart](getting-started/quickstart.md) and try it out! +The best way to learn Feast is to use it. Head over to our [Quickstart](getting-started/quickstart.md) and try it out! {% endhint %} Explore the following resources to get started with Feast: diff --git a/docs/community.md b/docs/community.md index 098b6b3f90..21cca702bf 100644 --- a/docs/community.md +++ b/docs/community.md @@ -4,13 +4,6 @@ * [GitHub Repository](https://github.com/feast-dev/feast/): Find the complete Feast codebase on GitHub. * [Community Governance Doc](https://github.com/feast-dev/feast/blob/master/community): See the governance model of Feast, including who the maintainers are and how decisions are made. -* [Slack](https://slack.feast.dev): Feel free to ask questions or say hello! This is the main place where maintainers and contributors brainstorm and where users ask questions or discuss best practices. - * Feast users should join `#feast-general` or `#feast-beginners` to ask questions - * Feast developers / contributors should join `#feast-development` -* [Mailing list](https://groups.google.com/d/forum/feast-dev): We have both a user and developer mailing list. - * Feast users should join [feast-discuss@googlegroups.com](mailto:feast-discuss@googlegroups.com) group by clicking [here](https://groups.google.com/g/feast-discuss). - * Feast developers / contributors should join [feast-dev@googlegroups.com](mailto:feast-dev@googlegroups.com) group by clicking [here](https://groups.google.com/d/forum/feast-dev). -* [Community Calendar](https://calendar.google.com/calendar/u/0?cid=ZTFsZHVhdGM3MDU3YTJucTBwMzNqNW5rajBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ): Includes community calls and design meetings. * [Google Folder](https://drive.google.com/drive/u/0/folders/1jgMHOPDT2DvBlJeO9LCM79DP4lm4eOrR): This folder is used as a central repository for all Feast resources. For example: * Design proposals in the form of Request for Comments (RFC). * User surveys and meeting minutes. @@ -19,36 +12,4 @@ ## How can I get help? -* **Slack:** Need to speak to a human? Come ask a question in our Slack channel (link above). * **GitHub Issues:** Found a bug or need a feature? [Create an issue on GitHub](https://github.com/feast-dev/feast/issues/new). -* **StackOverflow:** Need to ask a question on how to use Feast? We also monitor and respond to [StackOverflow](https://stackoverflow.com/questions/tagged/feast). - -## Community Calls - -### General community call (biweekly) -We have a user and contributor community call every two weeks (US & EU friendly). - -{% hint style="info" %} -Please join the above Feast user groups in order to see calendar invites to the community calls -{% endhint %} - -#### Frequency (every 2 weeks) - -* Tuesday 10:00 am to 10:30 am PST - -#### Links - -* Zoom: [https://zoom.us/j/6325193230](https://zoom.us/j/6325193230) -* Meeting notes (incl recordings): [https://bit.ly/feast-notes](https://bit.ly/feast-notes) - -### Developers call (biweekly) -We also have a `#feast-development` community call every two weeks, where we discuss contributions + brainstorm best practices. - -#### Frequency (every 2 weeks) - -* Tuesday 8:00 am to 8:30 am PST - -#### Links - -* Meeting notes (incl recordings): [Feast Development Biweekly](https://docs.google.com/document/d/1zUbIWFWjaBEVlToOdupnmKQwgAtFYx41sPoEEEdd2io/edit#) -* Zoom: [https://zoom.us/j/93657748160?pwd=K3ZpdzhqejgrcXNhc3BlSjFMdzUxdz09](https://zoom.us/j/93657748160?pwd=K3ZpdzhqejgrcXNhc3BlSjFMdzUxdz09) diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index a511ddb0dc..9b7eb834bf 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -3,7 +3,7 @@ {% hint style="info" %} **Don't see your question?** -We encourage you to ask questions on [Slack](https://slack.feast.dev) or [GitHub](https://github.com/feast-dev/feast). Even better, once you get an answer, add the answer to this FAQ via a [pull request](../project/development-guide.md)! +We encourage you to ask questions on [GitHub](https://github.com/feast-dev/feast). Even better, once you get an answer, add the answer to this FAQ via a [pull request](../project/development-guide.md)! {% endhint %} ## Getting started diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index b30bdb585c..d10e8a174a 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -555,9 +555,7 @@ show up in the upcoming concepts + architecture + tutorial pages as well. ## Next steps -* Join the [email newsletter](https://feast.dev/) to get new updates on Feast / feature stores. * Read the [Concepts](concepts/) page to understand the Feast data model. * Read the [Architecture](architecture-and-components/) page. * Check out our [Tutorials](../tutorials/tutorials-overview/) section for more examples on how to use Feast. * Follow our [Running Feast with Snowflake/GCP/AWS](../how-to-guides/feast-snowflake-gcp-aws/) guide for a more in-depth tutorial on using Feast. -* Join other Feast users and contributors in [Slack](https://slack.feast.dev) and become part of the community! diff --git a/docs/project/contributing.md b/docs/project/contributing.md index 9a3e3e1a3e..cded378951 100644 --- a/docs/project/contributing.md +++ b/docs/project/contributing.md @@ -2,21 +2,15 @@ ## Getting started After familiarizing yourself with the documentation, the simplest way to get started is to: -1. Join the `#feast-development` [Slack channel](https://tectonfeast.slack.com/archives/C01NTDB88QK), where contributors discuss ideas and PRs -2. Join our Google Groups in order to get access to RFC folders + get invites to community calls. See [community](../community.md) for more details. -3. Setup your developer environment by following [development guide](development-guide.md). -4. Either create a [GitHub issue](https://github.com/feast-dev/feast/issues) or make a draft PR (following [development guide](development-guide.md)) to get the ball rolling! +1. Setup your developer environment by following [development guide](development-guide.md). +2. Either create a [GitHub issue](https://github.com/feast-dev/feast/issues) or make a draft PR (following [development guide](development-guide.md)) to get the ball rolling! ## Decision making process *See [governance](../../community/governance.md) for more details here* We follow a process of [lazy consensus](http://community.apache.org/committers/lazyConsensus.html). If you believe you know what the project needs then just start development. As long as there is no active opposition and the PR has been approved by maintainers or CODEOWNERS, contributions will be merged. -We use our `#feast-development` [Slack channel](https://tectonfeast.slack.com/archives/C01NTDB88QK), [GitHub issues](https://github.com/feast-dev/feast/issues), and [GitHub pull requests](https://github.com/feast-dev/feast/pulls) to communicate development ideas. - -The general decision making workflow is as follows: - - +We use our [GitHub issues](https://github.com/feast-dev/feast/issues), and [GitHub pull requests](https://github.com/feast-dev/feast/pulls) to communicate development ideas. > **Note**: There may not always a corresponding CODEOWNER for the affected code, in which case the responsibility falls on other maintainers or contributors with write access to review + merge the PR @@ -30,7 +24,7 @@ See also [Making a pull request](development-guide.md#making-a-pull-request) for ## Resources -- [Community](../community.md) for other ways to get involved with the community (e.g. joining community calls) +- [Community](../community.md) for other ways to get involved with the community - [Development guide](development-guide.md) for tips on how to contribute - [Feast GitHub issues](https://github.com/feast-dev/feast/issues) to see what others are working on - [Feast RFCs](https://drive.google.com/drive/u/0/folders/1msUsgmDbVBaysmhBlg9lklYLLTMk4bC3) for a folder of previously written RFCs \ No newline at end of file diff --git a/docs/project/development-guide.md b/docs/project/development-guide.md index 69a1ab298a..931d0243d2 100644 --- a/docs/project/development-guide.md +++ b/docs/project/development-guide.md @@ -51,13 +51,6 @@ The compatibility policy for Feast can be found [here](compatibility.md), and sh ## Community See [Contribution process](./contributing.md) and [Community](../community.md) for details on how to get more involved in the community. -A quick few highlights: -- [RFCs](https://drive.google.com/drive/u/0/folders/0AAe8j7ZK3sxSUk9PVA) -- [Community Slack](https://slack.feast.dev/) -- [Feast Dev Mailing List](https://groups.google.com/g/feast-dev) -- [Community Calendar](https://calendar.google.com/calendar/u/0?cid=ZTFsZHVhdGM3MDU3YTJucTBwMzNqNW5rajBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ) - - Includes biweekly community calls at 10AM PST - ## Making a pull request We use the convention that the assignee of a PR is the person with the next action. diff --git a/docs/project/release-process.md b/docs/project/release-process.md index 0aa7d3fb5b..d3ff34bbc3 100644 --- a/docs/project/release-process.md +++ b/docs/project/release-process.md @@ -16,14 +16,14 @@ If you were cutting Feast 0.22.3, for example, you might do: After this step, you will have all the changes you need in the branch. -### 2. Pre-release verification +### 2. Pre-release verification (currently broken) A lot of things can go wrong. One of the most common is getting the wheels to build correctly (and not accidentally building dev wheels from improper tagging or local code changes during the release process). Another possible failure is that the Docker images might not build correctly. We verify the building the wheels and Docker images in **your fork** of Feast, not the main feast-dev/feast repo. -#### For minor releases (e.g. v0.22.0) +#### 2a. Verifying minor releases (e.g. v0.22.0) 1. Merge upstream master changes into your **fork**. Make sure you are running the workflow off of your fork! 2. Create a tag manually for the release on your fork. For example, if you are doing a release for version 0.22.0, create a tag by doing the following. - Checkout master branch and run `git tag v0.22.0`. @@ -37,17 +37,7 @@ We verify the building the wheels and Docker images in **your fork** of Feast, n 5. Run the workflow off of the tag you just created(`v0.22.0` in this case, **not** the master branch) and verify that the workflow worked (i.e ensure that all jobs are green). -#### For patch releases (e.g. v0.22.3) -You should already have checked out the existing minor release branch from step 1 (e.g. `v0.22-branch`). -1. Push the minor release branch to your fork (`git push -u origin `). -2. Add a patch release tag (e.g `v0.22.1`) by running `git tag `. - > This is important. If you don't have a tag, then the wheels you build will be **dev wheels**, which we can't - > push. The release process will automatically produce a tag for you via Semantic Release. -3. Push tags to your **origin branch** (not the upstream feast-dev/feast branch) with `git push origin `. -4. Kick off `build_wheels` workflow in your fork in the same way as is detailed in the last section, running the - workflow from this tag you just pushed up. - -### 3. Release for Python and Java SDK +### 2. Release for Python and Java SDK 1. Generate a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) or retrieve your saved personal access token. * The personal access token should have all of the permissions under the `repo` checkbox. 2. Access the `Actions` tab on the main `feast-dev/feast` repo and find the `release` action. @@ -81,7 +71,7 @@ so it helps to have a high level overview. See https://github.com/feast-dev/feas #### 4c: Update documentation -In the Feast Gitbook (ask [Danny Chiao](https://tectonfeast.slack.com/team/U029405HFEU) in Slack for access): +In the Feast Gitbook: 1. Create a new space within the Feast collection 2. Go to the overflow menu on the top -> Synchronize with Git 1. Specify GitHub as the provider diff --git a/docs/reference/feast-cli-commands.md b/docs/reference/feast-cli-commands.md index 38e85843d4..7bdea19e61 100644 --- a/docs/reference/feast-cli-commands.md +++ b/docs/reference/feast-cli-commands.md @@ -11,8 +11,6 @@ Usage: feast [OPTIONS] COMMAND [ARGS]... For more information, see our public docs at https://docs.feast.dev/ - For any questions, you can reach us at https://slack.feast.dev/ - Options: -c, --chdir TEXT Switch to a different feature repository directory before executing the given subcommand. diff --git a/docs/roadmap.md b/docs/roadmap.md index d5dc88005b..a04ede7c99 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -3,7 +3,6 @@ The list below contains the functionality that contributors are planning to develop for Feast. * We welcome contribution to all items in the roadmap! -* Have questions about the roadmap? Go to the Slack channel to ask on #feast-development. * **Data Sources** * [x] [Snowflake source](https://docs.feast.dev/reference/data-sources/snowflake) diff --git a/examples/quickstart/quickstart.ipynb b/examples/quickstart/quickstart.ipynb index 6e07d3e23b..f84457ac02 100644 --- a/examples/quickstart/quickstart.ipynb +++ b/examples/quickstart/quickstart.ipynb @@ -1066,7 +1066,6 @@ "- Read the [Concepts](https://docs.feast.dev/getting-started/concepts/) page to understand the Feast data model and architecture.\n", "- Check out our [Tutorials](https://docs.feast.dev/tutorials/tutorials-overview) section for more examples on how to use Feast.\n", "- Follow our [Running Feast with Snowflake/GCP/AWS](https://docs.feast.dev/how-to-guides/feast-snowflake-gcp-aws) guide for a more in-depth tutorial on using Feast.\n", - "- Join other Feast users and contributors in [Slack](https://slack.feast.dev/) and become part of the community!" ] } ], diff --git a/infra/templates/README.md.jinja2 b/infra/templates/README.md.jinja2 index 47779d4eb7..1cce08ecfa 100644 --- a/infra/templates/README.md.jinja2 +++ b/infra/templates/README.md.jinja2 @@ -25,7 +25,7 @@ Feast allows ML platform teams to: * **Avoid data leakage** by generating point-in-time correct feature sets so data scientists can focus on feature engineering rather than debugging error-prone dataset joining logic. This ensure that future feature values do not leak to models during training. * **Decouple ML from data infrastructure** by providing a single data access layer that abstracts feature storage from feature retrieval, ensuring models remain portable as you move from training models to serving models, from batch models to realtime models, and from one data infra system to another. -Please see our [documentation](https://docs.feast.dev/) for more information about the project, or sign up for an [email newsletter](https://feast.dev/). +Please see our [documentation](https://docs.feast.dev/) for more information about the project. ## 📐 Architecture ![](docs/assets/feast_marchitecture.png) @@ -149,7 +149,6 @@ Please refer to the official documentation at [Documentation](https://docs.feast * [Tutorials](https://docs.feast.dev/tutorials/tutorials-overview) * [Running Feast with Snowflake/GCP/AWS](https://docs.feast.dev/how-to-guides/feast-snowflake-gcp-aws) * [Change Log](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md) - * [Slack (#Feast)](https://slack.feast.dev/) ## 👋 Contributing Feast is a community project and is still under active development. Please have a look at our contributing and development guides if you want to contribute to the project: diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 53c346b6eb..3153f02e51 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -86,8 +86,6 @@ def cli( Feast CLI For more information, see our public docs at https://docs.feast.dev/ - - For any questions, you can reach us at https://slack.feast.dev/ """ ctx.ensure_object(dict) ctx.obj["CHDIR"] = Path.cwd() if chdir is None else Path(chdir).absolute() diff --git a/ui/CONTRIBUTING.md b/ui/CONTRIBUTING.md index 970bd3676c..3c13759e26 100644 --- a/ui/CONTRIBUTING.md +++ b/ui/CONTRIBUTING.md @@ -91,7 +91,7 @@ The Feast UI is published as a module to NPM and can be found here: https://www. ### Requirements To publish a new version of the module, you will need: -- to be part of the @feast-dev team in NPM. Ask `#feast-development` on http://slack.feast.dev to add you if necessary. +- to be part of the @feast-dev team in NPM. - to [login to your NPM account on the command line](https://docs.npmjs.com/cli/v8/commands/npm-adduser). ### Steps for Publishing From 2192e6527fa10f1580e4dd8f350e05e45af981b7 Mon Sep 17 00:00:00 2001 From: James Crabtree <77012363+james-crabtree-sp@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:53:10 -0500 Subject: [PATCH 08/41] feat: Add support for in_cluster config and additional labels for bytewax materialization (#3754) * SAASMLOPS-734 bytewax in-cluster config, custom labels, fix worker image deps Signed-off-by: James Crabtree * SAASMLOPS-769 make max parallelism configurable Signed-off-by: James Crabtree --------- Signed-off-by: James Crabtree --- .../bytewax/bytewax_materialization_engine.py | 25 +++++++++++-------- setup.py | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index b222128bbb..21b7a5da1f 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -61,6 +61,12 @@ class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): include_security_context_capabilities: bool = True """ (optional) Include security context capabilities in the init and job container spec """ + labels: dict = {} + """ (optional) additional labels to append to kubernetes objects """ + + max_parallelism: int = 10 + """ (optional) Maximum number of pods (default 10) allowed to run in parallel per job""" + class BytewaxMaterializationEngine(BatchMaterializationEngine): def __init__( @@ -82,7 +88,7 @@ def __init__( self.online_store = online_store # TODO: Configure k8s here - k8s_config.load_kube_config() + k8s_config.load_config() self.k8s_client = client.api_client.ApiClient() self.v1 = client.CoreV1Api(self.k8s_client) @@ -196,14 +202,13 @@ def _create_configuration_map(self, job_id, paths, feature_view, namespace): {"paths": paths, "feature_view": feature_view.name} ) + labels = {"feast-bytewax-materializer": "configmap"} configmap_manifest = { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": f"feast-{job_id}", - "labels": { - "feast-bytewax-materializer": "configmap", - }, + "labels": {**labels, **self.batch_engine_config.labels}, }, "data": { "feature_store.yaml": feature_store_configuration, @@ -260,27 +265,25 @@ def _create_job_definition(self, job_id, namespace, pods, env): "drop": ["ALL"], } + job_labels = {"feast-bytewax-materializer": "job"} + pod_labels = {"feast-bytewax-materializer": "pod"} job_definition = { "apiVersion": "batch/v1", "kind": "Job", "metadata": { "name": f"dataflow-{job_id}", "namespace": namespace, - "labels": { - "feast-bytewax-materializer": "job", - }, + "labels": {**job_labels, **self.batch_engine_config.labels}, }, "spec": { "ttlSecondsAfterFinished": 3600, "completions": pods, - "parallelism": pods, + "parallelism": min(pods, self.batch_engine_config.max_parallelism), "completionMode": "Indexed", "template": { "metadata": { "annotations": self.batch_engine_config.annotations, - "labels": { - "feast-bytewax-materializer": "pod", - }, + "labels": {**pod_labels, **self.batch_engine_config.labels}, }, "spec": { "restartPolicy": "Never", diff --git a/setup.py b/setup.py index f7b1ff0417..047100f03e 100644 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ "hiredis>=2.0.0,<3", ] -AWS_REQUIRED = ["boto3>=1.17.0,<2", "docker>=5.0.2"] +AWS_REQUIRED = ["boto3>=1.17.0,<2", "docker>=5.0.2", "s3fs"] BYTEWAX_REQUIRED = ["bytewax==0.15.1", "docker>=5.0.2", "kubernetes<=20.13.0"] From 88e764be57ed9e39dcef8a23baf91f3744b3ed1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:26:40 -0700 Subject: [PATCH 09/41] chore: Bump org.apache.avro:avro from 1.10.2 to 1.11.3 in /java/serving (#3775) Bumps org.apache.avro:avro from 1.10.2 to 1.11.3. --- updated-dependencies: - dependency-name: org.apache.avro:avro dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- java/serving/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/serving/pom.xml b/java/serving/pom.xml index 79f942d491..19e54e1362 100644 --- a/java/serving/pom.xml +++ b/java/serving/pom.xml @@ -274,7 +274,7 @@ org.apache.avro avro - 1.10.2 + 1.11.3 From 470f6a89ff7f485be9e916f748769dbbc3ddbfe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:26:57 -0700 Subject: [PATCH 10/41] chore: Bump cryptography from 41.0.3 to 41.0.4 in /sdk/python/requirements (#3773) chore: Bump cryptography in /sdk/python/requirements Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.3 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.3...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdk/python/requirements/py3.10-ci-requirements.txt | 2 +- sdk/python/requirements/py3.8-ci-requirements.txt | 2 +- sdk/python/requirements/py3.9-ci-requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index cf1f4e938e..46b6303a90 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -154,7 +154,7 @@ comm==0.1.4 # ipywidgets coverage[toml]==7.3.1 # via pytest-cov -cryptography==41.0.3 +cryptography==41.0.4 # via # adal # azure-identity diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index a5acaf55d4..01190cef29 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -158,7 +158,7 @@ comm==0.1.4 # ipywidgets coverage[toml]==7.3.1 # via pytest-cov -cryptography==41.0.3 +cryptography==41.0.4 # via # adal # azure-identity diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 3a453153e5..eabc2c0b8f 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -154,7 +154,7 @@ comm==0.1.4 # ipywidgets coverage[toml]==7.3.1 # via pytest-cov -cryptography==41.0.3 +cryptography==41.0.4 # via # adal # azure-identity From 96ece0fe94a07cc6f1dabf5d6c9b061b48b06d67 Mon Sep 17 00:00:00 2001 From: snowron <35639836+snowron@users.noreply.github.com> Date: Thu, 12 Oct 2023 00:13:20 +0300 Subject: [PATCH 11/41] fix: UI project cannot handle fallback routes (#3766) * fix: fallback route cannot navigate to react page Signed-off-by: snowron Signed-off-by: Danny Chiao * refactor fix Signed-off-by: snowron --------- Signed-off-by: snowron Signed-off-by: Danny Chiao --- sdk/python/feast/ui_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/ui_server.py b/sdk/python/feast/ui_server.py index 9950eea070..8a39293f91 100644 --- a/sdk/python/feast/ui_server.py +++ b/sdk/python/feast/ui_server.py @@ -77,7 +77,7 @@ def read_registry(): # For all other paths (such as paths that would otherwise be handled by react router), pass to React @app.api_route("/p/{path_name:path}", methods=["GET"]) def catch_all(): - filename = ui_dir + "index.html" + filename = ui_dir.joinpath("index.html") with open(filename) as f: content = f.read() From 018d0eab69dde63266f2c56813045ea5c5523f76 Mon Sep 17 00:00:00 2001 From: Achal Shah Date: Fri, 20 Oct 2023 10:36:41 -0700 Subject: [PATCH 12/41] fix: Update jackson and remove unnecessary logging (#3809) Signed-off-by: Achal Shah --- java/pom.xml | 2 +- .../feast/serving/service/config/ServingServiceV2Module.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 245d2d7cc6..3a94e5f19c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -61,7 +61,7 @@ 1.5.24 3.14.7 3.10 - 2.12.6 + 2.14.0 2.3.1 1.3.2 2.0.1.Final diff --git a/java/serving/src/main/java/feast/serving/service/config/ServingServiceV2Module.java b/java/serving/src/main/java/feast/serving/service/config/ServingServiceV2Module.java index 564159ceed..8beb91e485 100644 --- a/java/serving/src/main/java/feast/serving/service/config/ServingServiceV2Module.java +++ b/java/serving/src/main/java/feast/serving/service/config/ServingServiceV2Module.java @@ -54,7 +54,6 @@ public ServingServiceV2 registryBasedServingServiceV2( break; case REDIS: RedisClientAdapter redisClient = RedisClient.create(store.getRedisConfig()); - log.info("Created EntityKeySerializerV2"); retriever = new RedisOnlineRetriever( applicationProperties.getFeast().getProject(), @@ -69,8 +68,6 @@ public ServingServiceV2 registryBasedServingServiceV2( store.getType())); } - log.info("Working Directory = " + System.getProperty("user.dir")); - final OnlineTransformationService onlineTransformationService = new OnlineTransformationService( applicationProperties.getFeast().getTransformationServiceEndpoint(), From ea12133663cb56573f701b0f54863e87c37013ef Mon Sep 17 00:00:00 2001 From: Achal Shah Date: Fri, 20 Oct 2023 13:45:06 -0700 Subject: [PATCH 13/41] chore: Add harry to CODEOWNERS (#3808) * chore: Add harry to CODEOWNERS Signed-off-by: Achal Shah * add path Signed-off-by: Achal Shah --------- Signed-off-by: Achal Shah Signed-off-by: Achal Shah --- CODEOWNERS | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 4eae6d3524..bb154a7148 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,9 +10,14 @@ # Core utils /sdk/python/feast/infra/offline_stores/offline_utils.py @feast-dev/maintainers @sfc-gh-madkins -# BigQuery +# Offline interfaces /sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers +# BigQuery +/sdk/python/feast/infra/offline_stores/bigquery.py @sudohainguyen +/sdk/python/feast/infra/offline_stores/bigquery_source.py @sudohainguyen +/sdk/python/tests/integration/feature_repos/universal/data_sources/bigquery.py @sudohainguyen + # Snowflake /sdk/python/feast/infra/offline_stores/snowflake* @sfc-gh-madkins @@ -27,6 +32,10 @@ # ==== Online Stores ==== +# HBase +/sdk/python/feast/infra/online_stores/hbase.py @sudohainguyen +/sdk/python/feast/infra/online_stores/contrib/hbase_online_store @sudohainguyen + # Redis /sdk/python/feast/infra/online_stores/redis.py @DvirDukhan /java/feast/serving/connectors/redis/ @DvirDukhan From f7a98e21ab48a6dd101c2d0393489d04644e8480 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:07:12 +0000 Subject: [PATCH 14/41] chore: Bump golang.org/x/net from 0.7.0 to 0.17.0 (#3792) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 26 ++++++++++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 68d47d7070..20c52d3221 100644 --- a/go.mod +++ b/go.mod @@ -37,11 +37,11 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index a2c7501e82..990ff9b1ba 100644 --- a/go.sum +++ b/go.sum @@ -895,6 +895,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -951,10 +952,10 @@ golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1017,8 +1018,10 @@ golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1059,6 +1062,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1144,13 +1148,17 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1163,8 +1171,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1236,10 +1246,10 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 98e0ce812082a73b4d95065f557bf6569de26546 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:10:03 -0700 Subject: [PATCH 15/41] chore: Bump @babel/traverse from 7.17.10 to 7.23.2 in /sdk/python/feast/ui (#3802) chore: Bump @babel/traverse in /sdk/python/feast/ui Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.17.10 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdk/python/feast/ui/yarn.lock | 127 +++++++++++++++++----------------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index 6fb431576a..80a16dddca 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -26,12 +26,13 @@ dependencies: "@babel/highlight" "^7.16.7" -"@babel/code-frame@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.22.5" + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.10": version "7.17.10" @@ -77,12 +78,12 @@ "@jridgewell/gen-mapping" "^0.1.0" jsesc "^2.5.1" -"@babel/generator@^7.22.7": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" - integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.23.0" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -161,10 +162,10 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-explode-assignable-expression@^7.16.7": version "7.16.7" @@ -181,13 +182,13 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.17.0" -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" @@ -313,6 +314,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" @@ -351,13 +357,13 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" js-tokens "^4.0.0" "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10": @@ -365,10 +371,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78" integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ== -"@babel/parser@^7.22.5", "@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" @@ -1135,44 +1141,28 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/template@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.10.tgz#1ee1a5ac39f4eac844e6cf855b35520e5eb6f8b5" - integrity sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw== +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.10" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.10" - "@babel/types" "^7.17.10" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.4.5": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" debug "^4.1.0" globals "^11.1.0" @@ -1184,6 +1174,15 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" @@ -3690,7 +3689,7 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@^2.0.0, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== From f05a6e76d95ce2cd8673bfc934552eb700158cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:12:58 +0000 Subject: [PATCH 16/41] chore: Bump avro from 1.10.0 to 1.11.3 in /sdk/python/requirements (#3780) Bumps [avro](https://github.com/apache/avro) from 1.10.0 to 1.11.3. - [Release notes](https://github.com/apache/avro/releases) - [Commits](https://github.com/apache/avro/compare/release-1.10.0...release-1.11.3) --- updated-dependencies: - dependency-name: avro dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdk/python/requirements/py3.10-ci-requirements.txt | 2 +- sdk/python/requirements/py3.8-ci-requirements.txt | 2 +- sdk/python/requirements/py3.9-ci-requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 46b6303a90..7e36d414ca 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -56,7 +56,7 @@ attrs==23.1.0 # bowler # jsonschema # referencing -avro==1.10.0 +avro==1.11.3 # via feast (setup.py) azure-core==1.29.4 # via diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index 01190cef29..a6bfb313ba 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -56,7 +56,7 @@ attrs==23.1.0 # bowler # jsonschema # referencing -avro==1.10.0 +avro==1.11.3 # via feast (setup.py) azure-core==1.29.3 # via diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index eabc2c0b8f..f9181ea94c 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -56,7 +56,7 @@ attrs==23.1.0 # bowler # jsonschema # referencing -avro==1.10.0 +avro==1.11.3 # via feast (setup.py) azure-core==1.29.4 # via From fb6b807f8b32776d388757ca431d290c03170c66 Mon Sep 17 00:00:00 2001 From: Harry Date: Sat, 21 Oct 2023 05:13:49 +0700 Subject: [PATCH 17/41] feat: Support GCS filesystem for bytewax engine (#3774) * fix: Support param timeout when persisting Signed-off-by: Hai Nguyen * fix: fix java integration test Signed-off-by: Hai Nguyen --------- Signed-off-by: Hai Nguyen --- .../contrib/bytewax/bytewax_materialization_dataflow.py | 4 +--- setup.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py index bf5229303a..fe2a7f35c1 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py @@ -2,7 +2,6 @@ import pyarrow as pa import pyarrow.parquet as pq -import s3fs from bytewax.dataflow import Dataflow # type: ignore from bytewax.execution import cluster_main from bytewax.inputs import ManualInputConfig, distribute @@ -29,8 +28,7 @@ def __init__( self._run_dataflow() def process_path(self, path): - fs = s3fs.S3FileSystem() - dataset = pq.ParquetDataset(path, filesystem=fs, use_legacy_dataset=False) + dataset = pq.ParquetDataset(path, use_legacy_dataset=False) batches = [] for fragment in dataset.fragments: for batch in fragment.to_table().to_batches(): diff --git a/setup.py b/setup.py index 047100f03e..9fbc2bc2cd 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ "google-cloud-datastore>=2.1.0,<3", "google-cloud-storage>=1.34.0,<3", "google-cloud-bigtable>=2.11.0,<3", + "gcsfs", ] REDIS_REQUIRED = [ @@ -158,7 +159,7 @@ "moto", "mypy>=0.981,<0.990", "avro==1.10.0", - "gcsfs>=0.4.0,<=2022.01.0", + "gcsfs", "urllib3>=1.25.4,<2", "psutil==5.9.0", "py>=1.11.0", # https://github.com/pytest-dev/pytest/issues/10420 From 9b0e5ce2d1b617fcdcf0699c8b0cf8549a5e5ac5 Mon Sep 17 00:00:00 2001 From: Harry Date: Sat, 21 Oct 2023 05:14:06 +0700 Subject: [PATCH 18/41] feat: Make bytewax job write as mini-batches (#3777) Signed-off-by: Hai Nguyen --- .../bytewax_materialization_dataflow.py | 23 +++++++++++++++---- .../bytewax/bytewax_materialization_engine.py | 7 ++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py index fe2a7f35c1..e9d6a756b2 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py @@ -1,3 +1,4 @@ +import os from typing import List import pyarrow as pa @@ -11,6 +12,8 @@ from feast import FeatureStore, FeatureView, RepoConfig from feast.utils import _convert_arrow_to_proto, _run_pyarrow_field_mapping +DEFAULT_BATCH_SIZE = 1000 + class BytewaxMaterializationDataflow: def __init__( @@ -44,6 +47,11 @@ def input_builder(self, worker_index, worker_count, _state): return def output_builder(self, worker_index, worker_count): + def yield_batch(iterable, batch_size): + """Yield mini-batches from an iterable.""" + for i in range(0, len(iterable), batch_size): + yield iterable[i : i + batch_size] + def output_fn(batch): table = pa.Table.from_batches([batch]) @@ -62,12 +70,17 @@ def output_fn(batch): ) provider = self.feature_store._get_provider() with tqdm(total=len(rows_to_write)) as progress: - provider.online_write_batch( - config=self.config, - table=self.feature_view, - data=rows_to_write, - progress=progress.update, + # break rows_to_write to mini-batches + batch_size = int( + os.getenv("BYTEWAX_MINI_BATCH_SIZE", DEFAULT_BATCH_SIZE) ) + for mini_batch in yield_batch(rows_to_write, batch_size): + provider.online_write_batch( + config=self.config, + table=self.feature_view, + data=mini_batch, + progress=progress.update, + ) return output_fn diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index 21b7a5da1f..787dd585ff 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -67,6 +67,9 @@ class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): max_parallelism: int = 10 """ (optional) Maximum number of pods (default 10) allowed to run in parallel per job""" + mini_batch_size: int = 1000 + """ (optional) Number of rows to process per write operation (default 1000)""" + class BytewaxMaterializationEngine(BatchMaterializationEngine): def __init__( @@ -254,6 +257,10 @@ def _create_job_definition(self, job_id, namespace, pods, env): "name": "BYTEWAX_STATEFULSET_NAME", "value": f"dataflow-{job_id}", }, + { + "name": "BYTEWAX_MINI_BATCH_SIZE", + "value": str(self.batch_engine_config.mini_batch_size), + }, ] # Add any Feast configured environment variables job_env.extend(env) From 7376db8dbd1d3168a1262fbbc0ce3899be8d0c34 Mon Sep 17 00:00:00 2001 From: Harry Date: Sat, 21 Oct 2023 05:14:47 +0700 Subject: [PATCH 19/41] fix: Resolve hbase hotspot issue when materializing (#3790) * fix: Resolve hbase hotspot issue when materializing Signed-off-by: Hai Nguyen * chore: Refactor internal table id generator Signed-off-by: Hai Nguyen --------- Signed-off-by: Hai Nguyen --- .../contrib/hbase_online_store/hbase.py | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py b/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py index aff0c6c42c..2636cf95e2 100644 --- a/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py +++ b/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py @@ -8,7 +8,7 @@ from feast import Entity from feast.feature_view import FeatureView -from feast.infra.key_encoding_utils import serialize_entity_key +from feast.infra.online_stores.helpers import compute_entity_id from feast.infra.online_stores.online_store import OnlineStore from feast.infra.utils.hbase_utils import HbaseConstants, HbaseUtils from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto @@ -104,14 +104,15 @@ def online_write_batch( hbase = HbaseUtils(self._get_conn(config)) project = config.project - table_name = _table_id(project, table) + table_name = self._table_id(project, table) b = hbase.batch(table_name) for entity_key, values, timestamp, created_ts in data: - row_key = serialize_entity_key( + row_key = self._hbase_row_key( entity_key, - entity_key_serialization_version=config.entity_key_serialization_version, - ).hex() + feature_view_name=table.name, + config=config, + ) values_dict = {} for feature_name, val in values.items(): values_dict[ @@ -133,6 +134,9 @@ def online_write_batch( b.put(row_key, values_dict) b.send() + if progress: + progress(len(data)) + @log_exceptions_and_usage(online_store="hbase") def online_read( self, @@ -152,15 +156,16 @@ def online_read( """ hbase = HbaseUtils(self._get_conn(config)) project = config.project - table_name = _table_id(project, table) + table_name = self._table_id(project, table) result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = [] row_keys = [ - serialize_entity_key( + self._hbase_row_key( entity_key, - entity_key_serialization_version=config.entity_key_serialization_version, - ).hex() + feature_view_name=table.name, + config=config, + ) for entity_key in entity_keys ] rows = hbase.rows(table_name, row_keys=row_keys) @@ -206,12 +211,12 @@ def update( # We don't create any special state for the entites in this implementation. for table in tables_to_keep: - table_name = _table_id(project, table) + table_name = self._table_id(project, table) if not hbase.check_if_table_exist(table_name): hbase.create_table_with_default_cf(table_name) for table in tables_to_delete: - table_name = _table_id(project, table) + table_name = self._table_id(project, table) hbase.delete_table(table_name) def teardown( @@ -231,16 +236,43 @@ def teardown( project = config.project for table in tables: - table_name = _table_id(project, table) + table_name = self._table_id(project, table) hbase.delete_table(table_name) + def _hbase_row_key( + self, + entity_key: EntityKeyProto, + feature_view_name: str, + config: RepoConfig, + ) -> bytes: + """ + Computes the HBase row key for a given entity key and feature view name. -def _table_id(project: str, table: FeatureView) -> str: - """ - Returns table name given the project_name and the feature_view. + Args: + entity_key (EntityKeyProto): The entity key to compute the row key for. + feature_view_name (str): The name of the feature view to compute the row key for. + config (RepoConfig): The configuration for the Feast repository. - Args: - project: Name of the feast project. - table: Feast FeatureView. - """ - return f"{project}_{table.name}" + Returns: + bytes: The HBase row key for the given entity key and feature view name. + """ + entity_id = compute_entity_id( + entity_key, + entity_key_serialization_version=config.entity_key_serialization_version, + ) + # Even though `entity_id` uniquely identifies an entity, we use the same table + # for multiple feature_views with the same set of entities. + # To uniquely identify the row for a feature_view, we suffix the name of the feature_view itself. + # This also ensures that features for entities from various feature_views are + # colocated. + return f"{entity_id}#{feature_view_name}".encode() + + def _table_id(self, project: str, table: FeatureView) -> str: + """ + Returns table name given the project_name and the feature_view. + + Args: + project: Name of the feast project. + table: Feast FeatureView. + """ + return f"{project}:{table.name}" From 175d7969b1f75ab797aff9c92a70d845297444ad Mon Sep 17 00:00:00 2001 From: Miedviediev Dmytro Date: Sat, 21 Oct 2023 01:15:40 +0300 Subject: [PATCH 20/41] fix: Make generated temp table name escaped (#3797) * Fix critical issue with saving results in BigQuery Need to encapsulate table path as tmp table path can contain symbols that can't be process by BigQuery without encapsulation Signed-off-by: Dmytro Miedviediev * update numpy Signed-off-by: Dmytro Miedviediev * revert numpy version Signed-off-by: Dmytro Miedviediev --------- Signed-off-by: Dmytro Miedviediev --- sdk/python/feast/infra/offline_stores/bigquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 86c587c7fd..10c8aa783f 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -515,7 +515,7 @@ def to_bigquery( temp_dest_table = f"{tmp_dest['projectId']}.{tmp_dest['datasetId']}.{tmp_dest['tableId']}" # persist temp table - sql = f"CREATE TABLE `{dest}` AS SELECT * FROM {temp_dest_table}" + sql = f"CREATE TABLE `{dest}` AS SELECT * FROM `{temp_dest_table}`" self._execute_query(sql, timeout=timeout) print(f"Done writing to '{dest}'.") From b3852bfb8b27bf07736935f465da3067fcbac0ae Mon Sep 17 00:00:00 2001 From: Harry Date: Sun, 22 Oct 2023 02:28:30 +0700 Subject: [PATCH 21/41] fix: Adopt connection pooling for HBase (#3793) --- sdk/python/feast/feature_store.py | 6 +- .../contrib/hbase_online_store/hbase.py | 55 ++++---- sdk/python/feast/infra/utils/hbase_utils.py | 127 +++++++++++------- 3 files changed, 107 insertions(+), 81 deletions(-) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 70f7d3dcb7..4b8200d96f 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -287,7 +287,11 @@ def _list_feature_views( for fv in self._registry.list_feature_views( self.project, allow_cache=allow_cache ): - if hide_dummy_entity and fv.entities[0] == DUMMY_ENTITY_NAME: + if ( + hide_dummy_entity + and fv.entities + and fv.entities[0] == DUMMY_ENTITY_NAME + ): fv.entities = [] fv.entity_columns = [] feature_views.append(fv) diff --git a/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py b/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py index 2636cf95e2..1da9de89a8 100644 --- a/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py +++ b/sdk/python/feast/infra/online_stores/contrib/hbase_online_store/hbase.py @@ -3,14 +3,16 @@ from datetime import datetime from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple -from happybase import Connection +from happybase import ConnectionPool +from happybase.connection import DEFAULT_PROTOCOL, DEFAULT_TRANSPORT +from pydantic import StrictStr from pydantic.typing import Literal from feast import Entity from feast.feature_view import FeatureView from feast.infra.online_stores.helpers import compute_entity_id from feast.infra.online_stores.online_store import OnlineStore -from feast.infra.utils.hbase_utils import HbaseConstants, HbaseUtils +from feast.infra.utils.hbase_utils import HBaseConnector, HbaseConstants from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import FeastConfigBaseModel, RepoConfig @@ -23,35 +25,20 @@ class HbaseOnlineStoreConfig(FeastConfigBaseModel): type: Literal["hbase"] = "hbase" """Online store type selector""" - host: str + host: StrictStr """Hostname of Hbase Thrift server""" - port: str + port: StrictStr """Port in which Hbase Thrift server is running""" + connection_pool_size: int = 4 + """Number of connections to Hbase Thrift server to keep in the connection pool""" -class HbaseConnection: - """ - Hbase connecttion to connect to hbase. - - Attributes: - store_config: Online store config for Hbase store. - """ + protocol: StrictStr = DEFAULT_PROTOCOL + """Protocol used to communicate with Hbase Thrift server""" - def __init__(self, store_config: HbaseOnlineStoreConfig): - self._store_config = store_config - self._real_conn = Connection( - host=store_config.host, port=int(store_config.port) - ) - - @property - def real_conn(self) -> Connection: - """Stores the real happybase Connection to connect to hbase.""" - return self._real_conn - - def close(self) -> None: - """Close the happybase connection.""" - self.real_conn.close() + transport: StrictStr = DEFAULT_TRANSPORT + """Transport used to communicate with Hbase Thrift server""" class HbaseOnlineStore(OnlineStore): @@ -62,7 +49,7 @@ class HbaseOnlineStore(OnlineStore): _conn: Happybase Connection to connect to hbase thrift server. """ - _conn: Connection = None + _conn: ConnectionPool = None def _get_conn(self, config: RepoConfig): """ @@ -76,7 +63,13 @@ def _get_conn(self, config: RepoConfig): assert isinstance(store_config, HbaseOnlineStoreConfig) if not self._conn: - self._conn = Connection(host=store_config.host, port=int(store_config.port)) + self._conn = ConnectionPool( + host=store_config.host, + port=int(store_config.port), + size=int(store_config.connection_pool_size), + protocol=store_config.protocol, + transport=store_config.transport, + ) return self._conn @log_exceptions_and_usage(online_store="hbase") @@ -102,7 +95,7 @@ def online_write_batch( the online store. Can be used to display progress. """ - hbase = HbaseUtils(self._get_conn(config)) + hbase = HBaseConnector(self._get_conn(config)) project = config.project table_name = self._table_id(project, table) @@ -154,7 +147,7 @@ def online_read( entity_keys: a list of entity keys that should be read from the FeatureStore. requested_features: a list of requested feature names. """ - hbase = HbaseUtils(self._get_conn(config)) + hbase = HBaseConnector(self._get_conn(config)) project = config.project table_name = self._table_id(project, table) @@ -206,7 +199,7 @@ def update( tables_to_delete: Tables to delete from the Hbase Online Store. tables_to_keep: Tables to keep in the Hbase Online Store. """ - hbase = HbaseUtils(self._get_conn(config)) + hbase = HBaseConnector(self._get_conn(config)) project = config.project # We don't create any special state for the entites in this implementation. @@ -232,7 +225,7 @@ def teardown( config: The RepoConfig for the current FeatureStore. tables: Tables to delete from the feature repo. """ - hbase = HbaseUtils(self._get_conn(config)) + hbase = HBaseConnector(self._get_conn(config)) project = config.project for table in tables: diff --git a/sdk/python/feast/infra/utils/hbase_utils.py b/sdk/python/feast/infra/utils/hbase_utils.py index 4816a60087..d44f93f161 100644 --- a/sdk/python/feast/infra/utils/hbase_utils.py +++ b/sdk/python/feast/infra/utils/hbase_utils.py @@ -1,9 +1,6 @@ from typing import List -from happybase import Connection - -from feast.infra.key_encoding_utils import serialize_entity_key -from feast.protos.feast.types.EntityKey_pb2 import EntityKey +from happybase import ConnectionPool class HbaseConstants: @@ -28,7 +25,7 @@ def get_col_from_feature(feature): return HbaseConstants.DEFAULT_COLUMN_FAMILY + ":" + feature -class HbaseUtils: +class HBaseConnector: """ Utils class to manage different Hbase operations. @@ -40,14 +37,22 @@ class HbaseUtils: """ def __init__( - self, conn: Connection = None, host: str = None, port: int = None, timeout=None + self, + pool: ConnectionPool = None, + host: str = None, + port: int = None, + connection_pool_size: int = 4, ): - if conn is None: + if pool is None: self.host = host self.port = port - self.conn = Connection(host=host, port=port, timeout=timeout) + self.pool = ConnectionPool( + host=host, + port=port, + size=connection_pool_size, + ) else: - self.conn = conn + self.pool = pool def create_table(self, table_name: str, colm_family: List[str]): """ @@ -60,7 +65,9 @@ def create_table(self, table_name: str, colm_family: List[str]): cf_dict: dict = {} for cf in colm_family: cf_dict[cf] = dict() - return self.conn.create_table(table_name, cf_dict) + + with self.pool.connection() as conn: + return conn.create_table(table_name, cf_dict) def create_table_with_default_cf(self, table_name: str): """ @@ -69,7 +76,8 @@ def create_table_with_default_cf(self, table_name: str): Arguments: table_name: Name of the Hbase table. """ - return self.conn.create_table(table_name, {"default": dict()}) + with self.pool.connection() as conn: + return conn.create_table(table_name, {"default": dict()}) def check_if_table_exist(self, table_name: str): """ @@ -78,16 +86,18 @@ def check_if_table_exist(self, table_name: str): Arguments: table_name: Name of the Hbase table. """ - return bytes(table_name, "utf-8") in self.conn.tables() + with self.pool.connection() as conn: + return bytes(table_name, "utf-8") in conn.tables() def batch(self, table_name: str): """ - Returns a 'Batch' instance that can be used for mass data manipulation in the hbase table. + Returns a "Batch" instance that can be used for mass data manipulation in the hbase table. Arguments: table_name: Name of the Hbase table. """ - return self.conn.table(table_name).batch() + with self.pool.connection() as conn: + return conn.table(table_name).batch() def put(self, table_name: str, row_key: str, data: dict): """ @@ -98,8 +108,9 @@ def put(self, table_name: str, row_key: str, data: dict): row_key: Row key of the row to be inserted to hbase table. data: Mapping of column family name:column name to column values """ - table = self.conn.table(table_name) - table.put(row_key, data) + with self.pool.connection() as conn: + table = conn.table(table_name) + table.put(row_key, data) def row( self, @@ -119,8 +130,9 @@ def row( timestamp: timestamp specifies the maximum version the cells can have. include_timestamp: specifies if (column, timestamp) to be return instead of only column. """ - table = self.conn.table(table_name) - return table.row(row_key, columns, timestamp, include_timestamp) + with self.pool.connection() as conn: + table = conn.table(table_name) + return table.row(row_key, columns, timestamp, include_timestamp) def rows( self, @@ -140,52 +152,69 @@ def rows( timestamp: timestamp specifies the maximum version the cells can have. include_timestamp: specifies if (column, timestamp) to be return instead of only column. """ - table = self.conn.table(table_name) - return table.rows(row_keys, columns, timestamp, include_timestamp) + with self.pool.connection() as conn: + table = conn.table(table_name) + return table.rows(row_keys, columns, timestamp, include_timestamp) def print_table(self, table_name): """Prints the table scanning all the rows of the hbase table.""" - table = self.conn.table(table_name) - scan_data = table.scan() - for row_key, cols in scan_data: - print(row_key.decode("utf-8"), cols) + with self.pool.connection() as conn: + table = conn.table(table_name) + scan_data = table.scan() + for row_key, cols in scan_data: + print(row_key.decode("utf-8"), cols) def delete_table(self, table: str): """Deletes the hbase table given the table name.""" if self.check_if_table_exist(table): - self.conn.delete_table(table, disable=True) + with self.pool.connection() as conn: + conn.delete_table(table, disable=True) def close_conn(self): """Closes the happybase connection.""" - self.conn.close() + with self.pool.connection() as conn: + conn.close() def main(): + from feast.infra.key_encoding_utils import serialize_entity_key + from feast.protos.feast.types.EntityKey_pb2 import EntityKey from feast.protos.feast.types.Value_pb2 import Value - connection = Connection(host="localhost", port=9090) - table = connection.table("test_hbase_driver_hourly_stats") - row_keys = [ - serialize_entity_key( - EntityKey(join_keys=["driver_id"], entity_values=[Value(int64_val=1004)]), - entity_key_serialization_version=2, - ).hex(), - serialize_entity_key( - EntityKey(join_keys=["driver_id"], entity_values=[Value(int64_val=1005)]), - entity_key_serialization_version=2, - ).hex(), - serialize_entity_key( - EntityKey(join_keys=["driver_id"], entity_values=[Value(int64_val=1024)]), - entity_key_serialization_version=2, - ).hex(), - ] - rows = table.rows(row_keys) - - for row_key, row in rows: - for key, value in row.items(): - col_name = bytes.decode(key, "utf-8").split(":")[1] - print(col_name, value) - print() + pool = ConnectionPool( + host="localhost", + port=9090, + size=2, + ) + with pool.connection() as connection: + table = connection.table("test_hbase_driver_hourly_stats") + row_keys = [ + serialize_entity_key( + EntityKey( + join_keys=["driver_id"], entity_values=[Value(int64_val=1004)] + ), + entity_key_serialization_version=2, + ).hex(), + serialize_entity_key( + EntityKey( + join_keys=["driver_id"], entity_values=[Value(int64_val=1005)] + ), + entity_key_serialization_version=2, + ).hex(), + serialize_entity_key( + EntityKey( + join_keys=["driver_id"], entity_values=[Value(int64_val=1024)] + ), + entity_key_serialization_version=2, + ).hex(), + ] + rows = table.rows(row_keys) + + for _, row in rows: + for key, value in row.items(): + col_name = bytes.decode(key, "utf-8").split(":")[1] + print(col_name, value) + print() if __name__ == "__main__": From 417b16b57af7b38fbd0708b9a0c5d5035ed021fd Mon Sep 17 00:00:00 2001 From: James Crabtree <77012363+james-crabtree-sp@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:54:40 -0600 Subject: [PATCH 22/41] fix: Redundant feature materialization and premature incremental materialization timestamp updates (#3789) * SAASMLOPS-767 wait for jobs to complete Signed-off-by: James Crabtree * SAASMLOPS-805 Stopgap change to fix duplicate materialization of data Signed-off-by: James Crabtree * SAASMLOPS-805 save BYTEWAX_REPLICAS=1 Signed-off-by: James Crabtree * SAASMLOPS-809 fix bytewax workers so they only process a single file (#6) * SAASMLOPS-809 fix bytewax workers so they only process a single file * SAASMLOPS-809 fix newlines Signed-off-by: James Crabtree * SAASMLOPS-833 add configurable job timeout (#7) * SAASMLOPS-833 add configurable job timeout * SAASMLOPS-833 fix whitespace Signed-off-by: James Crabtree * develop Run large materializations in batches of pods Signed-off-by: James Crabtree * master Set job_batch_size at least equal to max_parallelism Signed-off-by: James Crabtree * master clarity max_parallelism description Signed-off-by: James Crabtree * master resolve bug that causes materialization to continue after job error Signed-off-by: James Crabtree * master resolve bug causing pod logs to not be printed Signed-off-by: James Crabtree --------- Signed-off-by: James Crabtree --- .../contrib/bytewax/Dockerfile | 2 +- .../bytewax_materialization_dataflow.py | 10 +- .../bytewax/bytewax_materialization_engine.py | 134 ++++++++++++++++-- .../bytewax/bytewax_materialization_job.py | 9 +- .../contrib/bytewax/dataflow.py | 3 + 5 files changed, 139 insertions(+), 19 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/Dockerfile b/sdk/python/feast/infra/materialization/contrib/bytewax/Dockerfile index 963924f38d..a26661ead3 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/Dockerfile +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/Dockerfile @@ -25,5 +25,5 @@ COPY README.md README.md # git dir to infer the version of feast we're installing. # https://github.com/pypa/setuptools_scm#usage-from-docker # I think it also assumes that this dockerfile is being built from the root of the directory. -RUN --mount=source=.git,target=.git,type=bind pip3 install --no-cache-dir -e '.[aws,gcp,bytewax]' +RUN --mount=source=.git,target=.git,type=bind pip3 install --no-cache-dir '.[aws,gcp,bytewax,snowflake]' diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py index e9d6a756b2..31be7a6b89 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py @@ -5,7 +5,7 @@ import pyarrow.parquet as pq from bytewax.dataflow import Dataflow # type: ignore from bytewax.execution import cluster_main -from bytewax.inputs import ManualInputConfig, distribute +from bytewax.inputs import ManualInputConfig from bytewax.outputs import ManualOutputConfig from tqdm import tqdm @@ -21,11 +21,13 @@ def __init__( config: RepoConfig, feature_view: FeatureView, paths: List[str], + worker_index: int, ): self.config = config self.feature_store = FeatureStore(config=config) self.feature_view = feature_view + self.worker_index = worker_index self.paths = paths self._run_dataflow() @@ -40,11 +42,7 @@ def process_path(self, path): return batches def input_builder(self, worker_index, worker_count, _state): - worker_paths = distribute(self.paths, worker_index, worker_count) - for path in worker_paths: - yield None, path - - return + return [(None, self.paths[self.worker_index])] def output_builder(self, worker_index, worker_count): def yield_batch(iterable, batch_size): diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index 787dd585ff..1c9dc6a6be 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -1,11 +1,14 @@ +import logging import uuid from datetime import datetime +from time import sleep from typing import Callable, List, Literal, Sequence, Union import yaml from kubernetes import client from kubernetes import config as k8s_config from kubernetes import utils +from kubernetes.client.exceptions import ApiException from kubernetes.utils import FailToCreateError from pydantic import StrictStr from tqdm import tqdm @@ -16,6 +19,7 @@ from feast.infra.materialization.batch_materialization_engine import ( BatchMaterializationEngine, MaterializationJob, + MaterializationJobStatus, MaterializationTask, ) from feast.infra.offline_stores.offline_store import OfflineStore @@ -27,6 +31,8 @@ from .bytewax_materialization_job import BytewaxMaterializationJob +logger = logging.getLogger(__name__) + class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): """Batch Materialization Engine config for Bytewax""" @@ -65,11 +71,26 @@ class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): """ (optional) additional labels to append to kubernetes objects """ max_parallelism: int = 10 - """ (optional) Maximum number of pods (default 10) allowed to run in parallel per job""" + """ (optional) Maximum number of pods allowed to run in parallel""" + + synchronous: bool = False + """ (optional) If true, wait for materialization for one feature to complete before moving to the next """ + + retry_limit: int = 2 + """ (optional) Maximum number of times to retry a materialization worker pod""" mini_batch_size: int = 1000 """ (optional) Number of rows to process per write operation (default 1000)""" + active_deadline_seconds: int = 86400 + """ (optional) Maximum amount of time a materialization job is allowed to run""" + + job_batch_size: int = 100 + """ (optional) Maximum number of pods to process per job. Only applies to synchronous materialization""" + + print_pod_logs_on_failure: bool = True + """(optional) Print pod logs on job failure. Only applies to synchronous materialization""" + class BytewaxMaterializationEngine(BatchMaterializationEngine): def __init__( @@ -173,8 +194,98 @@ def _materialize_one( ) paths = offline_job.to_remote_storage() + if self.batch_engine_config.synchronous: + offset = 0 + total_pods = len(paths) + batch_size = self.batch_engine_config.job_batch_size + if batch_size < 1: + raise ValueError("job_batch_size must be a value greater than 0") + if batch_size < self.batch_engine_config.max_parallelism: + logger.warning( + "job_batch_size is less than max_parallelism. Setting job_batch_size = max_parallelism" + ) + batch_size = self.batch_engine_config.max_parallelism + + while True: + next_offset = min(offset + batch_size, total_pods) + job = self._await_path_materialization( + paths[offset:next_offset], + feature_view, + offset, + next_offset, + total_pods, + ) + offset += batch_size + if ( + offset >= total_pods + or job.status() == MaterializationJobStatus.ERROR + ): + break + else: + job_id = str(uuid.uuid4()) + job = self._create_kubernetes_job(job_id, paths, feature_view) + + return job + + def _await_path_materialization( + self, paths, feature_view, batch_start, batch_end, total_pods + ): job_id = str(uuid.uuid4()) - return self._create_kubernetes_job(job_id, paths, feature_view) + job = self._create_kubernetes_job(job_id, paths, feature_view) + + try: + while job.status() in ( + MaterializationJobStatus.WAITING, + MaterializationJobStatus.RUNNING, + ): + logger.info( + f"{feature_view.name} materialization for pods {batch_start}-{batch_end} " + f"(of {total_pods}) running..." + ) + sleep(30) + logger.info( + f"{feature_view.name} materialization for pods {batch_start}-{batch_end} " + f"(of {total_pods}) complete with status {job.status()}" + ) + except BaseException as e: + logger.info(f"Deleting job {job.job_id()}") + try: + self.batch_v1.delete_namespaced_job(job.job_id(), self.namespace) + except ApiException as ae: + logger.warning(f"Could not delete job due to API Error: {ae.body}") + raise e + finally: + logger.info(f"Deleting configmap {self._configmap_name(job_id)}") + try: + self.v1.delete_namespaced_config_map( + self._configmap_name(job_id), self.namespace + ) + except ApiException as ae: + logger.warning( + f"Could not delete configmap due to API Error: {ae.body}" + ) + + if ( + job.status() == MaterializationJobStatus.ERROR + and self.batch_engine_config.print_pod_logs_on_failure + ): + self._print_pod_logs(job.job_id(), feature_view, batch_start) + + return job + + def _print_pod_logs(self, job_id, feature_view, offset=0): + pods_list = self.v1.list_namespaced_pod( + namespace=self.namespace, + label_selector=f"job-name={job_id}", + ).items + for i, pod in enumerate(pods_list): + logger.info(f"Logging output for {feature_view.name} pod {offset+i}") + try: + logger.info( + self.v1.read_namespaced_pod_log(pod.metadata.name, self.namespace) + ) + except ApiException as e: + logger.warning(f"Could not retrieve pod logs due to: {e.body}") def _create_kubernetes_job(self, job_id, paths, feature_view): try: @@ -210,7 +321,7 @@ def _create_configuration_map(self, job_id, paths, feature_view, namespace): "kind": "ConfigMap", "apiVersion": "v1", "metadata": { - "name": f"feast-{job_id}", + "name": self._configmap_name(job_id), "labels": {**labels, **self.batch_engine_config.labels}, }, "data": { @@ -223,7 +334,10 @@ def _create_configuration_map(self, job_id, paths, feature_view, namespace): body=configmap_manifest, ) - def _create_job_definition(self, job_id, namespace, pods, env): + def _configmap_name(self, job_id): + return f"feast-{job_id}" + + def _create_job_definition(self, job_id, namespace, pods, env, index_offset=0): """Create a kubernetes job definition.""" job_env = [ {"name": "RUST_BACKTRACE", "value": "full"}, @@ -284,8 +398,10 @@ def _create_job_definition(self, job_id, namespace, pods, env): }, "spec": { "ttlSecondsAfterFinished": 3600, + "backoffLimit": self.batch_engine_config.retry_limit, "completions": pods, "parallelism": min(pods, self.batch_engine_config.max_parallelism), + "activeDeadlineSeconds": self.batch_engine_config.active_deadline_seconds, "completionMode": "Indexed", "template": { "metadata": { @@ -324,7 +440,7 @@ def _create_job_definition(self, job_id, namespace, pods, env): }, { "mountPath": "/var/feast/", - "name": f"feast-{job_id}", + "name": self._configmap_name(job_id), }, ], } @@ -355,7 +471,7 @@ def _create_job_definition(self, job_id, namespace, pods, env): {"mountPath": "/etc/bytewax", "name": "hostfile"}, { "mountPath": "/var/feast/", - "name": f"feast-{job_id}", + "name": self._configmap_name(job_id), }, ], } @@ -365,13 +481,13 @@ def _create_job_definition(self, job_id, namespace, pods, env): { "configMap": { "defaultMode": 420, - "name": f"feast-{job_id}", + "name": self._configmap_name(job_id), }, "name": "python-files", }, { - "configMap": {"name": f"feast-{job_id}"}, - "name": f"feast-{job_id}", + "configMap": {"name": self._configmap_name(job_id)}, + "name": self._configmap_name(job_id), }, ], }, diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py index 77d2149eb5..4105be90ee 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py @@ -36,10 +36,13 @@ def status(self): if job_status.completion_time is None: return MaterializationJobStatus.RUNNING elif job_status.failed is not None: + self._error = Exception(f"Job {self.job_id()} failed") return MaterializationJobStatus.ERROR - elif job_status.active is None and job_status.succeeded is not None: - if job_status.conditions[0].type == "Complete": - return MaterializationJobStatus.SUCCEEDED + elif job_status.active is None: + if job_status.completion_time is not None: + if job_status.conditions[0].type == "Complete": + return MaterializationJobStatus.SUCCEEDED + return MaterializationJobStatus.WAITING def should_be_retried(self): return False diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py index e3d95e2a75..23cdc20ef3 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py @@ -1,3 +1,5 @@ +import os + import yaml from feast import FeatureStore, RepoConfig @@ -19,4 +21,5 @@ config, store.get_feature_view(bytewax_config["feature_view"]), bytewax_config["paths"], + int(os.environ["JOB_COMPLETION_INDEX"]), ) From 015196119945d962bb67db84de787364667cecca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 06:16:56 +0000 Subject: [PATCH 23/41] chore: Bump urllib3 from 1.26.16 to 1.26.18 in /sdk/python/requirements (#3803) Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.16 to 1.26.18. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.16...1.26.18) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdk/python/requirements/py3.10-ci-requirements.txt | 2 +- sdk/python/requirements/py3.10-requirements.txt | 2 +- sdk/python/requirements/py3.8-ci-requirements.txt | 2 +- sdk/python/requirements/py3.8-requirements.txt | 2 +- sdk/python/requirements/py3.9-ci-requirements.txt | 2 +- sdk/python/requirements/py3.9-requirements.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 7e36d414ca..616ec5a228 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1018,7 +1018,7 @@ uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.16 +urllib3==1.26.18 # via # botocore # docker diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 9ee910bf00..f4cac316d2 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -204,7 +204,7 @@ typing-extensions==4.7.1 # pydantic # sqlalchemy2-stubs # uvicorn -urllib3==2.0.4 +urllib3==1.26.18 # via requests uvicorn[standard]==0.23.2 # via feast (setup.py) diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index a6bfb313ba..b84305d92f 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -1034,7 +1034,7 @@ uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.16 +urllib3==1.26.18 # via # botocore # docker diff --git a/sdk/python/requirements/py3.8-requirements.txt b/sdk/python/requirements/py3.8-requirements.txt index 4011ce804f..ec4a9f5187 100644 --- a/sdk/python/requirements/py3.8-requirements.txt +++ b/sdk/python/requirements/py3.8-requirements.txt @@ -210,7 +210,7 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==2.0.4 +urllib3==1.26.18 # via requests uvicorn[standard]==0.23.2 # via feast (setup.py) diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index f9181ea94c..aee80ca040 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1030,7 +1030,7 @@ uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.16 +urllib3==1.26.18 # via # botocore # docker diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 432ac4cbe6..81a8afa699 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -205,7 +205,7 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==2.0.4 +urllib3==1.26.18 # via requests uvicorn[standard]==0.23.2 # via feast (setup.py) From 01db8cce6f82d4c6e496041351fb6b56eb2645b0 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 1 Nov 2023 01:14:06 +0700 Subject: [PATCH 24/41] feat: Add get online feature rpc to gprc server (#3815) Signed-off-by: Hai Nguyen --- protos/feast/serving/GrpcServer.proto | 11 +-- protos/feast/serving/ServingService.proto | 2 + sdk/python/feast/cli.py | 11 ++- sdk/python/feast/infra/contrib/grpc_server.py | 69 ++++++++++++++++--- sdk/python/feast/type_map.py | 17 +++-- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/protos/feast/serving/GrpcServer.proto b/protos/feast/serving/GrpcServer.proto index cd0274c5c7..34edb4ebe9 100644 --- a/protos/feast/serving/GrpcServer.proto +++ b/protos/feast/serving/GrpcServer.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "feast/serving/ServingService.proto"; + message PushRequest { map features = 1; string stream_feature_view = 2; @@ -8,7 +10,7 @@ message PushRequest { } message PushResponse { - bool status = 1; + bool status = 1; } message WriteToOnlineStoreRequest { @@ -18,10 +20,11 @@ message WriteToOnlineStoreRequest { } message WriteToOnlineStoreResponse { - bool status = 1; + bool status = 1; } service GrpcFeatureServer { - rpc Push (PushRequest) returns (PushResponse) {}; - rpc WriteToOnlineStore (WriteToOnlineStoreRequest) returns (WriteToOnlineStoreResponse); + rpc Push (PushRequest) returns (PushResponse) {}; + rpc WriteToOnlineStore (WriteToOnlineStoreRequest) returns (WriteToOnlineStoreResponse); + rpc GetOnlineFeatures (feast.serving.GetOnlineFeaturesRequest) returns (feast.serving.GetOnlineFeaturesResponse); } \ No newline at end of file diff --git a/protos/feast/serving/ServingService.proto b/protos/feast/serving/ServingService.proto index 0eef3cd883..154d850099 100644 --- a/protos/feast/serving/ServingService.proto +++ b/protos/feast/serving/ServingService.proto @@ -105,6 +105,8 @@ message GetOnlineFeaturesResponse { repeated FieldStatus statuses = 2; repeated google.protobuf.Timestamp event_timestamps = 3; } + + bool status = 3; } message GetOnlineFeaturesResponseMetadata { diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 3153f02e51..4cce8855f4 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -705,15 +705,24 @@ def serve_command( show_default=False, help="The maximum number of threads that can be used to execute the gRPC calls", ) +@click.option( + "--registry_ttl_sec", + "-r", + help="Number of seconds after which the registry is refreshed", + type=click.INT, + default=5, + show_default=True, +) @click.pass_context def listen_command( ctx: click.Context, address: str, max_workers: int, + registry_ttl_sec: int, ): """Start a gRPC feature server to ingest streaming features on given address""" store = create_feature_store(ctx) - server = get_grpc_server(address, store, max_workers) + server = get_grpc_server(address, store, max_workers, registry_ttl_sec) server.start() server.wait_for_termination() diff --git a/sdk/python/feast/infra/contrib/grpc_server.py b/sdk/python/feast/infra/contrib/grpc_server.py index 2017f1095b..27ac45e77c 100644 --- a/sdk/python/feast/infra/contrib/grpc_server.py +++ b/sdk/python/feast/infra/contrib/grpc_server.py @@ -1,12 +1,14 @@ import logging +import threading from concurrent import futures +from typing import Optional import grpc import pandas as pd from grpc_health.v1 import health, health_pb2_grpc from feast.data_source import PushMode -from feast.errors import PushSourceNotFoundException +from feast.errors import FeatureServiceNotFoundException, PushSourceNotFoundException from feast.feature_store import FeatureStore from feast.protos.feast.serving.GrpcServer_pb2 import ( PushResponse, @@ -16,6 +18,12 @@ GrpcFeatureServerServicer, add_GrpcFeatureServerServicer_to_server, ) +from feast.protos.feast.serving.ServingService_pb2 import ( + GetOnlineFeaturesRequest, + GetOnlineFeaturesResponse, +) + +logger = logging.getLogger(__name__) def parse(features): @@ -28,10 +36,16 @@ def parse(features): class GrpcFeatureServer(GrpcFeatureServerServicer): fs: FeatureStore - def __init__(self, fs: FeatureStore): + _shuting_down: bool = False + _active_timer: Optional[threading.Timer] = None + + def __init__(self, fs: FeatureStore, registry_ttl_sec: int = 5): self.fs = fs + self.registry_ttl_sec = registry_ttl_sec super().__init__() + self._async_refresh() + def Push(self, request, context): try: df = parse(request.features) @@ -53,19 +67,19 @@ def Push(self, request, context): to=to, ) except PushSourceNotFoundException as e: - logging.exception(str(e)) + logger.exception(str(e)) context.set_code(grpc.StatusCode.INVALID_ARGUMENT) context.set_details(str(e)) return PushResponse(status=False) except Exception as e: - logging.exception(str(e)) + logger.exception(str(e)) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return PushResponse(status=False) return PushResponse(status=True) def WriteToOnlineStore(self, request, context): - logging.warning( + logger.warning( "write_to_online_store is deprecated. Please consider using Push instead" ) try: @@ -76,16 +90,55 @@ def WriteToOnlineStore(self, request, context): allow_registry_cache=request.allow_registry_cache, ) except Exception as e: - logging.exception(str(e)) + logger.exception(str(e)) context.set_code(grpc.StatusCode.INTERNAL) context.set_details(str(e)) return PushResponse(status=False) return WriteToOnlineStoreResponse(status=True) + def GetOnlineFeatures(self, request: GetOnlineFeaturesRequest, context): + if request.HasField("feature_service"): + logger.info(f"Requesting feature service: {request.feature_service}") + try: + features = self.fs.get_feature_service( + request.feature_service, allow_cache=True + ) + except FeatureServiceNotFoundException as e: + logger.error(f"Feature service {request.feature_service} not found") + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details(str(e)) + return GetOnlineFeaturesResponse() + else: + features = list(request.features.val) + + result = self.fs._get_online_features( + features, + request.entities, + request.full_feature_names, + ).proto + + return result + + def _async_refresh(self): + self.fs.refresh_registry() + if self._shuting_down: + return + self._active_timer = threading.Timer(self.registry_ttl_sec, self._async_refresh) + self._active_timer.start() -def get_grpc_server(address: str, fs: FeatureStore, max_workers: int): + +def get_grpc_server( + address: str, + fs: FeatureStore, + max_workers: int, + registry_ttl_sec: int, +): + logger.info(f"Initializing gRPC server on {address}") server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers)) - add_GrpcFeatureServerServicer_to_server(GrpcFeatureServer(fs), server) + add_GrpcFeatureServerServicer_to_server( + GrpcFeatureServer(fs, registry_ttl_sec=registry_ttl_sec), + server, + ) health_servicer = health.HealthServicer( experimental_non_blocking=True, experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=max_workers), diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index 3f49069066..cdb65f886e 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -428,12 +428,15 @@ def _python_value_to_proto_value( for value in values ] if feast_value_type in PYTHON_SCALAR_VALUE_TYPE_TO_PROTO_VALUE: - return [ - ProtoValue(**{field_name: func(value)}) - if not pd.isnull(value) - else ProtoValue() - for value in values - ] + out = [] + for value in values: + if isinstance(value, ProtoValue): + out.append(value) + elif not pd.isnull(value): + out.append(ProtoValue(**{field_name: func(value)})) + else: + out.append(ProtoValue()) + return out raise Exception(f"Unsupported data type: ${str(type(values[0]))}") @@ -746,7 +749,7 @@ def spark_to_feast_value_type(spark_type_as_str: str) -> ValueType: "array": ValueType.UNIX_TIMESTAMP_LIST, } # TODO: Find better way of doing this. - if type(spark_type_as_str) != str or spark_type_as_str not in type_map: + if not isinstance(spark_type_as_str, str) or spark_type_as_str not in type_map: return ValueType.NULL return type_map[spark_type_as_str.lower()] From 9583ed6b4ae8d3b97934bf0c80ecb236ed1e2895 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 1 Nov 2023 01:14:42 +0700 Subject: [PATCH 25/41] fix: Add async refresh to prevent synchronous refresh in main thread (#3812) fix: Add async refresh to prevent synchronous refresh in main thread for http feature server Signed-off-by: Hai Nguyen --- sdk/python/feast/cli.py | 10 +++++++++ sdk/python/feast/feature_server.py | 33 ++++++++++++++++++++++++++++-- sdk/python/feast/feature_store.py | 2 ++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 4cce8855f4..2eb2c27bcb 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -663,6 +663,14 @@ def init_command(project_directory, minimal: bool, template: str): show_default=True, help="Timeout for keep alive", ) +@click.option( + "--registry_ttl_sec", + "-r", + help="Number of seconds after which the registry is refreshed", + type=click.INT, + default=5, + show_default=True, +) @click.pass_context def serve_command( ctx: click.Context, @@ -673,6 +681,7 @@ def serve_command( no_feature_log: bool, workers: int, keep_alive_timeout: int, + registry_ttl_sec: int = 5, ): """Start a feature server locally on a given port.""" store = create_feature_store(ctx) @@ -685,6 +694,7 @@ def serve_command( no_feature_log=no_feature_log, workers=workers, keep_alive_timeout=keep_alive_timeout, + registry_ttl_sec=registry_ttl_sec, ) diff --git a/sdk/python/feast/feature_server.py b/sdk/python/feast/feature_server.py index 7c638dd248..618aefb2f2 100644 --- a/sdk/python/feast/feature_server.py +++ b/sdk/python/feast/feature_server.py @@ -1,4 +1,5 @@ import json +import threading import traceback import warnings from typing import List, Optional @@ -44,14 +45,37 @@ class MaterializeIncrementalRequest(BaseModel): feature_views: Optional[List[str]] = None -def get_app(store: "feast.FeatureStore"): +def get_app(store: "feast.FeatureStore", registry_ttl_sec: int = 5): proto_json.patch() app = FastAPI() + # Asynchronously refresh registry, notifying shutdown and canceling the active timer if the app is shutting down + registry_proto = None + shutting_down = False + active_timer: Optional[threading.Timer] = None async def get_body(request: Request): return await request.body() + def async_refresh(): + store.refresh_registry() + nonlocal registry_proto + registry_proto = store.registry.proto() + if shutting_down: + return + nonlocal active_timer + active_timer = threading.Timer(registry_ttl_sec, async_refresh) + active_timer.start() + + @app.on_event("shutdown") + def shutdown_event(): + nonlocal shutting_down + shutting_down = True + if active_timer: + active_timer.cancel() + + async_refresh() + @app.post("/get-online-features") def get_online_features(body=Depends(get_body)): try: @@ -180,7 +204,10 @@ def materialize_incremental(body=Depends(get_body)): class FeastServeApplication(gunicorn.app.base.BaseApplication): def __init__(self, store: "feast.FeatureStore", **options): - self._app = get_app(store=store) + self._app = get_app( + store=store, + registry_ttl_sec=options.get("registry_ttl_sec", 5), + ) self._options = options super().__init__() @@ -202,6 +229,7 @@ def start_server( no_access_log: bool, workers: int, keep_alive_timeout: int, + registry_ttl_sec: int = 5, ): FeastServeApplication( store=store, @@ -209,4 +237,5 @@ def start_server( accesslog=None if no_access_log else "-", workers=workers, keepalive=keep_alive_timeout, + registry_ttl_sec=registry_ttl_sec, ).run() diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 4b8200d96f..d3f98f8032 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -2228,6 +2228,7 @@ def serve( no_feature_log: bool, workers: int, keep_alive_timeout: int, + registry_ttl_sec: int, ) -> None: """Start the feature consumption server locally on a given port.""" type_ = type_.lower() @@ -2243,6 +2244,7 @@ def serve( no_access_log=no_access_log, workers=workers, keep_alive_timeout=keep_alive_timeout, + registry_ttl_sec=registry_ttl_sec, ) @log_exceptions_and_usage From 25e97756adedfd1227d591ae74bdf60655f9067e Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 8 Nov 2023 08:38:58 +0700 Subject: [PATCH 26/41] fix: Bytewax engine create configmap from object (#3821) fix: bytewax engine create configmap from object Signed-off-by: Hai Nguyen --- .../contrib/bytewax/bytewax_materialization_engine.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index 1c9dc6a6be..b2f8985f87 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -27,7 +27,7 @@ from feast.infra.registry.base_registry import BaseRegistry from feast.repo_config import FeastConfigBaseModel from feast.stream_feature_view import StreamFeatureView -from feast.utils import _get_column_names, get_default_yaml_file_path +from feast.utils import _get_column_names from .bytewax_materialization_job import BytewaxMaterializationJob @@ -307,10 +307,7 @@ def _create_kubernetes_job(self, job_id, paths, feature_view): def _create_configuration_map(self, job_id, paths, feature_view, namespace): """Create a Kubernetes configmap for this job""" - repo_path = self.repo_config.repo_path - assert repo_path - feature_store_path = get_default_yaml_file_path(repo_path) - feature_store_configuration = feature_store_path.read_text() + feature_store_configuration = yaml.dump(self.repo_config.dict()) materialization_config = yaml.dump( {"paths": paths, "feature_view": feature_view.name} From 2151c39d1a8d8eba114306411dd4bd91ac0ce3f6 Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Sat, 4 Nov 2023 15:35:04 +0700 Subject: [PATCH 27/41] fix: Set upper bound for bigquery client due to its breaking changes Signed-off-by: Hai Nguyen --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9fbc2bc2cd..5084193d5e 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ GCP_REQUIRED = [ "google-api-core>=1.23.0,<3", "googleapis-common-protos>=1.52.0,<2", - "google-cloud-bigquery[pandas]>=2,<4", + "google-cloud-bigquery[pandas]>=2,<3.13.0", "google-cloud-bigquery-storage >= 2.0.0,<3", "google-cloud-datastore>=2.1.0,<3", "google-cloud-storage>=1.34.0,<3", From 5dc0b241ec68aa10fd783569bf0ae12c5752f20f Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Wed, 8 Nov 2023 10:56:28 +0700 Subject: [PATCH 28/41] fix: update dependencies versions due to conflicts Signed-off-by: Hai Nguyen --- setup.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 5084193d5e..38b7d295a9 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ from setuptools.command.install import install except ImportError: - from distutils.command.build_ext import build_ext as _build_ext from distutils.command.build_py import build_py from distutils.core import setup @@ -80,7 +79,7 @@ # FastAPI does not correctly pull starlette dependency on httpx see thread(https://github.com/tiangolo/fastapi/issues/5656). "httpx>=0.23.3", "importlib-resources>=6.0.0,<7", - "importlib_metadata>=6.8.0,<7" + "importlib_metadata>=6.8.0,<7", ] GCP_REQUIRED = [ @@ -91,7 +90,7 @@ "google-cloud-datastore>=2.1.0,<3", "google-cloud-storage>=1.34.0,<3", "google-cloud-bigtable>=2.11.0,<3", - "gcsfs", + "fsspec<2023.10.0", ] REDIS_REQUIRED = [ @@ -99,7 +98,7 @@ "hiredis>=2.0.0,<3", ] -AWS_REQUIRED = ["boto3>=1.17.0,<2", "docker>=5.0.2", "s3fs"] +AWS_REQUIRED = ["boto3>=1.17.0,<2", "docker>=5.0.2", "fsspec<2023.10.0"] BYTEWAX_REQUIRED = ["bytewax==0.15.1", "docker>=5.0.2", "kubernetes<=20.13.0"] @@ -159,8 +158,8 @@ "moto", "mypy>=0.981,<0.990", "avro==1.10.0", - "gcsfs", - "urllib3>=1.25.4,<2", + "fsspec<2023.10.0", + "urllib3>=1.25.4,<3", "psutil==5.9.0", "py>=1.11.0", # https://github.com/pytest-dev/pytest/issues/10420 "pytest>=6.0.0,<8", @@ -173,7 +172,6 @@ "pytest-mock==1.10.4", "Sphinx>4.0.0,<7", "testcontainers>=3.5,<4", - "adlfs==0.5.9", "firebase-admin>=5.2.0,<6", "pre-commit<3.3.2", "assertpy==1.1", @@ -184,10 +182,10 @@ "types-pytz", "types-PyYAML", "types-redis", - "types-requests", + "types-requests<2.31.0", "types-setuptools", "types-tabulate", - "virtualenv<20.24.2" + "virtualenv<20.24.2", ] + GCP_REQUIRED + REDIS_REQUIRED From 1f91fc69ed316e83acb60db2aa6e8a175c6a2ced Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Wed, 8 Nov 2023 10:56:32 +0700 Subject: [PATCH 29/41] chore: generate reqs file from up-to-date deps Signed-off-by: Hai Nguyen --- .../requirements/py3.10-ci-requirements.txt | 327 +++++++---------- .../requirements/py3.10-requirements.txt | 65 ++-- .../requirements/py3.8-ci-requirements.txt | 319 +++++++---------- .../requirements/py3.8-requirements.txt | 61 ++-- .../requirements/py3.9-ci-requirements.txt | 329 +++++++----------- .../requirements/py3.9-requirements.txt | 65 ++-- 6 files changed, 468 insertions(+), 698 deletions(-) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 616ec5a228..c74a3f12e1 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -4,93 +4,64 @@ # # pip-compile --extra=ci --output-file=sdk/python/requirements/py3.10-ci-requirements.txt # -adal==1.2.7 - # via msrestazure -adlfs==0.5.9 - # via feast (setup.py) -aiohttp==3.8.5 - # via - # adlfs - # gcsfs -aiosignal==1.3.1 - # via aiohttp alabaster==0.7.13 # via sphinx altair==4.2.0 # via great-expectations anyio==4.0.0 # via - # httpcore + # httpx # jupyter-server # starlette # watchfiles appdirs==1.4.4 # via fissix -appnope==0.1.3 - # via - # ipykernel - # ipython argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 # via argon2-cffi -arrow==1.2.3 +arrow==1.3.0 # via isoduration asn1crypto==1.5.1 - # via - # oscrypto - # snowflake-connector-python + # via snowflake-connector-python assertpy==1.1 # via feast (setup.py) -asttokens==2.4.0 +asttokens==2.4.1 # via stack-data async-lru==2.0.4 # via jupyterlab async-timeout==4.0.3 - # via - # aiohttp - # redis + # via redis attrs==23.1.0 # via - # aiohttp # bowler # jsonschema # referencing -avro==1.11.3 +avro==1.10.0 # via feast (setup.py) -azure-core==1.29.4 +azure-core==1.29.5 # via - # adlfs # azure-identity # azure-storage-blob - # msrest -azure-datalake-store==0.0.53 - # via adlfs -azure-identity==1.14.0 - # via - # adlfs - # feast (setup.py) -azure-storage-blob==12.17.0 - # via - # adlfs - # feast (setup.py) -babel==2.12.1 +azure-identity==1.15.0 + # via feast (setup.py) +azure-storage-blob==12.19.0 + # via feast (setup.py) +babel==2.13.1 # via # jupyterlab-server # sphinx -backcall==0.2.0 - # via ipython beautifulsoup4==4.12.2 # via nbconvert black==22.12.0 # via feast (setup.py) -bleach==6.0.0 +bleach==6.1.0 # via nbconvert -boto3==1.28.43 +boto3==1.28.80 # via # feast (setup.py) # moto -botocore==1.31.43 +botocore==1.31.80 # via # boto3 # moto @@ -105,7 +76,7 @@ bytewax==0.15.1 # via feast (setup.py) cachecontrol==0.13.1 # via firebase-admin -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth cassandra-driver==3.28.0 # via feast (setup.py) @@ -115,20 +86,17 @@ certifi==2023.7.22 # httpx # kubernetes # minio - # msrest # requests # snowflake-connector-python -cffi==1.15.1 +cffi==1.16.0 # via # argon2-cffi-bindings - # azure-datalake-store # cryptography # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via - # aiohttp # requests # snowflake-connector-python click==8.1.7 @@ -142,21 +110,20 @@ click==8.1.7 # moreorless # pip-tools # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via # feast (setup.py) # great-expectations -comm==0.1.4 +comm==0.2.0 # via # ipykernel # ipywidgets -coverage[toml]==7.3.1 +coverage[toml]==7.3.2 # via pytest-cov -cryptography==41.0.4 +cryptography==41.0.5 # via - # adal # azure-identity # azure-storage-blob # feast (setup.py) @@ -168,16 +135,14 @@ cryptography==41.0.4 # snowflake-connector-python # types-pyopenssl # types-redis -dask==2023.9.1 +dask==2023.10.1 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery -debugpy==1.7.0 +debugpy==1.8.0 # via ipykernel decorator==5.1.1 - # via - # gcsfs - # ipython + # via ipython defusedxml==0.7.1 # via nbconvert deprecation==2.1.0 @@ -204,17 +169,17 @@ exceptiongroup==1.1.3 # pytest execnet==2.0.2 # via pytest-xdist -executing==1.2.0 +executing==2.0.1 # via stack-data fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.3 +fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.0 +fastjsonschema==2.18.1 # via nbformat -filelock==3.12.3 +filelock==3.13.1 # via # snowflake-connector-python # virtualenv @@ -226,22 +191,15 @@ flake8==6.0.0 # via feast (setup.py) fqdn==1.5.1 # via jsonschema -frozenlist==1.4.0 +fsspec==2023.9.2 # via - # aiohttp - # aiosignal -fsspec==2022.1.0 - # via - # adlfs # dask - # gcsfs -gcsfs==2022.1.0 - # via feast (setup.py) + # feast (setup.py) geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.11.1 +google-api-core[grpc]==2.13.0 # via # feast (setup.py) # firebase-admin @@ -253,23 +211,19 @@ google-api-core[grpc]==2.11.1 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.98.0 +google-api-python-client==2.107.0 # via firebase-admin -google-auth==2.22.0 +google-auth==2.23.4 # via - # gcsfs # google-api-core # google-api-python-client # google-auth-httplib2 - # google-auth-oauthlib # google-cloud-core # google-cloud-storage # kubernetes -google-auth-httplib2==0.1.0 +google-auth-httplib2==0.1.1 # via google-api-python-client -google-auth-oauthlib==1.0.0 - # via gcsfs -google-cloud-bigquery[pandas]==3.11.4 +google-cloud-bigquery[pandas]==3.12.0 # via feast (setup.py) google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) @@ -284,20 +238,21 @@ google-cloud-core==2.3.3 # google-cloud-storage google-cloud-datastore==2.18.0 # via feast (setup.py) -google-cloud-firestore==2.11.1 +google-cloud-firestore==2.13.1 # via firebase-admin -google-cloud-storage==2.10.0 +google-cloud-storage==2.13.0 # via # feast (setup.py) # firebase-admin - # gcsfs google-crc32c==1.5.0 - # via google-resumable-media + # via + # google-cloud-storage + # google-resumable-media google-resumable-media==2.6.0 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.60.0 +googleapis-common-protos[grpc]==1.61.0 # via # feast (setup.py) # google-api-core @@ -305,9 +260,11 @@ googleapis-common-protos[grpc]==1.60.0 # grpcio-status great-expectations==0.15.50 # via feast (setup.py) +greenlet==3.0.1 + # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-bigtable -grpcio==1.58.0 +grpcio==1.59.2 # via # feast (setup.py) # google-api-core @@ -319,15 +276,15 @@ grpcio==1.58.0 # grpcio-status # grpcio-testing # grpcio-tools -grpcio-health-checking==1.58.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.58.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-status==1.58.0 +grpcio-status==1.59.2 # via google-api-core -grpcio-testing==1.58.0 +grpcio-testing==1.59.2 # via feast (setup.py) -grpcio-tools==1.58.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -341,17 +298,17 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==0.17.3 +httpcore==1.0.1 # via httpx httplib2==0.22.0 # via # google-api-python-client # google-auth-httplib2 -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) -identify==2.5.27 +identify==2.5.31 # via pre-commit idna==3.4 # via @@ -360,7 +317,6 @@ idna==3.4 # jsonschema # requests # snowflake-connector-python - # yarl imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 @@ -368,28 +324,26 @@ importlib-metadata==6.8.0 # dask # feast (setup.py) # great-expectations -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via feast (setup.py) iniconfig==2.0.0 # via pytest -ipykernel==6.25.2 +ipykernel==6.26.0 # via jupyterlab -ipython==8.15.0 +ipython==8.17.2 # via # great-expectations # ipykernel # ipywidgets -ipywidgets==8.1.0 +ipywidgets==8.1.1 # via great-expectations isodate==0.6.1 - # via - # azure-storage-blob - # msrest + # via azure-storage-blob isoduration==20.11.0 # via jsonschema isort==5.12.0 # via feast (setup.py) -jedi==0.19.0 +jedi==0.19.1 # via ipython jinja2==3.1.2 # via @@ -414,7 +368,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.0 +jsonschema[format-nongpl]==4.19.2 # via # altair # feast (setup.py) @@ -424,12 +378,12 @@ jsonschema[format-nongpl]==4.19.0 # nbformat jsonschema-specifications==2023.7.1 # via jsonschema -jupyter-client==8.3.1 +jupyter-client==8.6.0 # via # ipykernel # jupyter-server # nbclient -jupyter-core==5.3.1 +jupyter-core==5.5.0 # via # ipykernel # jupyter-client @@ -438,11 +392,11 @@ jupyter-core==5.3.1 # nbclient # nbconvert # nbformat -jupyter-events==0.7.0 +jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.7.3 +jupyter-server==2.10.0 # via # jupyter-lsp # jupyterlab @@ -451,15 +405,15 @@ jupyter-server==2.7.3 # notebook-shim jupyter-server-terminals==0.4.4 # via jupyter-server -jupyterlab==4.0.5 +jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.24.0 +jupyterlab-server==2.25.0 # via # jupyterlab # notebook -jupyterlab-widgets==3.0.8 +jupyterlab-widgets==3.0.9 # via ipywidgets kubernetes==20.13.0 # via feast (setup.py) @@ -482,7 +436,7 @@ mccabe==0.7.0 # via flake8 minio==7.1.0 # via feast (setup.py) -mistune==3.0.1 +mistune==3.0.2 # via # great-expectations # nbconvert @@ -492,25 +446,16 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.2 +moto==4.2.7 # via feast (setup.py) -msal==1.23.0 +msal==1.25.0 # via - # azure-datalake-store # azure-identity # msal-extensions msal-extensions==1.0.0 # via azure-identity -msgpack==1.0.5 +msgpack==1.0.7 # via cachecontrol -msrest==0.7.1 - # via msrestazure -msrestazure==0.6.4 - # via adlfs -multidict==6.0.4 - # via - # aiohttp - # yarl multiprocess==0.70.15 # via bytewax mypy==0.982 @@ -521,13 +466,13 @@ mypy-extensions==1.0.0 # via # black # mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) mysqlclient==2.2.0 # via feast (setup.py) -nbclient==0.8.0 +nbclient==0.9.0 # via nbconvert -nbconvert==7.8.0 +nbconvert==7.11.0 # via jupyter-server nbformat==5.9.2 # via @@ -535,11 +480,11 @@ nbformat==5.9.2 # jupyter-server # nbclient # nbconvert -nest-asyncio==1.5.7 +nest-asyncio==1.5.8 # via ipykernel nodeenv==1.8.0 # via pre-commit -notebook==7.0.3 +notebook==7.0.6 # via great-expectations notebook-shim==0.2.3 # via @@ -557,11 +502,9 @@ numpy==1.24.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -oscrypto==1.3.0 - # via snowflake-connector-python overrides==7.4.0 # via jupyter-server -packaging==23.1 +packaging==23.2 # via # build # dask @@ -595,19 +538,17 @@ pandocfilters==1.5.0 # via nbconvert parso==0.8.3 # via jedi -partd==1.4.0 +partd==1.4.1 # via dask pathspec==0.11.2 # via black -pbr==5.11.1 +pbr==6.0.0 # via mock pexpect==4.8.0 # via ipython -pickleshare==0.7.5 - # via ipython pip-tools==7.3.0 # via feast (setup.py) -platformdirs==3.8.1 +platformdirs==3.11.0 # via # black # jupyter-core @@ -617,11 +558,11 @@ pluggy==1.3.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.7.0 +portalocker==2.8.2 # via msal-extensions pre-commit==3.3.1 # via feast (setup.py) -prometheus-client==0.17.1 +prometheus-client==0.18.0 # via jupyter-server prompt-toolkit==3.0.39 # via ipython @@ -655,7 +596,7 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg2-binary==2.9.7 +psycopg2-binary==2.9.9 # via feast (setup.py) ptyprocess==0.7.0 # via @@ -687,9 +628,7 @@ pycodestyle==2.10.0 # via flake8 pycparser==2.21 # via cffi -pycryptodomex==3.18.0 - # via snowflake-connector-python -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -704,16 +643,15 @@ pygments==2.16.1 # sphinx pyjwt[crypto]==2.8.0 # via - # adal # msal # snowflake-connector-python -pymssql==2.2.8 +pymssql==2.2.10 # via feast (setup.py) pymysql==1.1.0 # via feast (setup.py) -pyodbc==4.0.39 +pyodbc==5.0.1 # via feast (setup.py) -pyopenssl==23.2.0 +pyopenssl==23.3.0 # via snowflake-connector-python pyparsing==3.1.1 # via @@ -721,9 +659,9 @@ pyparsing==3.1.1 # httplib2 pyproject-hooks==1.0.0 # via build -pyspark==3.4.1 +pyspark==3.5.0 # via feast (setup.py) -pytest==7.4.2 +pytest==7.4.3 # via # feast (setup.py) # pytest-benchmark @@ -749,7 +687,6 @@ pytest-xdist==3.3.1 # via feast (setup.py) python-dateutil==2.8.2 # via - # adal # arrow # botocore # google-cloud-bigquery @@ -791,18 +728,14 @@ referencing==0.30.2 # jsonschema # jsonschema-specifications # jupyter-events -regex==2023.8.8 +regex==2023.10.3 # via feast (setup.py) requests==2.31.0 # via - # adal - # adlfs # azure-core - # azure-datalake-store # cachecontrol # docker # feast (setup.py) - # gcsfs # google-api-core # google-cloud-bigquery # google-cloud-storage @@ -811,18 +744,14 @@ requests==2.31.0 # kubernetes # moto # msal - # msrest # requests-oauthlib # responses # snowflake-connector-python # sphinx # trino requests-oauthlib==1.3.1 - # via - # google-auth-oauthlib - # kubernetes - # msrest -responses==0.23.3 + # via kubernetes +responses==0.24.0 # via moto rfc3339-validator==0.1.4 # via @@ -834,7 +763,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -842,9 +771,9 @@ rsa==4.9 # via google-auth ruamel-yaml==0.17.17 # via great-expectations -s3transfer==0.6.2 +s3transfer==0.7.0 # via boto3 -scipy==1.11.2 +scipy==1.11.3 # via great-expectations send2trash==1.8.2 # via jupyter-server @@ -855,13 +784,10 @@ six==1.16.0 # bleach # cassandra-driver # geomet - # google-auth - # google-auth-httplib2 # happybase # isodate # kubernetes # mock - # msrestazure # pandavro # python-dateutil # rfc3339-validator @@ -869,11 +795,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.1.1 +snowflake-connector-python[pandas]==3.4.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -899,11 +824,11 @@ sphinxcontrib-qthelp==1.0.6 # via sphinx sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy -stack-data==0.6.2 +stack-data==0.6.3 # via ipython starlette==0.27.0 # via fastapi @@ -917,7 +842,7 @@ terminado==0.17.1 # jupyter-server-terminals testcontainers==3.7.1 # via feast (setup.py) -thriftpy2==0.4.16 +thriftpy2==0.4.17 # via happybase tinycss2==1.2.1 # via nbconvert @@ -933,7 +858,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.1 +tomlkit==0.12.2 # via snowflake-connector-python toolz==0.12.0 # via @@ -952,7 +877,7 @@ tqdm==4.66.1 # via # feast (setup.py) # great-expectations -traitlets==5.9.0 +traitlets==5.13.0 # via # comm # ipykernel @@ -967,7 +892,7 @@ traitlets==5.9.0 # nbclient # nbconvert # nbformat -trino==0.326.0 +trino==0.327.0 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) @@ -977,19 +902,19 @@ types-protobuf==3.19.22 # mypy-protobuf types-pymysql==1.1.0.1 # via feast (setup.py) -types-pyopenssl==23.2.0.2 +types-pyopenssl==23.3.0.0 # via types-redis types-python-dateutil==2.8.19.14 - # via feast (setup.py) -types-pytz==2023.3.0.1 - # via feast (setup.py) -types-pyyaml==6.0.12.11 # via + # arrow # feast (setup.py) - # responses -types-redis==4.6.0.5 +types-pytz==2023.3.1.1 + # via feast (setup.py) +types-pyyaml==6.0.12.12 + # via feast (setup.py) +types-redis==4.6.0.10 # via feast (setup.py) -types-requests==2.31.0.2 +types-requests==2.30.0.0 # via feast (setup.py) types-setuptools==68.2.0.0 # via feast (setup.py) @@ -997,20 +922,19 @@ types-tabulate==0.9.0.3 # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # async-lru # azure-core # azure-storage-blob # fastapi - # filelock # great-expectations # mypy # pydantic # snowflake-connector-python # sqlalchemy2-stubs # uvicorn -tzlocal==5.0.1 +tzlocal==5.2 # via # great-expectations # trino @@ -1023,7 +947,6 @@ urllib3==1.26.18 # botocore # docker # feast (setup.py) - # google-auth # great-expectations # kubernetes # minio @@ -1031,9 +954,9 @@ urllib3==1.26.18 # responses # rockset # snowflake-connector-python -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 # via @@ -1041,9 +964,9 @@ virtualenv==20.23.0 # pre-commit volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.6 +wcwidth==0.2.9 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -1051,26 +974,24 @@ webencodings==0.5.1 # via # bleach # tinycss2 -websocket-client==1.6.2 +websocket-client==1.6.4 # via # docker # jupyter-server # kubernetes -websockets==11.0.3 +websockets==12.0 # via uvicorn -werkzeug==2.3.7 +werkzeug==3.0.1 # via moto -wheel==0.41.2 +wheel==0.41.3 # via pip-tools -widgetsnbextension==4.0.8 +widgetsnbextension==4.0.9 # via ipywidgets wrapt==1.15.0 # via testcontainers xmltodict==0.13.0 # via moto -yarl==1.9.2 - # via aiohttp -zipp==3.16.2 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index f4cac316d2..41fee168bf 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -6,7 +6,7 @@ # anyio==4.0.0 # via - # httpcore + # httpx # starlette # watchfiles appdirs==1.4.4 @@ -23,7 +23,7 @@ certifi==2023.7.22 # httpcore # httpx # requests -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via requests click==8.1.7 # via @@ -32,11 +32,11 @@ click==8.1.7 # feast (setup.py) # moreorless # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via feast (setup.py) -dask==2023.9.1 +dask==2023.10.1 # via feast (setup.py) dill==0.3.7 # via feast (setup.py) @@ -44,25 +44,27 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.3 +fastavro==1.9.0 # via # feast (setup.py) # pandavro fissix==21.11.13 # via bowler -fsspec==2023.9.0 +fsspec==2023.10.0 # via dask -grpcio==1.58.0 +greenlet==3.0.1 + # via sqlalchemy +grpcio==1.59.2 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.58.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.58.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-tools==1.58.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -70,11 +72,11 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==0.17.3 +httpcore==1.0.1 # via httpx -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) idna==3.4 # via @@ -85,11 +87,11 @@ importlib-metadata==6.8.0 # via # dask # feast (setup.py) -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.0 +jsonschema==4.19.2 # via feast (setup.py) jsonschema-specifications==2023.7.1 # via jsonschema @@ -101,11 +103,11 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.5.1 +mypy==1.6.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) numpy==1.24.4 # via @@ -113,7 +115,7 @@ numpy==1.24.4 # pandas # pandavro # pyarrow -packaging==23.1 +packaging==23.2 # via # dask # gunicorn @@ -123,7 +125,7 @@ pandas==1.5.3 # pandavro pandavro==1.5.2 # via feast (setup.py) -partd==1.4.0 +partd==1.4.1 # via dask proto-plus==1.22.3 # via feast (setup.py) @@ -137,7 +139,7 @@ protobuf==4.23.3 # proto-plus pyarrow==11.0.0 # via feast (setup.py) -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -160,7 +162,7 @@ referencing==0.30.2 # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -171,11 +173,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -195,28 +196,28 @@ tqdm==4.66.1 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) -types-protobuf==4.24.0.1 +types-protobuf==4.24.0.4 # via mypy-protobuf -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # fastapi # mypy # pydantic # sqlalchemy2-stubs # uvicorn -urllib3==1.26.18 +urllib3==2.0.7 # via requests -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -websockets==11.0.3 +websockets==12.0 # via uvicorn -zipp==3.16.2 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index b84305d92f..855074057a 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -4,77 +4,50 @@ # # pip-compile --extra=ci --output-file=sdk/python/requirements/py3.8-ci-requirements.txt # -adal==1.2.7 - # via msrestazure -adlfs==0.5.9 - # via feast (setup.py) -aiohttp==3.8.5 - # via - # adlfs - # gcsfs -aiosignal==1.3.1 - # via aiohttp alabaster==0.7.13 # via sphinx altair==4.2.0 # via great-expectations anyio==4.0.0 # via - # httpcore + # httpx # jupyter-server # starlette # watchfiles appdirs==1.4.4 # via fissix -appnope==0.1.3 - # via - # ipykernel - # ipython argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 # via argon2-cffi -arrow==1.2.3 +arrow==1.3.0 # via isoduration asn1crypto==1.5.1 - # via - # oscrypto - # snowflake-connector-python + # via snowflake-connector-python assertpy==1.1 # via feast (setup.py) -asttokens==2.4.0 +asttokens==2.4.1 # via stack-data async-lru==2.0.4 # via jupyterlab async-timeout==4.0.3 - # via - # aiohttp - # redis + # via redis attrs==23.1.0 # via - # aiohttp # bowler # jsonschema # referencing -avro==1.11.3 +avro==1.10.0 # via feast (setup.py) -azure-core==1.29.3 +azure-core==1.29.5 # via - # adlfs # azure-identity # azure-storage-blob - # msrest -azure-datalake-store==0.0.53 - # via adlfs -azure-identity==1.14.0 - # via - # adlfs - # feast (setup.py) -azure-storage-blob==12.17.0 - # via - # adlfs - # feast (setup.py) -babel==2.12.1 +azure-identity==1.15.0 + # via feast (setup.py) +azure-storage-blob==12.19.0 + # via feast (setup.py) +babel==2.13.1 # via # jupyterlab-server # sphinx @@ -88,13 +61,13 @@ beautifulsoup4==4.12.2 # via nbconvert black==22.12.0 # via feast (setup.py) -bleach==6.0.0 +bleach==6.1.0 # via nbconvert -boto3==1.28.42 +boto3==1.28.80 # via # feast (setup.py) # moto -botocore==1.31.42 +botocore==1.31.80 # via # boto3 # moto @@ -109,7 +82,7 @@ bytewax==0.15.1 # via feast (setup.py) cachecontrol==0.13.1 # via firebase-admin -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth cassandra-driver==3.28.0 # via feast (setup.py) @@ -119,20 +92,17 @@ certifi==2023.7.22 # httpx # kubernetes # minio - # msrest # requests # snowflake-connector-python -cffi==1.15.1 +cffi==1.16.0 # via # argon2-cffi-bindings - # azure-datalake-store # cryptography # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via - # aiohttp # requests # snowflake-connector-python click==8.1.7 @@ -146,21 +116,20 @@ click==8.1.7 # moreorless # pip-tools # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via # feast (setup.py) # great-expectations -comm==0.1.4 +comm==0.2.0 # via # ipykernel # ipywidgets -coverage[toml]==7.3.1 +coverage[toml]==7.3.2 # via pytest-cov -cryptography==41.0.4 +cryptography==41.0.5 # via - # adal # azure-identity # azure-storage-blob # feast (setup.py) @@ -176,12 +145,10 @@ dask==2023.5.0 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery -debugpy==1.6.7.post1 +debugpy==1.8.0 # via ipykernel decorator==5.1.1 - # via - # gcsfs - # ipython + # via ipython defusedxml==0.7.1 # via nbconvert deprecation==2.1.0 @@ -207,17 +174,17 @@ exceptiongroup==1.1.3 # pytest execnet==2.0.2 # via pytest-xdist -executing==1.2.0 +executing==2.0.1 # via stack-data fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.2 +fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.0 +fastjsonschema==2.18.1 # via nbformat -filelock==3.12.3 +filelock==3.13.1 # via # snowflake-connector-python # virtualenv @@ -229,22 +196,15 @@ flake8==6.0.0 # via feast (setup.py) fqdn==1.5.1 # via jsonschema -frozenlist==1.4.0 +fsspec==2023.9.2 # via - # aiohttp - # aiosignal -fsspec==2022.1.0 - # via - # adlfs # dask - # gcsfs -gcsfs==2022.1.0 - # via feast (setup.py) + # feast (setup.py) geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.11.1 +google-api-core[grpc]==2.13.0 # via # feast (setup.py) # firebase-admin @@ -256,23 +216,19 @@ google-api-core[grpc]==2.11.1 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.98.0 +google-api-python-client==2.107.0 # via firebase-admin -google-auth==2.22.0 +google-auth==2.23.4 # via - # gcsfs # google-api-core # google-api-python-client # google-auth-httplib2 - # google-auth-oauthlib # google-cloud-core # google-cloud-storage # kubernetes -google-auth-httplib2==0.1.0 +google-auth-httplib2==0.1.1 # via google-api-python-client -google-auth-oauthlib==1.0.0 - # via gcsfs -google-cloud-bigquery[pandas]==3.11.4 +google-cloud-bigquery[pandas]==3.12.0 # via feast (setup.py) google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) @@ -287,20 +243,21 @@ google-cloud-core==2.3.3 # google-cloud-storage google-cloud-datastore==2.18.0 # via feast (setup.py) -google-cloud-firestore==2.11.1 +google-cloud-firestore==2.13.1 # via firebase-admin -google-cloud-storage==2.10.0 +google-cloud-storage==2.13.0 # via # feast (setup.py) # firebase-admin - # gcsfs google-crc32c==1.5.0 - # via google-resumable-media + # via + # google-cloud-storage + # google-resumable-media google-resumable-media==2.6.0 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.60.0 +googleapis-common-protos[grpc]==1.61.0 # via # feast (setup.py) # google-api-core @@ -308,9 +265,11 @@ googleapis-common-protos[grpc]==1.60.0 # grpcio-status great-expectations==0.15.50 # via feast (setup.py) +greenlet==3.0.1 + # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-bigtable -grpcio==1.57.0 +grpcio==1.59.2 # via # feast (setup.py) # google-api-core @@ -322,15 +281,15 @@ grpcio==1.57.0 # grpcio-status # grpcio-testing # grpcio-tools -grpcio-health-checking==1.57.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.57.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-status==1.57.0 +grpcio-status==1.59.2 # via google-api-core -grpcio-testing==1.57.0 +grpcio-testing==1.59.2 # via feast (setup.py) -grpcio-tools==1.57.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -344,17 +303,17 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==0.17.3 +httpcore==1.0.1 # via httpx httplib2==0.22.0 # via # google-api-python-client # google-auth-httplib2 -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) -identify==2.5.27 +identify==2.5.31 # via pre-commit idna==3.4 # via @@ -363,7 +322,6 @@ idna==3.4 # jsonschema # requests # snowflake-connector-python - # yarl imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 @@ -378,7 +336,7 @@ importlib-metadata==6.8.0 # jupyterlab-server # nbconvert # sphinx -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via # feast (setup.py) # jsonschema @@ -386,24 +344,22 @@ importlib-resources==6.0.1 # jupyterlab iniconfig==2.0.0 # via pytest -ipykernel==6.25.2 +ipykernel==6.26.0 # via jupyterlab -ipython==8.12.2 +ipython==8.12.3 # via # great-expectations # ipykernel # ipywidgets -ipywidgets==8.1.0 +ipywidgets==8.1.1 # via great-expectations isodate==0.6.1 - # via - # azure-storage-blob - # msrest + # via azure-storage-blob isoduration==20.11.0 # via jsonschema isort==5.12.0 # via feast (setup.py) -jedi==0.19.0 +jedi==0.19.1 # via ipython jinja2==3.1.2 # via @@ -428,7 +384,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.0 +jsonschema[format-nongpl]==4.19.2 # via # altair # feast (setup.py) @@ -438,12 +394,12 @@ jsonschema[format-nongpl]==4.19.0 # nbformat jsonschema-specifications==2023.7.1 # via jsonschema -jupyter-client==8.3.1 +jupyter-client==8.6.0 # via # ipykernel # jupyter-server # nbclient -jupyter-core==5.3.1 +jupyter-core==5.5.0 # via # ipykernel # jupyter-client @@ -452,11 +408,11 @@ jupyter-core==5.3.1 # nbclient # nbconvert # nbformat -jupyter-events==0.7.0 +jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.7.3 +jupyter-server==2.10.0 # via # jupyter-lsp # jupyterlab @@ -465,15 +421,15 @@ jupyter-server==2.7.3 # notebook-shim jupyter-server-terminals==0.4.4 # via jupyter-server -jupyterlab==4.0.5 +jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.24.0 +jupyterlab-server==2.25.0 # via # jupyterlab # notebook -jupyterlab-widgets==3.0.8 +jupyterlab-widgets==3.0.9 # via ipywidgets kubernetes==20.13.0 # via feast (setup.py) @@ -496,7 +452,7 @@ mccabe==0.7.0 # via flake8 minio==7.1.0 # via feast (setup.py) -mistune==3.0.1 +mistune==3.0.2 # via # great-expectations # nbconvert @@ -506,25 +462,16 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.2 +moto==4.2.7 # via feast (setup.py) -msal==1.23.0 +msal==1.25.0 # via - # azure-datalake-store # azure-identity # msal-extensions msal-extensions==1.0.0 # via azure-identity -msgpack==1.0.5 +msgpack==1.0.7 # via cachecontrol -msrest==0.7.1 - # via msrestazure -msrestazure==0.6.4 - # via adlfs -multidict==6.0.4 - # via - # aiohttp - # yarl multiprocess==0.70.15 # via bytewax mypy==0.982 @@ -539,9 +486,9 @@ mypy-protobuf==3.1.0 # via feast (setup.py) mysqlclient==2.2.0 # via feast (setup.py) -nbclient==0.8.0 +nbclient==0.9.0 # via nbconvert -nbconvert==7.8.0 +nbconvert==7.11.0 # via jupyter-server nbformat==5.9.2 # via @@ -549,11 +496,11 @@ nbformat==5.9.2 # jupyter-server # nbclient # nbconvert -nest-asyncio==1.5.7 +nest-asyncio==1.5.8 # via ipykernel nodeenv==1.8.0 # via pre-commit -notebook==7.0.3 +notebook==7.0.6 # via great-expectations notebook-shim==0.2.3 # via @@ -571,11 +518,9 @@ numpy==1.24.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -oscrypto==1.3.0 - # via snowflake-connector-python overrides==7.4.0 # via jupyter-server -packaging==23.1 +packaging==23.2 # via # build # dask @@ -609,11 +554,11 @@ pandocfilters==1.5.0 # via nbconvert parso==0.8.3 # via jedi -partd==1.4.0 +partd==1.4.1 # via dask pathspec==0.11.2 # via black -pbr==5.11.1 +pbr==6.0.0 # via mock pexpect==4.8.0 # via ipython @@ -623,7 +568,7 @@ pip-tools==7.3.0 # via feast (setup.py) pkgutil-resolve-name==1.3.10 # via jsonschema -platformdirs==3.8.1 +platformdirs==3.11.0 # via # black # jupyter-core @@ -633,11 +578,11 @@ pluggy==1.3.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.7.0 +portalocker==2.8.2 # via msal-extensions pre-commit==3.3.1 # via feast (setup.py) -prometheus-client==0.17.1 +prometheus-client==0.18.0 # via jupyter-server prompt-toolkit==3.0.39 # via ipython @@ -671,7 +616,7 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg2-binary==2.9.7 +psycopg2-binary==2.9.9 # via feast (setup.py) ptyprocess==0.7.0 # via @@ -703,9 +648,7 @@ pycodestyle==2.10.0 # via flake8 pycparser==2.21 # via cffi -pycryptodomex==3.18.0 - # via snowflake-connector-python -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -720,16 +663,15 @@ pygments==2.16.1 # sphinx pyjwt[crypto]==2.8.0 # via - # adal # msal # snowflake-connector-python -pymssql==2.2.8 +pymssql==2.2.10 # via feast (setup.py) pymysql==1.1.0 # via feast (setup.py) -pyodbc==4.0.39 +pyodbc==5.0.1 # via feast (setup.py) -pyopenssl==23.2.0 +pyopenssl==23.3.0 # via snowflake-connector-python pyparsing==3.1.1 # via @@ -737,9 +679,9 @@ pyparsing==3.1.1 # httplib2 pyproject-hooks==1.0.0 # via build -pyspark==3.4.1 +pyspark==3.5.0 # via feast (setup.py) -pytest==7.4.1 +pytest==7.4.3 # via # feast (setup.py) # pytest-benchmark @@ -765,7 +707,6 @@ pytest-xdist==3.3.1 # via feast (setup.py) python-dateutil==2.8.2 # via - # adal # arrow # botocore # google-cloud-bigquery @@ -808,18 +749,14 @@ referencing==0.30.2 # jsonschema # jsonschema-specifications # jupyter-events -regex==2023.8.8 +regex==2023.10.3 # via feast (setup.py) requests==2.31.0 # via - # adal - # adlfs # azure-core - # azure-datalake-store # cachecontrol # docker # feast (setup.py) - # gcsfs # google-api-core # google-cloud-bigquery # google-cloud-storage @@ -828,18 +765,14 @@ requests==2.31.0 # kubernetes # moto # msal - # msrest # requests-oauthlib # responses # snowflake-connector-python # sphinx # trino requests-oauthlib==1.3.1 - # via - # google-auth-oauthlib - # kubernetes - # msrest -responses==0.23.3 + # via kubernetes +responses==0.24.0 # via moto rfc3339-validator==0.1.4 # via @@ -851,7 +784,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -859,9 +792,9 @@ rsa==4.9 # via google-auth ruamel-yaml==0.17.17 # via great-expectations -ruamel-yaml-clib==0.2.7 +ruamel-yaml-clib==0.2.8 # via ruamel-yaml -s3transfer==0.6.2 +s3transfer==0.7.0 # via boto3 scipy==1.10.1 # via great-expectations @@ -874,13 +807,10 @@ six==1.16.0 # bleach # cassandra-driver # geomet - # google-auth - # google-auth-httplib2 # happybase # isodate # kubernetes # mock - # msrestazure # pandavro # python-dateutil # rfc3339-validator @@ -888,11 +818,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.1.1 +snowflake-connector-python[pandas]==3.4.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -912,11 +841,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy -stack-data==0.6.2 +stack-data==0.6.3 # via ipython starlette==0.27.0 # via fastapi @@ -930,7 +859,7 @@ terminado==0.17.1 # jupyter-server-terminals testcontainers==3.7.1 # via feast (setup.py) -thriftpy2==0.4.16 +thriftpy2==0.4.17 # via happybase tinycss2==1.2.1 # via nbconvert @@ -946,7 +875,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.1 +tomlkit==0.12.2 # via snowflake-connector-python toolz==0.12.0 # via @@ -965,7 +894,7 @@ tqdm==4.66.1 # via # feast (setup.py) # great-expectations -traitlets==5.9.0 +traitlets==5.13.0 # via # comm # ipykernel @@ -980,7 +909,7 @@ traitlets==5.9.0 # nbclient # nbconvert # nbformat -trino==0.326.0 +trino==0.327.0 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) @@ -990,19 +919,19 @@ types-protobuf==3.19.22 # mypy-protobuf types-pymysql==1.1.0.1 # via feast (setup.py) -types-pyopenssl==23.2.0.2 +types-pyopenssl==23.3.0.0 # via types-redis types-python-dateutil==2.8.19.14 - # via feast (setup.py) -types-pytz==2023.3.0.1 - # via feast (setup.py) -types-pyyaml==6.0.12.11 # via + # arrow # feast (setup.py) - # responses -types-redis==4.6.0.5 +types-pytz==2023.3.1.1 + # via feast (setup.py) +types-pyyaml==6.0.12.12 + # via feast (setup.py) +types-redis==4.6.0.10 # via feast (setup.py) -types-requests==2.31.0.2 +types-requests==2.30.0.0 # via feast (setup.py) types-setuptools==68.2.0.0 # via feast (setup.py) @@ -1010,14 +939,13 @@ types-tabulate==0.9.0.3 # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # async-lru # azure-core # azure-storage-blob # black # fastapi - # filelock # great-expectations # ipython # mypy @@ -1026,7 +954,7 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -tzlocal==5.0.1 +tzlocal==5.2 # via # great-expectations # trino @@ -1039,7 +967,6 @@ urllib3==1.26.18 # botocore # docker # feast (setup.py) - # google-auth # great-expectations # kubernetes # minio @@ -1047,9 +974,9 @@ urllib3==1.26.18 # responses # rockset # snowflake-connector-python -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 # via @@ -1057,9 +984,9 @@ virtualenv==20.23.0 # pre-commit volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.6 +wcwidth==0.2.9 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -1067,26 +994,24 @@ webencodings==0.5.1 # via # bleach # tinycss2 -websocket-client==1.6.2 +websocket-client==1.6.4 # via # docker # jupyter-server # kubernetes -websockets==11.0.3 +websockets==12.0 # via uvicorn -werkzeug==2.3.7 +werkzeug==3.0.1 # via moto -wheel==0.41.2 +wheel==0.41.3 # via pip-tools -widgetsnbextension==4.0.8 +widgetsnbextension==4.0.9 # via ipywidgets wrapt==1.15.0 # via testcontainers xmltodict==0.13.0 # via moto -yarl==1.9.2 - # via aiohttp -zipp==3.16.2 +zipp==3.17.0 # via # importlib-metadata # importlib-resources diff --git a/sdk/python/requirements/py3.8-requirements.txt b/sdk/python/requirements/py3.8-requirements.txt index ec4a9f5187..174dc7d6f5 100644 --- a/sdk/python/requirements/py3.8-requirements.txt +++ b/sdk/python/requirements/py3.8-requirements.txt @@ -6,7 +6,7 @@ # anyio==4.0.0 # via - # httpcore + # httpx # starlette # watchfiles appdirs==1.4.4 @@ -23,7 +23,7 @@ certifi==2023.7.22 # httpcore # httpx # requests -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via requests click==8.1.7 # via @@ -32,7 +32,7 @@ click==8.1.7 # feast (setup.py) # moreorless # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via feast (setup.py) @@ -44,25 +44,27 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.3 +fastavro==1.9.0 # via # feast (setup.py) # pandavro fissix==21.11.13 # via bowler -fsspec==2023.9.0 +fsspec==2023.10.0 # via dask -grpcio==1.58.0 +greenlet==3.0.1 + # via sqlalchemy +grpcio==1.59.2 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.58.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.58.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-tools==1.58.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -70,11 +72,11 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==0.17.3 +httpcore==1.0.1 # via httpx -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) idna==3.4 # via @@ -85,14 +87,14 @@ importlib-metadata==6.8.0 # via # dask # feast (setup.py) -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via # feast (setup.py) # jsonschema # jsonschema-specifications jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.0 +jsonschema==4.19.2 # via feast (setup.py) jsonschema-specifications==2023.7.1 # via jsonschema @@ -104,7 +106,7 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.5.1 +mypy==1.6.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -116,7 +118,7 @@ numpy==1.24.4 # pandas # pandavro # pyarrow -packaging==23.1 +packaging==23.2 # via # dask # gunicorn @@ -126,7 +128,7 @@ pandas==1.5.3 # pandavro pandavro==1.5.2 # via feast (setup.py) -partd==1.4.0 +partd==1.4.1 # via dask pkgutil-resolve-name==1.3.10 # via jsonschema @@ -142,7 +144,7 @@ protobuf==4.23.3 # proto-plus pyarrow==11.0.0 # via feast (setup.py) -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -165,7 +167,7 @@ referencing==0.30.2 # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -176,11 +178,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -200,9 +201,9 @@ tqdm==4.66.1 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) -types-protobuf==4.24.0.1 +types-protobuf==4.24.0.4 # via mypy-protobuf -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # fastapi # mypy @@ -210,19 +211,19 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==1.26.18 +urllib3==2.0.7 # via requests -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -websockets==11.0.3 +websockets==12.0 # via uvicorn -zipp==3.16.2 +zipp==3.17.0 # via # importlib-metadata # importlib-resources diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index aee80ca040..4bf35b4adf 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -4,93 +4,64 @@ # # pip-compile --extra=ci --output-file=sdk/python/requirements/py3.9-ci-requirements.txt # -adal==1.2.7 - # via msrestazure -adlfs==0.5.9 - # via feast (setup.py) -aiohttp==3.8.5 - # via - # adlfs - # gcsfs -aiosignal==1.3.1 - # via aiohttp alabaster==0.7.13 # via sphinx altair==4.2.0 # via great-expectations anyio==4.0.0 # via - # httpcore + # httpx # jupyter-server # starlette # watchfiles appdirs==1.4.4 # via fissix -appnope==0.1.3 - # via - # ipykernel - # ipython argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 # via argon2-cffi -arrow==1.2.3 +arrow==1.3.0 # via isoduration asn1crypto==1.5.1 - # via - # oscrypto - # snowflake-connector-python + # via snowflake-connector-python assertpy==1.1 # via feast (setup.py) -asttokens==2.4.0 +asttokens==2.4.1 # via stack-data async-lru==2.0.4 # via jupyterlab async-timeout==4.0.3 - # via - # aiohttp - # redis + # via redis attrs==23.1.0 # via - # aiohttp # bowler # jsonschema # referencing -avro==1.11.3 +avro==1.10.0 # via feast (setup.py) -azure-core==1.29.4 +azure-core==1.29.5 # via - # adlfs # azure-identity # azure-storage-blob - # msrest -azure-datalake-store==0.0.53 - # via adlfs -azure-identity==1.14.0 - # via - # adlfs - # feast (setup.py) -azure-storage-blob==12.17.0 - # via - # adlfs - # feast (setup.py) -babel==2.12.1 +azure-identity==1.15.0 + # via feast (setup.py) +azure-storage-blob==12.19.0 + # via feast (setup.py) +babel==2.13.1 # via # jupyterlab-server # sphinx -backcall==0.2.0 - # via ipython beautifulsoup4==4.12.2 # via nbconvert black==22.12.0 # via feast (setup.py) -bleach==6.0.0 +bleach==6.1.0 # via nbconvert -boto3==1.28.43 +boto3==1.28.80 # via # feast (setup.py) # moto -botocore==1.31.43 +botocore==1.31.80 # via # boto3 # moto @@ -105,7 +76,7 @@ bytewax==0.15.1 # via feast (setup.py) cachecontrol==0.13.1 # via firebase-admin -cachetools==5.3.1 +cachetools==5.3.2 # via google-auth cassandra-driver==3.28.0 # via feast (setup.py) @@ -115,20 +86,17 @@ certifi==2023.7.22 # httpx # kubernetes # minio - # msrest # requests # snowflake-connector-python -cffi==1.15.1 +cffi==1.16.0 # via # argon2-cffi-bindings - # azure-datalake-store # cryptography # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via - # aiohttp # requests # snowflake-connector-python click==8.1.7 @@ -142,21 +110,20 @@ click==8.1.7 # moreorless # pip-tools # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via # feast (setup.py) # great-expectations -comm==0.1.4 +comm==0.2.0 # via # ipykernel # ipywidgets -coverage[toml]==7.3.1 +coverage[toml]==7.3.2 # via pytest-cov -cryptography==41.0.4 +cryptography==41.0.5 # via - # adal # azure-identity # azure-storage-blob # feast (setup.py) @@ -168,16 +135,14 @@ cryptography==41.0.4 # snowflake-connector-python # types-pyopenssl # types-redis -dask==2023.9.1 +dask==2023.10.1 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery -debugpy==1.7.0 +debugpy==1.8.0 # via ipykernel decorator==5.1.1 - # via - # gcsfs - # ipython + # via ipython defusedxml==0.7.1 # via nbconvert deprecation==2.1.0 @@ -204,17 +169,17 @@ exceptiongroup==1.1.3 # pytest execnet==2.0.2 # via pytest-xdist -executing==1.2.0 +executing==2.0.1 # via stack-data fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.3 +fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.0 +fastjsonschema==2.18.1 # via nbformat -filelock==3.12.3 +filelock==3.13.1 # via # snowflake-connector-python # virtualenv @@ -226,22 +191,15 @@ flake8==6.0.0 # via feast (setup.py) fqdn==1.5.1 # via jsonschema -frozenlist==1.4.0 +fsspec==2023.9.2 # via - # aiohttp - # aiosignal -fsspec==2022.1.0 - # via - # adlfs # dask - # gcsfs -gcsfs==2022.1.0 - # via feast (setup.py) + # feast (setup.py) geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.11.1 +google-api-core[grpc]==2.13.0 # via # feast (setup.py) # firebase-admin @@ -253,23 +211,19 @@ google-api-core[grpc]==2.11.1 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.98.0 +google-api-python-client==2.107.0 # via firebase-admin -google-auth==2.22.0 +google-auth==2.23.4 # via - # gcsfs # google-api-core # google-api-python-client # google-auth-httplib2 - # google-auth-oauthlib # google-cloud-core # google-cloud-storage # kubernetes -google-auth-httplib2==0.1.0 +google-auth-httplib2==0.1.1 # via google-api-python-client -google-auth-oauthlib==1.0.0 - # via gcsfs -google-cloud-bigquery[pandas]==3.11.4 +google-cloud-bigquery[pandas]==3.12.0 # via feast (setup.py) google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) @@ -284,20 +238,21 @@ google-cloud-core==2.3.3 # google-cloud-storage google-cloud-datastore==2.18.0 # via feast (setup.py) -google-cloud-firestore==2.11.1 +google-cloud-firestore==2.13.1 # via firebase-admin -google-cloud-storage==2.10.0 +google-cloud-storage==2.13.0 # via # feast (setup.py) # firebase-admin - # gcsfs google-crc32c==1.5.0 - # via google-resumable-media + # via + # google-cloud-storage + # google-resumable-media google-resumable-media==2.6.0 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.60.0 +googleapis-common-protos[grpc]==1.61.0 # via # feast (setup.py) # google-api-core @@ -305,9 +260,11 @@ googleapis-common-protos[grpc]==1.60.0 # grpcio-status great-expectations==0.15.50 # via feast (setup.py) +greenlet==3.0.1 + # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-bigtable -grpcio==1.58.0 +grpcio==1.59.2 # via # feast (setup.py) # google-api-core @@ -319,15 +276,15 @@ grpcio==1.58.0 # grpcio-status # grpcio-testing # grpcio-tools -grpcio-health-checking==1.58.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.58.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-status==1.58.0 +grpcio-status==1.59.2 # via google-api-core -grpcio-testing==1.58.0 +grpcio-testing==1.59.2 # via feast (setup.py) -grpcio-tools==1.58.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -341,17 +298,17 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==0.17.3 +httpcore==1.0.1 # via httpx httplib2==0.22.0 # via # google-api-python-client # google-auth-httplib2 -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) -identify==2.5.27 +identify==2.5.31 # via pre-commit idna==3.4 # via @@ -360,7 +317,6 @@ idna==3.4 # jsonschema # requests # snowflake-connector-python - # yarl imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 @@ -375,28 +331,26 @@ importlib-metadata==6.8.0 # jupyterlab-server # nbconvert # sphinx -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via feast (setup.py) iniconfig==2.0.0 # via pytest -ipykernel==6.25.2 +ipykernel==6.26.0 # via jupyterlab -ipython==8.15.0 +ipython==8.17.2 # via # great-expectations # ipykernel # ipywidgets -ipywidgets==8.1.0 +ipywidgets==8.1.1 # via great-expectations isodate==0.6.1 - # via - # azure-storage-blob - # msrest + # via azure-storage-blob isoduration==20.11.0 # via jsonschema isort==5.12.0 # via feast (setup.py) -jedi==0.19.0 +jedi==0.19.1 # via ipython jinja2==3.1.2 # via @@ -421,7 +375,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.0 +jsonschema[format-nongpl]==4.19.2 # via # altair # feast (setup.py) @@ -431,12 +385,12 @@ jsonschema[format-nongpl]==4.19.0 # nbformat jsonschema-specifications==2023.7.1 # via jsonschema -jupyter-client==8.3.1 +jupyter-client==8.6.0 # via # ipykernel # jupyter-server # nbclient -jupyter-core==5.3.1 +jupyter-core==5.5.0 # via # ipykernel # jupyter-client @@ -445,11 +399,11 @@ jupyter-core==5.3.1 # nbclient # nbconvert # nbformat -jupyter-events==0.7.0 +jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.7.3 +jupyter-server==2.10.0 # via # jupyter-lsp # jupyterlab @@ -458,15 +412,15 @@ jupyter-server==2.7.3 # notebook-shim jupyter-server-terminals==0.4.4 # via jupyter-server -jupyterlab==4.0.5 +jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.24.0 +jupyterlab-server==2.25.0 # via # jupyterlab # notebook -jupyterlab-widgets==3.0.8 +jupyterlab-widgets==3.0.9 # via ipywidgets kubernetes==20.13.0 # via feast (setup.py) @@ -489,7 +443,7 @@ mccabe==0.7.0 # via flake8 minio==7.1.0 # via feast (setup.py) -mistune==3.0.1 +mistune==3.0.2 # via # great-expectations # nbconvert @@ -499,25 +453,16 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.2 +moto==4.2.7 # via feast (setup.py) -msal==1.23.0 +msal==1.25.0 # via - # azure-datalake-store # azure-identity # msal-extensions msal-extensions==1.0.0 # via azure-identity -msgpack==1.0.5 +msgpack==1.0.7 # via cachecontrol -msrest==0.7.1 - # via msrestazure -msrestazure==0.6.4 - # via adlfs -multidict==6.0.4 - # via - # aiohttp - # yarl multiprocess==0.70.15 # via bytewax mypy==0.982 @@ -528,13 +473,13 @@ mypy-extensions==1.0.0 # via # black # mypy -mypy-protobuf==3.1.0 +mypy-protobuf==3.1 # via feast (setup.py) mysqlclient==2.2.0 # via feast (setup.py) -nbclient==0.8.0 +nbclient==0.9.0 # via nbconvert -nbconvert==7.8.0 +nbconvert==7.11.0 # via jupyter-server nbformat==5.9.2 # via @@ -542,11 +487,11 @@ nbformat==5.9.2 # jupyter-server # nbclient # nbconvert -nest-asyncio==1.5.7 +nest-asyncio==1.5.8 # via ipykernel nodeenv==1.8.0 # via pre-commit -notebook==7.0.3 +notebook==7.0.6 # via great-expectations notebook-shim==0.2.3 # via @@ -564,11 +509,9 @@ numpy==1.24.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -oscrypto==1.3.0 - # via snowflake-connector-python overrides==7.4.0 # via jupyter-server -packaging==23.1 +packaging==23.2 # via # build # dask @@ -602,19 +545,17 @@ pandocfilters==1.5.0 # via nbconvert parso==0.8.3 # via jedi -partd==1.4.0 +partd==1.4.1 # via dask pathspec==0.11.2 # via black -pbr==5.11.1 +pbr==6.0.0 # via mock pexpect==4.8.0 # via ipython -pickleshare==0.7.5 - # via ipython pip-tools==7.3.0 # via feast (setup.py) -platformdirs==3.8.1 +platformdirs==3.11.0 # via # black # jupyter-core @@ -624,11 +565,11 @@ pluggy==1.3.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.7.0 +portalocker==2.8.2 # via msal-extensions pre-commit==3.3.1 # via feast (setup.py) -prometheus-client==0.17.1 +prometheus-client==0.18.0 # via jupyter-server prompt-toolkit==3.0.39 # via ipython @@ -662,7 +603,7 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg2-binary==2.9.7 +psycopg2-binary==2.9.9 # via feast (setup.py) ptyprocess==0.7.0 # via @@ -694,9 +635,7 @@ pycodestyle==2.10.0 # via flake8 pycparser==2.21 # via cffi -pycryptodomex==3.18.0 - # via snowflake-connector-python -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -711,16 +650,15 @@ pygments==2.16.1 # sphinx pyjwt[crypto]==2.8.0 # via - # adal # msal # snowflake-connector-python -pymssql==2.2.8 +pymssql==2.2.10 # via feast (setup.py) pymysql==1.1.0 # via feast (setup.py) -pyodbc==4.0.39 +pyodbc==5.0.1 # via feast (setup.py) -pyopenssl==23.2.0 +pyopenssl==23.3.0 # via snowflake-connector-python pyparsing==3.1.1 # via @@ -728,9 +666,9 @@ pyparsing==3.1.1 # httplib2 pyproject-hooks==1.0.0 # via build -pyspark==3.4.1 +pyspark==3.5.0 # via feast (setup.py) -pytest==7.4.2 +pytest==7.4.3 # via # feast (setup.py) # pytest-benchmark @@ -756,7 +694,6 @@ pytest-xdist==3.3.1 # via feast (setup.py) python-dateutil==2.8.2 # via - # adal # arrow # botocore # google-cloud-bigquery @@ -798,18 +735,14 @@ referencing==0.30.2 # jsonschema # jsonschema-specifications # jupyter-events -regex==2023.8.8 +regex==2023.10.3 # via feast (setup.py) requests==2.31.0 # via - # adal - # adlfs # azure-core - # azure-datalake-store # cachecontrol # docker # feast (setup.py) - # gcsfs # google-api-core # google-cloud-bigquery # google-cloud-storage @@ -818,18 +751,14 @@ requests==2.31.0 # kubernetes # moto # msal - # msrest # requests-oauthlib # responses # snowflake-connector-python # sphinx # trino requests-oauthlib==1.3.1 - # via - # google-auth-oauthlib - # kubernetes - # msrest -responses==0.23.3 + # via kubernetes +responses==0.24.0 # via moto rfc3339-validator==0.1.4 # via @@ -841,7 +770,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -849,11 +778,11 @@ rsa==4.9 # via google-auth ruamel-yaml==0.17.17 # via great-expectations -ruamel-yaml-clib==0.2.7 +ruamel-yaml-clib==0.2.8 # via ruamel-yaml -s3transfer==0.6.2 +s3transfer==0.7.0 # via boto3 -scipy==1.11.2 +scipy==1.11.3 # via great-expectations send2trash==1.8.2 # via jupyter-server @@ -864,13 +793,10 @@ six==1.16.0 # bleach # cassandra-driver # geomet - # google-auth - # google-auth-httplib2 # happybase # isodate # kubernetes # mock - # msrestazure # pandavro # python-dateutil # rfc3339-validator @@ -878,11 +804,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.1.1 +snowflake-connector-python[pandas]==3.4.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -908,11 +833,11 @@ sphinxcontrib-qthelp==1.0.6 # via sphinx sphinxcontrib-serializinghtml==1.1.9 # via sphinx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy -stack-data==0.6.2 +stack-data==0.6.3 # via ipython starlette==0.27.0 # via fastapi @@ -926,7 +851,7 @@ terminado==0.17.1 # jupyter-server-terminals testcontainers==3.7.1 # via feast (setup.py) -thriftpy2==0.4.16 +thriftpy2==0.4.17 # via happybase tinycss2==1.2.1 # via nbconvert @@ -942,7 +867,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.1 +tomlkit==0.12.2 # via snowflake-connector-python toolz==0.12.0 # via @@ -961,7 +886,7 @@ tqdm==4.66.1 # via # feast (setup.py) # great-expectations -traitlets==5.9.0 +traitlets==5.13.0 # via # comm # ipykernel @@ -976,7 +901,7 @@ traitlets==5.9.0 # nbclient # nbconvert # nbformat -trino==0.326.0 +trino==0.327.0 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) @@ -986,19 +911,19 @@ types-protobuf==3.19.22 # mypy-protobuf types-pymysql==1.1.0.1 # via feast (setup.py) -types-pyopenssl==23.2.0.2 +types-pyopenssl==23.3.0.0 # via types-redis types-python-dateutil==2.8.19.14 - # via feast (setup.py) -types-pytz==2023.3.0.1 - # via feast (setup.py) -types-pyyaml==6.0.12.11 # via + # arrow # feast (setup.py) - # responses -types-redis==4.6.0.5 +types-pytz==2023.3.1.1 + # via feast (setup.py) +types-pyyaml==6.0.12.12 + # via feast (setup.py) +types-redis==4.6.0.10 # via feast (setup.py) -types-requests==2.31.0.2 +types-requests==2.30.0.0 # via feast (setup.py) types-setuptools==68.2.0.0 # via feast (setup.py) @@ -1006,14 +931,13 @@ types-tabulate==0.9.0.3 # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # async-lru # azure-core # azure-storage-blob # black # fastapi - # filelock # great-expectations # ipython # mypy @@ -1022,7 +946,7 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -tzlocal==5.0.1 +tzlocal==5.2 # via # great-expectations # trino @@ -1035,7 +959,6 @@ urllib3==1.26.18 # botocore # docker # feast (setup.py) - # google-auth # great-expectations # kubernetes # minio @@ -1043,9 +966,9 @@ urllib3==1.26.18 # responses # rockset # snowflake-connector-python -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 # via @@ -1053,9 +976,9 @@ virtualenv==20.23.0 # pre-commit volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.6 +wcwidth==0.2.9 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -1063,26 +986,24 @@ webencodings==0.5.1 # via # bleach # tinycss2 -websocket-client==1.6.2 +websocket-client==1.6.4 # via # docker # jupyter-server # kubernetes -websockets==11.0.3 +websockets==12.0 # via uvicorn -werkzeug==2.3.7 +werkzeug==3.0.1 # via moto -wheel==0.41.2 +wheel==0.41.3 # via pip-tools -widgetsnbextension==4.0.8 +widgetsnbextension==4.0.9 # via ipywidgets wrapt==1.15.0 # via testcontainers xmltodict==0.13.0 # via moto -yarl==1.9.2 - # via aiohttp -zipp==3.16.2 +zipp==3.17.0 # via # importlib-metadata # importlib-resources diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 81a8afa699..12612c3465 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -6,7 +6,7 @@ # anyio==4.0.0 # via - # httpcore + # httpx # starlette # watchfiles appdirs==1.4.4 @@ -23,7 +23,7 @@ certifi==2023.7.22 # httpcore # httpx # requests -charset-normalizer==3.2.0 +charset-normalizer==3.3.2 # via requests click==8.1.7 # via @@ -32,11 +32,11 @@ click==8.1.7 # feast (setup.py) # moreorless # uvicorn -cloudpickle==2.2.1 +cloudpickle==3.0.0 # via dask colorama==0.4.6 # via feast (setup.py) -dask==2023.9.1 +dask==2023.10.1 # via feast (setup.py) dill==0.3.7 # via feast (setup.py) @@ -44,25 +44,27 @@ exceptiongroup==1.1.3 # via anyio fastapi==0.99.1 # via feast (setup.py) -fastavro==1.8.3 +fastavro==1.9.0 # via # feast (setup.py) # pandavro fissix==21.11.13 # via bowler -fsspec==2023.9.0 +fsspec==2023.10.0 # via dask -grpcio==1.58.0 +greenlet==3.0.1 + # via sqlalchemy +grpcio==1.59.2 # via # feast (setup.py) # grpcio-health-checking # grpcio-reflection # grpcio-tools -grpcio-health-checking==1.58.0 +grpcio-health-checking==1.59.2 # via feast (setup.py) -grpcio-reflection==1.58.0 +grpcio-reflection==1.59.2 # via feast (setup.py) -grpcio-tools==1.58.0 +grpcio-tools==1.59.2 # via feast (setup.py) gunicorn==21.2.0 # via feast (setup.py) @@ -70,11 +72,11 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==0.17.3 +httpcore==1.0.1 # via httpx -httptools==0.6.0 +httptools==0.6.1 # via uvicorn -httpx==0.24.1 +httpx==0.25.1 # via feast (setup.py) idna==3.4 # via @@ -85,11 +87,11 @@ importlib-metadata==6.8.0 # via # dask # feast (setup.py) -importlib-resources==6.0.1 +importlib-resources==6.1.1 # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.0 +jsonschema==4.19.2 # via feast (setup.py) jsonschema-specifications==2023.7.1 # via jsonschema @@ -101,11 +103,11 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.5.1 +mypy==1.6.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy -mypy-protobuf==3.1.0 +mypy-protobuf==3.1 # via feast (setup.py) numpy==1.24.4 # via @@ -113,7 +115,7 @@ numpy==1.24.4 # pandas # pandavro # pyarrow -packaging==23.1 +packaging==23.2 # via # dask # gunicorn @@ -123,7 +125,7 @@ pandas==1.5.3 # pandavro pandavro==1.5.2 # via feast (setup.py) -partd==1.4.0 +partd==1.4.1 # via dask proto-plus==1.22.3 # via feast (setup.py) @@ -137,7 +139,7 @@ protobuf==4.23.3 # proto-plus pyarrow==11.0.0 # via feast (setup.py) -pydantic==1.10.12 +pydantic==1.10.13 # via # fastapi # feast (setup.py) @@ -160,7 +162,7 @@ referencing==0.30.2 # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.10.2 +rpds-py==0.12.0 # via # jsonschema # referencing @@ -171,11 +173,10 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx -sqlalchemy[mypy]==1.4.49 +sqlalchemy[mypy]==1.4.50 # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a35 +sqlalchemy2-stubs==0.0.2a36 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -195,9 +196,9 @@ tqdm==4.66.1 # via feast (setup.py) typeguard==2.13.3 # via feast (setup.py) -types-protobuf==4.24.0.1 +types-protobuf==4.24.0.4 # via mypy-protobuf -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via # fastapi # mypy @@ -205,19 +206,19 @@ typing-extensions==4.7.1 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==1.26.18 +urllib3==2.0.7 # via requests -uvicorn[standard]==0.23.2 +uvicorn[standard]==0.24.0.post1 # via feast (setup.py) -uvloop==0.17.0 +uvloop==0.19.0 # via uvicorn volatile==2.1.0 # via bowler -watchfiles==0.20.0 +watchfiles==0.21.0 # via uvicorn -websockets==11.0.3 +websockets==12.0 # via uvicorn -zipp==3.16.2 +zipp==3.17.0 # via # importlib-metadata # importlib-resources From 9cf9d965a5566a87bb7419f2e8509666076f035f Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Sat, 4 Nov 2023 11:07:07 +0700 Subject: [PATCH 30/41] feat: Optimize bytewax pod resource with zero-copy Signed-off-by: Hai Nguyen --- .../bytewax_materialization_dataflow.py | 41 ++++++++----------- .../bytewax/bytewax_materialization_engine.py | 14 +++++-- .../contrib/bytewax/dataflow.py | 3 ++ 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py index 31be7a6b89..6fc53b67f2 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_dataflow.py @@ -1,3 +1,4 @@ +import logging import os from typing import List @@ -7,11 +8,11 @@ from bytewax.execution import cluster_main from bytewax.inputs import ManualInputConfig from bytewax.outputs import ManualOutputConfig -from tqdm import tqdm from feast import FeatureStore, FeatureView, RepoConfig from feast.utils import _convert_arrow_to_proto, _run_pyarrow_field_mapping +logger = logging.getLogger(__name__) DEFAULT_BATCH_SIZE = 1000 @@ -29,14 +30,20 @@ def __init__( self.feature_view = feature_view self.worker_index = worker_index self.paths = paths + self.mini_batch_size = int( + os.getenv("BYTEWAX_MINI_BATCH_SIZE", DEFAULT_BATCH_SIZE) + ) self._run_dataflow() def process_path(self, path): + logger.info(f"Processing path {path}") dataset = pq.ParquetDataset(path, use_legacy_dataset=False) batches = [] for fragment in dataset.fragments: - for batch in fragment.to_table().to_batches(): + for batch in fragment.to_table().to_batches( + max_chunksize=self.mini_batch_size + ): batches.append(batch) return batches @@ -45,40 +52,26 @@ def input_builder(self, worker_index, worker_count, _state): return [(None, self.paths[self.worker_index])] def output_builder(self, worker_index, worker_count): - def yield_batch(iterable, batch_size): - """Yield mini-batches from an iterable.""" - for i in range(0, len(iterable), batch_size): - yield iterable[i : i + batch_size] - - def output_fn(batch): - table = pa.Table.from_batches([batch]) + def output_fn(mini_batch): + table: pa.Table = pa.Table.from_batches([mini_batch]) if self.feature_view.batch_source.field_mapping is not None: table = _run_pyarrow_field_mapping( table, self.feature_view.batch_source.field_mapping ) - join_key_to_value_type = { entity.name: entity.dtype.to_value_type() for entity in self.feature_view.entity_columns } - rows_to_write = _convert_arrow_to_proto( table, self.feature_view, join_key_to_value_type ) - provider = self.feature_store._get_provider() - with tqdm(total=len(rows_to_write)) as progress: - # break rows_to_write to mini-batches - batch_size = int( - os.getenv("BYTEWAX_MINI_BATCH_SIZE", DEFAULT_BATCH_SIZE) - ) - for mini_batch in yield_batch(rows_to_write, batch_size): - provider.online_write_batch( - config=self.config, - table=self.feature_view, - data=mini_batch, - progress=progress.update, - ) + self.feature_store._get_provider().online_write_batch( + config=self.config, + table=self.feature_view, + data=rows_to_write, + progress=None, + ) return output_fn diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index b2f8985f87..16742ff0f7 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -82,6 +82,12 @@ class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): mini_batch_size: int = 1000 """ (optional) Number of rows to process per write operation (default 1000)""" + bytewax_replicas: int = 5 + """ (optional) Number of process to spawn in each pods to handle a file in parallel""" + + bytewax_worker_per_process: int = 1 + """ (optional) Number of threads as worker per bytewax process""" + active_deadline_seconds: int = 86400 """ (optional) Maximum amount of time a materialization job is allowed to run""" @@ -111,7 +117,6 @@ def __init__( self.offline_store = offline_store self.online_store = online_store - # TODO: Configure k8s here k8s_config.load_config() self.k8s_client = client.api_client.ApiClient() @@ -299,6 +304,9 @@ def _create_kubernetes_job(self, job_id, paths, feature_view): len(paths), # Create a pod for each parquet file self.batch_engine_config.env, ) + logger.info( + f"Created job `dataflow-{job_id}` on namespace `{self.namespace}`" + ) except FailToCreateError as failures: return BytewaxMaterializationJob(job_id, self.namespace, error=failures) @@ -345,7 +353,7 @@ def _create_job_definition(self, job_id, namespace, pods, env, index_offset=0): {"name": "BYTEWAX_WORKDIR", "value": "/bytewax"}, { "name": "BYTEWAX_WORKERS_PER_PROCESS", - "value": "1", + "value": f"{self.batch_engine_config.bytewax_worker_per_process}", }, { "name": "BYTEWAX_POD_NAME", @@ -358,7 +366,7 @@ def _create_job_definition(self, job_id, namespace, pods, env, index_offset=0): }, { "name": "BYTEWAX_REPLICAS", - "value": f"{pods}", + "value": f"{self.batch_engine_config.bytewax_replicas}", }, { "name": "BYTEWAX_KEEP_CONTAINER_ALIVE", diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py b/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py index 23cdc20ef3..9d9b328c0e 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/dataflow.py @@ -1,3 +1,4 @@ +import logging import os import yaml @@ -8,6 +9,8 @@ ) if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + with open("/var/feast/feature_store.yaml") as f: feast_config = yaml.safe_load(f) From 5983f40f8f5df5dbbcd2640f83ef82c19cdb4d19 Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Mon, 6 Nov 2023 16:19:22 +0700 Subject: [PATCH 31/41] fix: improve parsing bytewax job status Signed-off-by: Hai Nguyen --- .../bytewax/bytewax_materialization_job.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py index 4105be90ee..da969d5a88 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_job.py @@ -35,13 +35,22 @@ def status(self): if job_status.active is not None: if job_status.completion_time is None: return MaterializationJobStatus.RUNNING - elif job_status.failed is not None: - self._error = Exception(f"Job {self.job_id()} failed") - return MaterializationJobStatus.ERROR - elif job_status.active is None: - if job_status.completion_time is not None: - if job_status.conditions[0].type == "Complete": - return MaterializationJobStatus.SUCCEEDED + else: + if ( + job_status.completion_time is not None + and job_status.conditions[0].type == "Complete" + ): + return MaterializationJobStatus.SUCCEEDED + + if ( + job_status.conditions is not None + and job_status.conditions[0].type == "Failed" + ): + self._error = Exception( + f"Job {self.job_id()} failed with reason: " + f"{job_status.conditions[0].message}" + ) + return MaterializationJobStatus.ERROR return MaterializationJobStatus.WAITING def should_be_retried(self): From ae1bb8bdd1b9e293809519971935c93c2214d791 Mon Sep 17 00:00:00 2001 From: Hai Nguyen Date: Wed, 8 Nov 2023 13:42:39 +0700 Subject: [PATCH 32/41] fix: make bytewax settings unexposed Signed-off-by: Hai Nguyen --- .../contrib/bytewax/bytewax_materialization_engine.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py index 16742ff0f7..5c7a719532 100644 --- a/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/contrib/bytewax/bytewax_materialization_engine.py @@ -82,12 +82,6 @@ class BytewaxMaterializationEngineConfig(FeastConfigBaseModel): mini_batch_size: int = 1000 """ (optional) Number of rows to process per write operation (default 1000)""" - bytewax_replicas: int = 5 - """ (optional) Number of process to spawn in each pods to handle a file in parallel""" - - bytewax_worker_per_process: int = 1 - """ (optional) Number of threads as worker per bytewax process""" - active_deadline_seconds: int = 86400 """ (optional) Maximum amount of time a materialization job is allowed to run""" @@ -353,7 +347,7 @@ def _create_job_definition(self, job_id, namespace, pods, env, index_offset=0): {"name": "BYTEWAX_WORKDIR", "value": "/bytewax"}, { "name": "BYTEWAX_WORKERS_PER_PROCESS", - "value": f"{self.batch_engine_config.bytewax_worker_per_process}", + "value": "1", }, { "name": "BYTEWAX_POD_NAME", @@ -366,7 +360,7 @@ def _create_job_definition(self, job_id, namespace, pods, env, index_offset=0): }, { "name": "BYTEWAX_REPLICAS", - "value": f"{self.batch_engine_config.bytewax_replicas}", + "value": "1", }, { "name": "BYTEWAX_KEEP_CONTAINER_ALIVE", From 4337c89083a3cfca21ee1beef473fda13b0e9014 Mon Sep 17 00:00:00 2001 From: snowron Date: Thu, 2 Nov 2023 15:15:27 +0300 Subject: [PATCH 33/41] feat: add redis sentinel support Signed-off-by: snowron --- sdk/python/feast/infra/online_stores/redis.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sdk/python/feast/infra/online_stores/redis.py b/sdk/python/feast/infra/online_stores/redis.py index 83922068ac..f928f7ff49 100644 --- a/sdk/python/feast/infra/online_stores/redis.py +++ b/sdk/python/feast/infra/online_stores/redis.py @@ -43,6 +43,7 @@ try: from redis import Redis from redis.cluster import ClusterNode, RedisCluster + from redis.sentinel import Sentinel except ImportError as e: from feast.errors import FeastExtrasDependencyImportError @@ -54,6 +55,7 @@ class RedisType(str, Enum): redis = "redis" redis_cluster = "redis_cluster" + redis_sentinel = "redis_sentinel" class RedisOnlineStoreConfig(FeastConfigBaseModel): @@ -65,6 +67,9 @@ class RedisOnlineStoreConfig(FeastConfigBaseModel): redis_type: RedisType = RedisType.redis """Redis type: redis or redis_cluster""" + sentinel_master: StrictStr = "mymaster" + """Sentinel's master name""" + connection_string: StrictStr = "localhost:6379" """Connection string containing the host, port, and configuration parameters for Redis format: host:port,parameter1,parameter2 eg. redis:6379,db=0 """ @@ -178,6 +183,19 @@ def _get_client(self, online_store_config: RedisOnlineStoreConfig): ClusterNode(**node) for node in startup_nodes ] self._client = RedisCluster(**kwargs) + elif online_store_config.redis_type == RedisType.redis_sentinel: + sentinel_hosts = [] + + for item in startup_nodes: + sentinel_hosts.append((item['host'], int(item['port']))) + + sentinel = Sentinel( + sentinel_hosts, + **kwargs + ) + + master = sentinel.master_for(online_store_config.sentinel_master) + self._client = master else: kwargs["host"] = startup_nodes[0]["host"] kwargs["port"] = startup_nodes[0]["port"] From 3387a15d2b7e8dea430a271570be5a19b32bd3fe Mon Sep 17 00:00:00 2001 From: snowron Date: Thu, 2 Nov 2023 15:22:55 +0300 Subject: [PATCH 34/41] feat: add redis sentinel support Signed-off-by: snowron --- docs/reference/online-stores/redis.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/reference/online-stores/redis.md b/docs/reference/online-stores/redis.md index c08cef2a3e..ae7f8b4c5c 100644 --- a/docs/reference/online-stores/redis.md +++ b/docs/reference/online-stores/redis.md @@ -45,6 +45,21 @@ online_store: ``` {% endcode %} +Connecting to a Redis Sentinel with SSL enabled and password authentication: + +{% code title="feature_store.yaml" %} +```yaml +project: my_feature_repo +registry: data/registry.db +provider: local +online_store: + type: redis + redis_type: redis_sentinel + sentinel_master: mymaster + connection_string: "redis1:26379,ssl=true,password=my_password" +``` +{% endcode %} + Additionally, the redis online store also supports automatically deleting data via a TTL mechanism. The TTL is applied at the entity level, so feature values from any associated feature views for an entity are removed together. This TTL can be set in the `feature_store.yaml`, using the `key_ttl_seconds` field in the online store. For example: From aad8718d24d893b3ff8c5864c5b8d210cfcdb22f Mon Sep 17 00:00:00 2001 From: snowron Date: Thu, 2 Nov 2023 16:20:42 +0300 Subject: [PATCH 35/41] feat: add redis sentinel support format lint Signed-off-by: snowron --- sdk/python/feast/infra/online_stores/redis.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/redis.py b/sdk/python/feast/infra/online_stores/redis.py index f928f7ff49..9561705aaa 100644 --- a/sdk/python/feast/infra/online_stores/redis.py +++ b/sdk/python/feast/infra/online_stores/redis.py @@ -187,13 +187,9 @@ def _get_client(self, online_store_config: RedisOnlineStoreConfig): sentinel_hosts = [] for item in startup_nodes: - sentinel_hosts.append((item['host'], int(item['port']))) + sentinel_hosts.append((item["host"], int(item["port"]))) - sentinel = Sentinel( - sentinel_hosts, - **kwargs - ) - + sentinel = Sentinel(sentinel_hosts, **kwargs) master = sentinel.master_for(online_store_config.sentinel_master) self._client = master else: From 77d7eb0dd05bb8c408e79b2d9131f47e0df71136 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:54:18 +0000 Subject: [PATCH 36/41] chore: Bump pyarrow Bumps [pyarrow](https://github.com/apache/arrow) from 6.0.0 to 14.0.1. - [Commits](https://github.com/apache/arrow/compare/go/arrow/v6.0.0...apache-arrow-14.0.1) --- updated-dependencies: - dependency-name: pyarrow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../src/test/resources/docker-compose/feast10/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/serving/src/test/resources/docker-compose/feast10/requirements.txt b/java/serving/src/test/resources/docker-compose/feast10/requirements.txt index 94e4771de2..6ba2c53d81 100644 --- a/java/serving/src/test/resources/docker-compose/feast10/requirements.txt +++ b/java/serving/src/test/resources/docker-compose/feast10/requirements.txt @@ -1,5 +1,5 @@ # for source generation -pyarrow==6.0.0 +pyarrow==14.0.1 # temp fixes proto-plus From 052182bcca046e35456674fc7d524825882f4b35 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Fri, 17 Nov 2023 12:31:29 -0500 Subject: [PATCH 37/41] fix: upgrade the pyarrow to latest v14.0.1 for CVE-2023-47248. Signed-off-by: Shuchu Han --- .../feast/infra/offline_stores/bigquery.py | 12 ++- sdk/python/feast/infra/utils/aws_utils.py | 4 +- .../requirements/py3.10-ci-requirements.txt | 74 ++++++++++-------- .../requirements/py3.10-requirements.txt | 28 ++++--- .../requirements/py3.8-ci-requirements.txt | 72 ++++++++++-------- .../requirements/py3.8-requirements.txt | 26 ++++--- .../requirements/py3.9-ci-requirements.txt | 76 +++++++++++-------- .../requirements/py3.9-requirements.txt | 30 ++++---- setup.py | 2 +- 9 files changed, 188 insertions(+), 136 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 10c8aa783f..b7910c391c 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -353,7 +353,11 @@ def write_logged_features( return with tempfile.TemporaryFile() as parquet_temp_file: - pyarrow.parquet.write_table(table=data, where=parquet_temp_file) + # In Pyarrow v13.0, the parquet version was upgraded to v2.6 from v2.4. + # Set the coerce_timestamps to "us"(microseconds) for backward compatibility. + pyarrow.parquet.write_table( + table=data, where=parquet_temp_file, coerce_timestamps="us" + ) parquet_temp_file.seek(0) @@ -400,7 +404,11 @@ def offline_write_batch( ) with tempfile.TemporaryFile() as parquet_temp_file: - pyarrow.parquet.write_table(table=table, where=parquet_temp_file) + # In Pyarrow v13.0, the parquet version was upgraded to v2.6 from v2.4. + # Set the coerce_timestamps to "us"(microseconds) for backward compatibility. + pyarrow.parquet.write_table( + table=table, where=parquet_temp_file, coerce_timestamps="us" + ) parquet_temp_file.seek(0) diff --git a/sdk/python/feast/infra/utils/aws_utils.py b/sdk/python/feast/infra/utils/aws_utils.py index f48dfbb86b..728bcab791 100644 --- a/sdk/python/feast/infra/utils/aws_utils.py +++ b/sdk/python/feast/infra/utils/aws_utils.py @@ -351,7 +351,9 @@ def upload_arrow_table_to_redshift( else: # Write the PyArrow Table on disk in Parquet format and upload it to S3 with tempfile.TemporaryFile(suffix=".parquet") as parquet_temp_file: - pq.write_table(table, parquet_temp_file) + # In Pyarrow v13.0, the parquet version was upgraded to v2.6 from v2.4. + # Set the coerce_timestamps to "us"(microseconds) for backward compatibility. + pq.write_table(table, parquet_temp_file, coerce_timestamps="us") parquet_temp_file.seek(0) s3_resource.Object(bucket, key).put(Body=parquet_temp_file) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index c74a3f12e1..2cbfa6cbc9 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -57,11 +57,11 @@ black==22.12.0 # via feast (setup.py) bleach==6.1.0 # via nbconvert -boto3==1.28.80 +boto3==1.29.2 # via # feast (setup.py) # moto -botocore==1.31.80 +botocore==1.32.2 # via # boto3 # moto @@ -121,7 +121,9 @@ comm==0.2.0 # ipykernel # ipywidgets coverage[toml]==7.3.2 - # via pytest-cov + # via + # coverage + # pytest-cov cryptography==41.0.5 # via # azure-identity @@ -135,7 +137,7 @@ cryptography==41.0.5 # snowflake-connector-python # types-pyopenssl # types-redis -dask==2023.10.1 +dask==2023.11.0 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery @@ -177,7 +179,7 @@ fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.1 +fastjsonschema==2.19.0 # via nbformat filelock==3.13.1 # via @@ -199,7 +201,7 @@ geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.13.0 +google-api-core[grpc]==2.14.0 # via # feast (setup.py) # firebase-admin @@ -211,7 +213,7 @@ google-api-core[grpc]==2.13.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.107.0 +google-api-python-client==2.108.0 # via firebase-admin google-auth==2.23.4 # via @@ -224,7 +226,9 @@ google-auth==2.23.4 google-auth-httplib2==0.1.1 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) + # via + # feast (setup.py) + # google-cloud-bigquery google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) google-cloud-bigtable==2.21.0 @@ -262,7 +266,7 @@ great-expectations==0.15.50 # via feast (setup.py) greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-bigtable grpcio==1.59.2 # via @@ -298,7 +302,7 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httplib2==0.22.0 # via @@ -368,7 +372,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.2 +jsonschema[format-nongpl]==4.20.0 # via # altair # feast (setup.py) @@ -376,7 +380,7 @@ jsonschema[format-nongpl]==4.19.2 # jupyter-events # jupyterlab-server # nbformat -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema jupyter-client==8.6.0 # via @@ -396,7 +400,7 @@ jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.10.0 +jupyter-server==2.10.1 # via # jupyter-lsp # jupyterlab @@ -409,7 +413,7 @@ jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.25.0 +jupyterlab-server==2.25.1 # via # jupyterlab # notebook @@ -419,7 +423,7 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd -makefun==1.15.1 +makefun==1.15.2 # via great-expectations markupsafe==2.1.3 # via @@ -446,7 +450,7 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.7 +moto==4.2.9 # via feast (setup.py) msal==1.25.0 # via @@ -564,7 +568,7 @@ pre-commit==3.3.1 # via feast (setup.py) prometheus-client==0.18.0 # via jupyter-server -prompt-toolkit==3.0.39 +prompt-toolkit==3.0.41 # via ipython proto-plus==1.22.3 # via @@ -610,7 +614,7 @@ py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark -pyarrow==10.0.1 +pyarrow==14.0.1 # via # db-dtypes # feast (setup.py) @@ -683,7 +687,7 @@ pytest-ordering==0.6 # via feast (setup.py) pytest-timeout==1.4.2 # via feast (setup.py) -pytest-xdist==3.3.1 +pytest-xdist==3.4.0 # via feast (setup.py) python-dateutil==2.8.2 # via @@ -723,7 +727,7 @@ pyzmq==25.1.1 # jupyter-server redis==4.6.0 # via feast (setup.py) -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications @@ -751,7 +755,7 @@ requests==2.31.0 # trino requests-oauthlib==1.3.1 # via kubernetes -responses==0.24.0 +responses==0.24.1 # via moto rfc3339-validator==0.1.4 # via @@ -763,7 +767,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -798,8 +802,10 @@ sniffio==1.3.0 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.4.0 - # via feast (setup.py) +snowflake-connector-python[pandas]==3.5.0 + # via + # feast (setup.py) + # snowflake-connector-python sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 @@ -825,8 +831,10 @@ sphinxcontrib-qthelp==1.0.6 sphinxcontrib-serializinghtml==1.1.9 # via sphinx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy stack-data==0.6.3 # via ipython @@ -836,7 +844,7 @@ tabulate==0.9.0 # via feast (setup.py) tenacity==8.2.3 # via feast (setup.py) -terminado==0.17.1 +terminado==0.18.0 # via # jupyter-server # jupyter-server-terminals @@ -858,7 +866,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.2 +tomlkit==0.12.3 # via snowflake-connector-python toolz==0.12.0 # via @@ -916,7 +924,7 @@ types-redis==4.6.0.10 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==68.2.0.0 +types-setuptools==68.2.0.1 # via feast (setup.py) types-tabulate==0.9.0.3 # via feast (setup.py) @@ -955,7 +963,9 @@ urllib3==1.26.18 # rockset # snowflake-connector-python uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 @@ -966,7 +976,7 @@ volatile==2.1.0 # via bowler watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.9 +wcwidth==0.2.10 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -987,7 +997,7 @@ wheel==0.41.3 # via pip-tools widgetsnbextension==4.0.9 # via ipywidgets -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers xmltodict==0.13.0 # via moto diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 41fee168bf..0508614aa6 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -36,7 +36,7 @@ cloudpickle==3.0.0 # via dask colorama==0.4.6 # via feast (setup.py) -dask==2023.10.1 +dask==2023.11.0 # via feast (setup.py) dill==0.3.7 # via feast (setup.py) @@ -72,7 +72,7 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httptools==0.6.1 # via uvicorn @@ -91,9 +91,9 @@ importlib-resources==6.1.1 # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.2 +jsonschema==4.20.0 # via feast (setup.py) -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema locket==1.0.0 # via partd @@ -103,7 +103,7 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.6.1 +mypy==1.7.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -137,7 +137,7 @@ protobuf==4.23.3 # grpcio-tools # mypy-protobuf # proto-plus -pyarrow==11.0.0 +pyarrow==14.0.1 # via feast (setup.py) pydantic==1.10.13 # via @@ -156,13 +156,13 @@ pyyaml==6.0.1 # dask # feast (setup.py) # uvicorn -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -175,8 +175,10 @@ sniffio==1.3.0 # anyio # httpx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -205,10 +207,12 @@ typing-extensions==4.8.0 # pydantic # sqlalchemy2-stubs # uvicorn -urllib3==2.0.7 +urllib3==2.1.0 # via requests uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn volatile==2.1.0 diff --git a/sdk/python/requirements/py3.8-ci-requirements.txt b/sdk/python/requirements/py3.8-ci-requirements.txt index 855074057a..8449af824f 100644 --- a/sdk/python/requirements/py3.8-ci-requirements.txt +++ b/sdk/python/requirements/py3.8-ci-requirements.txt @@ -63,11 +63,11 @@ black==22.12.0 # via feast (setup.py) bleach==6.1.0 # via nbconvert -boto3==1.28.80 +boto3==1.29.2 # via # feast (setup.py) # moto -botocore==1.31.80 +botocore==1.32.2 # via # boto3 # moto @@ -127,7 +127,9 @@ comm==0.2.0 # ipykernel # ipywidgets coverage[toml]==7.3.2 - # via pytest-cov + # via + # coverage + # pytest-cov cryptography==41.0.5 # via # azure-identity @@ -182,7 +184,7 @@ fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.1 +fastjsonschema==2.19.0 # via nbformat filelock==3.13.1 # via @@ -204,7 +206,7 @@ geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.13.0 +google-api-core[grpc]==2.14.0 # via # feast (setup.py) # firebase-admin @@ -216,7 +218,7 @@ google-api-core[grpc]==2.13.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.107.0 +google-api-python-client==2.108.0 # via firebase-admin google-auth==2.23.4 # via @@ -229,7 +231,9 @@ google-auth==2.23.4 google-auth-httplib2==0.1.1 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) + # via + # feast (setup.py) + # google-cloud-bigquery google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) google-cloud-bigtable==2.21.0 @@ -267,7 +271,7 @@ great-expectations==0.15.50 # via feast (setup.py) greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-bigtable grpcio==1.59.2 # via @@ -303,7 +307,7 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httplib2==0.22.0 # via @@ -384,7 +388,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.2 +jsonschema[format-nongpl]==4.20.0 # via # altair # feast (setup.py) @@ -392,7 +396,7 @@ jsonschema[format-nongpl]==4.19.2 # jupyter-events # jupyterlab-server # nbformat -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema jupyter-client==8.6.0 # via @@ -412,7 +416,7 @@ jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.10.0 +jupyter-server==2.10.1 # via # jupyter-lsp # jupyterlab @@ -425,7 +429,7 @@ jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.25.0 +jupyterlab-server==2.25.1 # via # jupyterlab # notebook @@ -435,7 +439,7 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd -makefun==1.15.1 +makefun==1.15.2 # via great-expectations markupsafe==2.1.3 # via @@ -462,7 +466,7 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.7 +moto==4.2.9 # via feast (setup.py) msal==1.25.0 # via @@ -584,7 +588,7 @@ pre-commit==3.3.1 # via feast (setup.py) prometheus-client==0.18.0 # via jupyter-server -prompt-toolkit==3.0.39 +prompt-toolkit==3.0.41 # via ipython proto-plus==1.22.3 # via @@ -630,7 +634,7 @@ py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark -pyarrow==10.0.1 +pyarrow==14.0.1 # via # db-dtypes # feast (setup.py) @@ -703,7 +707,7 @@ pytest-ordering==0.6 # via feast (setup.py) pytest-timeout==1.4.2 # via feast (setup.py) -pytest-xdist==3.3.1 +pytest-xdist==3.4.0 # via feast (setup.py) python-dateutil==2.8.2 # via @@ -744,7 +748,7 @@ pyzmq==25.1.1 # jupyter-server redis==4.6.0 # via feast (setup.py) -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications @@ -772,7 +776,7 @@ requests==2.31.0 # trino requests-oauthlib==1.3.1 # via kubernetes -responses==0.24.0 +responses==0.24.1 # via moto rfc3339-validator==0.1.4 # via @@ -784,7 +788,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -821,8 +825,10 @@ sniffio==1.3.0 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.4.0 - # via feast (setup.py) +snowflake-connector-python[pandas]==3.5.0 + # via + # feast (setup.py) + # snowflake-connector-python sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 @@ -842,8 +848,10 @@ sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy stack-data==0.6.3 # via ipython @@ -853,7 +861,7 @@ tabulate==0.9.0 # via feast (setup.py) tenacity==8.2.3 # via feast (setup.py) -terminado==0.17.1 +terminado==0.18.0 # via # jupyter-server # jupyter-server-terminals @@ -875,7 +883,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.2 +tomlkit==0.12.3 # via snowflake-connector-python toolz==0.12.0 # via @@ -933,7 +941,7 @@ types-redis==4.6.0.10 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==68.2.0.0 +types-setuptools==68.2.0.1 # via feast (setup.py) types-tabulate==0.9.0.3 # via feast (setup.py) @@ -975,7 +983,9 @@ urllib3==1.26.18 # rockset # snowflake-connector-python uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 @@ -986,7 +996,7 @@ volatile==2.1.0 # via bowler watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.9 +wcwidth==0.2.10 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -1007,7 +1017,7 @@ wheel==0.41.3 # via pip-tools widgetsnbextension==4.0.9 # via ipywidgets -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers xmltodict==0.13.0 # via moto diff --git a/sdk/python/requirements/py3.8-requirements.txt b/sdk/python/requirements/py3.8-requirements.txt index 174dc7d6f5..3f6a15a1ef 100644 --- a/sdk/python/requirements/py3.8-requirements.txt +++ b/sdk/python/requirements/py3.8-requirements.txt @@ -72,7 +72,7 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httptools==0.6.1 # via uvicorn @@ -94,9 +94,9 @@ importlib-resources==6.1.1 # jsonschema-specifications jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.2 +jsonschema==4.20.0 # via feast (setup.py) -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema locket==1.0.0 # via partd @@ -106,7 +106,7 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.6.1 +mypy==1.7.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -142,7 +142,7 @@ protobuf==4.23.3 # grpcio-tools # mypy-protobuf # proto-plus -pyarrow==11.0.0 +pyarrow==14.0.1 # via feast (setup.py) pydantic==1.10.13 # via @@ -161,13 +161,13 @@ pyyaml==6.0.1 # dask # feast (setup.py) # uvicorn -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -180,8 +180,10 @@ sniffio==1.3.0 # anyio # httpx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -211,10 +213,12 @@ typing-extensions==4.8.0 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==2.0.7 +urllib3==2.1.0 # via requests uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn volatile==2.1.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 4bf35b4adf..5bc9210fd8 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -57,11 +57,11 @@ black==22.12.0 # via feast (setup.py) bleach==6.1.0 # via nbconvert -boto3==1.28.80 +boto3==1.29.2 # via # feast (setup.py) # moto -botocore==1.31.80 +botocore==1.32.2 # via # boto3 # moto @@ -121,7 +121,9 @@ comm==0.2.0 # ipykernel # ipywidgets coverage[toml]==7.3.2 - # via pytest-cov + # via + # coverage + # pytest-cov cryptography==41.0.5 # via # azure-identity @@ -135,7 +137,7 @@ cryptography==41.0.5 # snowflake-connector-python # types-pyopenssl # types-redis -dask==2023.10.1 +dask==2023.11.0 # via feast (setup.py) db-dtypes==1.1.1 # via google-cloud-bigquery @@ -177,7 +179,7 @@ fastavro==1.9.0 # via # feast (setup.py) # pandavro -fastjsonschema==2.18.1 +fastjsonschema==2.19.0 # via nbformat filelock==3.13.1 # via @@ -199,7 +201,7 @@ geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.13.0 +google-api-core[grpc]==2.14.0 # via # feast (setup.py) # firebase-admin @@ -211,7 +213,7 @@ google-api-core[grpc]==2.13.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.107.0 +google-api-python-client==2.108.0 # via firebase-admin google-auth==2.23.4 # via @@ -224,7 +226,9 @@ google-auth==2.23.4 google-auth-httplib2==0.1.1 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) + # via + # feast (setup.py) + # google-cloud-bigquery google-cloud-bigquery-storage==2.22.0 # via feast (setup.py) google-cloud-bigtable==2.21.0 @@ -262,7 +266,7 @@ great-expectations==0.15.50 # via feast (setup.py) greenlet==3.0.1 # via sqlalchemy -grpc-google-iam-v1==0.12.6 +grpc-google-iam-v1==0.12.7 # via google-cloud-bigtable grpcio==1.59.2 # via @@ -298,7 +302,7 @@ hazelcast-python-client==5.3.0 # via feast (setup.py) hiredis==2.2.3 # via feast (setup.py) -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httplib2==0.22.0 # via @@ -375,7 +379,7 @@ jsonpointer==2.4 # via # jsonpatch # jsonschema -jsonschema[format-nongpl]==4.19.2 +jsonschema[format-nongpl]==4.20.0 # via # altair # feast (setup.py) @@ -383,7 +387,7 @@ jsonschema[format-nongpl]==4.19.2 # jupyter-events # jupyterlab-server # nbformat -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema jupyter-client==8.6.0 # via @@ -403,7 +407,7 @@ jupyter-events==0.9.0 # via jupyter-server jupyter-lsp==2.2.0 # via jupyterlab -jupyter-server==2.10.0 +jupyter-server==2.10.1 # via # jupyter-lsp # jupyterlab @@ -416,7 +420,7 @@ jupyterlab==4.0.8 # via notebook jupyterlab-pygments==0.2.2 # via nbconvert -jupyterlab-server==2.25.0 +jupyterlab-server==2.25.1 # via # jupyterlab # notebook @@ -426,7 +430,7 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd -makefun==1.15.1 +makefun==1.15.2 # via great-expectations markupsafe==2.1.3 # via @@ -453,7 +457,7 @@ mock==2.0.0 # via feast (setup.py) moreorless==0.4.0 # via bowler -moto==4.2.7 +moto==4.2.9 # via feast (setup.py) msal==1.25.0 # via @@ -473,7 +477,7 @@ mypy-extensions==1.0.0 # via # black # mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) mysqlclient==2.2.0 # via feast (setup.py) @@ -571,7 +575,7 @@ pre-commit==3.3.1 # via feast (setup.py) prometheus-client==0.18.0 # via jupyter-server -prompt-toolkit==3.0.39 +prompt-toolkit==3.0.41 # via ipython proto-plus==1.22.3 # via @@ -617,7 +621,7 @@ py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark -pyarrow==10.0.1 +pyarrow==14.0.1 # via # db-dtypes # feast (setup.py) @@ -690,7 +694,7 @@ pytest-ordering==0.6 # via feast (setup.py) pytest-timeout==1.4.2 # via feast (setup.py) -pytest-xdist==3.3.1 +pytest-xdist==3.4.0 # via feast (setup.py) python-dateutil==2.8.2 # via @@ -730,7 +734,7 @@ pyzmq==25.1.1 # jupyter-server redis==4.6.0 # via feast (setup.py) -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications @@ -758,7 +762,7 @@ requests==2.31.0 # trino requests-oauthlib==1.3.1 # via kubernetes -responses==0.24.0 +responses==0.24.1 # via moto rfc3339-validator==0.1.4 # via @@ -770,7 +774,7 @@ rfc3986-validator==0.1.1 # jupyter-events rockset==2.1.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -807,8 +811,10 @@ sniffio==1.3.0 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.4.0 - # via feast (setup.py) +snowflake-connector-python[pandas]==3.5.0 + # via + # feast (setup.py) + # snowflake-connector-python sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 @@ -834,8 +840,10 @@ sphinxcontrib-qthelp==1.0.6 sphinxcontrib-serializinghtml==1.1.9 # via sphinx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy stack-data==0.6.3 # via ipython @@ -845,7 +853,7 @@ tabulate==0.9.0 # via feast (setup.py) tenacity==8.2.3 # via feast (setup.py) -terminado==0.17.1 +terminado==0.18.0 # via # jupyter-server # jupyter-server-terminals @@ -867,7 +875,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tomlkit==0.12.2 +tomlkit==0.12.3 # via snowflake-connector-python toolz==0.12.0 # via @@ -925,7 +933,7 @@ types-redis==4.6.0.10 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==68.2.0.0 +types-setuptools==68.2.0.1 # via feast (setup.py) types-tabulate==0.9.0.3 # via feast (setup.py) @@ -967,7 +975,9 @@ urllib3==1.26.18 # rockset # snowflake-connector-python uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 @@ -978,7 +988,7 @@ volatile==2.1.0 # via bowler watchfiles==0.21.0 # via uvicorn -wcwidth==0.2.9 +wcwidth==0.2.10 # via prompt-toolkit webcolors==1.13 # via jsonschema @@ -999,7 +1009,7 @@ wheel==0.41.3 # via pip-tools widgetsnbextension==4.0.9 # via ipywidgets -wrapt==1.15.0 +wrapt==1.16.0 # via testcontainers xmltodict==0.13.0 # via moto diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 12612c3465..120ecf9eb3 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -36,7 +36,7 @@ cloudpickle==3.0.0 # via dask colorama==0.4.6 # via feast (setup.py) -dask==2023.10.1 +dask==2023.11.0 # via feast (setup.py) dill==0.3.7 # via feast (setup.py) @@ -72,7 +72,7 @@ h11==0.14.0 # via # httpcore # uvicorn -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httptools==0.6.1 # via uvicorn @@ -91,9 +91,9 @@ importlib-resources==6.1.1 # via feast (setup.py) jinja2==3.1.2 # via feast (setup.py) -jsonschema==4.19.2 +jsonschema==4.20.0 # via feast (setup.py) -jsonschema-specifications==2023.7.1 +jsonschema-specifications==2023.11.1 # via jsonschema locket==1.0.0 # via partd @@ -103,11 +103,11 @@ mmh3==4.0.1 # via feast (setup.py) moreorless==0.4.0 # via bowler -mypy==1.6.1 +mypy==1.7.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy -mypy-protobuf==3.1 +mypy-protobuf==3.1.0 # via feast (setup.py) numpy==1.24.4 # via @@ -137,7 +137,7 @@ protobuf==4.23.3 # grpcio-tools # mypy-protobuf # proto-plus -pyarrow==11.0.0 +pyarrow==14.0.1 # via feast (setup.py) pydantic==1.10.13 # via @@ -156,13 +156,13 @@ pyyaml==6.0.1 # dask # feast (setup.py) # uvicorn -referencing==0.30.2 +referencing==0.31.0 # via # jsonschema # jsonschema-specifications requests==2.31.0 # via feast (setup.py) -rpds-py==0.12.0 +rpds-py==0.13.0 # via # jsonschema # referencing @@ -175,8 +175,10 @@ sniffio==1.3.0 # anyio # httpx sqlalchemy[mypy]==1.4.50 - # via feast (setup.py) -sqlalchemy2-stubs==0.0.2a36 + # via + # feast (setup.py) + # sqlalchemy +sqlalchemy2-stubs==0.0.2a37 # via sqlalchemy starlette==0.27.0 # via fastapi @@ -206,10 +208,12 @@ typing-extensions==4.8.0 # sqlalchemy2-stubs # starlette # uvicorn -urllib3==2.0.7 +urllib3==2.1.0 # via requests uvicorn[standard]==0.24.0.post1 - # via feast (setup.py) + # via + # feast (setup.py) + # uvicorn uvloop==0.19.0 # via uvicorn volatile==2.1.0 diff --git a/setup.py b/setup.py index 38b7d295a9..33bf76e181 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ # Higher than 4.23.4 seems to cause a seg fault "protobuf<4.23.4,>3.20", "proto-plus>=1.20.0,<2", - "pyarrow>=4,<12", + "pyarrow>=4", "pydantic>=1,<2", "pygments>=2.12.0,<3", "PyYAML>=5.4.0,<7", From 4f8efa74141498dd714b6d66913c93eb90f65d0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:13:12 -0500 Subject: [PATCH 38/41] chore: Bump follow-redirects from 1.15.0 to 1.15.4 in /sdk/python/feast/ui (#3879) chore: Bump follow-redirects in /sdk/python/feast/ui Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.0 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.0...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdk/python/feast/ui/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index 80a16dddca..31ee5ae176 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -5485,9 +5485,9 @@ focus-lock@^0.11.2: tslib "^2.0.3" follow-redirects@^1.0.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" - integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.2" From e436f776ccf291809c802c469073adf7f1540d69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:13:31 -0500 Subject: [PATCH 39/41] chore: Bump follow-redirects from 1.14.7 to 1.15.4 in /ui (#3878) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/yarn.lock b/ui/yarn.lock index 49bff13372..78fe6863bf 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -5708,9 +5708,9 @@ focus-lock@^0.11.2: tslib "^2.0.3" follow-redirects@^1.0.0: - version "1.14.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" - integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.0" From b26f25a9a95bd31b89272e9721506e89890d738f Mon Sep 17 00:00:00 2001 From: Willem Pienaar <6728866+woop@users.noreply.github.com> Date: Sat, 13 Jan 2024 01:20:26 -0500 Subject: [PATCH 40/41] Update release.yml to latest nodejs --- .github/workflows/release.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a01bae4068..b0d3e3cb39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,8 +36,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version: "lts/*" - name: Release (Dry Run) id: get_versions run: | @@ -60,10 +59,10 @@ jobs: NEXT_VERSION: ${{ needs.get_dry_release_versions.outputs.next_version }} steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version: "lts/*" - name: Bump file versions run: python ./infra/scripts/release/bump_file_versions.py ${CURRENT_VERSION} ${NEXT_VERSION} - name: Install yarn dependencies @@ -101,10 +100,10 @@ jobs: NEXT_VERSION: ${{ needs.get_dry_release_versions.outputs.next_version }} steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version: "lts/*" - name: Bump file versions (temporarily for Web UI publish) run: python ./infra/scripts/release/bump_file_versions.py ${CURRENT_VERSION} ${NEXT_VERSION} - name: Install yarn dependencies @@ -139,8 +138,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version: "lts/*" - name: Set up Homebrew id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@master From 9237361e8d87b8e5dc02dffa74905eab2380fbd7 Mon Sep 17 00:00:00 2001 From: feast-ci-bot Date: Sat, 13 Jan 2024 06:33:26 +0000 Subject: [PATCH 41/41] chore(release): release 0.35.0 # [0.35.0](https://github.com/feast-dev/feast/compare/v0.34.0...v0.35.0) (2024-01-13) ### Bug Fixes * Add async refresh to prevent synchronous refresh in main thread ([#3812](https://github.com/feast-dev/feast/issues/3812)) ([9583ed6](https://github.com/feast-dev/feast/commit/9583ed6b4ae8d3b97934bf0c80ecb236ed1e2895)) * Adopt connection pooling for HBase ([#3793](https://github.com/feast-dev/feast/issues/3793)) ([b3852bf](https://github.com/feast-dev/feast/commit/b3852bfb8b27bf07736935f465da3067fcbac0ae)) * Bytewax engine create configmap from object ([#3821](https://github.com/feast-dev/feast/issues/3821)) ([25e9775](https://github.com/feast-dev/feast/commit/25e97756adedfd1227d591ae74bdf60655f9067e)) * Fix warnings from deprecated paths and update default log level ([#3757](https://github.com/feast-dev/feast/issues/3757)) ([68a8737](https://github.com/feast-dev/feast/commit/68a87379c42567f338d86cb2be90520cc6d4bfb6)) * improve parsing bytewax job status ([5983f40](https://github.com/feast-dev/feast/commit/5983f40f8f5df5dbbcd2640f83ef82c19cdb4d19)) * make bytewax settings unexposed ([ae1bb8b](https://github.com/feast-dev/feast/commit/ae1bb8bdd1b9e293809519971935c93c2214d791)) * Make generated temp table name escaped ([#3797](https://github.com/feast-dev/feast/issues/3797)) ([175d796](https://github.com/feast-dev/feast/commit/175d7969b1f75ab797aff9c92a70d845297444ad)) * Pin numpy version to avoid spammy deprecation messages ([774ed33](https://github.com/feast-dev/feast/commit/774ed33a067bf9bf087520325b72f4f4d194106a)) * Redundant feature materialization and premature incremental materialization timestamp updates ([#3789](https://github.com/feast-dev/feast/issues/3789)) ([417b16b](https://github.com/feast-dev/feast/commit/417b16b57af7b38fbd0708b9a0c5d5035ed021fd)), closes [#6](https://github.com/feast-dev/feast/issues/6) [#7](https://github.com/feast-dev/feast/issues/7) * Resolve hbase hotspot issue when materializing ([#3790](https://github.com/feast-dev/feast/issues/3790)) ([7376db8](https://github.com/feast-dev/feast/commit/7376db8dbd1d3168a1262fbbc0ce3899be8d0c34)) * Set keepalives_idle None by default ([#3756](https://github.com/feast-dev/feast/issues/3756)) ([8717e9b](https://github.com/feast-dev/feast/commit/8717e9bf0fd253454982b9c9e9527c4d41906e9c)) * Set upper bound for bigquery client due to its breaking changes ([2151c39](https://github.com/feast-dev/feast/commit/2151c39d1a8d8eba114306411dd4bd91ac0ce3f6)) * UI project cannot handle fallback routes ([#3766](https://github.com/feast-dev/feast/issues/3766)) ([96ece0f](https://github.com/feast-dev/feast/commit/96ece0fe94a07cc6f1dabf5d6c9b061b48b06d67)) * update dependencies versions due to conflicts ([5dc0b24](https://github.com/feast-dev/feast/commit/5dc0b241ec68aa10fd783569bf0ae12c5752f20f)) * Update jackson and remove unnecessary logging ([#3809](https://github.com/feast-dev/feast/issues/3809)) ([018d0ea](https://github.com/feast-dev/feast/commit/018d0eab69dde63266f2c56813045ea5c5523f76)) * upgrade the pyarrow to latest v14.0.1 for CVE-2023-47248. ([052182b](https://github.com/feast-dev/feast/commit/052182bcca046e35456674fc7d524825882f4b35)) ### Features * Add get online feature rpc to gprc server ([#3815](https://github.com/feast-dev/feast/issues/3815)) ([01db8cc](https://github.com/feast-dev/feast/commit/01db8cce6f82d4c6e496041351fb6b56eb2645b0)) * Add materialize and materialize-incremental rest endpoints ([#3761](https://github.com/feast-dev/feast/issues/3761)) ([fa600fe](https://github.com/feast-dev/feast/commit/fa600fe3c4b1d5fdd383a9367511ac5616ee7a32)), closes [#3760](https://github.com/feast-dev/feast/issues/3760) * add redis sentinel support ([3387a15](https://github.com/feast-dev/feast/commit/3387a15d2b7e8dea430a271570be5a19b32bd3fe)) * add redis sentinel support ([4337c89](https://github.com/feast-dev/feast/commit/4337c89083a3cfca21ee1beef473fda13b0e9014)) * add redis sentinel support format lint ([aad8718](https://github.com/feast-dev/feast/commit/aad8718d24d893b3ff8c5864c5b8d210cfcdb22f)) * Add support for `table_create_disposition` in bigquery job for offline store ([#3762](https://github.com/feast-dev/feast/issues/3762)) ([6a728fe](https://github.com/feast-dev/feast/commit/6a728fe66db0286ea10301d1fe693d6dcba4e4f4)) * Add support for in_cluster config and additional labels for bytewax materialization ([#3754](https://github.com/feast-dev/feast/issues/3754)) ([2192e65](https://github.com/feast-dev/feast/commit/2192e6527fa10f1580e4dd8f350e05e45af981b7)) * Apply cache to load proto registry for performance ([#3702](https://github.com/feast-dev/feast/issues/3702)) ([709c709](https://github.com/feast-dev/feast/commit/709c7098dc28a35dd488f5079d3787cf1f74ec03)) * Make bytewax job write as mini-batches ([#3777](https://github.com/feast-dev/feast/issues/3777)) ([9b0e5ce](https://github.com/feast-dev/feast/commit/9b0e5ce2d1b617fcdcf0699c8b0cf8549a5e5ac5)) * Optimize bytewax pod resource with zero-copy ([9cf9d96](https://github.com/feast-dev/feast/commit/9cf9d965a5566a87bb7419f2e8509666076f035f)) * Support GCS filesystem for bytewax engine ([#3774](https://github.com/feast-dev/feast/issues/3774)) ([fb6b807](https://github.com/feast-dev/feast/commit/fb6b807f8b32776d388757ca431d290c03170c66)) --- CHANGELOG.md | 37 +++++++++++++++++++ infra/charts/feast-feature-server/Chart.yaml | 2 +- infra/charts/feast-feature-server/README.md | 4 +- infra/charts/feast-feature-server/values.yaml | 2 +- infra/charts/feast/Chart.yaml | 2 +- infra/charts/feast/README.md | 6 +-- .../feast/charts/feature-server/Chart.yaml | 4 +- .../feast/charts/feature-server/README.md | 6 +-- .../feast/charts/feature-server/values.yaml | 2 +- .../charts/transformation-service/Chart.yaml | 4 +- .../charts/transformation-service/README.md | 6 +-- .../charts/transformation-service/values.yaml | 2 +- infra/charts/feast/requirements.yaml | 4 +- java/pom.xml | 2 +- sdk/python/feast/ui/package.json | 2 +- sdk/python/feast/ui/yarn.lock | 8 ++-- ui/package.json | 2 +- 17 files changed, 66 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e5a430f0..26b8baa963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +# [0.35.0](https://github.com/feast-dev/feast/compare/v0.34.0...v0.35.0) (2024-01-13) + + +### Bug Fixes + +* Add async refresh to prevent synchronous refresh in main thread ([#3812](https://github.com/feast-dev/feast/issues/3812)) ([9583ed6](https://github.com/feast-dev/feast/commit/9583ed6b4ae8d3b97934bf0c80ecb236ed1e2895)) +* Adopt connection pooling for HBase ([#3793](https://github.com/feast-dev/feast/issues/3793)) ([b3852bf](https://github.com/feast-dev/feast/commit/b3852bfb8b27bf07736935f465da3067fcbac0ae)) +* Bytewax engine create configmap from object ([#3821](https://github.com/feast-dev/feast/issues/3821)) ([25e9775](https://github.com/feast-dev/feast/commit/25e97756adedfd1227d591ae74bdf60655f9067e)) +* Fix warnings from deprecated paths and update default log level ([#3757](https://github.com/feast-dev/feast/issues/3757)) ([68a8737](https://github.com/feast-dev/feast/commit/68a87379c42567f338d86cb2be90520cc6d4bfb6)) +* improve parsing bytewax job status ([5983f40](https://github.com/feast-dev/feast/commit/5983f40f8f5df5dbbcd2640f83ef82c19cdb4d19)) +* make bytewax settings unexposed ([ae1bb8b](https://github.com/feast-dev/feast/commit/ae1bb8bdd1b9e293809519971935c93c2214d791)) +* Make generated temp table name escaped ([#3797](https://github.com/feast-dev/feast/issues/3797)) ([175d796](https://github.com/feast-dev/feast/commit/175d7969b1f75ab797aff9c92a70d845297444ad)) +* Pin numpy version to avoid spammy deprecation messages ([774ed33](https://github.com/feast-dev/feast/commit/774ed33a067bf9bf087520325b72f4f4d194106a)) +* Redundant feature materialization and premature incremental materialization timestamp updates ([#3789](https://github.com/feast-dev/feast/issues/3789)) ([417b16b](https://github.com/feast-dev/feast/commit/417b16b57af7b38fbd0708b9a0c5d5035ed021fd)), closes [#6](https://github.com/feast-dev/feast/issues/6) [#7](https://github.com/feast-dev/feast/issues/7) +* Resolve hbase hotspot issue when materializing ([#3790](https://github.com/feast-dev/feast/issues/3790)) ([7376db8](https://github.com/feast-dev/feast/commit/7376db8dbd1d3168a1262fbbc0ce3899be8d0c34)) +* Set keepalives_idle None by default ([#3756](https://github.com/feast-dev/feast/issues/3756)) ([8717e9b](https://github.com/feast-dev/feast/commit/8717e9bf0fd253454982b9c9e9527c4d41906e9c)) +* Set upper bound for bigquery client due to its breaking changes ([2151c39](https://github.com/feast-dev/feast/commit/2151c39d1a8d8eba114306411dd4bd91ac0ce3f6)) +* UI project cannot handle fallback routes ([#3766](https://github.com/feast-dev/feast/issues/3766)) ([96ece0f](https://github.com/feast-dev/feast/commit/96ece0fe94a07cc6f1dabf5d6c9b061b48b06d67)) +* update dependencies versions due to conflicts ([5dc0b24](https://github.com/feast-dev/feast/commit/5dc0b241ec68aa10fd783569bf0ae12c5752f20f)) +* Update jackson and remove unnecessary logging ([#3809](https://github.com/feast-dev/feast/issues/3809)) ([018d0ea](https://github.com/feast-dev/feast/commit/018d0eab69dde63266f2c56813045ea5c5523f76)) +* upgrade the pyarrow to latest v14.0.1 for CVE-2023-47248. ([052182b](https://github.com/feast-dev/feast/commit/052182bcca046e35456674fc7d524825882f4b35)) + + +### Features + +* Add get online feature rpc to gprc server ([#3815](https://github.com/feast-dev/feast/issues/3815)) ([01db8cc](https://github.com/feast-dev/feast/commit/01db8cce6f82d4c6e496041351fb6b56eb2645b0)) +* Add materialize and materialize-incremental rest endpoints ([#3761](https://github.com/feast-dev/feast/issues/3761)) ([fa600fe](https://github.com/feast-dev/feast/commit/fa600fe3c4b1d5fdd383a9367511ac5616ee7a32)), closes [#3760](https://github.com/feast-dev/feast/issues/3760) +* add redis sentinel support ([3387a15](https://github.com/feast-dev/feast/commit/3387a15d2b7e8dea430a271570be5a19b32bd3fe)) +* add redis sentinel support ([4337c89](https://github.com/feast-dev/feast/commit/4337c89083a3cfca21ee1beef473fda13b0e9014)) +* add redis sentinel support format lint ([aad8718](https://github.com/feast-dev/feast/commit/aad8718d24d893b3ff8c5864c5b8d210cfcdb22f)) +* Add support for `table_create_disposition` in bigquery job for offline store ([#3762](https://github.com/feast-dev/feast/issues/3762)) ([6a728fe](https://github.com/feast-dev/feast/commit/6a728fe66db0286ea10301d1fe693d6dcba4e4f4)) +* Add support for in_cluster config and additional labels for bytewax materialization ([#3754](https://github.com/feast-dev/feast/issues/3754)) ([2192e65](https://github.com/feast-dev/feast/commit/2192e6527fa10f1580e4dd8f350e05e45af981b7)) +* Apply cache to load proto registry for performance ([#3702](https://github.com/feast-dev/feast/issues/3702)) ([709c709](https://github.com/feast-dev/feast/commit/709c7098dc28a35dd488f5079d3787cf1f74ec03)) +* Make bytewax job write as mini-batches ([#3777](https://github.com/feast-dev/feast/issues/3777)) ([9b0e5ce](https://github.com/feast-dev/feast/commit/9b0e5ce2d1b617fcdcf0699c8b0cf8549a5e5ac5)) +* Optimize bytewax pod resource with zero-copy ([9cf9d96](https://github.com/feast-dev/feast/commit/9cf9d965a5566a87bb7419f2e8509666076f035f)) +* Support GCS filesystem for bytewax engine ([#3774](https://github.com/feast-dev/feast/issues/3774)) ([fb6b807](https://github.com/feast-dev/feast/commit/fb6b807f8b32776d388757ca431d290c03170c66)) + # [0.34.0](https://github.com/feast-dev/feast/compare/v0.33.0...v0.34.0) (2023-09-07) diff --git a/infra/charts/feast-feature-server/Chart.yaml b/infra/charts/feast-feature-server/Chart.yaml index 6631d37784..6111639b68 100644 --- a/infra/charts/feast-feature-server/Chart.yaml +++ b/infra/charts/feast-feature-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: feast-feature-server description: Feast Feature Server in Go or Python type: application -version: 0.34.0 +version: 0.35.0 keywords: - machine learning - big data diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index ad88d08217..2467b60e1e 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -1,6 +1,6 @@ # Feast Python / Go Feature Server Helm Charts -Current chart version is `0.34.0` +Current chart version is `0.35.0` ## Installation @@ -30,7 +30,7 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/python-helm-d | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"feastdev/feature-server"` | Docker image for Feature Server repository | -| image.tag | string | `"0.34.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | +| image.tag | string | `"0.35.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | | imagePullSecrets | list | `[]` | | | livenessProbe.initialDelaySeconds | int | `30` | | | livenessProbe.periodSeconds | int | `30` | | diff --git a/infra/charts/feast-feature-server/values.yaml b/infra/charts/feast-feature-server/values.yaml index d46f1b685b..2383d6fa9f 100644 --- a/infra/charts/feast-feature-server/values.yaml +++ b/infra/charts/feast-feature-server/values.yaml @@ -9,7 +9,7 @@ image: repository: feastdev/feature-server pullPolicy: IfNotPresent # image.tag -- The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) - tag: 0.34.0 + tag: 0.35.0 imagePullSecrets: [] nameOverride: "" diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml index e0f530e05e..c53b73848c 100644 --- a/infra/charts/feast/Chart.yaml +++ b/infra/charts/feast/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 description: Feature store for machine learning name: feast -version: 0.34.0 +version: 0.35.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index fff6d0261b..d8d4a3d376 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -8,7 +8,7 @@ This repo contains Helm charts for Feast Java components that are being installe ## Chart: Feast -Feature store for machine learning Current chart version is `0.34.0` +Feature store for machine learning Current chart version is `0.35.0` ## Installation @@ -65,8 +65,8 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/java-demo) fo | Repository | Name | Version | |------------|------|---------| | https://charts.helm.sh/stable | redis | 10.5.6 | -| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.34.0 | -| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.34.0 | +| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.35.0 | +| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.35.0 | ## Values diff --git a/infra/charts/feast/charts/feature-server/Chart.yaml b/infra/charts/feast/charts/feature-server/Chart.yaml index bfb33b6140..122c80f9fd 100644 --- a/infra/charts/feast/charts/feature-server/Chart.yaml +++ b/infra/charts/feast/charts/feature-server/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Feast Feature Server: Online feature serving service for Feast" name: feature-server -version: 0.34.0 -appVersion: v0.34.0 +version: 0.35.0 +appVersion: v0.35.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/feature-server/README.md b/infra/charts/feast/charts/feature-server/README.md index f768a46cd5..514fcc5727 100644 --- a/infra/charts/feast/charts/feature-server/README.md +++ b/infra/charts/feast/charts/feature-server/README.md @@ -1,6 +1,6 @@ # feature-server -![Version: 0.34.0](https://img.shields.io/badge/Version-0.34.0-informational?style=flat-square) ![AppVersion: v0.34.0](https://img.shields.io/badge/AppVersion-v0.34.0-informational?style=flat-square) +![Version: 0.35.0](https://img.shields.io/badge/Version-0.35.0-informational?style=flat-square) ![AppVersion: v0.35.0](https://img.shields.io/badge/AppVersion-v0.35.0-informational?style=flat-square) Feast Feature Server: Online feature serving service for Feast @@ -17,7 +17,7 @@ Feast Feature Server: Online feature serving service for Feast | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-server-java"` | Docker image for Feature Server repository | -| image.tag | string | `"0.34.0"` | Image tag | +| image.tag | string | `"0.35.0"` | Image tag | | ingress.grpc.annotations | object | `{}` | Extra annotations for the ingress | | ingress.grpc.auth.enabled | bool | `false` | Flag to enable auth | | ingress.grpc.class | string | `"nginx"` | Which ingress controller to use | @@ -64,4 +64,4 @@ Feast Feature Server: Online feature serving service for Feast | transformationService.port | int | `6566` | | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) +Autogenerated from chart metadata using [helm-docs v1.12.0](https://github.com/norwoodj/helm-docs/releases/v1.12.0) diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index 24b8da1e39..a14cc2d28f 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Feature Server repository repository: feastdev/feature-server-java # image.tag -- Image tag - tag: 0.34.0 + tag: 0.35.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/transformation-service/Chart.yaml b/infra/charts/feast/charts/transformation-service/Chart.yaml index 5d8f157a48..852045f52b 100644 --- a/infra/charts/feast/charts/transformation-service/Chart.yaml +++ b/infra/charts/feast/charts/transformation-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Transformation service: to compute on-demand features" name: transformation-service -version: 0.34.0 -appVersion: v0.34.0 +version: 0.35.0 +appVersion: v0.35.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/transformation-service/README.md b/infra/charts/feast/charts/transformation-service/README.md index cf8c7eaae8..758620d56a 100644 --- a/infra/charts/feast/charts/transformation-service/README.md +++ b/infra/charts/feast/charts/transformation-service/README.md @@ -1,6 +1,6 @@ # transformation-service -![Version: 0.34.0](https://img.shields.io/badge/Version-0.34.0-informational?style=flat-square) ![AppVersion: v0.34.0](https://img.shields.io/badge/AppVersion-v0.34.0-informational?style=flat-square) +![Version: 0.35.0](https://img.shields.io/badge/Version-0.35.0-informational?style=flat-square) ![AppVersion: v0.35.0](https://img.shields.io/badge/AppVersion-v0.35.0-informational?style=flat-square) Transformation service: to compute on-demand features @@ -13,7 +13,7 @@ Transformation service: to compute on-demand features | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-transformation-server"` | Docker image for Transformation Server repository | -| image.tag | string | `"0.34.0"` | Image tag | +| image.tag | string | `"0.35.0"` | Image tag | | nodeSelector | object | `{}` | Node labels for pod assignment | | podLabels | object | `{}` | Labels to be added to Feast Serving pods | | replicaCount | int | `1` | Number of pods that will be created | @@ -25,4 +25,4 @@ Transformation service: to compute on-demand features | service.type | string | `"ClusterIP"` | Kubernetes service type | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) +Autogenerated from chart metadata using [helm-docs v1.12.0](https://github.com/norwoodj/helm-docs/releases/v1.12.0) diff --git a/infra/charts/feast/charts/transformation-service/values.yaml b/infra/charts/feast/charts/transformation-service/values.yaml index 6af9e569ea..e45ef47288 100644 --- a/infra/charts/feast/charts/transformation-service/values.yaml +++ b/infra/charts/feast/charts/transformation-service/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Transformation Server repository repository: feastdev/feature-transformation-server # image.tag -- Image tag - tag: 0.34.0 + tag: 0.35.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index b3236b0322..ec098f2f7b 100644 --- a/infra/charts/feast/requirements.yaml +++ b/infra/charts/feast/requirements.yaml @@ -1,12 +1,12 @@ dependencies: - name: feature-server alias: feature-server - version: 0.34.0 + version: 0.35.0 condition: feature-server.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: transformation-service alias: transformation-service - version: 0.34.0 + version: 0.35.0 condition: transformation-service.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: redis diff --git a/java/pom.xml b/java/pom.xml index 3a94e5f19c..59c6733784 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -35,7 +35,7 @@ - 0.34.0 + 0.35.0 https://github.com/feast-dev/feast UTF-8 diff --git a/sdk/python/feast/ui/package.json b/sdk/python/feast/ui/package.json index 80956440ab..1d2951cf57 100644 --- a/sdk/python/feast/ui/package.json +++ b/sdk/python/feast/ui/package.json @@ -6,7 +6,7 @@ "@elastic/datemath": "^5.0.3", "@elastic/eui": "^55.0.1", "@emotion/react": "^11.9.0", - "@feast-dev/feast-ui": "0.34.0", + "@feast-dev/feast-ui": "0.35.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", "@testing-library/user-event": "^13.5.0", diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index 31ee5ae176..4d6f690b94 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -1451,10 +1451,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@feast-dev/feast-ui@0.34.0": - version "0.34.0" - resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.34.0.tgz#21799c119da936814ff47e1d1d297258ecfda098" - integrity sha512-ErrnmaFPMgnmnTpBpn7T0oiC3cA+atE5yKBmoMFXBsK6aplu18aF53QBX4JJFKMKF+A2BcO37veeXm1k01SJQQ== +"@feast-dev/feast-ui@0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.35.0.tgz#f28eb82ae4855673230f14e3740a7786f545fdd7" + integrity sha512-t0Rd2TWUMim6ITfVVlQU8aBZboLvxla6Z7udGW+tQ3UUGcq1VbM6/y+GobuYQfbdHHeF2GmlYqp6zw5DuoIM+Q== dependencies: "@elastic/datemath" "^5.0.3" "@elastic/eui" "^55.0.1" diff --git a/ui/package.json b/ui/package.json index dc16a1e7a6..c826737cf6 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "@feast-dev/feast-ui", - "version": "0.34.0", + "version": "0.35.0", "private": false, "files": [ "dist"