From 83fad152ffe01a3b2691095a45b90eb30044c859 Mon Sep 17 00:00:00 2001 From: Hao Xu Date: Tue, 18 Jun 2024 11:48:40 -0700 Subject: [PATCH 01/44] feat: Entity key deserialization (#4284) * Add new version of serialization and desrialization Signed-off-by: cmuhao * Add new version of serialization and desrialization Signed-off-by: cmuhao * fix test Signed-off-by: cmuhao * fix test Signed-off-by: cmuhao * add test Signed-off-by: cmuhao * add test Signed-off-by: cmuhao * update doc Signed-off-by: cmuhao --------- Signed-off-by: cmuhao --- sdk/python/feast/infra/key_encoding_utils.py | 80 ++++++++++++++++++- sdk/python/feast/repo_config.py | 8 +- .../unit/infra/test_key_encoding_utils.py | 71 +++++++++++++++- 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/sdk/python/feast/infra/key_encoding_utils.py b/sdk/python/feast/infra/key_encoding_utils.py index ca834f19176..1f9ffeef140 100644 --- a/sdk/python/feast/infra/key_encoding_utils.py +++ b/sdk/python/feast/infra/key_encoding_utils.py @@ -20,7 +20,23 @@ def _serialize_val( return struct.pack(" ValueProto: + if value_type == ValueType.INT64: + value = struct.unpack(" bytes: @@ -50,6 +66,15 @@ def serialize_entity_key( serialize to the same byte string[1]. [1] https://developers.google.com/protocol-buffers/docs/encoding + + Args: + entity_key_serialization_version: version of the entity key serialization + version 1: int64 values are serialized as 4 bytes + version 2: int64 values are serialized as 8 bytes + version 3: entity_key size is added to the serialization for deserialization purposes + entity_key: EntityKeyProto + + Returns: bytes of the serialized entity key """ sorted_keys, sorted_values = zip( *sorted(zip(entity_key.join_keys, entity_key.entity_values)) @@ -58,6 +83,8 @@ def serialize_entity_key( output: List[bytes] = [] for k in sorted_keys: output.append(struct.pack(" 2: + output.append(struct.pack(" EntityKeyProto: + """ + Deserialize entity key from a bytestring. This function can only be used with entity_key_serialization_version > 2. + Args: + entity_key_serialization_version: version of the entity key serialization + serialized_entity_key: serialized entity key bytes + + Returns: EntityKeyProto + + """ + if entity_key_serialization_version <= 2: + raise ValueError( + "Deserialization of entity key with version <= 2 is not supported. Please use version > 2 by setting entity_key_serialization_version=3" + ) + offset = 0 + keys = [] + values = [] + while offset < len(serialized_entity_key): + key_type = struct.unpack_from(" Date: Tue, 18 Jun 2024 14:51:33 -0400 Subject: [PATCH 02/44] feat: Ignore paths feast apply (#4276) --- sdk/python/feast/repo_operations.py | 8 ++++++++ .../tests/unit/infra/scaffolding/test_repo_operations.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/sdk/python/feast/repo_operations.py b/sdk/python/feast/repo_operations.py index 274a0af02b0..05a7d05e235 100644 --- a/sdk/python/feast/repo_operations.py +++ b/sdk/python/feast/repo_operations.py @@ -83,6 +83,14 @@ def get_repo_files(repo_root: Path) -> List[Path]: # Read ignore paths from .feastignore and create a set of all files that match any of these paths ignore_paths = read_feastignore(repo_root) ignore_files = get_ignore_files(repo_root, ignore_paths) + ignore_paths += [ + ".git", + ".feastignore", + ".venv", + ".pytest_cache", + "__pycache__", + ".ipynb_checkpoints", + ] # List all Python files in the root directory (recursively) repo_files = { diff --git a/sdk/python/tests/unit/infra/scaffolding/test_repo_operations.py b/sdk/python/tests/unit/infra/scaffolding/test_repo_operations.py index 70c8b05c2ed..aa4ff1c40f7 100644 --- a/sdk/python/tests/unit/infra/scaffolding/test_repo_operations.py +++ b/sdk/python/tests/unit/infra/scaffolding/test_repo_operations.py @@ -15,12 +15,14 @@ def feature_repo(feastignore_contents: Optional[str]): repo_root = Path(tmp_dir) (repo_root / "foo").mkdir() (repo_root / "foo1").mkdir() + (repo_root / ".ipynb_checkpoints/").mkdir() (repo_root / "foo1/bar").mkdir() (repo_root / "bar").mkdir() (repo_root / "bar/subdir1").mkdir() (repo_root / "bar/subdir1/subdir2").mkdir() (repo_root / "a.py").touch() + (repo_root / ".ipynb_checkpoints/test-checkpoint.ipynb").touch() (repo_root / "foo/b.py").touch() (repo_root / "foo1/c.py").touch() (repo_root / "foo1/bar/d.py").touch() From 89bc5512572130510dd18690309b5a392aaf73b1 Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Wed, 19 Jun 2024 05:18:19 +0400 Subject: [PATCH 03/44] chore: Remove serverless feature server deployments (#4272) --- .github/workflows/master_only.yml | 2 +- Makefile | 8 - docs/SUMMARY.md | 2 - docs/getting-started/faq.md | 2 +- docs/reference/feature-servers/README.md | 5 +- .../alpha-aws-lambda-feature-server.md | 197 ---------- .../feature-servers/python-feature-server.md | 2 - sdk/python/feast/constants.py | 6 - sdk/python/feast/errors.py | 26 -- sdk/python/feast/infra/aws.py | 349 +----------------- .../feature_servers/aws_lambda/Dockerfile | 26 -- .../feature_servers/aws_lambda/__init__.py | 0 .../infra/feature_servers/aws_lambda/app.py | 27 -- .../feature_servers/aws_lambda/config.py | 21 -- .../aws_lambda/requirements.txt | 2 - .../feature_servers/gcp_cloudrun/Dockerfile | 32 -- .../feature_servers/gcp_cloudrun/__init__.py | 0 .../infra/feature_servers/gcp_cloudrun/app.py | 24 -- .../feature_servers/gcp_cloudrun/config.py | 18 - .../gcp_cloudrun/requirements.txt | 1 - sdk/python/feast/repo_config.py | 4 - sdk/python/tests/conftest.py | 18 +- .../feature_repos/repo_configuration.py | 23 +- setup.py | 2 +- 24 files changed, 13 insertions(+), 784 deletions(-) delete mode 100644 docs/reference/feature-servers/alpha-aws-lambda-feature-server.md delete mode 100644 sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile delete mode 100644 sdk/python/feast/infra/feature_servers/aws_lambda/__init__.py delete mode 100644 sdk/python/feast/infra/feature_servers/aws_lambda/app.py delete mode 100644 sdk/python/feast/infra/feature_servers/aws_lambda/config.py delete mode 100644 sdk/python/feast/infra/feature_servers/aws_lambda/requirements.txt delete mode 100644 sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile delete mode 100644 sdk/python/feast/infra/feature_servers/gcp_cloudrun/__init__.py delete mode 100644 sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py delete mode 100644 sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py delete mode 100644 sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml index 1b401997a7b..7166246da5f 100644 --- a/.github/workflows/master_only.yml +++ b/.github/workflows/master_only.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - component: [ feature-server-python-aws, feature-server-java, feature-transformation-server ] + component: [ feature-server-java, feature-transformation-server ] env: MAVEN_CACHE: gs://feast-templocation-kf-feast/.m2.2020-08-19.tar REGISTRY: gcr.io/kf-feast diff --git a/Makefile b/Makefile index b44aaf0ee5a..2ad693c7a12 100644 --- a/Makefile +++ b/Makefile @@ -397,14 +397,6 @@ build-feature-server-docker: -t $(REGISTRY)/feature-server:$$VERSION \ -f sdk/python/feast/infra/feature_servers/multicloud/Dockerfile --load . -push-feature-server-python-aws-docker: - docker push $(REGISTRY)/feature-server-python-aws:$$VERSION - -build-feature-server-python-aws-docker: - docker buildx build --build-arg VERSION=$$VERSION \ - -t $(REGISTRY)/feature-server-python-aws:$$VERSION \ - -f sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile --load . - push-feature-transformation-server-docker: docker push $(REGISTRY)/feature-transformation-server:$(VERSION) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 06c5edcc8b0..3f0506cf1ee 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -118,9 +118,7 @@ * [Feature servers](reference/feature-servers/README.md) * [Python feature server](reference/feature-servers/python-feature-server.md) * [\[Alpha\] Go feature server](reference/feature-servers/go-feature-server.md) - * [\[Alpha\] AWS Lambda feature server](reference/feature-servers/alpha-aws-lambda-feature-server.md) * [Offline Feature Server](reference/feature-servers/offline-feature-server) - * [\[Beta\] Web UI](reference/alpha-web-ui.md) * [\[Alpha\] On demand feature view](reference/alpha-on-demand-feature-view.md) * [\[Alpha\] Data quality monitoring](reference/dqm.md) diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 8948eed5880..02f9db7d0ca 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -95,7 +95,7 @@ The list of supported offline and online stores can be found [here](../reference ### Does Feast support using different clouds for offline vs online stores? -Yes. Using a GCP or AWS provider in `feature_store.yaml` primarily sets default offline / online stores and configures where the remote registry file can live (Using the AWS provider also allows for deployment to AWS Lambda). You can override the offline and online stores to be in different clouds if you wish. +Yes. Using a GCP or AWS provider in `feature_store.yaml` primarily sets default offline / online stores and configures where the remote registry file can live. You can override the offline and online stores to be in different clouds if you wish. ### What is the difference between a data source and an offline store? diff --git a/docs/reference/feature-servers/README.md b/docs/reference/feature-servers/README.md index d5a4312f73a..124834f8a73 100644 --- a/docs/reference/feature-servers/README.md +++ b/docs/reference/feature-servers/README.md @@ -8,10 +8,7 @@ Feast users can choose to retrieve features from a feature server, as opposed to {% content-ref url="go-feature-server.md" %} [go-feature-server.md](go-feature-server.md) -{% endcontent-ref %} - -{% content-ref url="alpha-aws-lambda-feature-server.md" %} -[alpha-aws-lambda-feature-server.md](alpha-aws-lambda-feature-server.md) +======= {% endcontent-ref %} {% content-ref url="offline-feature-server.md" %} diff --git a/docs/reference/feature-servers/alpha-aws-lambda-feature-server.md b/docs/reference/feature-servers/alpha-aws-lambda-feature-server.md deleted file mode 100644 index caf5542bdc1..00000000000 --- a/docs/reference/feature-servers/alpha-aws-lambda-feature-server.md +++ /dev/null @@ -1,197 +0,0 @@ -# \[Alpha] AWS Lambda feature server - -**Warning**: This is an _experimental_ feature. It's intended for early testing and feedback, and could change without warnings in future releases. - -## Overview - -The AWS Lambda feature server is an HTTP endpoint that serves features with JSON I/O, deployed as a Docker image through AWS Lambda and AWS API Gateway. This enables users to get features from Feast using any programming language that can make HTTP requests. A [local feature server](python-feature-server.md) is also available. A remote feature server on GCP Cloud Run is currently being developed. - -## Deployment - -The AWS Lambda feature server is only available to projects using the `AwsProvider` with registries on S3. It is disabled by default. To enable it, `feature_store.yaml` must be modified; specifically, the `enable` flag must be on and an `execution_role_name` must be specified. For example, after running `feast init -t aws`, changing the registry to be on S3, and enabling the feature server, the contents of `feature_store.yaml` should look similar to the following: - -``` -project: dev -registry: s3://feast/registries/dev -provider: aws -online_store: - region: us-west-2 -offline_store: - cluster_id: feast - region: us-west-2 - user: admin - database: feast - s3_staging_location: s3://feast/redshift/tests/staging_location - iam_role: arn:aws:iam::{aws_account}:role/redshift_s3_access_role -feature_server: - enabled: True - execution_role_name: arn:aws:iam::{aws_account}:role/lambda_execution_role -``` - -If enabled, the feature server will be deployed during `feast apply`. After it is deployed, the `feast endpoint` CLI command will indicate the server's endpoint. - -## Permissions - -Feast requires the following permissions in order to deploy and teardown AWS Lambda feature server: - -| Permissions | Resources | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -|

lambda:CreateFunction

lambda:GetFunction

lambda:DeleteFunction

lambda:AddPermission

lambda:UpdateFunctionConfiguration

| arn:aws:lambda:\:\:function:feast-\* | -|

ecr:CreateRepository

ecr:DescribeRepositories

ecr:DeleteRepository

ecr:PutImage

ecr:DescribeImages

ecr:BatchDeleteImage

ecr:CompleteLayerUpload

ecr:UploadLayerPart

ecr:InitiateLayerUpload

ecr:BatchCheckLayerAvailability

ecr:GetDownloadUrlForLayer

ecr:GetRepositoryPolicy

ecr:SetRepositoryPolicy

ecr:GetAuthorizationToken

| \* | -|

iam:PassRole

| arn:aws:iam::\:role/ | -|

apigateway:*

|

arn:aws:apigateway:*::/apis/*/routes/*/routeresponses

arn:aws:apigateway:*::/apis/*/routes/*/routeresponses/*

arn:aws:apigateway:*::/apis/*/routes/*

arn:aws:apigateway:*::/apis/*/routes

arn:aws:apigateway:*::/apis/*/integrations

arn:aws:apigateway:*::/apis/*/stages/*/routesettings/*

arn:aws:apigateway:*::/apis/*

arn:aws:apigateway:*::/apis

| - -The following inline policy can be used to grant Feast the necessary permissions: - -```javascript -{ - "Statement": [ - { - Action = [ - "lambda:CreateFunction", - "lambda:GetFunction", - "lambda:DeleteFunction", - "lambda:AddPermission", - "lambda:UpdateFunctionConfiguration", - ] - Effect = "Allow" - Resource = "arn:aws:lambda:::function:feast-*" - }, - { - Action = [ - "ecr:CreateRepository", - "ecr:DescribeRepositories", - "ecr:DeleteRepository", - "ecr:PutImage", - "ecr:DescribeImages", - "ecr:BatchDeleteImage", - "ecr:CompleteLayerUpload", - "ecr:UploadLayerPart", - "ecr:InitiateLayerUpload", - "ecr:BatchCheckLayerAvailability", - "ecr:GetDownloadUrlForLayer", - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:GetAuthorizationToken" - ] - Effect = "Allow" - Resource = "*" - }, - { - Action = "iam:PassRole" - Effect = "Allow" - Resource = "arn:aws:iam:::role/" - }, - { - Effect = "Allow" - Action = "apigateway:*" - Resource = [ - "arn:aws:apigateway:*::/apis/*/routes/*/routeresponses", - "arn:aws:apigateway:*::/apis/*/routes/*/routeresponses/*", - "arn:aws:apigateway:*::/apis/*/routes/*", - "arn:aws:apigateway:*::/apis/*/routes", - "arn:aws:apigateway:*::/apis/*/integrations", - "arn:aws:apigateway:*::/apis/*/stages/*/routesettings/*", - "arn:aws:apigateway:*::/apis/*", - "arn:aws:apigateway:*::/apis", - ] - }, - ], - "Version": "2012-10-17" -} -``` - -## Example - -After `feature_store.yaml` has been modified as described in the previous section, it can be deployed as follows: - -```bash -$ feast apply -10/07/2021 03:57:26 PM INFO:Pulling remote image feastdev/feature-server-python-aws:aws: -10/07/2021 03:57:28 PM INFO:Creating remote ECR repository feast-python-server-key_shark-0_13_1_dev23_gb3c08320: -10/07/2021 03:57:29 PM INFO:Pushing local image to remote 402087665549.dkr.ecr.us-west-2.amazonaws.com/feast-python-server-key_shark-0_13_1_dev23_gb3c08320:0_13_1_dev23_gb3c08320: -10/07/2021 03:58:44 PM INFO:Deploying feature server... -10/07/2021 03:58:45 PM INFO: Creating AWS Lambda... -10/07/2021 03:58:46 PM INFO: Creating AWS API Gateway... -Registered entity driver_id -Registered feature view driver_hourly_stats -Deploying infrastructure for driver_hourly_stats - -$ feast endpoint -10/07/2021 03:59:01 PM INFO:Feature server endpoint: https://hkosgmz4m2.execute-api.us-west-2.amazonaws.com - -$ feast materialize-incremental $(date +%Y-%m-%d) -Materializing 1 feature views to 2021-10-06 17:00:00-07:00 into the dynamodb online store. - -driver_hourly_stats from 2020-10-08 23:01:34-07:00 to 2021-10-06 17:00:00-07:00: -100%|█████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 16.89it/s] -``` - -After the feature server starts, we can execute cURL commands against it: - -```bash -$ curl -X POST \ - "https://hkosgmz4m2.execute-api.us-west-2.amazonaws.com/get-online-features" \ - -H "Content-type: application/json" \ - -H "Accept: application/json" \ - -d '{ - "features": [ - "driver_hourly_stats:conv_rate", - "driver_hourly_stats:acc_rate", - "driver_hourly_stats:avg_daily_trips" - ], - "entities": { - "driver_id": [1001, 1002, 1003] - }, - "full_feature_names": true - }' | jq - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 1346 100 1055 100 291 3436 947 --:--:-- --:--:-- --:--:-- 4370 -{ - "field_values": [ - { - "fields": { - "driver_id": 1001, - "driver_hourly_stats__conv_rate": 0.025330161675810814, - "driver_hourly_stats__avg_daily_trips": 785, - "driver_hourly_stats__acc_rate": 0.835975170135498 - }, - "statuses": { - "driver_hourly_stats__avg_daily_trips": "PRESENT", - "driver_id": "PRESENT", - "driver_hourly_stats__conv_rate": "PRESENT", - "driver_hourly_stats__acc_rate": "PRESENT" - } - }, - { - "fields": { - "driver_hourly_stats__conv_rate": 0.7595187425613403, - "driver_hourly_stats__acc_rate": 0.1740121990442276, - "driver_id": 1002, - "driver_hourly_stats__avg_daily_trips": 875 - }, - "statuses": { - "driver_hourly_stats__acc_rate": "PRESENT", - "driver_id": "PRESENT", - "driver_hourly_stats__avg_daily_trips": "PRESENT", - "driver_hourly_stats__conv_rate": "PRESENT" - } - }, - { - "fields": { - "driver_hourly_stats__acc_rate": 0.7785481214523315, - "driver_hourly_stats__conv_rate": 0.33832859992980957, - "driver_hourly_stats__avg_daily_trips": 846, - "driver_id": 1003 - }, - "statuses": { - "driver_id": "PRESENT", - "driver_hourly_stats__conv_rate": "PRESENT", - "driver_hourly_stats__acc_rate": "PRESENT", - "driver_hourly_stats__avg_daily_trips": "PRESENT" - } - } - ] -} -``` diff --git a/docs/reference/feature-servers/python-feature-server.md b/docs/reference/feature-servers/python-feature-server.md index c189f97ae03..0d8a0aef756 100644 --- a/docs/reference/feature-servers/python-feature-server.md +++ b/docs/reference/feature-servers/python-feature-server.md @@ -12,8 +12,6 @@ There is a CLI command that starts the server: `feast serve`. By default, Feast One can deploy a feature server by building a docker image that bundles in the project's `feature_store.yaml`. See this [helm chart](https://github.com/feast-dev/feast/blob/master/infra/charts/feast-feature-server) for an example on how to run Feast on Kubernetes. -A [remote feature server](alpha-aws-lambda-feature-server.md) on AWS Lambda is also available. - ## Example ### Initializing a feature server diff --git a/sdk/python/feast/constants.py b/sdk/python/feast/constants.py index fa8674d91d2..7dd39458211 100644 --- a/sdk/python/feast/constants.py +++ b/sdk/python/feast/constants.py @@ -17,9 +17,6 @@ # Maximum interval(secs) to wait between retries for retry function MAX_WAIT_INTERVAL: str = "60" -AWS_LAMBDA_FEATURE_SERVER_IMAGE = "feastdev/feature-server-python-aws" -AWS_LAMBDA_FEATURE_SERVER_REPOSITORY = "feast-python-server" - # feature_store.yaml environment variable name for remote feature server FEATURE_STORE_YAML_ENV_NAME: str = "FEATURE_STORE_YAML_BASE64" @@ -44,8 +41,5 @@ # Default offline server port DEFAULT_OFFLINE_SERVER_PORT = 8815 -# Environment variable for feature server docker image tag -DOCKER_IMAGE_TAG_ENV_NAME: str = "FEAST_SERVER_DOCKER_IMAGE_TAG" - # Default feature server registry ttl (seconds) DEFAULT_FEATURE_SERVER_REGISTRY_TTL = 5 diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py index 52fefce9d90..22de402f20a 100644 --- a/sdk/python/feast/errors.py +++ b/sdk/python/feast/errors.py @@ -350,32 +350,6 @@ def __init__(self, feature_view_name: str): ) -class RepoConfigPathDoesNotExist(Exception): - def __init__(self): - super().__init__("The repo_path attribute does not exist for the repo_config.") - - -class AwsLambdaDoesNotExist(Exception): - def __init__(self, resource_name: str): - super().__init__( - f"The AWS Lambda function {resource_name} should have been created properly, but does not exist." - ) - - -class AwsAPIGatewayDoesNotExist(Exception): - def __init__(self, resource_name: str): - super().__init__( - f"The AWS API Gateway {resource_name} should have been created properly, but does not exist." - ) - - -class IncompatibleRegistryStoreClass(Exception): - def __init__(self, actual_class: str, expected_class: str): - super().__init__( - f"The registry store class was expected to be {expected_class}, but was instead {actual_class}." - ) - - class FeastInvalidInfraObjectType(Exception): def __init__(self): super().__init__("Could not identify the type of the InfraObject.") diff --git a/sdk/python/feast/infra/aws.py b/sdk/python/feast/infra/aws.py index bb896fa961f..47fec9b05bd 100644 --- a/sdk/python/feast/infra/aws.py +++ b/sdk/python/feast/infra/aws.py @@ -1,350 +1,9 @@ -import base64 -import hashlib -import logging -import os -import uuid -import warnings -from typing import Optional, Sequence - -from colorama import Fore, Style - -from feast import utils -from feast.constants import ( - AWS_LAMBDA_FEATURE_SERVER_IMAGE, - AWS_LAMBDA_FEATURE_SERVER_REPOSITORY, - DOCKER_IMAGE_TAG_ENV_NAME, - FEATURE_STORE_YAML_ENV_NAME, -) -from feast.entity import Entity -from feast.errors import ( - AwsAPIGatewayDoesNotExist, - AwsLambdaDoesNotExist, - IncompatibleRegistryStoreClass, - RepoConfigPathDoesNotExist, -) -from feast.feature_view import FeatureView -from feast.infra.feature_servers.aws_lambda.config import AwsLambdaFeatureServerConfig from feast.infra.passthrough_provider import PassthroughProvider -from feast.infra.registry.registry import get_registry_store_class_from_scheme -from feast.infra.registry.s3 import S3RegistryStore -from feast.infra.utils import aws_utils -from feast.version import get_version - -try: - import boto3 -except ImportError as e: - from feast.errors import FeastExtrasDependencyImportError - - raise FeastExtrasDependencyImportError("aws", str(e)) - -_logger = logging.getLogger(__name__) class AwsProvider(PassthroughProvider): - def update_infra( - self, - project: str, - tables_to_delete: Sequence[FeatureView], - tables_to_keep: Sequence[FeatureView], - entities_to_delete: Sequence[Entity], - entities_to_keep: Sequence[Entity], - partial: bool, - ): - # Call update only if there is an online store - if self.online_store: - self.online_store.update( - config=self.repo_config, - tables_to_delete=tables_to_delete, - tables_to_keep=tables_to_keep, - entities_to_keep=entities_to_keep, - entities_to_delete=entities_to_delete, - partial=partial, - ) - - if self.repo_config.feature_server and self.repo_config.feature_server.enabled: - warnings.warn( - "AWS Lambda based feature serving is an experimental feature. " - "We do not guarantee that future changes will maintain backward compatibility.", - RuntimeWarning, - ) - - # Since the AWS Lambda feature server will attempt to load the registry, we - # only allow the registry to be in S3. - registry_path = ( - self.repo_config.registry - if isinstance(self.repo_config.registry, str) - else self.repo_config.registry.path - ) - registry_store_class = get_registry_store_class_from_scheme(registry_path) - if registry_store_class != S3RegistryStore: - raise IncompatibleRegistryStoreClass( - registry_store_class.__name__, S3RegistryStore.__name__ - ) - - ecr_client = boto3.client("ecr") - docker_image_version = _get_docker_image_version() - repository_uri = self._create_or_get_repository_uri(ecr_client) - # Only download & upload the docker image if it doesn't already exist in ECR - if not ecr_client.batch_get_image( - repositoryName=AWS_LAMBDA_FEATURE_SERVER_REPOSITORY, - imageIds=[{"imageTag": docker_image_version}], - ).get("images"): - image_uri = self._upload_docker_image( - ecr_client, repository_uri, docker_image_version - ) - else: - image_uri = f"{repository_uri}:{docker_image_version}" - - self._deploy_feature_server(project, image_uri) - - if self.batch_engine: - self.batch_engine.update( - project, - tables_to_delete, - tables_to_keep, - entities_to_delete, - entities_to_keep, - ) - - def _deploy_feature_server(self, project: str, image_uri: str): - _logger.info("Deploying feature server...") - - if not self.repo_config.repo_path: - raise RepoConfigPathDoesNotExist() - - with open( - utils.get_default_yaml_file_path(self.repo_config.repo_path), "rb" - ) as f: - config_bytes = f.read() - config_base64 = base64.b64encode(config_bytes).decode() - - resource_name = _get_lambda_name(project) - lambda_client = boto3.client("lambda") - api_gateway_client = boto3.client("apigatewayv2") - function = aws_utils.get_lambda_function(lambda_client, resource_name) - _logger.debug("Using function name: %s", resource_name) - _logger.debug("Found function: %s", function) - - if function is None: - # If the Lambda function does not exist, create it. - _logger.info(" Creating AWS Lambda...") - assert isinstance( - self.repo_config.feature_server, AwsLambdaFeatureServerConfig - ) - lambda_client.create_function( - FunctionName=resource_name, - Role=self.repo_config.feature_server.execution_role_name, - Code={"ImageUri": image_uri}, - PackageType="Image", - MemorySize=1769, - Environment={"Variables": {FEATURE_STORE_YAML_ENV_NAME: config_base64}}, - Tags={ - "feast-owned": "True", - "project": project, - "feast-sdk-version": get_version(), - }, - ) - function = aws_utils.get_lambda_function(lambda_client, resource_name) - if not function: - raise AwsLambdaDoesNotExist(resource_name) - else: - # If the feature_store.yaml has changed, need to update the environment variable. - env = function.get("Environment", {}).get("Variables", {}) - if env.get(FEATURE_STORE_YAML_ENV_NAME) != config_base64: - # Note, that this does not update Lambda gracefully (e.g. no rolling deployment). - # It's expected that feature_store.yaml is not regularly updated while the lambda - # is serving production traffic. However, the update in registry (e.g. modifying - # feature views, feature services, and other definitions does not update lambda). - _logger.info(" Updating AWS Lambda...") - - aws_utils.update_lambda_function_environment( - lambda_client, - resource_name, - {"Variables": {FEATURE_STORE_YAML_ENV_NAME: config_base64}}, - ) - - api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name) - if not api: - # If the API Gateway doesn't exist, create it - _logger.info(" Creating AWS API Gateway...") - api = api_gateway_client.create_api( - Name=resource_name, - ProtocolType="HTTP", - Target=function["FunctionArn"], - RouteKey="POST /get-online-features", - Tags={ - "feast-owned": "True", - "project": project, - "feast-sdk-version": get_version(), - }, - ) - if not api: - raise AwsAPIGatewayDoesNotExist(resource_name) - # Make sure to give AWS Lambda a permission to be invoked by the newly created API Gateway - api_id = api["ApiId"] - region = lambda_client.meta.region_name - account_id = aws_utils.get_account_id() - lambda_client.add_permission( - FunctionName=function["FunctionArn"], - StatementId=str(uuid.uuid4()), - Action="lambda:InvokeFunction", - Principal="apigateway.amazonaws.com", - SourceArn=f"arn:aws:execute-api:{region}:{account_id}:{api_id}/*/*/get-online-features", - ) - - def teardown_infra( - self, - project: str, - tables: Sequence[FeatureView], - entities: Sequence[Entity], - ) -> None: - super(AwsProvider, self).teardown_infra(project, tables, entities) - - if ( - self.repo_config.feature_server is not None - and self.repo_config.feature_server.enabled - ): - _logger.info("Tearing down feature server...") - resource_name = _get_lambda_name(project) - lambda_client = boto3.client("lambda") - api_gateway_client = boto3.client("apigatewayv2") - - function = aws_utils.get_lambda_function(lambda_client, resource_name) - - if function is not None: - _logger.info(" Tearing down AWS Lambda...") - aws_utils.delete_lambda_function(lambda_client, resource_name) - - api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name) - if api is not None: - _logger.info(" Tearing down AWS API Gateway...") - aws_utils.delete_api_gateway(api_gateway_client, api["ApiId"]) - - def get_feature_server_endpoint(self) -> Optional[str]: - project = self.repo_config.project - resource_name = _get_lambda_name(project) - api_gateway_client = boto3.client("apigatewayv2") - api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name) - - if not api: - return None - - api_id = api["ApiId"] - lambda_client = boto3.client("lambda") - region = lambda_client.meta.region_name - return f"https://{api_id}.execute-api.{region}.amazonaws.com" - - def _upload_docker_image( - self, ecr_client, repository_uri: str, docker_image_version: str - ) -> str: - """ - Pulls the AWS Lambda docker image from Dockerhub and uploads it to AWS ECR. - - Returns: - The URI of the uploaded docker image. - """ - try: - import docker - from docker.errors import APIError - except ImportError as e: - from feast.errors import FeastExtrasDependencyImportError - - raise FeastExtrasDependencyImportError("docker", str(e)) - - try: - docker_client = docker.from_env() - except APIError: - from feast.errors import DockerDaemonNotRunning - - raise DockerDaemonNotRunning() - - dockerhub_image = f"{AWS_LAMBDA_FEATURE_SERVER_IMAGE}:{docker_image_version}" - _logger.info( - f"Pulling remote image {Style.BRIGHT + Fore.GREEN}{dockerhub_image}{Style.RESET_ALL}" - ) - for line in docker_client.api.pull(dockerhub_image, stream=True, decode=True): - _logger.debug(f" {line}") - - auth_token = ecr_client.get_authorization_token()["authorizationData"][0][ - "authorizationToken" - ] - username, password = base64.b64decode(auth_token).decode("utf-8").split(":") - - ecr_address = repository_uri.split("/")[0] - _logger.info( - f"Logging in Docker client to {Style.BRIGHT + Fore.GREEN}{ecr_address}{Style.RESET_ALL}" - ) - login_status = docker_client.login( - username=username, password=password, registry=ecr_address - ) - _logger.debug(f" {login_status}") - - image = docker_client.images.get(dockerhub_image) - image_remote_name = f"{repository_uri}:{docker_image_version}" - _logger.info( - f"Pushing local image to remote {Style.BRIGHT + Fore.GREEN}{image_remote_name}{Style.RESET_ALL}" - ) - image.tag(image_remote_name) - for line in docker_client.api.push( - repository_uri, tag=docker_image_version, stream=True, decode=True - ): - _logger.debug(f" {line}") - - return image_remote_name - - def _create_or_get_repository_uri(self, ecr_client): - try: - return ecr_client.describe_repositories( - repositoryNames=[AWS_LAMBDA_FEATURE_SERVER_REPOSITORY] - )["repositories"][0]["repositoryUri"] - except ecr_client.exceptions.RepositoryNotFoundException: - _logger.info( - f"Creating remote ECR repository {Style.BRIGHT + Fore.GREEN}{AWS_LAMBDA_FEATURE_SERVER_REPOSITORY}{Style.RESET_ALL}" - ) - response = ecr_client.create_repository( - repositoryName=AWS_LAMBDA_FEATURE_SERVER_REPOSITORY - ) - return response["repository"]["repositoryUri"] - - -def _get_lambda_name(project: str): - lambda_prefix = AWS_LAMBDA_FEATURE_SERVER_REPOSITORY - lambda_suffix = f"{project}-{_get_docker_image_version().replace('.', '_')}" - # AWS Lambda name can't have the length greater than 64 bytes. - # This usually occurs during integration tests where feast version is long - if len(lambda_prefix) + len(lambda_suffix) >= 63: - lambda_suffix = hashlib.md5(lambda_suffix.encode()).hexdigest() - return f"{lambda_prefix}-{lambda_suffix}" - - -def _get_docker_image_version() -> str: - """Returns a version for the feature server Docker image. - - If the feast.constants.DOCKER_IMAGE_TAG_ENV_NAME environment variable is set, - we return that (mostly used for integration tests, but can be used for local testing too). - - For public Feast releases this equals to the Feast SDK version modified by replacing "." with "_". - For example, Feast SDK version "0.14.1" would correspond to Docker image version "0_14_1". - - During development (when Feast is installed in editable mode) this equals to the Feast SDK version - modified by removing the "dev..." suffix and replacing "." with "_". For example, Feast SDK version - "0.14.1.dev41+g1cbfa225.d20211103" would correspond to Docker image version "0_14_1". This way, - Feast SDK will use an already existing Docker image built during the previous public release. - """ - tag = os.environ.get(DOCKER_IMAGE_TAG_ENV_NAME) - if tag is not None: - return tag - else: - version = get_version() - if "dev" in version: - version = version[: version.find("dev") - 1] - _logger.warning( - "You are trying to use AWS Lambda feature server while Feast is in a development mode. " - f"Feast will use a docker image version {version} derived from Feast SDK " - f"version {get_version()}. If you want to update the Feast SDK version, make " - "sure to first fetch all new release tags from Github and then reinstall the library:\n" - "> git fetch --all --tags\n" - "> pip install -e '.'" - ) - return version + This class only exists for backwards compatibility. + """ + + pass diff --git a/sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile b/sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile deleted file mode 100644 index 929227a8106..00000000000 --- a/sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM public.ecr.aws/lambda/python:3.9 - -RUN yum install -y git - - -# Copy app handler code -COPY sdk/python/feast/infra/feature_servers/aws_lambda/app.py ${LAMBDA_TASK_ROOT} - -# Copy necessary parts of the Feast codebase -COPY sdk/python sdk/python -COPY protos protos -COPY go go -COPY setup.py setup.py -COPY pyproject.toml pyproject.toml -COPY README.md README.md - -# Install Feast for AWS with Lambda dependencies -# We need this mount thingy because setuptools_scm needs access to the -# 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,redis]' -RUN pip3 install -r sdk/python/feast/infra/feature_servers/aws_lambda/requirements.txt --target "${LAMBDA_TASK_ROOT}" - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.handler" ] diff --git a/sdk/python/feast/infra/feature_servers/aws_lambda/__init__.py b/sdk/python/feast/infra/feature_servers/aws_lambda/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/sdk/python/feast/infra/feature_servers/aws_lambda/app.py b/sdk/python/feast/infra/feature_servers/aws_lambda/app.py deleted file mode 100644 index e90364ed680..00000000000 --- a/sdk/python/feast/infra/feature_servers/aws_lambda/app.py +++ /dev/null @@ -1,27 +0,0 @@ -import base64 -import os -import tempfile -from pathlib import Path - -from mangum import Mangum - -from feast import FeatureStore -from feast.constants import FEATURE_STORE_YAML_ENV_NAME -from feast.feature_server import get_app - -# Load RepoConfig -config_base64 = os.environ[FEATURE_STORE_YAML_ENV_NAME] -config_bytes = base64.b64decode(config_base64) - -# Create a new unique directory for writing feature_store.yaml -repo_path = Path(tempfile.mkdtemp()) - -with open(repo_path / "feature_store.yaml", "wb") as f: - f.write(config_bytes) - -# Initialize the feature store -store = FeatureStore(repo_path=str(repo_path.resolve())) - -# Create the FastAPI app and AWS Lambda handler -app = get_app(store) -handler = Mangum(app) diff --git a/sdk/python/feast/infra/feature_servers/aws_lambda/config.py b/sdk/python/feast/infra/feature_servers/aws_lambda/config.py deleted file mode 100644 index 946831a18fb..00000000000 --- a/sdk/python/feast/infra/feature_servers/aws_lambda/config.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Literal - -from pydantic import StrictBool, StrictStr - -from feast.infra.feature_servers.base_config import BaseFeatureServerConfig - - -class AwsLambdaFeatureServerConfig(BaseFeatureServerConfig): - """Feature server config for AWS Lambda.""" - - type: Literal["aws_lambda"] = "aws_lambda" - """Feature server type selector.""" - - public: StrictBool = True - """Whether the endpoint should be publicly accessible.""" - - auth: Literal["none", "api-key"] = "none" - """Authentication method for the endpoint.""" - - execution_role_name: StrictStr - """The execution role for the AWS Lambda function.""" diff --git a/sdk/python/feast/infra/feature_servers/aws_lambda/requirements.txt b/sdk/python/feast/infra/feature_servers/aws_lambda/requirements.txt deleted file mode 100644 index 845aa14802d..00000000000 --- a/sdk/python/feast/infra/feature_servers/aws_lambda/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -fastapi -mangum diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile deleted file mode 100644 index 6b89d4f73c1..00000000000 --- a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM python:3.11-slim - -RUN apt-get update && apt-get install -y git - -# Allow statements and log messages to immediately appear in the Knative logs -ENV PYTHONUNBUFFERED True - -# Copy local code to the container image. -ENV APP_HOME /app -WORKDIR $APP_HOME - -# Copy app handler code -COPY sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py ./app.py - -# Copy necessary parts of the Feast codebase -COPY sdk/python ./sdk/python -COPY protos ./protos -COPY setup.py setup.py -COPY pyproject.toml pyproject.toml -COPY README.md ./README.md - -# Install production dependencies. -RUN --mount=source=.git,target=.git,type=bind pip install --no-cache-dir \ - -e '.[gcp,redis]' \ - -r ./sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt - -# Run the web service on container startup. Here we use the gunicorn -# webserver, with one worker process and 8 threads. -# For environments with multiple CPU cores, increase the number of workers -# to be equal to the cores available. -# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling. -CMD exec gunicorn -k uvicorn.workers.UvicornWorker --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/__init__.py b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py deleted file mode 100644 index 06749b0cd39..00000000000 --- a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py +++ /dev/null @@ -1,24 +0,0 @@ -import base64 -import os -import tempfile -from pathlib import Path - -from feast import FeatureStore -from feast.constants import FEATURE_STORE_YAML_ENV_NAME -from feast.feature_server import get_app - -# Load RepoConfig -config_base64 = os.environ[FEATURE_STORE_YAML_ENV_NAME] -config_bytes = base64.b64decode(config_base64) - -# Create a new unique directory for writing feature_store.yaml -repo_path = Path(tempfile.mkdtemp()) - -with open(repo_path / "feature_store.yaml", "wb") as f: - f.write(config_bytes) - -# Initialize the feature store -store = FeatureStore(repo_path=str(repo_path.resolve())) - -# Create the FastAPI app -app = get_app(store) diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py deleted file mode 100644 index ddcbde7924a..00000000000 --- a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Literal - -from pydantic import StrictBool - -from feast.infra.feature_servers.base_config import BaseFeatureServerConfig - - -class GcpCloudRunFeatureServerConfig(BaseFeatureServerConfig): - """Feature server config for GCP CloudRun.""" - - type: Literal["gcp_cloudrun"] = "gcp_cloudrun" - """Feature server type selector.""" - - public: StrictBool = True - """Whether the endpoint should be publicly accessible.""" - - auth: Literal["none", "api-key"] = "none" - """Authentication method for the endpoint.""" diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt deleted file mode 100644 index 8f22dccf99a..00000000000 --- a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -gunicorn diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index aacb95f4205..1c8041b4ddf 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -82,14 +82,10 @@ } FEATURE_SERVER_CONFIG_CLASS_FOR_TYPE = { - "aws_lambda": "feast.infra.feature_servers.aws_lambda.config.AwsLambdaFeatureServerConfig", - "gcp_cloudrun": "feast.infra.feature_servers.gcp_cloudrun.config.GcpCloudRunFeatureServerConfig", "local": "feast.infra.feature_servers.local_process.config.LocalFeatureServerConfig", } FEATURE_SERVER_TYPE_FOR_PROVIDER = { - "aws": "aws_lambda", - "gcp": "gcp_cloudrun", "local": "local", } diff --git a/sdk/python/tests/conftest.py b/sdk/python/tests/conftest.py index 48f482f5428..fb6b7e56085 100644 --- a/sdk/python/tests/conftest.py +++ b/sdk/python/tests/conftest.py @@ -257,12 +257,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc): extra_dimensions: List[Dict[str, Any]] = [{}] if "python_server" in metafunc.fixturenames: - extra_dimensions.extend( - [ - {"python_feature_server": True}, - {"python_feature_server": True, "provider": "aws"}, - ] - ) + extra_dimensions.extend([{"python_feature_server": True}]) configs = [] if offline_stores: @@ -277,17 +272,6 @@ def pytest_generate_tests(metafunc: pytest.Metafunc): **dim, } - # aws lambda works only with dynamo - if ( - config.get("python_feature_server") - and config.get("provider") == "aws" - and ( - not isinstance(online_store, dict) - or online_store["type"] != "dynamodb" - ) - ): - continue - c = IntegrationTestRepoConfig(**config) if c not in _config_cache: diff --git a/sdk/python/tests/integration/feature_repos/repo_configuration.py b/sdk/python/tests/integration/feature_repos/repo_configuration.py index 7123bd0fc15..9e3c02b9c01 100644 --- a/sdk/python/tests/integration/feature_repos/repo_configuration.py +++ b/sdk/python/tests/integration/feature_repos/repo_configuration.py @@ -474,27 +474,12 @@ def construct_test_environment( else: online_creator = None - if test_repo_config.python_feature_server and test_repo_config.provider == "aws": - from feast.infra.feature_servers.aws_lambda.config import ( - AwsLambdaFeatureServerConfig, - ) - - feature_server: Any = AwsLambdaFeatureServerConfig( - enabled=True, - execution_role_name=os.getenv( - "AWS_LAMBDA_ROLE", - "arn:aws:iam::402087665549:role/lambda_execution_role", - ), - ) - else: - feature_server = LocalFeatureServerConfig( - feature_logging=FeatureLoggingConfig(enabled=True) - ) + feature_server = LocalFeatureServerConfig( + feature_logging=FeatureLoggingConfig(enabled=True) + ) repo_dir_name = tempfile.mkdtemp() - if ( - test_repo_config.python_feature_server and test_repo_config.provider == "aws" - ) or test_repo_config.registry_location == RegistryLocation.S3: + if test_repo_config.registry_location == RegistryLocation.S3: aws_registry_path = os.getenv( "AWS_REGISTRY_PATH", "s3://feast-int-bucket/registries" ) diff --git a/setup.py b/setup.py index 9b3d0e55e62..f954f198988 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ "hiredis>=2.0.0,<3", ] -AWS_REQUIRED = ["boto3>=1.17.0,<2", "docker>=5.0.2", "fsspec<=2024.1.0", "aiobotocore>2,<3"] +AWS_REQUIRED = ["boto3>=1.17.0,<2", "fsspec<=2024.1.0", "aiobotocore>2,<3"] KUBERNETES_REQUIRED = ["kubernetes<=20.13.0"] From 8028ae0f39e706637bc2781850a3b7d8925a87f7 Mon Sep 17 00:00:00 2001 From: Meenakshi Sistla <85261163+msistla96@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:24:23 -0500 Subject: [PATCH 04/44] fix: Update Feast object metadata in the registry (#4257) --- sdk/python/feast/feature_view.py | 10 + .../feast/infra/registry/base_registry.py | 28 ++ sdk/python/feast/infra/registry/registry.py | 34 +- sdk/python/feast/infra/registry/remote.py | 1 + sdk/python/feast/infra/registry/sql.py | 18 + sdk/python/feast/registry_server.py | 5 +- .../registration/test_universal_registry.py | 324 +++++++++++++++++- .../test_local_feature_store.py | 14 + sdk/python/tests/unit/test_feature_views.py | 53 ++- .../tests/unit/test_stream_feature_view.py | 82 ++++- sdk/python/tests/utils/e2e_test_validation.py | 10 + 11 files changed, 570 insertions(+), 9 deletions(-) diff --git a/sdk/python/feast/feature_view.py b/sdk/python/feast/feature_view.py index ff41400eace..1a85a4b90c0 100644 --- a/sdk/python/feast/feature_view.py +++ b/sdk/python/feast/feature_view.py @@ -311,6 +311,16 @@ def with_join_key_map(self, join_key_map: Dict[str, str]): return cp + def update_materialization_intervals( + self, existing_materialization_intervals: List[Tuple[datetime, datetime]] + ): + if ( + len(existing_materialization_intervals) > 0 + and len(self.materialization_intervals) == 0 + ): + for interval in existing_materialization_intervals: + self.materialization_intervals.append((interval[0], interval[1])) + def to_proto(self) -> FeatureViewProto: """ Converts a feature view object to its protobuf representation. diff --git a/sdk/python/feast/infra/registry/base_registry.py b/sdk/python/feast/infra/registry/base_registry.py index bc08796e39d..03bec648306 100644 --- a/sdk/python/feast/infra/registry/base_registry.py +++ b/sdk/python/feast/infra/registry/base_registry.py @@ -29,7 +29,19 @@ from feast.infra.infra_object import Infra from feast.on_demand_feature_view import OnDemandFeatureView from feast.project_metadata import ProjectMetadata +from feast.protos.feast.core.Entity_pb2 import Entity as EntityProto +from feast.protos.feast.core.FeatureService_pb2 import ( + FeatureService as FeatureServiceProto, +) +from feast.protos.feast.core.FeatureView_pb2 import FeatureView as FeatureViewProto +from feast.protos.feast.core.OnDemandFeatureView_pb2 import ( + OnDemandFeatureView as OnDemandFeatureViewProto, +) from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto +from feast.protos.feast.core.SavedDataset_pb2 import SavedDataset as SavedDatasetProto +from feast.protos.feast.core.StreamFeatureView_pb2 import ( + StreamFeatureView as StreamFeatureViewProto, +) from feast.saved_dataset import SavedDataset, ValidationReference from feast.stream_feature_view import StreamFeatureView from feast.transformation.pandas_transformation import PandasTransformation @@ -705,3 +717,19 @@ def to_dict(self, project: str) -> Dict[str, List[Any]]: self._message_to_sorted_dict(infra_object.to_proto()) ) return registry_dict + + @staticmethod + def deserialize_registry_values(serialized_proto, feast_obj_type) -> Any: + if feast_obj_type == Entity: + return EntityProto.FromString(serialized_proto) + if feast_obj_type == SavedDataset: + return SavedDatasetProto.FromString(serialized_proto) + if feast_obj_type == FeatureView: + return FeatureViewProto.FromString(serialized_proto) + if feast_obj_type == StreamFeatureView: + return StreamFeatureViewProto.FromString(serialized_proto) + if feast_obj_type == OnDemandFeatureView: + return OnDemandFeatureViewProto.FromString(serialized_proto) + if feast_obj_type == FeatureService: + return FeatureServiceProto.FromString(serialized_proto) + return None diff --git a/sdk/python/feast/infra/registry/registry.py b/sdk/python/feast/infra/registry/registry.py index 39cdedb4906..4d6bff4cc7c 100644 --- a/sdk/python/feast/infra/registry/registry.py +++ b/sdk/python/feast/infra/registry/registry.py @@ -265,9 +265,13 @@ def apply_entity(self, entity: Entity, project: str, commit: bool = True): existing_entity_proto.spec.name == entity_proto.spec.name and existing_entity_proto.spec.project == project ): + entity.created_timestamp = ( + existing_entity_proto.meta.created_timestamp.ToDatetime() + ) + entity_proto = entity.to_proto() + entity_proto.spec.project = project del self.cached_registry_proto.entities[idx] break - self.cached_registry_proto.entities.append(entity_proto) if commit: self.commit() @@ -346,6 +350,11 @@ def apply_feature_service( == feature_service_proto.spec.name and existing_feature_service_proto.spec.project == project ): + feature_service.created_timestamp = ( + existing_feature_service_proto.meta.created_timestamp.ToDatetime() + ) + feature_service_proto = feature_service.to_proto() + feature_service_proto.spec.project = project del registry.feature_services[idx] registry.feature_services.append(feature_service_proto) if commit: @@ -421,6 +430,18 @@ def apply_feature_view( ): return else: + existing_feature_view = type(feature_view).from_proto( + existing_feature_view_proto + ) + feature_view.created_timestamp = ( + existing_feature_view.created_timestamp + ) + if isinstance(feature_view, (FeatureView, StreamFeatureView)): + feature_view.update_materialization_intervals( + existing_feature_view.materialization_intervals + ) + feature_view_proto = feature_view.to_proto() + feature_view_proto.spec.project = project del existing_feature_views_of_same_type[idx] break @@ -660,6 +681,17 @@ def apply_saved_dataset( existing_saved_dataset_proto.spec.name == saved_dataset_proto.spec.name and existing_saved_dataset_proto.spec.project == project ): + saved_dataset.created_timestamp = ( + existing_saved_dataset_proto.meta.created_timestamp.ToDatetime() + ) + saved_dataset.min_event_timestamp = ( + existing_saved_dataset_proto.meta.min_event_timestamp.ToDatetime() + ) + saved_dataset.max_event_timestamp = ( + existing_saved_dataset_proto.meta.max_event_timestamp.ToDatetime() + ) + saved_dataset_proto = saved_dataset.to_proto() + saved_dataset_proto.spec.project = project del self.cached_registry_proto.saved_datasets[idx] break diff --git a/sdk/python/feast/infra/registry/remote.py b/sdk/python/feast/infra/registry/remote.py index 0eddf03cf64..9fa6d8ebee5 100644 --- a/sdk/python/feast/infra/registry/remote.py +++ b/sdk/python/feast/infra/registry/remote.py @@ -296,6 +296,7 @@ def apply_materialization( start_date_timestamp.FromDatetime(start_date) end_date_timestamp.FromDatetime(end_date) + # TODO: for this to work for stream feature views, ApplyMaterializationRequest needs to be updated request = RegistryServer_pb2.ApplyMaterializationRequest( feature_view=feature_view.to_proto(), project=project, diff --git a/sdk/python/feast/infra/registry/sql.py b/sdk/python/feast/infra/registry/sql.py index d0af6872c1c..42bd19eb5f8 100644 --- a/sdk/python/feast/infra/registry/sql.py +++ b/sdk/python/feast/infra/registry/sql.py @@ -713,6 +713,24 @@ def _apply_object( obj.last_updated_timestamp = update_datetime if row: + if proto_field_name in [ + "entity_proto", + "saved_dataset_proto", + "feature_view_proto", + "feature_service_proto", + ]: + deserialized_proto = self.deserialize_registry_values( + row._mapping[proto_field_name], type(obj) + ) + obj.created_timestamp = ( + deserialized_proto.meta.created_timestamp.ToDatetime() + ) + if isinstance(obj, (FeatureView, StreamFeatureView)): + obj.update_materialization_intervals( + type(obj) + .from_proto(deserialized_proto) + .materialization_intervals + ) values = { proto_field_name: obj.to_proto().SerializeToString(), "last_updated_timestamp": update_time, diff --git a/sdk/python/feast/registry_server.py b/sdk/python/feast/registry_server.py index 1b6798b022c..4a96ba76a8e 100644 --- a/sdk/python/feast/registry_server.py +++ b/sdk/python/feast/registry_server.py @@ -3,6 +3,7 @@ import grpc from google.protobuf.empty_pb2 import Empty +from pytz import utc from feast import FeatureStore from feast.data_source import DataSource @@ -313,10 +314,10 @@ def ApplyMaterialization( feature_view=FeatureView.from_proto(request.feature_view), project=request.project, start_date=datetime.fromtimestamp( - request.start_date.seconds + request.start_date.nanos / 1e9 + request.start_date.seconds + request.start_date.nanos / 1e9, tz=utc ), end_date=datetime.fromtimestamp( - request.end_date.seconds + request.end_date.nanos / 1e9 + request.end_date.seconds + request.end_date.nanos / 1e9, tz=utc ), commit=request.commit, ) diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index cd741853cc5..24ba9fe42a5 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -14,7 +14,7 @@ import logging import os import time -from datetime import timedelta +from datetime import datetime, timedelta from tempfile import mkstemp from unittest import mock @@ -22,12 +22,13 @@ import pandas as pd import pytest from pytest_lazyfixture import lazy_fixture +from pytz import utc from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.minio import MinioContainer from testcontainers.mysql import MySqlContainer -from feast import FileSource, RequestSource +from feast import FeatureService, FileSource, RequestSource from feast.data_format import AvroFormat, ParquetFormat from feast.data_source import KafkaSource from feast.entity import Entity @@ -308,6 +309,22 @@ def test_apply_entity_success(test_registry): # After the first apply, the created_timestamp should be the same as the last_update_timestamp. assert entity.created_timestamp == entity.last_updated_timestamp + # Update entity + updated_entity = Entity( + name="driver_car_id", + description="Car driver Id", + tags={"team": "matchmaking"}, + ) + test_registry.apply_entity(updated_entity, project) + + updated_entity = test_registry.get_entity("driver_car_id", project) + + # The created_timestamp for the entity should be set to the created_timestamp value stored from the previous apply + assert ( + updated_entity.created_timestamp is not None + and updated_entity.created_timestamp == entity.created_timestamp + ) + test_registry.delete_entity("driver_car_id", project) assert_project_uuid(project, project_uuid, test_registry) entities = test_registry.list_entities(project) @@ -601,11 +618,54 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: data["odfv1_my_feature_2"] = feature_df["my_input_1"].astype("int32") return data + def simple_udf(x: int): + return x + 3 + + entity_sfv = Entity(name="sfv_my_entity_1", join_keys=["test_key"]) + + stream_source = KafkaSource( + name="kafka", + timestamp_field="event_timestamp", + kafka_bootstrap_servers="", + message_format=AvroFormat(""), + topic="topic", + batch_source=FileSource(path="some path"), + watermark_delay_threshold=timedelta(days=1), + ) + + sfv = StreamFeatureView( + name="test kafka stream feature view", + entities=[entity_sfv], + ttl=timedelta(days=30), + owner="test@example.com", + online=True, + schema=[Field(name="dummy_field", dtype=Float32)], + description="desc", + aggregations=[ + Aggregation( + column="dummy_field", + function="max", + time_window=timedelta(days=1), + ), + Aggregation( + column="dummy_field2", + function="count", + time_window=timedelta(days=24), + ), + ], + timestamp_field="event_timestamp", + mode="spark", + source=stream_source, + udf=simple_udf, + tags={}, + ) + project = "project" # Register Feature Views test_registry.apply_feature_view(odfv1, project) test_registry.apply_feature_view(fv1, project) + test_registry.apply_feature_view(sfv, project) # Modify odfv by changing a single feature dtype @on_demand_feature_view( @@ -621,6 +681,8 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: data["odfv1_my_feature_2"] = feature_df["my_input_1"].astype("int32") return data + existing_odfv = test_registry.get_on_demand_feature_view("odfv1", project) + # Apply the modified odfv test_registry.apply_feature_view(odfv1, project) @@ -655,6 +717,11 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: and list(request_schema.values())[0] == ValueType.INT32 ) + assert ( + feature_view.created_timestamp is not None + and feature_view.created_timestamp == existing_odfv.created_timestamp + ) + # Make sure fv1 is untouched feature_views = test_registry.list_feature_views(project, tags=fv1.tags) @@ -675,7 +742,162 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: and feature_view.entities[0] == "fs1_my_entity_1" ) - test_registry.teardown() + # Simulate materialization + current_date = datetime.utcnow() + end_date = current_date.replace(tzinfo=utc) + start_date = (current_date - timedelta(days=1)).replace(tzinfo=utc) + test_registry.apply_materialization(feature_view, project, start_date, end_date) + materialized_feature_view = test_registry.get_feature_view( + "my_feature_view_1", project + ) + + # Check if created_timestamp, along with materialized_intervals are updated + assert ( + materialized_feature_view.created_timestamp is not None + and materialized_feature_view.created_timestamp + == feature_view.created_timestamp + and len(materialized_feature_view.materialization_intervals) > 0 + and materialized_feature_view.materialization_intervals[0][0] == start_date + and materialized_feature_view.materialization_intervals[0][1] == end_date + ) + + # Modify fv1 by changing a single dtype + updated_fv1 = FeatureView( + name="my_feature_view_1", + schema=[ + Field(name="test", dtype=Int64), + Field(name="fs1_my_feature_1", dtype=String), + ], + entities=[entity], + tags={"team": "matchmaking"}, + source=batch_source, + ttl=timedelta(minutes=5), + ) + + # Check that these fields are empty before apply + assert updated_fv1.created_timestamp is None + assert len(updated_fv1.materialization_intervals) == 0 + + # Apply the modified fv1 + test_registry.apply_feature_view(updated_fv1, project) + + # Verify feature view after modification + updated_feature_views = test_registry.list_feature_views(project) + + # List Feature Views + assert ( + len(updated_feature_views) == 1 + and updated_feature_views[0].name == "my_feature_view_1" + and updated_feature_views[0].features[0].name == "fs1_my_feature_1" + and updated_feature_views[0].features[0].dtype == String + and updated_feature_views[0].entities[0] == "fs1_my_entity_1" + ) + + updated_feature_view = test_registry.get_feature_view("my_feature_view_1", project) + assert ( + updated_feature_view.name == "my_feature_view_1" + and updated_feature_view.features[0].name == "fs1_my_feature_1" + and updated_feature_view.features[0].dtype == String + and updated_feature_view.entities[0] == "fs1_my_entity_1" + ) + + # Check if materialization_intervals and created_timestamp values propagates on each apply + # materialization_intervals will populate only when it's empty + assert ( + updated_feature_view.created_timestamp is not None + and updated_feature_view.created_timestamp == feature_view.created_timestamp + and len(updated_feature_view.materialization_intervals) == 1 + and updated_feature_view.materialization_intervals[0][0] == start_date + and updated_feature_view.materialization_intervals[0][1] == end_date + ) + + # Simulate materialization a second time + current_date = datetime.utcnow() + end_date_1 = current_date.replace(tzinfo=utc) + start_date_1 = (current_date - timedelta(days=1)).replace(tzinfo=utc) + test_registry.apply_materialization( + updated_feature_view, project, start_date_1, end_date_1 + ) + materialized_feature_view_1 = test_registry.get_feature_view( + "my_feature_view_1", project + ) + + assert ( + materialized_feature_view_1.created_timestamp is not None + and materialized_feature_view_1.created_timestamp + == feature_view.created_timestamp + and len(materialized_feature_view_1.materialization_intervals) == 2 + and materialized_feature_view_1.materialization_intervals[0][0] == start_date + and materialized_feature_view_1.materialization_intervals[0][1] == end_date + and materialized_feature_view_1.materialization_intervals[1][0] == start_date_1 + and materialized_feature_view_1.materialization_intervals[1][1] == end_date_1 + ) + + # Modify sfv by changing the dtype + + sfv = StreamFeatureView( + name="test kafka stream feature view", + entities=[entity_sfv], + ttl=timedelta(days=30), + owner="test@example.com", + online=True, + schema=[Field(name="dummy_field", dtype=String)], + description="desc", + aggregations=[ + Aggregation( + column="dummy_field", + function="max", + time_window=timedelta(days=1), + ), + Aggregation( + column="dummy_field2", + function="count", + time_window=timedelta(days=24), + ), + ], + timestamp_field="event_timestamp", + mode="spark", + source=stream_source, + udf=simple_udf, + tags={}, + ) + + existing_sfv = test_registry.get_stream_feature_view( + "test kafka stream feature view", project + ) + # Apply the modified sfv + test_registry.apply_feature_view(sfv, project) + + # Verify feature view after modification + updated_stream_feature_views = test_registry.list_stream_feature_views(project) + + # List Feature Views + assert ( + len(updated_stream_feature_views) == 1 + and updated_stream_feature_views[0].name == "test kafka stream feature view" + and updated_stream_feature_views[0].features[0].name == "dummy_field" + and updated_stream_feature_views[0].features[0].dtype == String + and updated_stream_feature_views[0].entities[0] == "sfv_my_entity_1" + ) + + updated_sfv = test_registry.get_stream_feature_view( + "test kafka stream feature view", project + ) + assert ( + updated_sfv.name == "test kafka stream feature view" + and updated_sfv.features[0].name == "dummy_field" + and updated_sfv.features[0].dtype == String + and updated_sfv.entities[0] == "sfv_my_entity_1" + ) + + # The created_timestamp for the stream feature view should be set to the created_timestamp value stored from the + # previous apply + # Materialization_intervals is not set + assert ( + updated_sfv.created_timestamp is not None + and updated_sfv.created_timestamp == existing_sfv.created_timestamp + and len(updated_sfv.materialization_intervals) == 0 + ) @pytest.mark.integration @@ -825,7 +1047,7 @@ def simple_udf(x: int): project = "project" - # Register Feature View + # Register Stream Feature View test_registry.apply_feature_view(sfv, project) stream_feature_views = test_registry.list_stream_feature_views( @@ -843,6 +1065,100 @@ def simple_udf(x: int): test_registry.teardown() +@pytest.mark.integration +@pytest.mark.parametrize( + "test_registry", + all_fixtures, +) +def test_apply_feature_service_success(test_registry): + # Create Feature Service + file_source = FileSource(name="my_file_source", path="test.parquet") + feature_view = FeatureView( + name="my_feature_view", + entities=[], + schema=[ + Field(name="feature1", dtype=Float32), + Field(name="feature2", dtype=Float32), + ], + source=file_source, + ) + fs = FeatureService( + name="my_feature_service_1", features=[feature_view[["feature1", "feature2"]]] + ) + project = "project" + + # Register Feature Service + test_registry.apply_feature_service(fs, project) + + feature_services = test_registry.list_feature_services(project) + + # List Feature Services + assert len(feature_services) == 1 + assert feature_services[0] == fs + + # Delete Feature Service + test_registry.delete_feature_service("my_feature_service_1", project) + feature_services = test_registry.list_feature_services(project) + assert len(feature_services) == 0 + + test_registry.teardown() + + +@pytest.mark.integration +@pytest.mark.parametrize( + "test_registry", + all_fixtures, +) +def test_modify_feature_service_success(test_registry): + # Create Feature Service + file_source = FileSource(name="my_file_source", path="test.parquet") + feature_view = FeatureView( + name="my_feature_view", + entities=[], + schema=[ + Field(name="feature1", dtype=Float32), + Field(name="feature2", dtype=Float32), + ], + source=file_source, + ) + fs = FeatureService( + name="my_feature_service_1", features=[feature_view[["feature1", "feature2"]]] + ) + project = "project" + + # Register Feature service + test_registry.apply_feature_service(fs, project) + + feature_services = test_registry.list_feature_services(project) + + # List Feature Services + assert len(feature_services) == 1 + assert feature_services[0] == fs + + # Modify Feature Service by removing a feature + fs = FeatureService( + name="my_feature_service_1", features=[feature_view[["feature1"]]] + ) + + # Apply modified Feature Service + test_registry.apply_feature_service(fs, project) + + updated_feature_services = test_registry.list_feature_services(project) + + # Verify Feature Services + assert len(updated_feature_services) == 1 + assert updated_feature_services[0] == fs + # The created_timestamp for the feature service should be set to the created_timestamp value stored from the + # previous apply + assert ( + updated_feature_services[0].created_timestamp is not None + and updated_feature_services[0].created_timestamp + == feature_services[0].created_timestamp + ) + + test_registry.teardown() + + @pytest.mark.integration def test_commit(): fd, registry_path = mkstemp() diff --git a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py index 6b7856f347c..9b75d1a2c93 100644 --- a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py +++ b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py @@ -389,6 +389,20 @@ def test_reapply_feature_view(test_feature_store, dataframe_source): # Check Feature View fv_stored = test_feature_store.get_feature_view(fv1.name) + assert len(fv_stored.materialization_intervals) == 1 + + # Change and apply Feature View, this time, only the name + fv2 = FeatureView( + name="my_feature_view_2", + schema=[Field(name="int64_col", dtype=Int64)], + entities=[e], + source=file_source, + ttl=timedelta(minutes=5), + ) + test_feature_store.apply([fv2]) + + # Check Feature View + fv_stored = test_feature_store.get_feature_view(fv2.name) assert len(fv_stored.materialization_intervals) == 0 test_feature_store.teardown() diff --git a/sdk/python/tests/unit/test_feature_views.py b/sdk/python/tests/unit/test_feature_views.py index 0220d1a8a95..b387f55d8b0 100644 --- a/sdk/python/tests/unit/test_feature_views.py +++ b/sdk/python/tests/unit/test_feature_views.py @@ -1,8 +1,9 @@ -from datetime import timedelta +from datetime import datetime, timedelta import pytest from typeguard import TypeCheckError +from feast import utils from feast.batch_feature_view import BatchFeatureView from feast.data_format import AvroFormat from feast.data_source import KafkaSource @@ -117,3 +118,53 @@ def test_hash(): def test_field_types(): with pytest.raises(TypeCheckError): Field(name="name", dtype=ValueType.INT32) + + +def test_update_materialization_intervals(): + batch_source = FileSource(path="some path") + entity = Entity(name="entity_1", description="Some entity") + # Create a feature view that is already present in the SQL registry + stored_feature_view = FeatureView( + name="my-feature-view", + entities=[entity], + ttl=timedelta(days=1), + source=batch_source, + ) + + # Update the Feature View without modifying anything + updated_feature_view = FeatureView( + name="my-feature-view", + entities=[entity], + ttl=timedelta(days=1), + source=batch_source, + ) + updated_feature_view.update_materialization_intervals( + stored_feature_view.materialization_intervals + ) + assert len(updated_feature_view.materialization_intervals) == 0 + + current_time = datetime.utcnow() + start_date = utils.make_tzaware(current_time - timedelta(days=1)) + end_date = utils.make_tzaware(current_time) + updated_feature_view.materialization_intervals.append((start_date, end_date)) + + # Update the Feature View, i.e. simply update the name + second_updated_feature_view = FeatureView( + name="my-feature-view-1", + entities=[entity], + ttl=timedelta(days=1), + source=batch_source, + ) + + second_updated_feature_view.update_materialization_intervals( + updated_feature_view.materialization_intervals + ) + assert len(second_updated_feature_view.materialization_intervals) == 1 + assert ( + second_updated_feature_view.materialization_intervals[0][0] + == updated_feature_view.materialization_intervals[0][0] + ) + assert ( + second_updated_feature_view.materialization_intervals[0][1] + == updated_feature_view.materialization_intervals[0][1] + ) diff --git a/sdk/python/tests/unit/test_stream_feature_view.py b/sdk/python/tests/unit/test_stream_feature_view.py index b53f9a593ae..77431666c30 100644 --- a/sdk/python/tests/unit/test_stream_feature_view.py +++ b/sdk/python/tests/unit/test_stream_feature_view.py @@ -1,8 +1,9 @@ import copy -from datetime import timedelta +from datetime import datetime, timedelta import pytest +from feast import utils from feast.aggregation import Aggregation from feast.batch_feature_view import BatchFeatureView from feast.data_format import AvroFormat @@ -250,3 +251,82 @@ def test_stream_feature_view_copy(): aggregations=[], ) assert sfv == copy.copy(sfv) + + +def test_update_materialization_intervals(): + entity = Entity(name="driver_entity", join_keys=["test_key"]) + stream_source = KafkaSource( + name="kafka", + timestamp_field="event_timestamp", + kafka_bootstrap_servers="", + message_format=AvroFormat(""), + topic="topic", + batch_source=FileSource(path="some path"), + ) + + # Create a stream feature view that is already present in the SQL registry + stored_stream_feature_view = StreamFeatureView( + name="test kafka stream feature view", + entities=[entity], + ttl=timedelta(days=30), + owner="test@example.com", + online=True, + schema=[Field(name="dummy_field", dtype=Float32)], + description="desc", + aggregations=[ + Aggregation( + column="dummy_field", + function="max", + time_window=timedelta(days=1), + ) + ], + timestamp_field="event_timestamp", + mode="spark", + source=stream_source, + udf=simple_udf, + tags={}, + ) + current_time = datetime.utcnow() + start_date = utils.make_tzaware(current_time - timedelta(days=1)) + end_date = utils.make_tzaware(current_time) + stored_stream_feature_view.materialization_intervals.append((start_date, end_date)) + + # Update the stream feature view i.e. here it's simply the name + updated_stream_feature_view = StreamFeatureView( + name="test kafka stream feature view updated", + entities=[entity], + ttl=timedelta(days=30), + owner="test@example.com", + online=True, + schema=[Field(name="dummy_field", dtype=Float32)], + description="desc", + aggregations=[ + Aggregation( + column="dummy_field", + function="max", + time_window=timedelta(days=1), + ) + ], + timestamp_field="event_timestamp", + mode="spark", + source=stream_source, + udf=simple_udf, + tags={}, + ) + + updated_stream_feature_view.update_materialization_intervals( + stored_stream_feature_view.materialization_intervals + ) + + assert ( + updated_stream_feature_view.materialization_intervals is not None + and len(stored_stream_feature_view.materialization_intervals) == 1 + ) + assert ( + updated_stream_feature_view.materialization_intervals[0][0] + == stored_stream_feature_view.materialization_intervals[0][0] + ) + assert ( + updated_stream_feature_view.materialization_intervals[0][1] + == stored_stream_feature_view.materialization_intervals[0][1] + ) diff --git a/sdk/python/tests/utils/e2e_test_validation.py b/sdk/python/tests/utils/e2e_test_validation.py index 885798db109..d9104bae420 100644 --- a/sdk/python/tests/utils/e2e_test_validation.py +++ b/sdk/python/tests/utils/e2e_test_validation.py @@ -78,6 +78,16 @@ def validate_offline_online_store_consistency( # run materialize_incremental() fs.materialize_incremental(feature_views=[fv.name], end_date=now) + updated_fv = fs.registry.get_feature_view(fv.name, fs.project) + + # Check if materialization_intervals was updated by the registry + assert ( + len(updated_fv.materialization_intervals) == 2 + and updated_fv.materialization_intervals[0][0] == start_date + and updated_fv.materialization_intervals[0][1] == end_date + and updated_fv.materialization_intervals[1][0] == end_date + and updated_fv.materialization_intervals[1][1] == now.replace(tzinfo=utc) + ) # check result of materialize_incremental() _check_offline_and_online_features( From 6c75e84b036f84910dcbd7f1733ebd0d8839ab6c Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Thu, 20 Jun 2024 00:49:51 -0400 Subject: [PATCH 05/44] fix: Minor typo in the unit test. (#4296) Signed-off-by: Shuchu Han --- .../tests/unit/local_feast_tests/test_local_feature_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py index 9b75d1a2c93..0e834e314b1 100644 --- a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py +++ b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py @@ -134,7 +134,7 @@ def test_apply_feature_view(test_feature_store): tags_filter = utils.tags_str_to_dict("('team:matchmaking',)") assert tags_filter == tags_dict tags_filter = utils.tags_list_to_dict(("team:matchmaking", "test")) - assert tags_dict == tags_dict + assert tags_filter == tags_dict # List Feature Views feature_views = test_feature_store.list_batch_feature_views(tags=tags_filter) From de5b0eb8e4922f16b7a8f36ed6373490f8b2da8d Mon Sep 17 00:00:00 2001 From: Theodor Mihalache <84387487+tmihalac@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:27:09 -0400 Subject: [PATCH 06/44] refactor: Add parameters validation to OfflineServer (#4289) Add parameters validation to OfflineServer Signed-off-by: Theodor Mihalache --- sdk/python/feast/offline_server.py | 96 +++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/sdk/python/feast/offline_server.py b/sdk/python/feast/offline_server.py index 718da1b109f..be92620d682 100644 --- a/sdk/python/feast/offline_server.py +++ b/sdk/python/feast/offline_server.py @@ -74,14 +74,15 @@ def do_put( logger.debug(f"do_put: command is{command}, data is {data}") self.flights[key] = data - self._call_api(command, key) + self._call_api(command["api"], command, key) else: logger.warning(f"No 'api' field in command: {command}") - def _call_api(self, command: dict, key: str): + def _call_api(self, api: str, command: dict, key: str): + assert api is not None, "api can not be empty" + remove_data = False try: - api = command["api"] if api == OfflineServer.offline_write_batch.__name__: self.offline_write_batch(command, key) remove_data = True @@ -89,7 +90,7 @@ def _call_api(self, command: dict, key: str): self.write_logged_features(command, key) remove_data = True elif api == OfflineServer.persist.__name__: - self.persist(command["retrieve_func"], command, key) + self.persist(command, key) remove_data = True except Exception as e: remove_data = True @@ -150,6 +151,9 @@ def list_feature_views_by_name( for index, fv_name in enumerate(feature_view_names) ] + def _validate_do_get_parameters(self, command: dict): + assert "api" in command, "api parameter is mandatory" + # Extracts the API parameters from the flights dictionary, delegates the execution to the FeatureStore instance # and returns the stream of data def do_get(self, context: fl.ServerCallContext, ticket: fl.Ticket): @@ -159,6 +163,9 @@ def do_get(self, context: fl.ServerCallContext, ticket: fl.Ticket): return None command = json.loads(key[1]) + + self._validate_do_get_parameters(command) + api = command["api"] logger.debug(f"get command is {command}") logger.debug(f"requested api is {api}") @@ -180,13 +187,26 @@ def do_get(self, context: fl.ServerCallContext, ticket: fl.Ticket): del self.flights[key] return fl.RecordBatchStream(table) - def offline_write_batch(self, command: dict, key: str): + def _validate_offline_write_batch_parameters(self, command: dict): + assert ( + "feature_view_names" in command + ), "feature_view_names is a mandatory parameter" + assert "name_aliases" in command, "name_aliases is a mandatory parameter" + feature_view_names = command["feature_view_names"] assert ( len(feature_view_names) == 1 ), "feature_view_names list should only have one item" + name_aliases = command["name_aliases"] assert len(name_aliases) == 1, "name_aliases list should only have one item" + + def offline_write_batch(self, command: dict, key: str): + self._validate_offline_write_batch_parameters(command) + + feature_view_names = command["feature_view_names"] + name_aliases = command["name_aliases"] + project = self.store.config.project feature_views = self.list_feature_views_by_name( feature_view_names=feature_view_names, @@ -194,19 +214,25 @@ def offline_write_batch(self, command: dict, key: str): project=project, ) - assert len(feature_views) == 1 + assert len(feature_views) == 1, "incorrect feature view" table = self.flights[key] self.offline_store.offline_write_batch( self.store.config, feature_views[0], table, command["progress"] ) + def _validate_write_logged_features_parameters(self, command: dict): + assert "feature_service_name" in command + def write_logged_features(self, command: dict, key: str): + self._validate_write_logged_features_parameters(command) table = self.flights[key] feature_service = self.store.get_feature_service( command["feature_service_name"] ) - assert feature_service.logging_config is not None + assert ( + feature_service.logging_config is not None + ), "feature service must have logging_config set" self.offline_store.write_logged_features( config=self.store.config, @@ -218,7 +244,23 @@ def write_logged_features(self, command: dict, key: str): registry=self.store.registry, ) + def _validate_pull_all_from_table_or_query_parameters(self, command: dict): + assert ( + "data_source_name" in command + ), "data_source_name is a mandatory parameter" + assert ( + "join_key_columns" in command + ), "join_key_columns is a mandatory parameter" + assert ( + "feature_name_columns" in command + ), "feature_name_columns is a mandatory parameter" + assert "timestamp_field" in command, "timestamp_field is a mandatory parameter" + assert "start_date" in command, "start_date is a mandatory parameter" + assert "end_date" in command, "end_date is a mandatory parameter" + def pull_all_from_table_or_query(self, command: dict): + self._validate_pull_all_from_table_or_query_parameters(command) + return self.offline_store.pull_all_from_table_or_query( self.store.config, self.store.get_data_source(command["data_source_name"]), @@ -229,7 +271,23 @@ def pull_all_from_table_or_query(self, command: dict): utils.make_tzaware(datetime.fromisoformat(command["end_date"])), ) + def _validate_pull_latest_from_table_or_query_parameters(self, command: dict): + assert ( + "data_source_name" in command + ), "data_source_name is a mandatory parameter" + assert ( + "join_key_columns" in command + ), "join_key_columns is a mandatory parameter" + assert ( + "feature_name_columns" in command + ), "feature_name_columns is a mandatory parameter" + assert "timestamp_field" in command, "timestamp_field is a mandatory parameter" + assert "start_date" in command, "start_date is a mandatory parameter" + assert "end_date" in command, "end_date is a mandatory parameter" + def pull_latest_from_table_or_query(self, command: dict): + self._validate_pull_latest_from_table_or_query_parameters(command) + return self.offline_store.pull_latest_from_table_or_query( self.store.config, self.store.get_data_source(command["data_source_name"]), @@ -258,20 +316,33 @@ def list_actions(self, context): ), ] + def _validate_get_historical_features_parameters(self, command: dict, key: str): + assert key in self.flights, f"missing key={key}" + assert "feature_view_names" in command, "feature_view_names is mandatory" + assert "name_aliases" in command, "name_aliases is mandatory" + assert "feature_refs" in command, "feature_refs is mandatory" + assert "project" in command, "project is mandatory" + assert "full_feature_names" in command, "full_feature_names is mandatory" + def get_historical_features(self, command: dict, key: str): + self._validate_get_historical_features_parameters(command, key) + # Extract parameters from the internal flights dictionary entity_df_value = self.flights[key] entity_df = pa.Table.to_pandas(entity_df_value) + feature_view_names = command["feature_view_names"] name_aliases = command["name_aliases"] feature_refs = command["feature_refs"] project = command["project"] full_feature_names = command["full_feature_names"] + feature_views = self.list_feature_views_by_name( feature_view_names=feature_view_names, name_aliases=name_aliases, project=project, ) + retJob = self.offline_store.get_historical_features( config=self.store.config, feature_views=feature_views, @@ -281,10 +352,19 @@ def get_historical_features(self, command: dict, key: str): project=project, full_feature_names=full_feature_names, ) + return retJob - def persist(self, retrieve_func: str, command: dict, key: str): + def _validate_persist_parameters(self, command: dict): + assert "retrieve_func" in command, "retrieve_func is mandatory" + assert "data_source_name" in command, "data_source_name is mandatory" + assert "allow_overwrite" in command, "allow_overwrite is mandatory" + + def persist(self, command: dict, key: str): + self._validate_persist_parameters(command) + try: + retrieve_func = command["retrieve_func"] if retrieve_func == OfflineServer.get_historical_features.__name__: ret_job = self.get_historical_features(command, key) elif ( From 21deec8495a101442e78cabc9a30cb5fbee5382f Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Mon, 24 Jun 2024 21:08:06 -0400 Subject: [PATCH 07/44] fix: Deprecated the datetime.utcfromtimestamp(). (#4306) --- sdk/python/feast/infra/registry/snowflake.py | 4 ++-- sdk/python/feast/infra/registry/sql.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/python/feast/infra/registry/snowflake.py b/sdk/python/feast/infra/registry/snowflake.py index aaf6c4c48dc..d7ab67e7d0e 100644 --- a/sdk/python/feast/infra/registry/snowflake.py +++ b/sdk/python/feast/infra/registry/snowflake.py @@ -2,7 +2,7 @@ import os import uuid from binascii import hexlify -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from enum import Enum from threading import Lock from typing import Any, Callable, List, Literal, Optional, Set, Union @@ -994,7 +994,7 @@ def _get_last_updated_metadata(self, project: str): if df.empty: return None - return datetime.utcfromtimestamp(int(df.squeeze())) + return datetime.fromtimestamp(int(df.squeeze()), tz=timezone.utc) def _infer_fv_classes(self, feature_view): if isinstance(feature_view, StreamFeatureView): diff --git a/sdk/python/feast/infra/registry/sql.py b/sdk/python/feast/infra/registry/sql.py index 42bd19eb5f8..239898677c2 100644 --- a/sdk/python/feast/infra/registry/sql.py +++ b/sdk/python/feast/infra/registry/sql.py @@ -1,6 +1,6 @@ import logging import uuid -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Set, Union @@ -903,7 +903,7 @@ def _get_last_updated_metadata(self, project: str): return None update_time = int(row._mapping["last_updated_timestamp"]) - return datetime.utcfromtimestamp(update_time) + return datetime.fromtimestamp(update_time, tz=timezone.utc) def _get_all_projects(self) -> Set[str]: projects = set() From 86af60ad87d537b17e4ce6ec7a5eac0d637fb32d Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Tue, 25 Jun 2024 21:51:39 +0200 Subject: [PATCH 08/44] fix: Added missing type (#4315) Added missing type Signed-off-by: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> --- sdk/python/feast/diff/registry_diff.py | 2 ++ sdk/python/feast/feast_object.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/diff/registry_diff.py b/sdk/python/feast/diff/registry_diff.py index b608757496f..9236b087d4b 100644 --- a/sdk/python/feast/diff/registry_diff.py +++ b/sdk/python/feast/diff/registry_diff.py @@ -20,6 +20,7 @@ OnDemandFeatureView as OnDemandFeatureViewProto, ) from feast.protos.feast.core.OnDemandFeatureView_pb2 import OnDemandFeatureViewSpec +from feast.protos.feast.core.SavedDataset_pb2 import SavedDataset as SavedDatasetProto from feast.protos.feast.core.StreamFeatureView_pb2 import ( StreamFeatureView as StreamFeatureViewProto, ) @@ -109,6 +110,7 @@ def tag_objects_for_keep_delete_update_add( OnDemandFeatureViewProto, StreamFeatureViewProto, ValidationReferenceProto, + SavedDatasetProto, ) diff --git a/sdk/python/feast/feast_object.py b/sdk/python/feast/feast_object.py index 2d06d8d669d..d9505dcb9f0 100644 --- a/sdk/python/feast/feast_object.py +++ b/sdk/python/feast/feast_object.py @@ -11,11 +11,12 @@ from .protos.feast.core.FeatureService_pb2 import FeatureServiceSpec from .protos.feast.core.FeatureView_pb2 import FeatureViewSpec from .protos.feast.core.OnDemandFeatureView_pb2 import OnDemandFeatureViewSpec +from .protos.feast.core.SavedDataset_pb2 import SavedDatasetSpec from .protos.feast.core.StreamFeatureView_pb2 import StreamFeatureViewSpec from .protos.feast.core.ValidationProfile_pb2 import ( ValidationReference as ValidationReferenceProto, ) -from .saved_dataset import ValidationReference +from .saved_dataset import SavedDataset, ValidationReference from .stream_feature_view import StreamFeatureView # Convenience type representing all Feast objects @@ -28,6 +29,7 @@ FeatureService, DataSource, ValidationReference, + SavedDataset, ] FeastObjectSpecProto = Union[ @@ -38,4 +40,5 @@ FeatureServiceSpec, DataSourceProto, ValidationReferenceProto, + SavedDatasetSpec, ] From 372fd75394c36ab4d864758eedcfa94aafdb6a2e Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Thu, 27 Jun 2024 02:27:13 +0400 Subject: [PATCH 09/44] chore: Remove existing providers from the repo (#4298) remove providers Signed-off-by: tokoko --- sdk/python/feast/errors.py | 12 ---- sdk/python/feast/infra/aws.py | 9 --- .../feast/infra/contrib/azure_provider.py | 72 ------------------- sdk/python/feast/infra/gcp.py | 9 --- sdk/python/feast/infra/local.py | 23 ------ .../feast/infra/passthrough_provider.py | 13 ++++ sdk/python/feast/infra/provider.py | 8 +-- sdk/python/feast/repo_config.py | 66 ++--------------- .../offline_stores/test_offline_store.py | 4 +- .../infra/scaffolding/test_repo_config.py | 59 ++++----------- 10 files changed, 40 insertions(+), 235 deletions(-) delete mode 100644 sdk/python/feast/infra/aws.py delete mode 100644 sdk/python/feast/infra/contrib/azure_provider.py delete mode 100644 sdk/python/feast/infra/gcp.py delete mode 100644 sdk/python/feast/infra/local.py diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py index 22de402f20a..6083b3d5540 100644 --- a/sdk/python/feast/errors.py +++ b/sdk/python/feast/errors.py @@ -119,23 +119,11 @@ def __init__(self, provider_name): super().__init__(f"Provider '{provider_name}' is not implemented") -class FeastProviderNotSetError(Exception): - def __init__(self): - super().__init__("Provider is not set, but is required") - - class FeastRegistryNotSetError(Exception): def __init__(self): super().__init__("Registry is not set, but is required") -class FeastFeatureServerTypeSetError(Exception): - def __init__(self, feature_server_type: str): - super().__init__( - f"Feature server type was set to {feature_server_type}, but the type should be determined by the provider" - ) - - class FeastFeatureServerTypeInvalidError(Exception): def __init__(self, feature_server_type: str): super().__init__( diff --git a/sdk/python/feast/infra/aws.py b/sdk/python/feast/infra/aws.py deleted file mode 100644 index 47fec9b05bd..00000000000 --- a/sdk/python/feast/infra/aws.py +++ /dev/null @@ -1,9 +0,0 @@ -from feast.infra.passthrough_provider import PassthroughProvider - - -class AwsProvider(PassthroughProvider): - """ - This class only exists for backwards compatibility. - """ - - pass diff --git a/sdk/python/feast/infra/contrib/azure_provider.py b/sdk/python/feast/infra/contrib/azure_provider.py deleted file mode 100644 index ac56a2b33e2..00000000000 --- a/sdk/python/feast/infra/contrib/azure_provider.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -from datetime import datetime -from typing import Callable - -from tqdm import tqdm - -from feast.feature_view import FeatureView -from feast.infra.passthrough_provider import PassthroughProvider -from feast.infra.registry.base_registry import BaseRegistry -from feast.repo_config import RepoConfig -from feast.utils import ( - _convert_arrow_to_proto, - _get_column_names, - _run_pyarrow_field_mapping, -) - -DEFAULT_BATCH_SIZE = 10_000 - - -class AzureProvider(PassthroughProvider): - def materialize_single_feature_view( - self, - config: RepoConfig, - feature_view: FeatureView, - start_date: datetime, - end_date: datetime, - registry: BaseRegistry, - project: str, - tqdm_builder: Callable[[int], tqdm], - ) -> None: - # TODO(kevjumba): untested - entities = [] - for entity_name in feature_view.entities: - entities.append(registry.get_entity(entity_name, project)) - - ( - join_key_columns, - feature_name_columns, - event_timestamp_column, - created_timestamp_column, - ) = _get_column_names(feature_view, entities) - - offline_job = self.offline_store.pull_latest_from_table_or_query( - config=config, - data_source=feature_view.batch_source, - join_key_columns=join_key_columns, - feature_name_columns=feature_name_columns, - timestamp_field=event_timestamp_column, - created_timestamp_column=created_timestamp_column, - start_date=start_date, - end_date=end_date, - ) - - table = offline_job.to_arrow() - - if feature_view.batch_source.field_mapping is not None: - table = _run_pyarrow_field_mapping( - table, feature_view.batch_source.field_mapping - ) - - join_keys = {entity.join_key: entity.value_type for entity in entities} - - with tqdm_builder(table.num_rows) as pbar: - for batch in table.to_batches(DEFAULT_BATCH_SIZE): - rows_to_write = _convert_arrow_to_proto(batch, feature_view, join_keys) - self.online_write_batch( - self.repo_config, - feature_view, - rows_to_write, - lambda x: pbar.update(x), - ) diff --git a/sdk/python/feast/infra/gcp.py b/sdk/python/feast/infra/gcp.py deleted file mode 100644 index 512378237a6..00000000000 --- a/sdk/python/feast/infra/gcp.py +++ /dev/null @@ -1,9 +0,0 @@ -from feast.infra.passthrough_provider import PassthroughProvider - - -class GcpProvider(PassthroughProvider): - """ - This class only exists for backwards compatibility. - """ - - pass diff --git a/sdk/python/feast/infra/local.py b/sdk/python/feast/infra/local.py deleted file mode 100644 index 1226ceaf375..00000000000 --- a/sdk/python/feast/infra/local.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import List - -from feast.infra.infra_object import Infra, InfraObject -from feast.infra.passthrough_provider import PassthroughProvider -from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto -from feast.repo_config import RepoConfig - - -class LocalProvider(PassthroughProvider): - """ - This class only exists for backwards compatibility. - """ - - def plan_infra( - self, config: RepoConfig, desired_registry_proto: RegistryProto - ) -> Infra: - infra = Infra() - if self.online_store: - infra_objects: List[InfraObject] = self.online_store.plan( - config, desired_registry_proto - ) - infra.infra_objects += infra_objects - return infra diff --git a/sdk/python/feast/infra/passthrough_provider.py b/sdk/python/feast/infra/passthrough_provider.py index e707f9495db..bad6f86cc65 100644 --- a/sdk/python/feast/infra/passthrough_provider.py +++ b/sdk/python/feast/infra/passthrough_provider.py @@ -12,6 +12,7 @@ from feast.feature_logging import FeatureServiceLoggingSource from feast.feature_service import FeatureService from feast.feature_view import FeatureView +from feast.infra.infra_object import Infra, InfraObject from feast.infra.materialization.batch_materialization_engine import ( BatchMaterializationEngine, MaterializationJobStatus, @@ -22,6 +23,7 @@ from feast.infra.online_stores.helpers import get_online_store_from_config from feast.infra.provider import Provider from feast.infra.registry.base_registry import BaseRegistry +from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto 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 BATCH_ENGINE_CLASS_FOR_TYPE, RepoConfig @@ -103,6 +105,17 @@ def batch_engine(self) -> BatchMaterializationEngine: self._batch_engine = _batch_engine return _batch_engine + def plan_infra( + self, config: RepoConfig, desired_registry_proto: RegistryProto + ) -> Infra: + infra = Infra() + if self.online_store: + infra_objects: List[InfraObject] = self.online_store.plan( + config, desired_registry_proto + ) + infra.infra_objects += infra_objects + return infra + def update_infra( self, project: str, diff --git a/sdk/python/feast/infra/provider.py b/sdk/python/feast/infra/provider.py index 93077f40b97..75afd6bba86 100644 --- a/sdk/python/feast/infra/provider.py +++ b/sdk/python/feast/infra/provider.py @@ -22,10 +22,10 @@ from feast.saved_dataset import SavedDataset PROVIDERS_CLASS_FOR_TYPE = { - "gcp": "feast.infra.gcp.GcpProvider", - "aws": "feast.infra.aws.AwsProvider", - "local": "feast.infra.local.LocalProvider", - "azure": "feast.infra.contrib.azure_provider.AzureProvider", + "gcp": "feast.infra.passthrough_provider.PassthroughProvider", + "aws": "feast.infra.passthrough_provider.PassthroughProvider", + "local": "feast.infra.passthrough_provider.PassthroughProvider", + "azure": "feast.infra.passthrough_provider.PassthroughProvider", } diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 1c8041b4ddf..99b90a09a52 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -18,10 +18,8 @@ from feast.errors import ( FeastFeatureServerTypeInvalidError, - FeastFeatureServerTypeSetError, FeastOfflineStoreInvalidName, FeastOnlineStoreInvalidName, - FeastProviderNotSetError, FeastRegistryNotSetError, FeastRegistryTypeInvalidError, ) @@ -85,10 +83,6 @@ "local": "feast.infra.feature_servers.local_process.config.LocalFeatureServerConfig", } -FEATURE_SERVER_TYPE_FOR_PROVIDER = { - "local": "local", -} - class FeastBaseModel(BaseModel): """Feast Pydantic Configuration Class""" @@ -138,7 +132,7 @@ class RepoConfig(FeastBaseModel): provider account, as long as they have different project ids. """ - provider: StrictStr + provider: StrictStr = "local" """ str: local or gcp or aws """ registry_config: Any = Field(alias="registry", default="data/registry.db") @@ -191,30 +185,10 @@ def __init__(self, **data: Any): self.registry_config = data["registry"] self._offline_store = None - if "offline_store" in data: - self.offline_config = data["offline_store"] - else: - if data["provider"] == "local": - self.offline_config = "file" - elif data["provider"] == "gcp": - self.offline_config = "bigquery" - elif data["provider"] == "aws": - self.offline_config = "redshift" - elif data["provider"] == "azure": - self.offline_config = "mssql" + self.offline_config = data.get("offline_store", "file") self._online_store = None - if "online_store" in data: - self.online_config = data["online_store"] - else: - if data["provider"] == "local": - self.online_config = "sqlite" - elif data["provider"] == "gcp": - self.online_config = "datastore" - elif data["provider"] == "aws": - self.online_config = "dynamodb" - elif data["provider"] == "rockset": - self.online_config = "rockset" + self.online_config = data.get("online_store", "sqlite") self._batch_engine = None if "batch_engine" in data: @@ -325,20 +299,11 @@ def _validate_online_store_config(cls, values: Any) -> Any: values["online_store"] = None return values - # Make sure that the provider configuration is set. We need it to set the defaults - if "provider" not in values: - raise FeastProviderNotSetError() - # Set the default type # This is only direct reference to a provider or online store that we should have # for backwards compatibility. if "type" not in values["online_store"]: - if values["provider"] == "local": - values["online_store"]["type"] = "sqlite" - elif values["provider"] == "gcp": - values["online_store"]["type"] = "datastore" - elif values["provider"] == "aws": - values["online_store"]["type"] = "dynamodb" + values["online_store"]["type"] = "sqlite" online_store_type = values["online_store"]["type"] @@ -361,20 +326,9 @@ def _validate_offline_store_config(cls, values: Any) -> Any: if not isinstance(values["offline_store"], Dict): return values - # Make sure that the provider configuration is set. We need it to set the defaults - if "provider" not in values: - raise FeastProviderNotSetError() - # Set the default type if "type" not in values["offline_store"]: - if values["provider"] == "local": - values["offline_store"]["type"] = "file" - elif values["provider"] == "gcp": - values["offline_store"]["type"] = "bigquery" - elif values["provider"] == "aws": - values["offline_store"]["type"] = "redshift" - if values["provider"] == "azure": - values["offline_store"]["type"] = "mssql" + values["offline_store"]["type"] = "file" offline_store_type = values["offline_store"]["type"] @@ -398,15 +352,7 @@ def _validate_feature_server_config(cls, values: Any) -> Any: if not isinstance(values["feature_server"], Dict): return values - # Make sure that the provider configuration is set. We need it to set the defaults - if "provider" not in values: - raise FeastProviderNotSetError() - - default_type = FEATURE_SERVER_TYPE_FOR_PROVIDER.get(values["provider"]) - defined_type = values["feature_server"].get("type", default_type) - # Make sure that the type is either not set, or set correctly, since it's defined by the provider - if defined_type not in (default_type, "local"): - raise FeastFeatureServerTypeSetError(defined_type) + defined_type = values["feature_server"].get("type", "local") values["feature_server"]["type"] = defined_type # Validate the dict to ensure one of the union types match diff --git a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py index fd50d376322..3589c8a3fad 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py @@ -124,7 +124,7 @@ def retrieval_job(request, environment): iam_role="arn:aws:iam::585132637328:role/service-role/AmazonRedshift-CommandsAccessRole-20240403T092631", workgroup="", ) - config = environment.config.copy( + config = environment.config.model_copy( update={"offline_config": offline_store_config} ) return RedshiftRetrievalJob( @@ -147,7 +147,7 @@ def retrieval_job(request, environment): storage_integration_name="FEAST_S3", blob_export_location="s3://feast-snowflake-offload/export", ) - config = environment.config.copy( + config = environment.config.model_copy( update={"offline_config": offline_store_config} ) environment.project = "project" diff --git a/sdk/python/tests/unit/infra/scaffolding/test_repo_config.py b/sdk/python/tests/unit/infra/scaffolding/test_repo_config.py index e1839fbd8b4..98d82ce3575 100644 --- a/sdk/python/tests/unit/infra/scaffolding/test_repo_config.py +++ b/sdk/python/tests/unit/infra/scaffolding/test_repo_config.py @@ -33,36 +33,6 @@ def _test_config(config_text, expect_error: Optional[str]): return rc -def test_nullable_online_store_aws(): - _test_config( - dedent( - """ - project: foo - registry: "registry.db" - provider: aws - online_store: null - entity_key_serialization_version: 2 - """ - ), - expect_error="4 validation errors for RepoConfig\nregion\n Field required", - ) - - -def test_nullable_online_store_gcp(): - _test_config( - dedent( - """ - project: foo - registry: "registry.db" - provider: gcp - online_store: null - entity_key_serialization_version: 2 - """ - ), - expect_error=None, - ) - - def test_nullable_online_store_local(): _test_config( dedent( @@ -125,20 +95,6 @@ def test_local_config_with_full_online_class_directly(): assert isinstance(c.online_store, SqliteOnlineStoreConfig) -def test_gcp_config(): - _test_config( - dedent( - """ - project: foo - registry: gs://registry.db - provider: gcp - entity_key_serialization_version: 2 - """ - ), - expect_error=None, - ) - - def test_extra_field(): _test_config( dedent( @@ -224,3 +180,18 @@ def test_invalid_project_name(): ), expect_error="alphanumerical values ", ) + + +def test_no_provider(): + _test_config( + dedent( + """ + project: foo + registry: "registry.db" + online_store: + path: "blah" + entity_key_serialization_version: 2 + """ + ), + expect_error=None, + ) From 2c3894693e9079b8ad7873b139b30440c919e913 Mon Sep 17 00:00:00 2001 From: okramarenko <97118627+okramarenko@users.noreply.github.com> Date: Thu, 27 Jun 2024 01:27:33 +0300 Subject: [PATCH 10/44] feat: Add SingleStore as an OnlineStore (#4285) Add SingleStore as an OnlineStore Signed-off-by: Olha Kramarenko --- Makefile | 11 + docs/SUMMARY.md | 1 + docs/reference/online-stores/README.md | 3 + docs/reference/online-stores/singlestore.md | 51 ++++ .../feast.infra.online_stores.contrib.rst | 8 + .../feast.infra.registry.contrib.postgres.rst | 21 ++ .../source/feast.infra.registry.contrib.rst | 1 + .../singlestore_online_store/singlestore.py | 235 ++++++++++++++++++ .../contrib/singlestore_repo_configuration.py | 10 + sdk/python/feast/repo_config.py | 1 + .../requirements/py3.10-ci-requirements.txt | 17 +- .../requirements/py3.11-ci-requirements.txt | 16 +- .../requirements/py3.9-ci-requirements.txt | 17 +- .../universal/online_store/singlestore.py | 43 ++++ setup.py | 4 + 15 files changed, 433 insertions(+), 6 deletions(-) create mode 100644 docs/reference/online-stores/singlestore.md create mode 100644 sdk/python/docs/source/feast.infra.registry.contrib.postgres.rst create mode 100644 sdk/python/feast/infra/online_stores/contrib/singlestore_online_store/singlestore.py create mode 100644 sdk/python/feast/infra/online_stores/contrib/singlestore_repo_configuration.py create mode 100644 sdk/python/tests/integration/feature_repos/universal/online_store/singlestore.py diff --git a/Makefile b/Makefile index 2ad693c7a12..39406cc17da 100644 --- a/Makefile +++ b/Makefile @@ -331,6 +331,17 @@ test-python-universal-cassandra-no-cloud-providers: not test_snowflake" \ sdk/python/tests +test-python-universal-singlestore-online: + PYTHONPATH='.' \ + FULL_REPO_CONFIGS_MODULE=sdk.python.feast.infra.online_stores.contrib.singlestore_repo_configuration \ + PYTEST_PLUGINS=sdk.python.tests.integration.feature_repos.universal.online_store.singlestore \ + python -m pytest -n 8 --integration \ + -k "not test_universal_cli and \ + not gcs_registry and \ + not s3_registry and \ + not test_snowflake" \ + sdk/python/tests + test-python-universal: python -m pytest -n 8 --integration sdk/python/tests diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3f0506cf1ee..a40c60d97c0 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -103,6 +103,7 @@ * [Rockset (contrib)](reference/online-stores/rockset.md) * [Hazelcast (contrib)](reference/online-stores/hazelcast.md) * [ScyllaDB (contrib)](reference/online-stores/scylladb.md) + * [SingleStore (contrib)](reference/online-stores/singlestore.md) * [Providers](reference/providers/README.md) * [Local](reference/providers/local.md) * [Google Cloud Platform](reference/providers/google-cloud-platform.md) diff --git a/docs/reference/online-stores/README.md b/docs/reference/online-stores/README.md index b5f4eb8de89..0acf6701f92 100644 --- a/docs/reference/online-stores/README.md +++ b/docs/reference/online-stores/README.md @@ -64,4 +64,7 @@ Please see [Online Store](../../getting-started/architecture-and-components/onli {% content-ref url="remote.md" %} [remote.md](remote.md) + +{% content-ref url="singlestore.md" %} +[singlestore.md](singlestore.md) {% endcontent-ref %} diff --git a/docs/reference/online-stores/singlestore.md b/docs/reference/online-stores/singlestore.md new file mode 100644 index 00000000000..1777787f227 --- /dev/null +++ b/docs/reference/online-stores/singlestore.md @@ -0,0 +1,51 @@ +# SingleStore online store (contrib) + +## Description + +The SingleStore online store provides support for materializing feature values into a SingleStore database for serving online features. + +## Getting started +In order to use this online store, you'll need to run `pip install 'feast[singlestore]'`. You can get started by then running `feast init` and then setting the `feature_store.yaml` as described below. + +## Example + +{% code title="feature_store.yaml" %} +```yaml +project: my_feature_repo +registry: data/registry.db +provider: local +online_store: + type: singlestore + host: DB_HOST + port: DB_PORT + database: DB_NAME + user: DB_USERNAME + password: DB_PASSWORD +``` +{% endcode %} + +## Functionality Matrix + +The set of functionality supported by online stores is described in detail [here](overview.md#functionality). +Below is a matrix indicating which functionality is supported by the SingleStore online store. + +| | SingleStore | +| :-------------------------------------------------------- | :----------- | +| write feature values to the online store | yes | +| read feature values from the online store | yes | +| update infrastructure (e.g. tables) in the online store | yes | +| teardown infrastructure (e.g. tables) in the online store | yes | +| generate a plan of infrastructure changes | no | +| support for on-demand transforms | yes | +| readable by Python SDK | yes | +| readable by Java | no | +| readable by Go | no | +| support for entityless feature views | yes | +| support for concurrent writing to the same key | no | +| support for ttl (time to live) at retrieval | no | +| support for deleting expired data | no | +| collocated by feature view | yes | +| collocated by feature service | no | +| collocated by entity key | no | + +To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/sdk/python/docs/source/feast.infra.online_stores.contrib.rst b/sdk/python/docs/source/feast.infra.online_stores.contrib.rst index d614438e3d5..9d301fcd0de 100644 --- a/sdk/python/docs/source/feast.infra.online_stores.contrib.rst +++ b/sdk/python/docs/source/feast.infra.online_stores.contrib.rst @@ -89,6 +89,14 @@ feast.infra.online\_stores.contrib.postgres\_repo\_configuration module :undoc-members: :show-inheritance: +feast.infra.online\_stores.contrib.singlestore\_repo\_configuration module +-------------------------------------------------------------------------- + +.. automodule:: feast.infra.online_stores.contrib.singlestore_repo_configuration + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/sdk/python/docs/source/feast.infra.registry.contrib.postgres.rst b/sdk/python/docs/source/feast.infra.registry.contrib.postgres.rst new file mode 100644 index 00000000000..3f319908057 --- /dev/null +++ b/sdk/python/docs/source/feast.infra.registry.contrib.postgres.rst @@ -0,0 +1,21 @@ +feast.infra.registry.contrib.postgres package +============================================= + +Submodules +---------- + +feast.infra.registry.contrib.postgres.postgres\_registry\_store module +---------------------------------------------------------------------- + +.. automodule:: feast.infra.registry.contrib.postgres.postgres_registry_store + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: feast.infra.registry.contrib.postgres + :members: + :undoc-members: + :show-inheritance: diff --git a/sdk/python/docs/source/feast.infra.registry.contrib.rst b/sdk/python/docs/source/feast.infra.registry.contrib.rst index 83417109b86..44b89736adb 100644 --- a/sdk/python/docs/source/feast.infra.registry.contrib.rst +++ b/sdk/python/docs/source/feast.infra.registry.contrib.rst @@ -8,6 +8,7 @@ Subpackages :maxdepth: 4 feast.infra.registry.contrib.azure + feast.infra.registry.contrib.postgres Module contents --------------- diff --git a/sdk/python/feast/infra/online_stores/contrib/singlestore_online_store/singlestore.py b/sdk/python/feast/infra/online_stores/contrib/singlestore_online_store/singlestore.py new file mode 100644 index 00000000000..e17a059c1a8 --- /dev/null +++ b/sdk/python/feast/infra/online_stores/contrib/singlestore_online_store/singlestore.py @@ -0,0 +1,235 @@ +from __future__ import absolute_import + +from collections import defaultdict +from datetime import datetime +from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple + +import pytz +import singlestoredb +from pydantic import StrictStr +from singlestoredb.connection import Connection, Cursor +from singlestoredb.exceptions import InterfaceError + +from feast import Entity, FeatureView, RepoConfig +from feast.infra.key_encoding_utils import serialize_entity_key +from feast.infra.online_stores.online_store import OnlineStore +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 + + +class SingleStoreOnlineStoreConfig(FeastConfigBaseModel): + """ + Configuration for the SingleStore online store. + NOTE: The class *must* end with the `OnlineStoreConfig` suffix. + """ + + type: Literal["singlestore"] = "singlestore" + + host: Optional[StrictStr] = None + user: Optional[StrictStr] = None + password: Optional[StrictStr] = None + database: Optional[StrictStr] = None + port: Optional[int] = None + + +class SingleStoreOnlineStore(OnlineStore): + """ + An online store implementation that uses SingleStore. + NOTE: The class *must* end with the `OnlineStore` suffix. + """ + + _conn: Optional[Connection] = None + + def _init_conn(self, config: RepoConfig) -> Connection: + online_store_config = config.online_store + assert isinstance(online_store_config, SingleStoreOnlineStoreConfig) + return singlestoredb.connect( + host=online_store_config.host or "127.0.0.1", + user=online_store_config.user or "test", + password=online_store_config.password or "test", + database=online_store_config.database or "feast", + port=online_store_config.port or 3306, + autocommit=True, + ) + + def _get_cursor(self, config: RepoConfig) -> Any: + # This will try to reconnect also. + # In case it fails, we will have to create a new connection. + if not self._conn: + self._conn = self._init_conn(config) + try: + self._conn.ping(reconnect=True) + except InterfaceError: + self._conn = self._init_conn(config) + return self._conn.cursor() + + def online_write_batch( + self, + config: RepoConfig, + table: FeatureView, + data: List[ + Tuple[EntityKeyProto, Dict[str, ValueProto], datetime, Optional[datetime]] + ], + progress: Optional[Callable[[int], Any]], + ) -> None: + project = config.project + with self._get_cursor(config) as cur: + insert_values = [] + for entity_key, values, timestamp, created_ts in data: + entity_key_bin = serialize_entity_key( + entity_key, + entity_key_serialization_version=2, + ).hex() + timestamp = _to_naive_utc(timestamp) + if created_ts is not None: + created_ts = _to_naive_utc(created_ts) + + for feature_name, val in values.items(): + insert_values.append( + ( + entity_key_bin, + feature_name, + val.SerializeToString(), + timestamp, + created_ts, + ) + ) + # Control the batch so that we can update the progress + batch_size = 50000 + for i in range(0, len(insert_values), batch_size): + current_batch = insert_values[i : i + batch_size] + cur.executemany( + f""" + INSERT INTO {_table_id(project, table)} + (entity_key, feature_name, value, event_ts, created_ts) + values (%s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + value = VALUES(value), + event_ts = VALUES(event_ts), + created_ts = VALUES(created_ts); + """, + current_batch, + ) + if progress: + progress(len(current_batch)) + + def online_read( + self, + config: RepoConfig, + table: FeatureView, + entity_keys: List[EntityKeyProto], + requested_features: Optional[List[str]] = None, + ) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]: + project = config.project + result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = [] + with self._get_cursor(config) as cur: + keys = [] + for entity_key in entity_keys: + keys.append( + serialize_entity_key( + entity_key, + entity_key_serialization_version=2, + ).hex() + ) + + if not requested_features: + entity_key_placeholders = ",".join(["%s" for _ in keys]) + cur.execute( + f""" + SELECT entity_key, feature_name, value, event_ts FROM {_table_id(project, table)} + WHERE entity_key IN ({entity_key_placeholders}) + ORDER BY event_ts; + """, + tuple(keys), + ) + else: + entity_key_placeholders = ",".join(["%s" for _ in keys]) + requested_features_placeholders = ",".join( + ["%s" for _ in requested_features] + ) + cur.execute( + f""" + SELECT entity_key, feature_name, value, event_ts FROM {_table_id(project, table)} + WHERE entity_key IN ({entity_key_placeholders}) and feature_name IN ({requested_features_placeholders}) + ORDER BY event_ts; + """, + tuple(keys + requested_features), + ) + rows = cur.fetchall() or [] + + # Since we don't know the order returned from MySQL we'll need + # to construct a dict to be able to quickly look up the correct row + # when we iterate through the keys since they are in the correct order + values_dict = defaultdict(list) + for row in rows: + values_dict[row[0]].append(row[1:]) + + for key in keys: + if key in values_dict: + key_values = values_dict[key] + res = {} + res_ts: Optional[datetime] = None + for feature_name, value_bin, event_ts in key_values: + val = ValueProto() + val.ParseFromString(bytes(value_bin)) + res[feature_name] = val + res_ts = event_ts + result.append((res_ts, res)) + else: + result.append((None, None)) + return result + + def update( + self, + config: RepoConfig, + tables_to_delete: Sequence[FeatureView], + tables_to_keep: Sequence[FeatureView], + entities_to_delete: Sequence[Entity], + entities_to_keep: Sequence[Entity], + partial: bool, + ) -> None: + project = config.project + with self._get_cursor(config) as cur: + # We don't create any special state for the entities in this implementation. + for table in tables_to_keep: + cur.execute( + f"""CREATE TABLE IF NOT EXISTS {_table_id(project, table)} (entity_key VARCHAR(512), + feature_name VARCHAR(256), + value BLOB, + event_ts timestamp NULL DEFAULT NULL, + created_ts timestamp NULL DEFAULT NULL, + PRIMARY KEY(entity_key, feature_name), + INDEX {_table_id(project, table)}_ek (entity_key))""" + ) + + for table in tables_to_delete: + _drop_table_and_index(cur, project, table) + + def teardown( + self, + config: RepoConfig, + tables: Sequence[FeatureView], + entities: Sequence[Entity], + ) -> None: + project = config.project + with self._get_cursor(config) as cur: + for table in tables: + _drop_table_and_index(cur, project, table) + + +def _drop_table_and_index(cur: Cursor, project: str, table: FeatureView) -> None: + table_name = _table_id(project, table) + cur.execute(f"DROP INDEX {table_name}_ek ON {table_name};") + cur.execute(f"DROP TABLE IF EXISTS {table_name}") + + +def _table_id(project: str, table: FeatureView) -> str: + return f"{project}_{table.name}" + + +def _to_naive_utc(ts: datetime) -> datetime: + if ts.tzinfo is None: + return ts + else: + return ts.astimezone(pytz.utc).replace(tzinfo=None) diff --git a/sdk/python/feast/infra/online_stores/contrib/singlestore_repo_configuration.py b/sdk/python/feast/infra/online_stores/contrib/singlestore_repo_configuration.py new file mode 100644 index 00000000000..2debe0f0ee1 --- /dev/null +++ b/sdk/python/feast/infra/online_stores/contrib/singlestore_repo_configuration.py @@ -0,0 +1,10 @@ +from tests.integration.feature_repos.integration_test_repo_config import ( + IntegrationTestRepoConfig, +) +from tests.integration.feature_repos.universal.online_store.singlestore import ( + SingleStoreOnlineStoreCreator, +) + +FULL_REPO_CONFIGS = [ + IntegrationTestRepoConfig(online_store_creator=SingleStoreOnlineStoreCreator), +] diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 99b90a09a52..f3c379020d2 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -63,6 +63,7 @@ "ikv": "feast.infra.online_stores.contrib.ikv_online_store.ikv.IKVOnlineStore", "elasticsearch": "feast.infra.online_stores.contrib.elasticsearch.ElasticSearchOnlineStore", "remote": "feast.infra.online_stores.remote.RemoteOnlineStore", + "singlestore": "feast.infra.online_stores.contrib.singlestore_online_store.singlestore.SingleStoreOnlineStore", } OFFLINE_STORE_CLASS_FOR_TYPE = { diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 97bdfc159ba..a0faf3d9efa 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -68,7 +68,9 @@ botocore==1.34.99 # moto # s3transfer build==1.2.1 - # via pip-tools + # via + # pip-tools + # singlestoredb cachecontrol==0.14.0 # via firebase-admin cachetools==5.3.3 @@ -505,6 +507,8 @@ pandas==2.2.2 # snowflake-connector-python pandocfilters==1.5.1 # via nbconvert +parsimonious==0.10.0 + # via singlestoredb parso==0.8.4 # via jedi parsy==2.1 @@ -610,6 +614,7 @@ pygments==2.18.0 pyjwt[crypto]==2.8.0 # via # msal + # singlestoredb # snowflake-connector-python pymssql==2.3.0 pymysql==1.1.1 @@ -705,6 +710,7 @@ requests==2.31.0 # msal # requests-oauthlib # responses + # singlestoredb # snowflake-connector-python # sphinx # trino @@ -745,8 +751,10 @@ setuptools==70.0.0 # grpcio-tools # kubernetes # pip-tools + # singlestoredb shellingham==1.5.4 # via typer +singlestoredb==1.3.1 six==1.16.0 # via # asttokens @@ -794,6 +802,8 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 +sqlparams==6.0.1 + # via singlestoredb stack-data==0.6.3 # via ipython starlette==0.37.2 @@ -821,6 +831,7 @@ tomli==2.0.1 # pip-tools # pytest # pytest-env + # singlestoredb tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -946,7 +957,9 @@ websockets==12.0 werkzeug==3.0.3 # via moto wheel==0.43.0 - # via pip-tools + # via + # pip-tools + # singlestoredb widgetsnbextension==4.0.11 # via ipywidgets wrapt==1.16.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index f6db0af6bc0..cea8cc22d0c 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -64,7 +64,9 @@ botocore==1.34.99 # moto # s3transfer build==1.2.1 - # via pip-tools + # via + # pip-tools + # singlestoredb cachecontrol==0.14.0 # via firebase-admin cachetools==5.3.3 @@ -496,6 +498,8 @@ pandas==2.2.2 # snowflake-connector-python pandocfilters==1.5.1 # via nbconvert +parsimonious==0.10.0 + # via singlestoredb parso==0.8.4 # via jedi parsy==2.1 @@ -601,6 +605,7 @@ pygments==2.18.0 pyjwt[crypto]==2.8.0 # via # msal + # singlestoredb # snowflake-connector-python pymssql==2.3.0 pymysql==1.1.1 @@ -696,6 +701,7 @@ requests==2.31.0 # msal # requests-oauthlib # responses + # singlestoredb # snowflake-connector-python # sphinx # trino @@ -736,8 +742,10 @@ setuptools==70.0.0 # grpcio-tools # kubernetes # pip-tools + # singlestoredb shellingham==1.5.4 # via typer +singlestoredb==1.3.1 six==1.16.0 # via # asttokens @@ -785,6 +793,8 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 +sqlparams==6.0.1 + # via singlestoredb stack-data==0.6.3 # via ipython starlette==0.37.2 @@ -925,7 +935,9 @@ websockets==12.0 werkzeug==3.0.3 # via moto wheel==0.43.0 - # via pip-tools + # via + # pip-tools + # singlestoredb widgetsnbextension==4.0.11 # via ipywidgets wrapt==1.16.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 135b65a0ccc..d7df488a881 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -68,7 +68,9 @@ botocore==1.34.99 # moto # s3transfer build==1.2.1 - # via pip-tools + # via + # pip-tools + # singlestoredb cachecontrol==0.14.0 # via firebase-admin cachetools==5.3.3 @@ -514,6 +516,8 @@ pandas==2.2.2 # snowflake-connector-python pandocfilters==1.5.1 # via nbconvert +parsimonious==0.10.0 + # via singlestoredb parso==0.8.4 # via jedi parsy==2.1 @@ -619,6 +623,7 @@ pygments==2.18.0 pyjwt[crypto]==2.8.0 # via # msal + # singlestoredb # snowflake-connector-python pymssql==2.3.0 pymysql==1.1.1 @@ -714,6 +719,7 @@ requests==2.31.0 # msal # requests-oauthlib # responses + # singlestoredb # snowflake-connector-python # sphinx # trino @@ -756,8 +762,10 @@ setuptools==70.0.0 # grpcio-tools # kubernetes # pip-tools + # singlestoredb shellingham==1.5.4 # via typer +singlestoredb==1.3.1 six==1.16.0 # via # asttokens @@ -805,6 +813,8 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 +sqlparams==6.0.1 + # via singlestoredb stack-data==0.6.3 # via ipython starlette==0.37.2 @@ -832,6 +842,7 @@ tomli==2.0.1 # pip-tools # pytest # pytest-env + # singlestoredb tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -960,7 +971,9 @@ websockets==12.0 werkzeug==3.0.3 # via moto wheel==0.43.0 - # via pip-tools + # via + # pip-tools + # singlestoredb widgetsnbextension==4.0.11 # via ipywidgets wrapt==1.16.0 diff --git a/sdk/python/tests/integration/feature_repos/universal/online_store/singlestore.py b/sdk/python/tests/integration/feature_repos/universal/online_store/singlestore.py new file mode 100644 index 00000000000..d3a02421d0a --- /dev/null +++ b/sdk/python/tests/integration/feature_repos/universal/online_store/singlestore.py @@ -0,0 +1,43 @@ +import subprocess +import time +from typing import Dict + +from testcontainers.core.container import DockerContainer + +from tests.integration.feature_repos.universal.online_store_creator import ( + OnlineStoreCreator, +) + + +class SingleStoreOnlineStoreCreator(OnlineStoreCreator): + def __init__(self, project_name: str, **kwargs): + super().__init__(project_name) + self.container = ( + DockerContainer("ghcr.io/singlestore-labs/singlestoredb-dev:latest") + .with_exposed_ports(3306) + .with_env("USER", "root") + .with_env("ROOT_PASSWORD", "test") + # this license key is authorized solely for use in SingleStore Feast tests and is subject to strict usage restrictions + # if you want a free SingleStore license for your own use please visit https://www.singlestore.com/cloud-trial/ + .with_env( + "LICENSE_KEY", + "BGIxODZiYTg1YWUxYjRlODRhYzRjMGFmYTA1OTkxYzgyAAAAAAAAAAABAAAAAAAAACgwNQIZANx4NIXJ7CWvKYYb3wIyRXxBY7fdAnLeSwIYLy2Q0jA124GAkl04yuGrD59Zpv85DVYXAA==", + ) + ) + + def create_online_store(self) -> Dict[str, str]: + self.container.start() + time.sleep(30) + exposed_port = self.container.get_exposed_port("3306") + command = f"mysql -uroot -ptest -P {exposed_port} -e 'CREATE DATABASE feast;'" + subprocess.run(command, shell=True, check=True) + return { + "type": "singlestore", + "user": "root", + "password": "test", + "database": "feast", + "port": exposed_port, + } + + def teardown(self): + self.container.stop() diff --git a/setup.py b/setup.py index f954f198988..cffd91a0c58 100644 --- a/setup.py +++ b/setup.py @@ -155,6 +155,8 @@ ELASTICSEARCH_REQUIRED = ["elasticsearch>=8.13.0"] +SINGLESTORE_REQUIRED = ["singlestoredb"] + CI_REQUIRED = ( [ "build", @@ -218,6 +220,7 @@ + DELTA_REQUIRED + ELASTICSEARCH_REQUIRED + SQLITE_VEC_REQUIRED + + SINGLESTORE_REQUIRED ) DOCS_REQUIRED = CI_REQUIRED @@ -386,6 +389,7 @@ def run(self): "delta": DELTA_REQUIRED, "elasticsearch": ELASTICSEARCH_REQUIRED, "sqlite_vec": SQLITE_VEC_REQUIRED, + "singlestore": SINGLESTORE_REQUIRED, }, include_package_data=True, license="Apache", From 43e198f6945c5e868ade341309f2c5ca39ac563e Mon Sep 17 00:00:00 2001 From: "bdodla@expedia.com" <13788369+EXPEbdodla@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:58:35 -0700 Subject: [PATCH 11/44] fix: CGO Memory leak issue in GO Feature server (#4291) --- go/internal/feast/server/http_server.go | 24 +++++++++++++++---- .../feast/transformation/transformation.go | 12 ++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index 75cdbe9929a..7ebab429e7e 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -4,14 +4,16 @@ import ( "context" "encoding/json" "fmt" + "net/http" + "time" + "github.com/feast-dev/feast/go/internal/feast" "github.com/feast-dev/feast/go/internal/feast/model" + "github.com/feast-dev/feast/go/internal/feast/onlineserving" "github.com/feast-dev/feast/go/internal/feast/server/logging" "github.com/feast-dev/feast/go/protos/feast/serving" prototypes "github.com/feast-dev/feast/go/protos/feast/types" "github.com/feast-dev/feast/go/types" - "net/http" - "time" ) type httpServer struct { @@ -210,6 +212,8 @@ func (s *httpServer) getOnlineFeatures(w http.ResponseWriter, r *http.Request) { "results": results, } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) if err != nil { @@ -217,8 +221,6 @@ func (s *httpServer) getOnlineFeatures(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") - if featureService != nil && featureService.LoggingConfig != nil && s.loggingService != nil { logger, err := s.loggingService.GetOrCreateLogger(featureService) if err != nil { @@ -250,11 +252,19 @@ func (s *httpServer) getOnlineFeatures(w http.ResponseWriter, r *http.Request) { return } } + go releaseCGOMemory(featureVectors) +} + +func releaseCGOMemory(featureVectors []*onlineserving.FeatureVector) { + for _, vector := range featureVectors { + vector.Values.Release() + } } func (s *httpServer) Serve(host string, port int) error { s.server = &http.Server{Addr: fmt.Sprintf("%s:%d", host, port), Handler: nil} http.HandleFunc("/get-online-features", s.getOnlineFeatures) + http.HandleFunc("/health", healthCheckHandler) err := s.server.ListenAndServe() // Don't return the error if it's caused by graceful shutdown using Stop() if err == http.ErrServerClosed { @@ -262,6 +272,12 @@ func (s *httpServer) Serve(host string, port int) error { } return err } + +func healthCheckHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Healthy") +} + func (s *httpServer) Stop() error { if s.server != nil { return s.server.Shutdown(context.Background()) diff --git a/go/internal/feast/transformation/transformation.go b/go/internal/feast/transformation/transformation.go index 1cf1dd3311b..7e63aec2243 100644 --- a/go/internal/feast/transformation/transformation.go +++ b/go/internal/feast/transformation/transformation.go @@ -46,6 +46,7 @@ func AugmentResponseWithOnDemandTransforms( for name, values := range requestData { requestContextArrow[name], err = types.ProtoValuesToArrowArray(values.Val, arrowMemory, numRows) if err != nil { + ReleaseArrowContext(requestContextArrow) return nil, err } } @@ -53,6 +54,7 @@ func AugmentResponseWithOnDemandTransforms( for name, values := range entityRows { requestContextArrow[name], err = types.ProtoValuesToArrowArray(values.Val, arrowMemory, numRows) if err != nil { + ReleaseArrowContext(requestContextArrow) return nil, err } } @@ -71,14 +73,24 @@ func AugmentResponseWithOnDemandTransforms( fullFeatureNames, ) if err != nil { + ReleaseArrowContext(requestContextArrow) return nil, err } result = append(result, onDemandFeatures...) + + ReleaseArrowContext(requestContextArrow) } return result, nil } +func ReleaseArrowContext(requestContextArrow map[string]arrow.Array) { + // Release memory used by requestContextArrow + for _, arrowArray := range requestContextArrow { + arrowArray.Release() + } +} + func CallTransformations( featureView *model.OnDemandFeatureView, retrievedFeatures map[string]arrow.Array, From 9451d9ca15f234e8e16e81351294fd63b33c1af2 Mon Sep 17 00:00:00 2001 From: Job Almekinders <55230856+job-almekinders@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:04:59 +0200 Subject: [PATCH 12/44] feat: Bump psycopg2 to psycopg3 for all Postgres components (#4303) * Makefile: Formatting Signed-off-by: Job Almekinders * Makefile: Exclude Snowflake tests for postgres offline store tests Signed-off-by: Job Almekinders * Bootstrap: Use conninfo Signed-off-by: Job Almekinders * Tests: Make connection string compatible with psycopg3 Signed-off-by: Job Almekinders * Tests: Test connection type pool and singleton Signed-off-by: Job Almekinders * Global: Replace conn.set_session() calls to be psycopg3 compatible Set connection read only Signed-off-by: Job Almekinders * Offline: Use psycopg3 Signed-off-by: Job Almekinders * Online: Use psycopg3 Signed-off-by: Job Almekinders * Online: Restructure online_write_batch Addition Signed-off-by: Job Almekinders * Online: Use correct placeholder Signed-off-by: Job Almekinders * Online: Handle bytes properly in online_read() Signed-off-by: Job Almekinders * Online: Whitespace Signed-off-by: Job Almekinders * Online: Open ConnectionPool Signed-off-by: Job Almekinders * Online: Add typehint Signed-off-by: Job Almekinders * Utils: Use psycopg3 Use new ConnectionPool Pass kwargs as named argument Use executemany over execute_values Remove not-required open argument in psycopg.connect Improve Use SpooledTemporaryFile Use max_size and add docstring Properly write with StringIO Utils: Use SpooledTemporaryFile over StringIO object Add replace Fix df_to_postgres_table Remove import Utils Signed-off-by: Job Almekinders * Lint: Raise exceptions if cursor returned no columns or rows Add log statement Lint: Fix _to_arrow_internal Lint: Fix _get_entity_df_event_timestamp_range Update exception Use ZeroColumnQueryResult Signed-off-by: Job Almekinders * Add comment on +psycopg string Signed-off-by: Job Almekinders * Docs: Remove mention of psycopg2 Signed-off-by: Job Almekinders * Lint: Fix Signed-off-by: Job Almekinders * Default to postgresql+psycopg and log warning Update warning Fix Format warning Add typehints Use better variable name Signed-off-by: Job Almekinders * Solve merge conflicts Signed-off-by: Job Almekinders --------- Signed-off-by: Job Almekinders --- Makefile | 7 +- docs/tutorials/using-scalable-registry.md | 2 +- sdk/python/feast/errors.py | 10 + .../postgres_offline_store/postgres.py | 27 +- .../postgres_offline_store/postgres_source.py | 8 +- .../infra/online_stores/contrib/postgres.py | 120 +++---- .../infra/utils/postgres/connection_utils.py | 85 ++--- sdk/python/feast/repo_config.py | 16 + .../feast/templates/postgres/bootstrap.py | 16 +- .../requirements/py3.10-ci-requirements.txt | 297 ++++++++++++------ .../requirements/py3.10-requirements.txt | 48 ++- .../requirements/py3.11-ci-requirements.txt | 295 ++++++++++++----- .../requirements/py3.11-requirements.txt | 48 ++- .../requirements/py3.9-ci-requirements.txt | 291 ++++++++++++----- .../requirements/py3.9-requirements.txt | 48 ++- .../online_store/test_universal_online.py | 9 +- .../registration/test_universal_registry.py | 4 +- setup.py | 2 +- 18 files changed, 925 insertions(+), 408 deletions(-) diff --git a/Makefile b/Makefile index 39406cc17da..d2fbb34e1f5 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ install-python: python setup.py develop lock-python-dependencies: - uv pip compile --system --no-strip-extras setup.py --output-file sdk/python/requirements/py$(PYTHON)-requirements.txt + uv pip compile --system --no-strip-extras setup.py --output-file sdk/python/requirements/py$(PYTHON)-requirements.txt lock-python-dependencies-all: pixi run --environment py39 --manifest-path infra/scripts/pixi/pixi.toml "uv pip compile --system --no-strip-extras setup.py --output-file sdk/python/requirements/py3.9-requirements.txt" @@ -164,7 +164,7 @@ test-python-universal-mssql: sdk/python/tests -# To use Athena as an offline store, you need to create an Athena database and an S3 bucket on AWS. +# To use Athena as an offline store, you need to create an Athena database and an S3 bucket on AWS. # https://docs.aws.amazon.com/athena/latest/ug/getting-started.html # Modify environment variables ATHENA_REGION, ATHENA_DATA_SOURCE, ATHENA_DATABASE, ATHENA_WORKGROUP or # ATHENA_S3_BUCKET_NAME according to your needs. If tests fail with the pytest -n 8 option, change the number to 1. @@ -191,7 +191,7 @@ test-python-universal-athena: not s3_registry and \ not test_snowflake" \ sdk/python/tests - + test-python-universal-postgres-offline: PYTHONPATH='.' \ FULL_REPO_CONFIGS_MODULE=sdk.python.feast.infra.offline_stores.contrib.postgres_repo_configuration \ @@ -209,6 +209,7 @@ test-python-universal-postgres-offline: not test_push_features_to_offline_store and \ not gcs_registry and \ not s3_registry and \ + not test_snowflake and \ not test_universal_types" \ sdk/python/tests diff --git a/docs/tutorials/using-scalable-registry.md b/docs/tutorials/using-scalable-registry.md index 30b8e01ed51..25746f60e23 100644 --- a/docs/tutorials/using-scalable-registry.md +++ b/docs/tutorials/using-scalable-registry.md @@ -49,7 +49,7 @@ When this happens, your database is likely using what is referred to as an in `SQLAlchemy` terminology. See your database's documentation for examples on how to set its scheme in the Database URL. -`Psycopg2`, which is the database library leveraged by the online and offline +`Psycopg`, which is the database library leveraged by the online and offline stores, is not impacted by the need to speak a particular dialect, and so the following only applies to the registry. diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py index 6083b3d5540..c4c11576269 100644 --- a/sdk/python/feast/errors.py +++ b/sdk/python/feast/errors.py @@ -389,3 +389,13 @@ def __init__(self, input_dict: dict): super().__init__( f"Failed to serialize the provided dictionary into a pandas DataFrame: {input_dict.keys()}" ) + + +class ZeroRowsQueryResult(Exception): + def __init__(self, query: str): + super().__init__(f"This query returned zero rows:\n{query}") + + +class ZeroColumnQueryResult(Exception): + def __init__(self, query: str): + super().__init__(f"This query returned zero columns:\n{query}") diff --git a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py index cb08b5f0168..c4740a960ef 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py +++ b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py @@ -19,11 +19,11 @@ import pandas as pd import pyarrow as pa from jinja2 import BaseLoader, Environment -from psycopg2 import sql +from psycopg import sql from pytz import utc from feast.data_source import DataSource -from feast.errors import InvalidEntityType +from feast.errors import InvalidEntityType, ZeroColumnQueryResult, ZeroRowsQueryResult from feast.feature_view import DUMMY_ENTITY_ID, DUMMY_ENTITY_VAL, FeatureView from feast.infra.offline_stores import offline_utils from feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source import ( @@ -274,8 +274,10 @@ def to_sql(self) -> str: def _to_arrow_internal(self, timeout: Optional[int] = None) -> pa.Table: with self._query_generator() as query: with _get_conn(self.config.offline_store) as conn, conn.cursor() as cur: - conn.set_session(readonly=True) + conn.read_only = True cur.execute(query) + if not cur.description: + raise ZeroColumnQueryResult(query) fields = [ (c.name, pg_type_code_to_arrow(c.type_code)) for c in cur.description @@ -331,16 +333,19 @@ def _get_entity_df_event_timestamp_range( entity_df_event_timestamp.max().to_pydatetime(), ) elif isinstance(entity_df, str): - # If the entity_df is a string (SQL query), determine range - # from table + # If the entity_df is a string (SQL query), determine range from table with _get_conn(config.offline_store) as conn, conn.cursor() as cur: - ( - cur.execute( - f"SELECT MIN({entity_df_event_timestamp_col}) AS min, MAX({entity_df_event_timestamp_col}) AS max FROM ({entity_df}) as tmp_alias" - ), - ) + query = f""" + SELECT + MIN({entity_df_event_timestamp_col}) AS min, + MAX({entity_df_event_timestamp_col}) AS max + FROM ({entity_df}) AS tmp_alias + """ + cur.execute(query) res = cur.fetchone() - entity_df_event_timestamp_range = (res[0], res[1]) + if not res: + raise ZeroRowsQueryResult(query) + entity_df_event_timestamp_range = (res[0], res[1]) else: raise InvalidEntityType(type(entity_df)) diff --git a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres_source.py b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres_source.py index bbb3f768fda..c216328b8d0 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres_source.py +++ b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres_source.py @@ -4,7 +4,7 @@ from typeguard import typechecked from feast.data_source import DataSource -from feast.errors import DataSourceNoNameException +from feast.errors import DataSourceNoNameException, ZeroColumnQueryResult from feast.infra.utils.postgres.connection_utils import _get_conn from feast.protos.feast.core.DataSource_pb2 import DataSource as DataSourceProto from feast.protos.feast.core.SavedDataset_pb2 import ( @@ -111,7 +111,11 @@ def get_table_column_names_and_types( self, config: RepoConfig ) -> Iterable[Tuple[str, str]]: with _get_conn(config.offline_store) as conn, conn.cursor() as cur: - cur.execute(f"SELECT * FROM {self.get_table_query_string()} AS sub LIMIT 0") + query = f"SELECT * FROM {self.get_table_query_string()} AS sub LIMIT 0" + cur.execute(query) + if not cur.description: + raise ZeroColumnQueryResult(query) + return ( (c.name, pg_type_code_to_pg_type(c.type_code)) for c in cur.description ) diff --git a/sdk/python/feast/infra/online_stores/contrib/postgres.py b/sdk/python/feast/infra/online_stores/contrib/postgres.py index 3eddd8ba203..8715f0f65bb 100644 --- a/sdk/python/feast/infra/online_stores/contrib/postgres.py +++ b/sdk/python/feast/infra/online_stores/contrib/postgres.py @@ -2,13 +2,22 @@ import logging from collections import defaultdict from datetime import datetime -from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + Literal, + Optional, + Sequence, + Tuple, +) -import psycopg2 import pytz -from psycopg2 import sql -from psycopg2.extras import execute_values -from psycopg2.pool import SimpleConnectionPool +from psycopg import sql +from psycopg.connection import Connection +from psycopg_pool import ConnectionPool from feast import Entity from feast.feature_view import FeatureView @@ -39,15 +48,17 @@ class PostgreSQLOnlineStoreConfig(PostgreSQLConfig): class PostgreSQLOnlineStore(OnlineStore): - _conn: Optional[psycopg2._psycopg.connection] = None - _conn_pool: Optional[SimpleConnectionPool] = None + _conn: Optional[Connection] = None + _conn_pool: Optional[ConnectionPool] = None @contextlib.contextmanager - def _get_conn(self, config: RepoConfig): + def _get_conn(self, config: RepoConfig) -> Generator[Connection, Any, Any]: assert config.online_store.type == "postgres" + if config.online_store.conn_type == ConnectionType.pool: if not self._conn_pool: self._conn_pool = _get_connection_pool(config.online_store) + self._conn_pool.open() connection = self._conn_pool.getconn() yield connection self._conn_pool.putconn(connection) @@ -64,57 +75,56 @@ def online_write_batch( Tuple[EntityKeyProto, Dict[str, ValueProto], datetime, Optional[datetime]] ], progress: Optional[Callable[[int], Any]], + batch_size: int = 5000, ) -> None: - project = config.project + # Format insert values + insert_values = [] + for entity_key, values, timestamp, created_ts in data: + entity_key_bin = serialize_entity_key( + entity_key, + entity_key_serialization_version=config.entity_key_serialization_version, + ) + timestamp = _to_naive_utc(timestamp) + if created_ts is not None: + created_ts = _to_naive_utc(created_ts) - with self._get_conn(config) as conn, conn.cursor() as cur: - insert_values = [] - for entity_key, values, timestamp, created_ts in data: - entity_key_bin = serialize_entity_key( - entity_key, - entity_key_serialization_version=config.entity_key_serialization_version, - ) - timestamp = _to_naive_utc(timestamp) - if created_ts is not None: - created_ts = _to_naive_utc(created_ts) - - for feature_name, val in values.items(): - vector_val = None - if config.online_store.pgvector_enabled: - vector_val = get_list_val_str(val) - insert_values.append( - ( - entity_key_bin, - feature_name, - val.SerializeToString(), - vector_val, - timestamp, - created_ts, - ) + for feature_name, val in values.items(): + vector_val = None + if config.online_store.pgvector_enabled: + vector_val = get_list_val_str(val) + insert_values.append( + ( + entity_key_bin, + feature_name, + val.SerializeToString(), + vector_val, + timestamp, + created_ts, ) - # Control the batch so that we can update the progress - batch_size = 5000 + ) + + # Create insert query + sql_query = sql.SQL( + """ + INSERT INTO {} + (entity_key, feature_name, value, vector_value, event_ts, created_ts) + VALUES (%s, %s, %s, %s, %s, %s) + ON CONFLICT (entity_key, feature_name) DO + UPDATE SET + value = EXCLUDED.value, + vector_value = EXCLUDED.vector_value, + event_ts = EXCLUDED.event_ts, + created_ts = EXCLUDED.created_ts; + """ + ).format(sql.Identifier(_table_id(config.project, table))) + + # Push data in batches to online store + with self._get_conn(config) as conn, conn.cursor() as cur: for i in range(0, len(insert_values), batch_size): cur_batch = insert_values[i : i + batch_size] - execute_values( - cur, - sql.SQL( - """ - INSERT INTO {} - (entity_key, feature_name, value, vector_value, event_ts, created_ts) - VALUES %s - ON CONFLICT (entity_key, feature_name) DO - UPDATE SET - value = EXCLUDED.value, - vector_value = EXCLUDED.vector_value, - event_ts = EXCLUDED.event_ts, - created_ts = EXCLUDED.created_ts; - """, - ).format(sql.Identifier(_table_id(project, table))), - cur_batch, - page_size=batch_size, - ) + cur.executemany(sql_query, cur_batch) conn.commit() + if progress: progress(len(cur_batch)) @@ -172,7 +182,9 @@ def online_read( # when we iterate through the keys since they are in the correct order values_dict = defaultdict(list) for row in rows if rows is not None else []: - values_dict[row[0].tobytes()].append(row[1:]) + values_dict[ + row[0] if isinstance(row[0], bytes) else row[0].tobytes() + ].append(row[1:]) for key in keys: if key in values_dict: diff --git a/sdk/python/feast/infra/utils/postgres/connection_utils.py b/sdk/python/feast/infra/utils/postgres/connection_utils.py index 0d99c8ab993..e0599019b96 100644 --- a/sdk/python/feast/infra/utils/postgres/connection_utils.py +++ b/sdk/python/feast/infra/utils/postgres/connection_utils.py @@ -1,50 +1,59 @@ -from typing import Dict +from typing import Any, Dict import numpy as np import pandas as pd -import psycopg2 -import psycopg2.extras +import psycopg import pyarrow as pa -from psycopg2.pool import SimpleConnectionPool +from psycopg.connection import Connection +from psycopg_pool import ConnectionPool from feast.infra.utils.postgres.postgres_config import PostgreSQLConfig from feast.type_map import arrow_to_pg_type -def _get_conn(config: PostgreSQLConfig): - conn = psycopg2.connect( - dbname=config.database, - host=config.host, - port=int(config.port), - user=config.user, - password=config.password, - sslmode=config.sslmode, - sslkey=config.sslkey_path, - sslcert=config.sslcert_path, - sslrootcert=config.sslrootcert_path, - options="-c search_path={}".format(config.db_schema or config.user), +def _get_conn(config: PostgreSQLConfig) -> Connection: + """Get a psycopg `Connection`.""" + conn = psycopg.connect( + conninfo=_get_conninfo(config), keepalives_idle=config.keepalives_idle, + **_get_conn_kwargs(config), ) return conn -def _get_connection_pool(config: PostgreSQLConfig): - return SimpleConnectionPool( - config.min_conn, - config.max_conn, - dbname=config.database, - host=config.host, - port=int(config.port), - user=config.user, - password=config.password, - sslmode=config.sslmode, - sslkey=config.sslkey_path, - sslcert=config.sslcert_path, - sslrootcert=config.sslrootcert_path, - options="-c search_path={}".format(config.db_schema or config.user), +def _get_connection_pool(config: PostgreSQLConfig) -> ConnectionPool: + """Get a psycopg `ConnectionPool`.""" + return ConnectionPool( + conninfo=_get_conninfo(config), + min_size=config.min_conn, + max_size=config.max_conn, + open=False, + kwargs=_get_conn_kwargs(config), ) +def _get_conninfo(config: PostgreSQLConfig) -> str: + """Get the `conninfo` argument required for connection objects.""" + return ( + f"postgresql://{config.user}" + f":{config.password}" + f"@{config.host}" + f":{int(config.port)}" + f"/{config.database}" + ) + + +def _get_conn_kwargs(config: PostgreSQLConfig) -> Dict[str, Any]: + """Get the additional `kwargs` required for connection objects.""" + return { + "sslmode": config.sslmode, + "sslkey": config.sslkey_path, + "sslcert": config.sslcert_path, + "sslrootcert": config.sslrootcert_path, + "options": "-c search_path={}".format(config.db_schema or config.user), + } + + def _df_to_create_table_sql(entity_df, table_name) -> str: pa_table = pa.Table.from_pandas(entity_df) columns = [ @@ -63,16 +72,14 @@ def df_to_postgres_table( """ Create a table for the data frame, insert all the values, and return the table schema """ + nr_columns = df.shape[1] + placeholders = ", ".join(["%s"] * nr_columns) + query = f"INSERT INTO {table_name} VALUES ({placeholders})" + values = df.replace({np.NaN: None}).to_numpy().tolist() + with _get_conn(config) as conn, conn.cursor() as cur: cur.execute(_df_to_create_table_sql(df, table_name)) - psycopg2.extras.execute_values( - cur, - f""" - INSERT INTO {table_name} - VALUES %s - """, - df.replace({np.NaN: None}).to_numpy(), - ) + cur.executemany(query, values) return dict(zip(df.columns, df.dtypes)) @@ -82,7 +89,7 @@ def get_query_schema(config: PostgreSQLConfig, sql_query: str) -> Dict[str, np.d new table """ with _get_conn(config) as conn: - conn.set_session(readonly=True) + conn.read_only = True df = pd.read_sql( f"SELECT * FROM {sql_query} LIMIT 0", conn, diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index f3c379020d2..8d6bff28187 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -12,6 +12,7 @@ StrictInt, StrictStr, ValidationError, + ValidationInfo, field_validator, model_validator, ) @@ -123,6 +124,21 @@ class RegistryConfig(FeastBaseModel): sqlalchemy_config_kwargs: Dict[str, Any] = {} """ Dict[str, Any]: Extra arguments to pass to SQLAlchemy.create_engine. """ + @field_validator("path") + def validate_path(cls, path: str, values: ValidationInfo) -> str: + if values.data.get("registry_type") == "sql": + if path.startswith("postgresql://"): + _logger.warning( + "The `path` of the `RegistryConfig` starts with a plain " + "`postgresql` string. We are updating this to `postgresql+psycopg` " + "to ensure that the `psycopg3` driver is used by `sqlalchemy`. If " + "you want to use `psycopg2` pass `postgresql+psycopg2` explicitely " + "to `path`. To silence this warning, pass `postgresql+psycopg` " + "explicitely to `path`." + ) + return path.replace("postgresql://", "postgresql+psycopg://") + return path + class RepoConfig(FeastBaseModel): """Repo config. Typically loaded from `feature_store.yaml`""" diff --git a/sdk/python/feast/templates/postgres/bootstrap.py b/sdk/python/feast/templates/postgres/bootstrap.py index 9f6e8a988d6..6ed13e4e39a 100644 --- a/sdk/python/feast/templates/postgres/bootstrap.py +++ b/sdk/python/feast/templates/postgres/bootstrap.py @@ -1,5 +1,5 @@ import click -import psycopg2 +import psycopg from feast.file_utils import replace_str_in_file from feast.infra.utils.postgres.connection_utils import df_to_postgres_table @@ -34,12 +34,14 @@ def bootstrap(): 'Should I upload example data to Postgres (overwriting "feast_driver_hourly_stats" table)?', default=True, ): - db_connection = psycopg2.connect( - dbname=postgres_database, - host=postgres_host, - port=int(postgres_port), - user=postgres_user, - password=postgres_password, + db_connection = psycopg.connect( + conninfo=( + f"postgresql://{postgres_user}" + f":{postgres_password}" + f"@{postgres_host}" + f":{int(postgres_port)}" + f"/{postgres_database}" + ), options=f"-c search_path={postgres_schema}", ) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index a0faf3d9efa..3aa7130ccf2 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt -aiobotocore==2.13.0 +aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -11,14 +12,16 @@ alabaster==0.7.16 # via sphinx altair==4.2.2 # via great-expectations -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.3.0 +anyio==4.4.0 # via # httpx # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -43,12 +47,14 @@ attrs==23.2.0 # aiohttp # jsonschema # referencing -azure-core==1.30.1 +azure-core==1.30.2 # via # azure-identity # azure-storage-blob -azure-identity==1.16.0 +azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -59,9 +65,11 @@ bidict==0.23.1 # via ibis-framework bleach==6.1.0 # via nbconvert -boto3==1.34.99 - # via moto -botocore==1.34.99 +boto3==1.34.131 + # via + # feast (setup.py) + # moto +botocore==1.34.131 # via # aiobotocore # boto3 @@ -69,6 +77,7 @@ botocore==1.34.99 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -76,7 +85,8 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.2.2 + # via feast (setup.py) +certifi==2024.6.2 # via # elastic-transport # httpcore @@ -98,6 +108,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -107,15 +118,18 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel # ipywidgets -coverage[toml]==7.5.3 +coverage[toml]==7.5.4 # via pytest-cov -cryptography==42.0.7 +cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -126,20 +140,24 @@ cryptography==42.0.7 # snowflake-connector-python # types-pyopenssl # types-redis -dask[dataframe]==2024.5.0 - # via dask-expr -dask-expr==1.1.0 +dask[dataframe]==2024.6.2 + # via + # feast (setup.py) + # dask-expr +dask-expr==1.1.6 # via dask db-dtypes==1.2.0 # via google-cloud-bigquery -debugpy==1.8.1 +debugpy==1.8.2 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.17.3 +deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -152,12 +170,13 @@ duckdb==0.10.3 # via # duckdb-engine # ibis-framework -duckdb-engine==0.12.1 +duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch -elasticsearch==8.13.2 -email-validator==2.1.1 +elasticsearch==8.14.0 + # via feast (setup.py) +email-validator==2.2.0 # via fastapi entrypoints==0.4 # via altair @@ -171,16 +190,17 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via fastapi-cli -fastapi-cli==0.0.2 + # via feast (setup.py) +fastapi-cli==0.0.4 # via fastapi -fastjsonschema==2.19.1 +fastjsonschema==2.20.0 # via nbformat -filelock==3.14.0 +filelock==3.15.4 # via # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -188,13 +208,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.19.0 +google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -204,9 +227,9 @@ google-api-core[grpc]==2.19.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.131.0 +google-api-python-client==2.134.0 # via firebase-admin -google-auth==2.29.0 +google-auth==2.30.0 # via # google-api-core # google-api-python-client @@ -219,8 +242,11 @@ google-auth==2.29.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 -google-cloud-bigtable==2.23.1 + # via feast (setup.py) +google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -229,30 +255,34 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin -google-cloud-storage==2.16.0 - # via firebase-admin +google-cloud-storage==2.17.0 + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.7.0 +google-resumable-media==2.7.1 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.63.0 +googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status -great-expectations==0.18.15 -greenlet==3.0.3 - # via sqlalchemy -grpc-google-iam-v1==0.13.0 +great-expectations==0.18.16 + # via feast (setup.py) +grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable -grpcio==1.64.0 +grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -263,19 +293,27 @@ grpcio==1.64.0 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -286,11 +324,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==8.0.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==3.2.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -304,7 +346,7 @@ idna==3.7 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==8.0.0 # via dask iniconfig==2.0.0 # via pytest @@ -325,6 +367,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -342,12 +385,13 @@ json5==0.9.25 # via jupyterlab-server jsonpatch==1.33 # via great-expectations -jsonpointer==2.4 +jsonpointer==3.0.0 # via # jsonpatch # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -382,7 +426,7 @@ jupyter-server==2.14.1 # notebook-shim jupyter-server-terminals==0.5.3 # via jupyter-server -jupyterlab==4.2.1 +jupyterlab==4.2.3 # via notebook jupyterlab-pygments==0.3.0 # via nbconvert @@ -393,6 +437,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -404,7 +449,7 @@ markupsafe==2.1.5 # jinja2 # nbconvert # werkzeug -marshmallow==3.21.2 +marshmallow==3.21.3 # via great-expectations matplotlib-inline==0.1.7 # via @@ -413,18 +458,22 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 -msal==1.28.0 + # via feast (setup.py) +msal==1.29.0 # via # azure-identity # msal-extensions -msal-extensions==1.1.0 +msal-extensions==1.2.0 # via azure-identity msgpack==1.0.8 # via cachecontrol @@ -434,11 +483,14 @@ multidict==6.0.5 # yarl multipledispatch==1.0.0 # via ibis-framework -mypy==1.10.0 - # via sqlalchemy +mypy==1.10.1 + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -451,9 +503,9 @@ nbformat==5.10.4 # nbconvert nest-asyncio==1.6.0 # via ipykernel -nodeenv==1.9.0 +nodeenv==1.9.1 # via pre-commit -notebook==7.2.0 +notebook==7.2.1 # via great-expectations notebook-shim==0.2.4 # via @@ -461,6 +513,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -471,11 +524,11 @@ numpy==1.26.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -orjson==3.10.3 +orjson==3.10.5 # via fastapi overrides==7.7.0 # via jupyter-server -packaging==24.0 +packaging==24.1 # via # build # dask @@ -490,13 +543,13 @@ packaging==24.0 # jupyterlab # jupyterlab-server # marshmallow - # msal-extensions # nbconvert # pytest # snowflake-connector-python # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -519,9 +572,10 @@ pbr==6.0.0 # via mock pexpect==4.9.0 # via ipython -pip==24.0 +pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -531,14 +585,15 @@ pluggy==1.5.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.8.2 +portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server -prompt-toolkit==3.0.45 +prompt-toolkit==3.0.47 # via ipython -proto-plus==1.23.0 +proto-plus==1.24.0 # via # google-api-core # google-cloud-bigquery @@ -548,6 +603,7 @@ proto-plus==1.23.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -565,8 +621,15 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel -psycopg2-binary==2.9.9 + # via + # feast (setup.py) + # ipykernel +psycopg[binary, pool]==3.1.19 + # via feast (setup.py) +psycopg-binary==3.1.19 + # via psycopg +psycopg-pool==3.2.2 + # via psycopg ptyprocess==0.7.0 # via # pexpect @@ -574,12 +637,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -597,16 +662,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi -pydantic==2.7.1 +pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations -pydantic-core==2.18.2 +pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -617,8 +685,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -630,8 +701,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -641,13 +714,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -676,6 +757,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -689,14 +771,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events -regex==2024.4.28 -requests==2.31.0 +regex==2024.5.15 # via + # feast (setup.py) + # parsimonious +requests==2.32.3 + # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -716,7 +803,7 @@ requests==2.31.0 # trino requests-oauthlib==2.0.0 # via kubernetes -responses==0.25.0 +responses==0.25.3 # via moto rfc3339-validator==0.1.4 # via @@ -731,6 +818,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -739,22 +827,25 @@ rsa==4.9 # via google-auth ruamel-yaml==0.17.17 # via great-expectations -ruff==0.4.6 -s3transfer==0.10.1 +ruff==0.4.10 + # via feast (setup.py) +s3transfer==0.10.2 # via boto3 -scipy==1.13.1 +scipy==1.14.0 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==70.0.0 +setuptools==70.1.1 # via # grpcio-tools + # jupyterlab # kubernetes # pip-tools # singlestoredb shellingham==1.5.4 # via typer -singlestoredb==1.3.1 +singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -774,12 +865,14 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.10.1 +snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -792,8 +885,9 @@ sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx -sqlalchemy[mypy]==2.0.30 +sqlalchemy[mypy]==2.0.31 # via + # feast (setup.py) # duckdb-engine # ibis-framework # sqlalchemy-views @@ -802,6 +896,7 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -811,17 +906,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 -tenacity==8.3.0 + # via feast (setup.py) +tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 -thriftpy2==0.5.0 + # via feast (setup.py) +thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via # build @@ -849,7 +948,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -866,38 +967,55 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 -typeguard==4.2.1 + # via feast (setup.py) +typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf -types-pymysql==1.1.0.20240425 + # via + # feast (setup.py) + # mypy-protobuf +types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 -types-setuptools==70.0.0.20240524 - # via types-cffi + # via feast (setup.py) +types-setuptools==70.1.0.20240627 + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # anyio # async-lru # azure-core + # azure-identity # azure-storage-blob # fastapi # great-expectations # ibis-framework # ipython # mypy + # psycopg + # psycopg-pool # pydantic # pydantic-core # snowflake-connector-python @@ -912,14 +1030,15 @@ tzlocal==5.2 # via # great-expectations # trino -ujson==5.9.0 +ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.18 +urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -930,19 +1049,21 @@ urllib3==1.26.18 # responses # rockset # testcontainers -uvicorn[standard]==0.29.0 +uvicorn[standard]==0.30.1 # via + # feast (setup.py) # fastapi - # fastapi-cli uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit -watchfiles==0.21.0 + # via + # feast (setup.py) + # pre-commit +watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit -webcolors==1.13 +webcolors==24.6.0 # via jsonschema webencodings==0.5.1 # via @@ -970,5 +1091,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 99c9bfc3fee..72124636b63 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -20,17 +20,22 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -38,14 +43,15 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -65,8 +71,11 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -78,13 +87,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -96,20 +108,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -120,6 +141,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -127,6 +149,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -142,11 +165,15 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -154,7 +181,9 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -178,6 +207,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index cea8cc22d0c..673047b5c78 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt -aiobotocore==2.13.0 +aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -11,14 +12,16 @@ alabaster==0.7.16 # via sphinx altair==4.2.2 # via great-expectations -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.3.0 +anyio==4.4.0 # via # httpx # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -39,12 +43,14 @@ attrs==23.2.0 # aiohttp # jsonschema # referencing -azure-core==1.30.1 +azure-core==1.30.2 # via # azure-identity # azure-storage-blob -azure-identity==1.16.0 +azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -55,9 +61,11 @@ bidict==0.23.1 # via ibis-framework bleach==6.1.0 # via nbconvert -boto3==1.34.99 - # via moto -botocore==1.34.99 +boto3==1.34.131 + # via + # feast (setup.py) + # moto +botocore==1.34.131 # via # aiobotocore # boto3 @@ -65,6 +73,7 @@ botocore==1.34.99 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -72,7 +81,8 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.2.2 + # via feast (setup.py) +certifi==2024.6.2 # via # elastic-transport # httpcore @@ -94,6 +104,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -103,15 +114,18 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel # ipywidgets -coverage[toml]==7.5.3 +coverage[toml]==7.5.4 # via pytest-cov -cryptography==42.0.7 +cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -122,20 +136,24 @@ cryptography==42.0.7 # snowflake-connector-python # types-pyopenssl # types-redis -dask[dataframe]==2024.5.0 - # via dask-expr -dask-expr==1.1.0 +dask[dataframe]==2024.6.2 + # via + # feast (setup.py) + # dask-expr +dask-expr==1.1.6 # via dask db-dtypes==1.2.0 # via google-cloud-bigquery -debugpy==1.8.1 +debugpy==1.8.2 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.17.4 +deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -148,12 +166,13 @@ duckdb==0.10.3 # via # duckdb-engine # ibis-framework -duckdb-engine==0.12.1 +duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch -elasticsearch==8.13.2 -email-validator==2.1.1 +elasticsearch==8.14.0 + # via feast (setup.py) +email-validator==2.2.0 # via fastapi entrypoints==0.4 # via altair @@ -162,16 +181,17 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via fastapi-cli -fastapi-cli==0.0.2 + # via feast (setup.py) +fastapi-cli==0.0.4 # via fastapi -fastjsonschema==2.19.1 +fastjsonschema==2.20.0 # via nbformat -filelock==3.14.0 +filelock==3.15.4 # via # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -179,13 +199,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.19.0 +google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -195,9 +218,9 @@ google-api-core[grpc]==2.19.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.131.0 +google-api-python-client==2.134.0 # via firebase-admin -google-auth==2.29.0 +google-auth==2.30.0 # via # google-api-core # google-api-python-client @@ -210,8 +233,11 @@ google-auth==2.29.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 -google-cloud-bigtable==2.23.1 + # via feast (setup.py) +google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -220,30 +246,34 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin -google-cloud-storage==2.16.0 - # via firebase-admin +google-cloud-storage==2.17.0 + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.7.0 +google-resumable-media==2.7.1 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.63.0 +googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status -great-expectations==0.18.15 -greenlet==3.0.3 - # via sqlalchemy -grpc-google-iam-v1==0.13.0 +great-expectations==0.18.16 + # via feast (setup.py) +grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable -grpcio==1.64.0 +grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -254,19 +284,27 @@ grpcio==1.64.0 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -277,11 +315,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==8.0.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==3.2.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -295,7 +337,7 @@ idna==3.7 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==8.0.0 # via dask iniconfig==2.0.0 # via pytest @@ -316,6 +358,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -333,12 +376,13 @@ json5==0.9.25 # via jupyterlab-server jsonpatch==1.33 # via great-expectations -jsonpointer==2.4 +jsonpointer==3.0.0 # via # jsonpatch # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -373,7 +417,7 @@ jupyter-server==2.14.1 # notebook-shim jupyter-server-terminals==0.5.3 # via jupyter-server -jupyterlab==4.2.1 +jupyterlab==4.2.3 # via notebook jupyterlab-pygments==0.3.0 # via nbconvert @@ -384,6 +428,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -395,7 +440,7 @@ markupsafe==2.1.5 # jinja2 # nbconvert # werkzeug -marshmallow==3.21.2 +marshmallow==3.21.3 # via great-expectations matplotlib-inline==0.1.7 # via @@ -404,18 +449,22 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 -msal==1.28.0 + # via feast (setup.py) +msal==1.29.0 # via # azure-identity # msal-extensions -msal-extensions==1.1.0 +msal-extensions==1.2.0 # via azure-identity msgpack==1.0.8 # via cachecontrol @@ -425,11 +474,14 @@ multidict==6.0.5 # yarl multipledispatch==1.0.0 # via ibis-framework -mypy==1.10.0 - # via sqlalchemy +mypy==1.10.1 + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -442,9 +494,9 @@ nbformat==5.10.4 # nbconvert nest-asyncio==1.6.0 # via ipykernel -nodeenv==1.9.0 +nodeenv==1.9.1 # via pre-commit -notebook==7.2.0 +notebook==7.2.1 # via great-expectations notebook-shim==0.2.4 # via @@ -452,6 +504,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -462,11 +515,11 @@ numpy==1.26.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -orjson==3.10.3 +orjson==3.10.5 # via fastapi overrides==7.7.0 # via jupyter-server -packaging==24.0 +packaging==24.1 # via # build # dask @@ -481,13 +534,13 @@ packaging==24.0 # jupyterlab # jupyterlab-server # marshmallow - # msal-extensions # nbconvert # pytest # snowflake-connector-python # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -510,9 +563,10 @@ pbr==6.0.0 # via mock pexpect==4.9.0 # via ipython -pip==24.0 +pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -522,14 +576,15 @@ pluggy==1.5.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.8.2 +portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server -prompt-toolkit==3.0.45 +prompt-toolkit==3.0.47 # via ipython -proto-plus==1.23.0 +proto-plus==1.24.0 # via # google-api-core # google-cloud-bigquery @@ -539,6 +594,7 @@ proto-plus==1.23.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -556,8 +612,15 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel -psycopg2-binary==2.9.9 + # via + # feast (setup.py) + # ipykernel +psycopg[binary, pool]==3.1.19 + # via feast (setup.py) +psycopg-binary==3.1.19 + # via psycopg +psycopg-pool==3.2.2 + # via psycopg ptyprocess==0.7.0 # via # pexpect @@ -565,12 +628,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -588,16 +653,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi -pydantic==2.7.1 +pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations -pydantic-core==2.18.2 +pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -608,8 +676,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -621,8 +692,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -632,13 +705,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -667,6 +748,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -680,14 +762,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 -requests==2.31.0 # via + # feast (setup.py) + # parsimonious +requests==2.32.3 + # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -707,7 +794,7 @@ requests==2.31.0 # trino requests-oauthlib==2.0.0 # via kubernetes -responses==0.25.0 +responses==0.25.3 # via moto rfc3339-validator==0.1.4 # via @@ -722,6 +809,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -730,22 +818,25 @@ rsa==4.9 # via google-auth ruamel-yaml==0.17.17 # via great-expectations -ruff==0.4.6 -s3transfer==0.10.1 +ruff==0.4.10 + # via feast (setup.py) +s3transfer==0.10.2 # via boto3 -scipy==1.13.1 +scipy==1.14.0 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==70.0.0 +setuptools==70.1.1 # via # grpcio-tools + # jupyterlab # kubernetes # pip-tools # singlestoredb shellingham==1.5.4 # via typer -singlestoredb==1.3.1 +singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -765,12 +856,14 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.10.1 +snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -783,8 +876,9 @@ sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx -sqlalchemy[mypy]==2.0.30 +sqlalchemy[mypy]==2.0.31 # via + # feast (setup.py) # duckdb-engine # ibis-framework # sqlalchemy-views @@ -793,6 +887,7 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -802,17 +897,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 -tenacity==8.3.0 + # via feast (setup.py) +tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 -thriftpy2==0.5.0 + # via feast (setup.py) +thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -830,7 +929,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -847,36 +948,53 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 -typeguard==4.2.1 + # via feast (setup.py) +typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf -types-pymysql==1.1.0.20240425 + # via + # feast (setup.py) + # mypy-protobuf +types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 -types-setuptools==70.0.0.20240524 - # via types-cffi + # via feast (setup.py) +types-setuptools==70.1.0.20240627 + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # azure-core + # azure-identity # azure-storage-blob # fastapi # great-expectations # ibis-framework # ipython # mypy + # psycopg + # psycopg-pool # pydantic # pydantic-core # snowflake-connector-python @@ -890,14 +1008,15 @@ tzlocal==5.2 # via # great-expectations # trino -ujson==5.9.0 +ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.18 +urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -908,19 +1027,21 @@ urllib3==1.26.18 # responses # rockset # testcontainers -uvicorn[standard]==0.29.0 +uvicorn[standard]==0.30.1 # via + # feast (setup.py) # fastapi - # fastapi-cli uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit -watchfiles==0.21.0 + # via + # feast (setup.py) + # pre-commit +watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit -webcolors==1.13 +webcolors==24.6.0 # via jsonschema webencodings==0.5.1 # via @@ -948,5 +1069,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index c34b610d14c..408f3925150 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -20,30 +20,36 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 # via fastapi fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -63,8 +69,11 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -76,13 +85,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -94,20 +106,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -118,6 +139,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -125,6 +147,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -140,17 +163,23 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) toolz==0.12.1 # via # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -172,6 +201,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index d7df488a881..83009f8730d 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt -aiobotocore==2.13.0 +aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -11,14 +12,16 @@ alabaster==0.7.16 # via sphinx altair==4.2.2 # via great-expectations -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.3.0 +anyio==4.4.0 # via # httpx # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -43,12 +47,14 @@ attrs==23.2.0 # aiohttp # jsonschema # referencing -azure-core==1.30.1 +azure-core==1.30.2 # via # azure-identity # azure-storage-blob -azure-identity==1.16.0 +azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -59,9 +65,11 @@ bidict==0.23.1 # via ibis-framework bleach==6.1.0 # via nbconvert -boto3==1.34.99 - # via moto -botocore==1.34.99 +boto3==1.34.131 + # via + # feast (setup.py) + # moto +botocore==1.34.131 # via # aiobotocore # boto3 @@ -69,6 +77,7 @@ botocore==1.34.99 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -76,7 +85,8 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.2.2 + # via feast (setup.py) +certifi==2024.6.2 # via # elastic-transport # httpcore @@ -98,6 +108,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -107,15 +118,18 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel # ipywidgets -coverage[toml]==7.5.3 +coverage[toml]==7.5.4 # via pytest-cov -cryptography==42.0.7 +cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -126,20 +140,24 @@ cryptography==42.0.7 # snowflake-connector-python # types-pyopenssl # types-redis -dask[dataframe]==2024.5.0 - # via dask-expr -dask-expr==1.1.0 +dask[dataframe]==2024.6.2 + # via + # feast (setup.py) + # dask-expr +dask-expr==1.1.6 # via dask db-dtypes==1.2.0 # via google-cloud-bigquery -debugpy==1.8.1 +debugpy==1.8.2 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.17.4 +deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -152,12 +170,13 @@ duckdb==0.10.3 # via # duckdb-engine # ibis-framework -duckdb-engine==0.12.1 +duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch -elasticsearch==8.13.2 -email-validator==2.1.1 +elasticsearch==8.14.0 + # via feast (setup.py) +email-validator==2.2.0 # via fastapi entrypoints==0.4 # via altair @@ -171,16 +190,17 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via fastapi-cli -fastapi-cli==0.0.2 + # via feast (setup.py) +fastapi-cli==0.0.4 # via fastapi -fastjsonschema==2.19.1 +fastjsonschema==2.20.0 # via nbformat -filelock==3.14.0 +filelock==3.15.4 # via # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -188,13 +208,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.19.0 +google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -204,9 +227,9 @@ google-api-core[grpc]==2.19.0 # google-cloud-datastore # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.131.0 +google-api-python-client==2.134.0 # via firebase-admin -google-auth==2.29.0 +google-auth==2.30.0 # via # google-api-core # google-api-python-client @@ -219,8 +242,11 @@ google-auth==2.29.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 -google-cloud-bigtable==2.23.1 + # via feast (setup.py) +google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -229,30 +255,34 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin -google-cloud-storage==2.16.0 - # via firebase-admin +google-cloud-storage==2.17.0 + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.7.0 +google-resumable-media==2.7.1 # via # google-cloud-bigquery # google-cloud-storage -googleapis-common-protos[grpc]==1.63.0 +googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status -great-expectations==0.18.15 -greenlet==3.0.3 - # via sqlalchemy -grpc-google-iam-v1==0.13.0 +great-expectations==0.18.16 + # via feast (setup.py) +grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable -grpcio==1.64.0 +grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -263,19 +293,27 @@ grpcio==1.64.0 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -286,11 +324,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==8.0.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==3.2.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -304,7 +346,7 @@ idna==3.7 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==8.0.0 # via # build # dask @@ -334,6 +376,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -351,12 +394,13 @@ json5==0.9.25 # via jupyterlab-server jsonpatch==1.33 # via great-expectations -jsonpointer==2.4 +jsonpointer==3.0.0 # via # jsonpatch # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -391,7 +435,7 @@ jupyter-server==2.14.1 # notebook-shim jupyter-server-terminals==0.5.3 # via jupyter-server -jupyterlab==4.2.1 +jupyterlab==4.2.3 # via notebook jupyterlab-pygments==0.3.0 # via nbconvert @@ -402,6 +446,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -413,7 +458,7 @@ markupsafe==2.1.5 # jinja2 # nbconvert # werkzeug -marshmallow==3.21.2 +marshmallow==3.21.3 # via great-expectations matplotlib-inline==0.1.7 # via @@ -422,18 +467,22 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 -msal==1.28.0 + # via feast (setup.py) +msal==1.29.0 # via # azure-identity # msal-extensions -msal-extensions==1.1.0 +msal-extensions==1.2.0 # via azure-identity msgpack==1.0.8 # via cachecontrol @@ -443,11 +492,14 @@ multidict==6.0.5 # yarl multipledispatch==1.0.0 # via ibis-framework -mypy==1.10.0 - # via sqlalchemy +mypy==1.10.1 + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -460,9 +512,9 @@ nbformat==5.10.4 # nbconvert nest-asyncio==1.6.0 # via ipykernel -nodeenv==1.9.0 +nodeenv==1.9.1 # via pre-commit -notebook==7.2.0 +notebook==7.2.1 # via great-expectations notebook-shim==0.2.4 # via @@ -470,6 +522,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -480,11 +533,11 @@ numpy==1.26.4 # scipy oauthlib==3.2.2 # via requests-oauthlib -orjson==3.10.3 +orjson==3.10.5 # via fastapi overrides==7.7.0 # via jupyter-server -packaging==24.0 +packaging==24.1 # via # build # dask @@ -499,13 +552,13 @@ packaging==24.0 # jupyterlab # jupyterlab-server # marshmallow - # msal-extensions # nbconvert # pytest # snowflake-connector-python # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -528,9 +581,10 @@ pbr==6.0.0 # via mock pexpect==4.9.0 # via ipython -pip==24.0 +pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -540,14 +594,15 @@ pluggy==1.5.0 # via pytest ply==3.11 # via thriftpy2 -portalocker==2.8.2 +portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server -prompt-toolkit==3.0.45 +prompt-toolkit==3.0.47 # via ipython -proto-plus==1.23.0 +proto-plus==1.24.0 # via # google-api-core # google-cloud-bigquery @@ -557,6 +612,7 @@ proto-plus==1.23.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -574,8 +630,15 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel -psycopg2-binary==2.9.9 + # via + # feast (setup.py) + # ipykernel +psycopg[binary, pool]==3.1.18 + # via feast (setup.py) +psycopg-binary==3.1.18 + # via psycopg +psycopg-pool==3.2.2 + # via psycopg ptyprocess==0.7.0 # via # pexpect @@ -583,12 +646,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -606,16 +671,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi -pydantic==2.7.1 +pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations -pydantic-core==2.18.2 +pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -626,8 +694,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -639,8 +710,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -650,13 +723,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -685,6 +766,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -698,14 +780,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 -requests==2.31.0 # via + # feast (setup.py) + # parsimonious +requests==2.32.3 + # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -725,7 +812,7 @@ requests==2.31.0 # trino requests-oauthlib==2.0.0 # via kubernetes -responses==0.25.0 +responses==0.25.3 # via moto rfc3339-validator==0.1.4 # via @@ -740,6 +827,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -750,22 +838,25 @@ ruamel-yaml==0.17.17 # via great-expectations ruamel-yaml-clib==0.2.8 # via ruamel-yaml -ruff==0.4.6 -s3transfer==0.10.1 +ruff==0.4.10 + # via feast (setup.py) +s3transfer==0.10.2 # via boto3 scipy==1.13.1 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==70.0.0 +setuptools==70.1.1 # via # grpcio-tools + # jupyterlab # kubernetes # pip-tools # singlestoredb shellingham==1.5.4 # via typer -singlestoredb==1.3.1 +singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -785,12 +876,14 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.10.1 +snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -803,8 +896,9 @@ sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx -sqlalchemy[mypy]==2.0.30 +sqlalchemy[mypy]==2.0.31 # via + # feast (setup.py) # duckdb-engine # ibis-framework # sqlalchemy-views @@ -813,6 +907,7 @@ sqlalchemy-views==0.3.2 sqlglot==20.11.0 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -822,17 +917,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 -tenacity==8.3.0 + # via feast (setup.py) +tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 -thriftpy2==0.5.0 + # via feast (setup.py) +thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via # build @@ -860,7 +959,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -877,39 +978,56 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 -typeguard==4.2.1 + # via feast (setup.py) +typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 -types-setuptools==70.0.0.20240524 - # via types-cffi + # via feast (setup.py) +types-setuptools==70.1.0.20240627 + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # aioitertools # anyio # async-lru # azure-core + # azure-identity # azure-storage-blob # fastapi # great-expectations # ibis-framework # ipython # mypy + # psycopg + # psycopg-pool # pydantic # pydantic-core # snowflake-connector-python @@ -925,14 +1043,15 @@ tzlocal==5.2 # via # great-expectations # trino -ujson==5.9.0 +ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.18 +urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -944,19 +1063,21 @@ urllib3==1.26.18 # rockset # snowflake-connector-python # testcontainers -uvicorn[standard]==0.29.0 +uvicorn[standard]==0.30.1 # via + # feast (setup.py) # fastapi - # fastapi-cli uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit -watchfiles==0.21.0 + # via + # feast (setup.py) + # pre-commit +watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit -webcolors==1.13 +webcolors==24.6.0 # via jsonschema webencodings==0.5.1 # via @@ -984,5 +1105,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 149a96626ef..3c833438de9 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -20,17 +20,22 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -38,14 +43,15 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -67,8 +73,11 @@ importlib-metadata==7.1.0 # dask # typeguard jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -80,13 +89,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -98,20 +110,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -122,6 +143,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -129,6 +151,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -144,11 +167,15 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -156,7 +183,9 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -181,6 +210,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index e78c1053bf8..c6b034e2aae 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -39,13 +39,18 @@ @pytest.mark.integration @pytest.mark.universal_online_stores(only=["postgres"]) +@pytest.mark.parametrize( + "conn_type", + [ConnectionType.singleton, ConnectionType.pool], + ids=lambda v: f"conn_type:{v}", +) def test_connection_pool_online_stores( - environment, universal_data_sources, fake_ingest_data + environment, universal_data_sources, fake_ingest_data, conn_type ): if os.getenv("FEAST_IS_LOCAL_TEST", "False") == "True": return fs = environment.feature_store - fs.config.online_store.conn_type = ConnectionType.pool + fs.config.online_store.conn_type = conn_type fs.config.online_store.min_conn = 1 fs.config.online_store.max_conn = 10 diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index 24ba9fe42a5..c119ae800a2 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -149,7 +149,9 @@ def pg_registry(): registry_config = RegistryConfig( registry_type="sql", - path=f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{container_host}:{container_port}/{POSTGRES_DB}", + # The `path` must include `+psycopg` in order for `sqlalchemy.create_engine()` + # to understand that we are using psycopg3. + path=f"postgresql+psycopg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{container_host}:{container_port}/{POSTGRES_DB}", sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, ) diff --git a/setup.py b/setup.py index cffd91a0c58..958e93799d9 100644 --- a/setup.py +++ b/setup.py @@ -102,7 +102,7 @@ TRINO_REQUIRED = ["trino>=0.305.0,<0.400.0", "regex"] POSTGRES_REQUIRED = [ - "psycopg2-binary>=2.8.3,<3", + "psycopg[binary,pool]>=3.0.0,<4", ] MYSQL_REQUIRED = ["pymysql", "types-PyMySQL"] From 7072fd0e2e1d2f4d9a3e8f02d04ae042b3d9c0d4 Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Tue, 2 Jul 2024 01:05:44 +0400 Subject: [PATCH 13/44] feat: Move get_online_features to OnlineStore interface (#4319) * move get_online_features to OnlineStore interface Signed-off-by: tokoko * fix pydantic warnings Signed-off-by: tokoko * run ruff format Signed-off-by: tokoko --------- Signed-off-by: tokoko --- sdk/python/feast/feature_store.py | 185 +----------------- .../kubernetes/k8s_materialization_engine.py | 4 +- .../feast/infra/online_stores/online_store.py | 182 ++++++++++++++++- .../feast/infra/passthrough_provider.py | 46 ++++- sdk/python/feast/infra/provider.py | 34 +++- sdk/python/tests/foo_provider.py | 32 ++- .../universal/data_sources/file.py | 2 +- 7 files changed, 303 insertions(+), 182 deletions(-) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index b7e4ef619f0..6476af5ac85 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -1559,75 +1559,16 @@ def get_online_features( ... ) >>> online_response_dict = online_response.to_dict() """ - if isinstance(entity_rows, list): - columnar: Dict[str, List[Any]] = {k: [] for k in entity_rows[0].keys()} - for entity_row in entity_rows: - for key, value in entity_row.items(): - try: - columnar[key].append(value) - except KeyError as e: - raise ValueError( - "All entity_rows must have the same keys." - ) from e - - entity_rows = columnar + provider = self._get_provider() - ( - join_key_values, - grouped_refs, - entity_name_to_join_key_map, - requested_on_demand_feature_views, - feature_refs, - requested_result_row_names, - online_features_response, - ) = utils._prepare_entities_to_read_from_online_store( + return provider.get_online_features( + config=self.config, + features=features, + entity_rows=entity_rows, registry=self._registry, project=self.project, - features=features, - entity_values=entity_rows, full_feature_names=full_feature_names, - native_entity_values=True, - ) - - provider = self._get_provider() - for table, requested_features in grouped_refs: - # Get the correct set of entity values with the correct join keys. - table_entity_values, idxs = utils._get_unique_entities( - table, - join_key_values, - entity_name_to_join_key_map, - ) - - # Fetch feature data for the minimum set of Entities. - feature_data = self._read_from_online_store( - table_entity_values, - provider, - requested_features, - table, - ) - - # Populate the result_rows with the Features from the OnlineStore inplace. - utils._populate_response_from_feature_data( - feature_data, - idxs, - online_features_response, - full_feature_names, - requested_features, - table, - ) - - if requested_on_demand_feature_views: - utils._augment_response_with_on_demand_transforms( - online_features_response, - feature_refs, - requested_on_demand_feature_views, - full_feature_names, - ) - - utils._drop_unneeded_columns( - online_features_response, requested_result_row_names ) - return OnlineResponse(online_features_response) async def get_online_features_async( self, @@ -1664,75 +1605,16 @@ async def get_online_features_async( Raises: Exception: No entity with the specified name exists. """ - if isinstance(entity_rows, list): - columnar: Dict[str, List[Any]] = {k: [] for k in entity_rows[0].keys()} - for entity_row in entity_rows: - for key, value in entity_row.items(): - try: - columnar[key].append(value) - except KeyError as e: - raise ValueError( - "All entity_rows must have the same keys." - ) from e - - entity_rows = columnar + provider = self._get_provider() - ( - join_key_values, - grouped_refs, - entity_name_to_join_key_map, - requested_on_demand_feature_views, - feature_refs, - requested_result_row_names, - online_features_response, - ) = utils._prepare_entities_to_read_from_online_store( + return await provider.get_online_features_async( + config=self.config, + features=features, + entity_rows=entity_rows, registry=self._registry, project=self.project, - features=features, - entity_values=entity_rows, full_feature_names=full_feature_names, - native_entity_values=True, - ) - - provider = self._get_provider() - for table, requested_features in grouped_refs: - # Get the correct set of entity values with the correct join keys. - table_entity_values, idxs = utils._get_unique_entities( - table, - join_key_values, - entity_name_to_join_key_map, - ) - - # Fetch feature data for the minimum set of Entities. - feature_data = await self._read_from_online_store_async( - table_entity_values, - provider, - requested_features, - table, - ) - - # Populate the result_rows with the Features from the OnlineStore inplace. - utils._populate_response_from_feature_data( - feature_data, - idxs, - online_features_response, - full_feature_names, - requested_features, - table, - ) - - if requested_on_demand_feature_views: - utils._augment_response_with_on_demand_transforms( - online_features_response, - feature_refs, - requested_on_demand_feature_views, - full_feature_names, - ) - - utils._drop_unneeded_columns( - online_features_response, requested_result_row_names ) - return OnlineResponse(online_features_response) def retrieve_online_documents( self, @@ -1806,53 +1688,6 @@ def retrieve_online_documents( ) return OnlineResponse(online_features_response) - def _read_from_online_store( - self, - entity_rows: Iterable[Mapping[str, Value]], - provider: Provider, - requested_features: List[str], - table: FeatureView, - ) -> List[Tuple[List[Timestamp], List["FieldStatus.ValueType"], List[Value]]]: - """Read and process data from the OnlineStore for a given FeatureView. - - This method guarantees that the order of the data in each element of the - List returned is the same as the order of `requested_features`. - - This method assumes that `provider.online_read` returns data for each - combination of Entities in `entity_rows` in the same order as they - are provided. - """ - entity_key_protos = utils._get_entity_key_protos(entity_rows) - - # Fetch data for Entities. - read_rows = provider.online_read( - config=self.config, - table=table, - entity_keys=entity_key_protos, - requested_features=requested_features, - ) - - return utils._convert_rows_to_protobuf(requested_features, read_rows) - - async def _read_from_online_store_async( - self, - entity_rows: Iterable[Mapping[str, Value]], - provider: Provider, - requested_features: List[str], - table: FeatureView, - ) -> List[Tuple[List[Timestamp], List["FieldStatus.ValueType"], List[Value]]]: - entity_key_protos = utils._get_entity_key_protos(entity_rows) - - # Fetch data for Entities. - read_rows = await provider.online_read_async( - config=self.config, - table=table, - entity_keys=entity_key_protos, - requested_features=requested_features, - ) - - return utils._convert_rows_to_protobuf(requested_features, read_rows) - def _retrieve_from_online_store( self, provider: Provider, diff --git a/sdk/python/feast/infra/materialization/kubernetes/k8s_materialization_engine.py b/sdk/python/feast/infra/materialization/kubernetes/k8s_materialization_engine.py index 2e7129b0376..510b6b4e4c7 100644 --- a/sdk/python/feast/infra/materialization/kubernetes/k8s_materialization_engine.py +++ b/sdk/python/feast/infra/materialization/kubernetes/k8s_materialization_engine.py @@ -306,7 +306,9 @@ 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""" - feature_store_configuration = yaml.dump(self.repo_config.dict(by_alias=True)) + feature_store_configuration = yaml.dump( + self.repo_config.model_dump(by_alias=True) + ) materialization_config = yaml.dump( {"paths": paths, "feature_view": feature_view.name} diff --git a/sdk/python/feast/infra/online_stores/online_store.py b/sdk/python/feast/infra/online_stores/online_store.py index 05983a494c0..9cf2ef95f68 100644 --- a/sdk/python/feast/infra/online_stores/online_store.py +++ b/sdk/python/feast/infra/online_stores/online_store.py @@ -14,13 +14,17 @@ from abc import ABC, abstractmethod from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union -from feast import Entity +from feast import Entity, utils +from feast.feature_service import FeatureService from feast.feature_view import FeatureView from feast.infra.infra_object import InfraObject +from feast.infra.registry.base_registry import BaseRegistry +from feast.online_response import OnlineResponse from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.protos.feast.types.Value_pb2 import RepeatedValue from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import RepoConfig @@ -105,6 +109,180 @@ async def online_read_async( f"Online store {self.__class__.__name__} does not support online read async" ) + def get_online_features( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + if isinstance(entity_rows, list): + columnar: Dict[str, List[Any]] = {k: [] for k in entity_rows[0].keys()} + for entity_row in entity_rows: + for key, value in entity_row.items(): + try: + columnar[key].append(value) + except KeyError as e: + raise ValueError( + "All entity_rows must have the same keys." + ) from e + + entity_rows = columnar + + ( + join_key_values, + grouped_refs, + entity_name_to_join_key_map, + requested_on_demand_feature_views, + feature_refs, + requested_result_row_names, + online_features_response, + ) = utils._prepare_entities_to_read_from_online_store( + registry=registry, + project=project, + features=features, + entity_values=entity_rows, + full_feature_names=full_feature_names, + native_entity_values=True, + ) + + for table, requested_features in grouped_refs: + # Get the correct set of entity values with the correct join keys. + table_entity_values, idxs = utils._get_unique_entities( + table, + join_key_values, + entity_name_to_join_key_map, + ) + + entity_key_protos = utils._get_entity_key_protos(table_entity_values) + + # Fetch data for Entities. + read_rows = self.online_read( + config=config, + table=table, + entity_keys=entity_key_protos, + requested_features=requested_features, + ) + + feature_data = utils._convert_rows_to_protobuf( + requested_features, read_rows + ) + + # Populate the result_rows with the Features from the OnlineStore inplace. + utils._populate_response_from_feature_data( + feature_data, + idxs, + online_features_response, + full_feature_names, + requested_features, + table, + ) + + if requested_on_demand_feature_views: + utils._augment_response_with_on_demand_transforms( + online_features_response, + feature_refs, + requested_on_demand_feature_views, + full_feature_names, + ) + + utils._drop_unneeded_columns( + online_features_response, requested_result_row_names + ) + return OnlineResponse(online_features_response) + + async def get_online_features_async( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + if isinstance(entity_rows, list): + columnar: Dict[str, List[Any]] = {k: [] for k in entity_rows[0].keys()} + for entity_row in entity_rows: + for key, value in entity_row.items(): + try: + columnar[key].append(value) + except KeyError as e: + raise ValueError( + "All entity_rows must have the same keys." + ) from e + + entity_rows = columnar + + ( + join_key_values, + grouped_refs, + entity_name_to_join_key_map, + requested_on_demand_feature_views, + feature_refs, + requested_result_row_names, + online_features_response, + ) = utils._prepare_entities_to_read_from_online_store( + registry=registry, + project=project, + features=features, + entity_values=entity_rows, + full_feature_names=full_feature_names, + native_entity_values=True, + ) + + for table, requested_features in grouped_refs: + # Get the correct set of entity values with the correct join keys. + table_entity_values, idxs = utils._get_unique_entities( + table, + join_key_values, + entity_name_to_join_key_map, + ) + + entity_key_protos = utils._get_entity_key_protos(table_entity_values) + + # Fetch data for Entities. + read_rows = await self.online_read_async( + config=config, + table=table, + entity_keys=entity_key_protos, + requested_features=requested_features, + ) + + feature_data = utils._convert_rows_to_protobuf( + requested_features, read_rows + ) + + # Populate the result_rows with the Features from the OnlineStore inplace. + utils._populate_response_from_feature_data( + feature_data, + idxs, + online_features_response, + full_feature_names, + requested_features, + table, + ) + + if requested_on_demand_feature_views: + utils._augment_response_with_on_demand_transforms( + online_features_response, + feature_refs, + requested_on_demand_feature_views, + full_feature_names, + ) + + utils._drop_unneeded_columns( + online_features_response, requested_result_row_names + ) + return OnlineResponse(online_features_response) + @abstractmethod def update( self, diff --git a/sdk/python/feast/infra/passthrough_provider.py b/sdk/python/feast/infra/passthrough_provider.py index bad6f86cc65..c3c3048a896 100644 --- a/sdk/python/feast/infra/passthrough_provider.py +++ b/sdk/python/feast/infra/passthrough_provider.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union import pandas as pd import pyarrow as pa @@ -23,8 +23,10 @@ from feast.infra.online_stores.helpers import get_online_store_from_config from feast.infra.provider import Provider from feast.infra.registry.base_registry import BaseRegistry +from feast.online_response import OnlineResponse from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.protos.feast.types.Value_pb2 import RepeatedValue from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import BATCH_ENGINE_CLASS_FOR_TYPE, RepoConfig from feast.saved_dataset import SavedDataset @@ -193,6 +195,48 @@ def online_read( ) return result + def get_online_features( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + return self.online_store.get_online_features( + config=config, + features=features, + entity_rows=entity_rows, + registry=registry, + project=project, + full_feature_names=full_feature_names, + ) + + async def get_online_features_async( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + return await self.online_store.get_online_features_async( + config=config, + features=features, + entity_rows=entity_rows, + registry=registry, + project=project, + full_feature_names=full_feature_names, + ) + async def online_read_async( self, config: RepoConfig, diff --git a/sdk/python/feast/infra/provider.py b/sdk/python/feast/infra/provider.py index 75afd6bba86..9940af1d028 100644 --- a/sdk/python/feast/infra/provider.py +++ b/sdk/python/feast/infra/provider.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from datetime import datetime from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union import pandas as pd import pyarrow @@ -15,8 +15,10 @@ from feast.infra.infra_object import Infra from feast.infra.offline_stores.offline_store import RetrievalJob from feast.infra.registry.base_registry import BaseRegistry +from feast.online_response import OnlineResponse from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.protos.feast.types.Value_pb2 import RepeatedValue from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import RepoConfig from feast.saved_dataset import SavedDataset @@ -230,6 +232,36 @@ def online_read( """ pass + @abstractmethod + def get_online_features( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + pass + + @abstractmethod + async def get_online_features_async( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + pass + @abstractmethod async def online_read_async( self, diff --git a/sdk/python/tests/foo_provider.py b/sdk/python/tests/foo_provider.py index bd1e247a7b9..8e8f54db242 100644 --- a/sdk/python/tests/foo_provider.py +++ b/sdk/python/tests/foo_provider.py @@ -1,6 +1,6 @@ from datetime import datetime from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union import pandas import pyarrow @@ -11,7 +11,9 @@ from feast.infra.offline_stores.offline_store import RetrievalJob from feast.infra.provider import Provider from feast.infra.registry.base_registry import BaseRegistry +from feast.online_response import OnlineResponse from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.protos.feast.types.Value_pb2 import RepeatedValue from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.saved_dataset import SavedDataset @@ -138,3 +140,31 @@ def validate_data_source( data_source: DataSource, ): pass + + def get_online_features( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + pass + + async def get_online_features_async( + self, + config: RepoConfig, + features: Union[List[str], FeatureService], + entity_rows: Union[ + List[Dict[str, Any]], + Mapping[str, Union[Sequence[Any], Sequence[ValueProto], RepeatedValue]], + ], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> OnlineResponse: + pass diff --git a/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py b/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py index f7ab55d868a..4a4a7360d8c 100644 --- a/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py +++ b/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py @@ -381,7 +381,7 @@ def setup(self, registry: RegistryConfig): repo_path = Path(tempfile.mkdtemp()) with open(repo_path / "feature_store.yaml", "w") as outfile: - yaml.dump(config.dict(by_alias=True), outfile) + yaml.dump(config.model_dump(by_alias=True), outfile) repo_path = str(repo_path.resolve()) self.server_port = free_port() From 398ea3b86c83605963124404ff4baa95162dc1f4 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Mon, 1 Jul 2024 17:06:27 -0400 Subject: [PATCH 14/44] fix: Fix SQLite import issue (#4294) adding try and except block for import Co-authored-by: Francisco Javier Arceo --- .../feast/infra/online_stores/sqlite.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/sqlite.py b/sdk/python/feast/infra/online_stores/sqlite.py index 41af14aaf16..9896b766d47 100644 --- a/sdk/python/feast/infra/online_stores/sqlite.py +++ b/sdk/python/feast/infra/online_stores/sqlite.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import itertools +import logging import os import sqlite3 import struct @@ -20,7 +21,6 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Union -import sqlite_vec from google.protobuf.internal.containers import RepeatedScalarFieldContainer from pydantic import StrictStr @@ -84,7 +84,9 @@ def _get_conn(self, config: RepoConfig): if not self._conn: db_path = self._get_db_path(config) self._conn = _initialize_conn(db_path) - if sys.version_info[0:2] == (3, 10): + if sys.version_info[0:2] == (3, 10) and config.online_store.vec_enabled: + import sqlite_vec # noqa: F401 + self._conn.enable_load_extension(True) # type: ignore sqlite_vec.load(self._conn) @@ -410,6 +412,10 @@ def retrieve_online_documents( def _initialize_conn(db_path: str): + try: + import sqlite_vec # noqa: F401 + except ModuleNotFoundError: + logging.warning("Cannot use sqlite_vec for vector search") Path(db_path).parent.mkdir(exist_ok=True) return sqlite3.connect( db_path, @@ -482,8 +488,13 @@ def from_proto(sqlite_table_proto: SqliteTableProto) -> Any: def update(self): if sys.version_info[0:2] == (3, 10): - self.conn.enable_load_extension(True) - sqlite_vec.load(self.conn) + try: + import sqlite_vec # noqa: F401 + + self.conn.enable_load_extension(True) + sqlite_vec.load(self.conn) + except ModuleNotFoundError: + logging.warning("Cannot use sqlite_vec for vector search") self.conn.execute( f"CREATE TABLE IF NOT EXISTS {self.name} (entity_key BLOB, feature_name TEXT, value BLOB, vector_value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))" ) From 98ff63cd389207998b3452ec46e5a2f0fc70485c Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Mon, 1 Jul 2024 22:54:28 -0400 Subject: [PATCH 15/44] fix: Using one single function call for utcnow(). (#4307) Signed-off-by: Shuchu Han --- sdk/python/feast/feature_store.py | 9 +++--- .../feast/infra/offline_stores/bigquery.py | 4 +-- .../trino_offline_store/trino_queries.py | 6 ++-- .../feast/infra/online_stores/datastore.py | 6 ++-- .../feast/infra/registry/caching_registry.py | 9 +++--- .../contrib/azure/azure_registry_store.py | 4 +-- sdk/python/feast/infra/registry/file.py | 4 +-- sdk/python/feast/infra/registry/gcs.py | 4 +-- sdk/python/feast/infra/registry/registry.py | 21 ++++++------ sdk/python/feast/infra/registry/s3.py | 4 +-- sdk/python/feast/infra/registry/snowflake.py | 22 ++++++------- sdk/python/feast/infra/registry/sql.py | 9 +++--- sdk/python/feast/on_demand_feature_view.py | 6 ++-- sdk/python/feast/utils.py | 4 +++ sdk/python/tests/conftest.py | 11 ++++--- sdk/python/tests/data/data_creator.py | 15 +++++---- sdk/python/tests/doctest/test_all.py | 7 ++-- .../feature_repos/repo_configuration.py | 3 +- .../materialization/test_snowflake.py | 5 +-- .../offline_store/test_offline_write.py | 9 +++--- .../test_push_features_to_offline_store.py | 5 ++- .../test_universal_historical_retrieval.py | 13 ++++---- .../offline_store/test_validation.py | 5 ++- .../test_push_features_to_online_store.py | 7 ++-- .../test_python_feature_server.py | 10 +++--- .../online_store/test_remote_online_store.py | 4 +-- .../online_store/test_universal_online.py | 13 ++++---- .../test_universal_odfv_feature_inference.py | 7 ++-- .../registration/test_universal_registry.py | 7 ++-- .../registration/test_universal_types.py | 3 +- sdk/python/tests/unit/cli/test_cli_chdir.py | 5 +-- .../tests/unit/local_feast_tests/test_init.py | 5 +-- .../online_store/test_online_retrieval.py | 32 +++++++++---------- sdk/python/tests/unit/test_datetime.py | 6 ++++ sdk/python/tests/unit/test_feature_views.py | 10 +++--- .../tests/unit/test_stream_feature_view.py | 10 +++--- .../tests/utils/basic_read_write_test.py | 7 ++-- .../tests/utils/dynamo_table_creator.py | 5 ++- sdk/python/tests/utils/e2e_test_validation.py | 3 +- .../tests/utils/online_write_benchmark.py | 6 ++-- sdk/python/tests/utils/test_log_creator.py | 8 ++--- 41 files changed, 176 insertions(+), 157 deletions(-) create mode 100644 sdk/python/tests/unit/test_datetime.py diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 6476af5ac85..9600732e176 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -86,6 +86,7 @@ from feast.repo_contents import RepoContents from feast.saved_dataset import SavedDataset, SavedDatasetStorage, ValidationReference from feast.stream_feature_view import StreamFeatureView +from feast.utils import _utc_now from feast.version import get_version warnings.simplefilter("once", DeprecationWarning) @@ -1246,7 +1247,7 @@ def materialize_incremental( >>> from feast import FeatureStore, RepoConfig >>> from datetime import datetime, timedelta >>> fs = FeatureStore(repo_path="project/feature_repo") - >>> fs.materialize_incremental(end_date=datetime.utcnow() - timedelta(minutes=5)) + >>> fs.materialize_incremental(end_date=_utc_now() - timedelta(minutes=5)) Materializing... ... @@ -1270,7 +1271,7 @@ def materialize_incremental( f" either a ttl to be set or for materialize() to have been run at least once." ) elif feature_view.ttl.total_seconds() > 0: - start_date = datetime.utcnow() - feature_view.ttl + start_date = _utc_now() - feature_view.ttl else: # TODO(felixwang9817): Find the earliest timestamp for this specific feature # view from the offline store, and set the start date to that timestamp. @@ -1278,7 +1279,7 @@ def materialize_incremental( f"Since the ttl is 0 for feature view {Style.BRIGHT + Fore.GREEN}{feature_view.name}{Style.RESET_ALL}, " "the start date will be set to 1 year before the current time." ) - start_date = datetime.utcnow() - timedelta(weeks=52) + start_date = _utc_now() - timedelta(weeks=52) provider = self._get_provider() print( f"{Style.BRIGHT + Fore.GREEN}{feature_view.name}{Style.RESET_ALL}" @@ -1335,7 +1336,7 @@ def materialize( >>> from datetime import datetime, timedelta >>> fs = FeatureStore(repo_path="project/feature_repo") >>> fs.materialize( - ... start_date=datetime.utcnow() - timedelta(hours=3), end_date=datetime.utcnow() - timedelta(minutes=10) + ... start_date=_utc_now() - timedelta(hours=3), end_date=_utc_now() - timedelta(minutes=10) ... ) Materializing... diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 36334b606d4..3e4a0f1b997 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -45,7 +45,7 @@ from feast.on_demand_feature_view import OnDemandFeatureView from feast.repo_config import FeastConfigBaseModel, RepoConfig from feast.saved_dataset import SavedDatasetStorage -from feast.utils import get_user_agent +from feast.utils import _utc_now, get_user_agent from .bigquery_source import ( BigQueryLoggingDestination, @@ -701,7 +701,7 @@ def _upload_entity_df( # Ensure that the table expires after some time table = client.get_table(table=table_name) - table.expires = datetime.utcnow() + timedelta(minutes=30) + table.expires = _utc_now() + timedelta(minutes=30) client.update_table(table, ["expires"]) return table diff --git a/sdk/python/feast/infra/offline_stores/contrib/trino_offline_store/trino_queries.py b/sdk/python/feast/infra/offline_stores/contrib/trino_offline_store/trino_queries.py index 50472407bc6..3a26583af24 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/trino_offline_store/trino_queries.py +++ b/sdk/python/feast/infra/offline_stores/contrib/trino_offline_store/trino_queries.py @@ -1,6 +1,5 @@ from __future__ import annotations -import datetime import signal from dataclasses import dataclass from enum import Enum @@ -16,6 +15,7 @@ from feast.infra.offline_stores.contrib.trino_offline_store.trino_type_map import ( trino_to_pa_value_type, ) +from feast.utils import _utc_now class QueryStatus(Enum): @@ -97,12 +97,12 @@ def __init__(self, query_text: str, cursor: Cursor): def execute(self) -> Results: try: self.status = QueryStatus.RUNNING - start_time = datetime.datetime.utcnow() + start_time = _utc_now() self._cursor.execute(operation=self.query_text) rows = self._cursor.fetchall() - end_time = datetime.datetime.utcnow() + end_time = _utc_now() self.execution_time = end_time - start_time self.status = QueryStatus.COMPLETED diff --git a/sdk/python/feast/infra/online_stores/datastore.py b/sdk/python/feast/infra/online_stores/datastore.py index b33767cea56..9ae10792f5a 100644 --- a/sdk/python/feast/infra/online_stores/datastore.py +++ b/sdk/python/feast/infra/online_stores/datastore.py @@ -44,7 +44,7 @@ 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 -from feast.utils import get_user_agent +from feast.utils import _utc_now, get_user_agent LOGGER = logging.getLogger(__name__) @@ -122,7 +122,7 @@ def update( entity = datastore.Entity( key=key, exclude_from_indexes=("created_ts", "event_ts", "values") ) - entity.update({"created_ts": datetime.utcnow()}) + entity.update({"created_ts": _utc_now()}) client.put(entity) for table in tables_to_delete: @@ -457,7 +457,7 @@ def update(self): entity = datastore.Entity( key=key, exclude_from_indexes=("created_ts", "event_ts", "values") ) - entity.update({"created_ts": datetime.utcnow()}) + entity.update({"created_ts": _utc_now()}) client.put(entity) def teardown(self): diff --git a/sdk/python/feast/infra/registry/caching_registry.py b/sdk/python/feast/infra/registry/caching_registry.py index 6336dd7fee5..f7eab7d70a5 100644 --- a/sdk/python/feast/infra/registry/caching_registry.py +++ b/sdk/python/feast/infra/registry/caching_registry.py @@ -1,6 +1,6 @@ import logging from abc import abstractmethod -from datetime import datetime, timedelta +from datetime import timedelta from threading import Lock from typing import List, Optional @@ -15,6 +15,7 @@ from feast.project_metadata import ProjectMetadata from feast.saved_dataset import SavedDataset, ValidationReference from feast.stream_feature_view import StreamFeatureView +from feast.utils import _utc_now logger = logging.getLogger(__name__) @@ -27,7 +28,7 @@ def __init__( ): self.cached_registry_proto = self.proto() proto_registry_utils.init_project_metadata(self.cached_registry_proto, project) - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() self._refresh_lock = Lock() self.cached_registry_proto_ttl = timedelta( seconds=cache_ttl_seconds if cache_ttl_seconds is not None else 0 @@ -318,7 +319,7 @@ def refresh(self, project: Optional[str] = None): self.cached_registry_proto, project ) self.cached_registry_proto = self.proto() - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() def _refresh_cached_registry_if_necessary(self): with self._refresh_lock: @@ -329,7 +330,7 @@ def _refresh_cached_registry_if_necessary(self): self.cached_registry_proto_ttl.total_seconds() > 0 # 0 ttl means infinity and ( - datetime.utcnow() + _utc_now() > ( self.cached_registry_proto_created + self.cached_registry_proto_ttl diff --git a/sdk/python/feast/infra/registry/contrib/azure/azure_registry_store.py b/sdk/python/feast/infra/registry/contrib/azure/azure_registry_store.py index 9c00170b0f6..f9317bf7a45 100644 --- a/sdk/python/feast/infra/registry/contrib/azure/azure_registry_store.py +++ b/sdk/python/feast/infra/registry/contrib/azure/azure_registry_store.py @@ -3,7 +3,6 @@ import os import uuid -from datetime import datetime from pathlib import Path from tempfile import TemporaryFile from urllib.parse import urlparse @@ -11,6 +10,7 @@ from feast.infra.registry.registry import RegistryConfig from feast.infra.registry.registry_store import RegistryStore from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto +from feast.utils import _utc_now REGISTRY_SCHEMA_VERSION = "1" @@ -89,7 +89,7 @@ def teardown(self): def _write_registry(self, registry_proto: RegistryProto): registry_proto.version_id = str(uuid.uuid4()) - registry_proto.last_updated.FromDatetime(datetime.utcnow()) + registry_proto.last_updated.FromDatetime(_utc_now()) file_obj = TemporaryFile() file_obj.write(registry_proto.SerializeToString()) diff --git a/sdk/python/feast/infra/registry/file.py b/sdk/python/feast/infra/registry/file.py index 7117a0d2c6b..ae783bf82c4 100644 --- a/sdk/python/feast/infra/registry/file.py +++ b/sdk/python/feast/infra/registry/file.py @@ -1,10 +1,10 @@ import uuid -from datetime import datetime from pathlib import Path from feast.infra.registry.registry_store import RegistryStore from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.repo_config import RegistryConfig +from feast.utils import _utc_now class FileRegistryStore(RegistryStore): @@ -37,7 +37,7 @@ def teardown(self): def _write_registry(self, registry_proto: RegistryProto): registry_proto.version_id = str(uuid.uuid4()) - registry_proto.last_updated.FromDatetime(datetime.utcnow()) + registry_proto.last_updated.FromDatetime(_utc_now()) file_dir = self._filepath.parent file_dir.mkdir(exist_ok=True) with open(self._filepath, mode="wb", buffering=0) as f: diff --git a/sdk/python/feast/infra/registry/gcs.py b/sdk/python/feast/infra/registry/gcs.py index 7e4b7104cf1..72498ad054d 100644 --- a/sdk/python/feast/infra/registry/gcs.py +++ b/sdk/python/feast/infra/registry/gcs.py @@ -1,5 +1,4 @@ import uuid -from datetime import datetime from pathlib import Path from tempfile import TemporaryFile from urllib.parse import urlparse @@ -7,6 +6,7 @@ from feast.infra.registry.registry_store import RegistryStore from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.repo_config import RegistryConfig +from feast.utils import _utc_now class GCSRegistryStore(RegistryStore): @@ -62,7 +62,7 @@ def teardown(self): def _write_registry(self, registry_proto: RegistryProto): registry_proto.version_id = str(uuid.uuid4()) - registry_proto.last_updated.FromDatetime(datetime.utcnow()) + registry_proto.last_updated.FromDatetime(_utc_now()) # we have already checked the bucket exists so no need to do it again gs_bucket = self.gcs_client.get_bucket(self._bucket) blob = gs_bucket.blob(self._blob) diff --git a/sdk/python/feast/infra/registry/registry.py b/sdk/python/feast/infra/registry/registry.py index 4d6bff4cc7c..fe44e6253a8 100644 --- a/sdk/python/feast/infra/registry/registry.py +++ b/sdk/python/feast/infra/registry/registry.py @@ -47,6 +47,7 @@ from feast.repo_contents import RepoContents from feast.saved_dataset import SavedDataset, ValidationReference from feast.stream_feature_view import StreamFeatureView +from feast.utils import _utc_now REGISTRY_SCHEMA_VERSION = "1" @@ -217,7 +218,7 @@ def clone(self) -> "Registry": if self.cached_registry_proto else RegistryProto() ) - new_registry.cached_registry_proto_created = datetime.utcnow() + new_registry.cached_registry_proto_created = _utc_now() new_registry._registry_store = NoopRegistryStore() return new_registry @@ -248,7 +249,7 @@ def get_infra(self, project: str, allow_cache: bool = False) -> Infra: def apply_entity(self, entity: Entity, project: str, commit: bool = True): entity.is_valid() - now = datetime.utcnow() + now = _utc_now() if not entity.created_timestamp: entity.created_timestamp = now entity.last_updated_timestamp = now @@ -334,7 +335,7 @@ def delete_data_source(self, name: str, project: str, commit: bool = True): def apply_feature_service( self, feature_service: FeatureService, project: str, commit: bool = True ): - now = datetime.utcnow() + now = _utc_now() if not feature_service.created_timestamp: feature_service.created_timestamp = now feature_service.last_updated_timestamp = now @@ -390,7 +391,7 @@ def apply_feature_view( ): feature_view.ensure_valid() - now = datetime.utcnow() + now = _utc_now() if not feature_view.created_timestamp: feature_view.created_timestamp = now feature_view.last_updated_timestamp = now @@ -517,7 +518,7 @@ def apply_materialization( existing_feature_view.materialization_intervals.append( (start_date, end_date) ) - existing_feature_view.last_updated_timestamp = datetime.utcnow() + existing_feature_view.last_updated_timestamp = _utc_now() feature_view_proto = existing_feature_view.to_proto() feature_view_proto.spec.project = project del self.cached_registry_proto.feature_views[idx] @@ -539,7 +540,7 @@ def apply_materialization( existing_stream_feature_view.materialization_intervals.append( (start_date, end_date) ) - existing_stream_feature_view.last_updated_timestamp = datetime.utcnow() + existing_stream_feature_view.last_updated_timestamp = _utc_now() stream_feature_view_proto = existing_stream_feature_view.to_proto() stream_feature_view_proto.spec.project = project del self.cached_registry_proto.stream_feature_views[idx] @@ -664,7 +665,7 @@ def apply_saved_dataset( project: str, commit: bool = True, ): - now = datetime.utcnow() + now = _utc_now() if not saved_dataset.created_timestamp: saved_dataset.created_timestamp = now saved_dataset.last_updated_timestamp = now @@ -812,7 +813,7 @@ def _prepare_registry_for_changes(self, project: str): registry_proto = RegistryProto() registry_proto.registry_schema_version = REGISTRY_SCHEMA_VERSION self.cached_registry_proto = registry_proto - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() # Initialize project metadata if needed assert self.cached_registry_proto @@ -848,7 +849,7 @@ def _get_registry_proto( self.cached_registry_proto_ttl.total_seconds() > 0 # 0 ttl means infinity and ( - datetime.utcnow() + _utc_now() > ( self.cached_registry_proto_created + self.cached_registry_proto_ttl @@ -871,7 +872,7 @@ def _get_registry_proto( logger.info("Registry cache expired, so refreshing") registry_proto = self._registry_store.get_registry_proto() self.cached_registry_proto = registry_proto - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() if not project: return registry_proto diff --git a/sdk/python/feast/infra/registry/s3.py b/sdk/python/feast/infra/registry/s3.py index cbae3af11cc..8aac4d52ee3 100644 --- a/sdk/python/feast/infra/registry/s3.py +++ b/sdk/python/feast/infra/registry/s3.py @@ -1,6 +1,5 @@ import os import uuid -from datetime import datetime from pathlib import Path from tempfile import TemporaryFile from urllib.parse import urlparse @@ -9,6 +8,7 @@ from feast.infra.registry.registry_store import RegistryStore from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.repo_config import RegistryConfig +from feast.utils import _utc_now try: import boto3 @@ -70,7 +70,7 @@ def teardown(self): def _write_registry(self, registry_proto: RegistryProto): registry_proto.version_id = str(uuid.uuid4()) - registry_proto.last_updated.FromDatetime(datetime.utcnow()) + registry_proto.last_updated.FromDatetime(_utc_now()) # we have already checked the bucket exists so no need to do it again file_obj = TemporaryFile() file_obj.write(registry_proto.SerializeToString()) diff --git a/sdk/python/feast/infra/registry/snowflake.py b/sdk/python/feast/infra/registry/snowflake.py index d7ab67e7d0e..f2bc09e7e42 100644 --- a/sdk/python/feast/infra/registry/snowflake.py +++ b/sdk/python/feast/infra/registry/snowflake.py @@ -10,7 +10,6 @@ from pydantic import ConfigDict, Field, StrictStr import feast -from feast import utils from feast.base_feature_view import BaseFeatureView from feast.data_source import DataSource from feast.entity import Entity @@ -54,6 +53,7 @@ from feast.repo_config import RegistryConfig from feast.saved_dataset import SavedDataset, ValidationReference from feast.stream_feature_view import StreamFeatureView +from feast.utils import _utc_now, has_all_tags logger = logging.getLogger(__name__) @@ -126,16 +126,15 @@ def __init__( with GetSnowflakeConnection(self.registry_config) as conn: sql_function_file = f"{os.path.dirname(feast.__file__)}/infra/utils/snowflake/registry/snowflake_table_creation.sql" with open(sql_function_file, "r") as file: - sqlFile = file.read() - - sqlCommands = sqlFile.split(";") - for command in sqlCommands: + sql_file = file.read() + sql_cmds = sql_file.split(";") + for command in sql_cmds: query = command.replace("REGISTRY_PATH", f"{self.registry_path}") execute_snowflake_statement(conn, query) self.cached_registry_proto = self.proto() proto_registry_utils.init_project_metadata(self.cached_registry_proto, project) - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() self._refresh_lock = Lock() self.cached_registry_proto_ttl = timedelta( seconds=registry_config.cache_ttl_seconds @@ -154,7 +153,7 @@ def refresh(self, project: Optional[str] = None): self.cached_registry_proto, project ) self.cached_registry_proto = self.proto() - self.cached_registry_proto_created = datetime.utcnow() + self.cached_registry_proto_created = _utc_now() def _refresh_cached_registry_if_necessary(self): with self._refresh_lock: @@ -165,7 +164,7 @@ def _refresh_cached_registry_if_necessary(self): self.cached_registry_proto_ttl.total_seconds() > 0 # 0 ttl means infinity and ( - datetime.utcnow() + _utc_now() > ( self.cached_registry_proto_created + self.cached_registry_proto_ttl @@ -182,7 +181,6 @@ def teardown(self): sql_function_file = f"{os.path.dirname(feast.__file__)}/infra/utils/snowflake/registry/snowflake_table_deletion.sql" with open(sql_function_file, "r") as file: sqlFile = file.read() - sqlCommands = sqlFile.split(";") for command in sqlCommands: query = command.replace("REGISTRY_PATH", f"{self.registry_path}") @@ -281,7 +279,7 @@ def _apply_object( name = name or (obj.name if hasattr(obj, "name") else None) assert name, f"name needs to be provided for {obj}" - update_datetime = datetime.utcnow() + update_datetime = _utc_now() if hasattr(obj, "last_updated_timestamp"): obj.last_updated_timestamp = update_datetime @@ -416,7 +414,7 @@ def _delete_object( if cursor.rowcount < 1 and not_found_exception: # type: ignore raise not_found_exception(name, project) - self._set_last_updated_metadata(datetime.utcnow(), project) + self._set_last_updated_metadata(_utc_now(), project) return cursor.rowcount @@ -787,7 +785,7 @@ def _list_objects( obj = python_class.from_proto( proto_class.FromString(row[1][proto_field_name]) ) - if utils.has_all_tags(obj.tags, tags): + if has_all_tags(obj.tags, tags): objects.append(obj) return objects return [] diff --git a/sdk/python/feast/infra/registry/sql.py b/sdk/python/feast/infra/registry/sql.py index 239898677c2..6ef08989b76 100644 --- a/sdk/python/feast/infra/registry/sql.py +++ b/sdk/python/feast/infra/registry/sql.py @@ -60,6 +60,7 @@ from feast.repo_config import RegistryConfig from feast.saved_dataset import SavedDataset, ValidationReference from feast.stream_feature_view import StreamFeatureView +from feast.utils import _utc_now metadata = MetaData() @@ -591,7 +592,7 @@ def apply_user_metadata( table.c.project_id == project, ) row = conn.execute(stmt).first() - update_datetime = datetime.utcnow() + update_datetime = _utc_now() update_time = int(update_datetime.timestamp()) if row: values = { @@ -703,7 +704,7 @@ def _apply_object( assert name, f"name needs to be provided for {obj}" with self.engine.begin() as conn: - update_datetime = datetime.utcnow() + update_datetime = _utc_now() update_time = int(update_datetime.timestamp()) stmt = select(table).where( getattr(table.c, id_field_name) == name, table.c.project_id == project @@ -770,7 +771,7 @@ def _apply_object( def _maybe_init_project_metadata(self, project): # Initialize project metadata if needed with self.engine.begin() as conn: - update_datetime = datetime.utcnow() + update_datetime = _utc_now() update_time = int(update_datetime.timestamp()) stmt = select(feast_metadata).where( feast_metadata.c.metadata_key == FeastMetadataKeys.PROJECT_UUID.value, @@ -803,7 +804,7 @@ def _delete_object( rows = conn.execute(stmt) if rows.rowcount < 1 and not_found_exception: raise not_found_exception(name, project) - self._set_last_updated_metadata(datetime.utcnow(), project) + self._set_last_updated_metadata(_utc_now(), project) return rows.rowcount diff --git a/sdk/python/feast/on_demand_feature_view.py b/sdk/python/feast/on_demand_feature_view.py index 839ce4d64ca..586f5d1bac9 100644 --- a/sdk/python/feast/on_demand_feature_view.py +++ b/sdk/python/feast/on_demand_feature_view.py @@ -2,7 +2,6 @@ import functools import inspect import warnings -from datetime import datetime from types import FunctionType from typing import Any, Optional, Union @@ -34,6 +33,7 @@ from feast.transformation.pandas_transformation import PandasTransformation from feast.transformation.python_transformation import PythonTransformation from feast.transformation.substrait_transformation import SubstraitTransformation +from feast.utils import _utc_now from feast.value_type import ValueType warnings.simplefilter("once", DeprecationWarning) @@ -549,7 +549,7 @@ def _construct_random_input(self) -> dict[str, list[Any]]: ValueType.DOUBLE: [1.0], ValueType.FLOAT: [1.0], ValueType.BOOL: [True], - ValueType.UNIX_TIMESTAMP: [datetime.utcnow()], + ValueType.UNIX_TIMESTAMP: [_utc_now()], ValueType.BYTES_LIST: [[str.encode("hello world")]], ValueType.STRING_LIST: [["hello world"]], ValueType.INT32_LIST: [[1]], @@ -557,7 +557,7 @@ def _construct_random_input(self) -> dict[str, list[Any]]: ValueType.DOUBLE_LIST: [[1.0]], ValueType.FLOAT_LIST: [[1.0]], ValueType.BOOL_LIST: [[True]], - ValueType.UNIX_TIMESTAMP_LIST: [[datetime.utcnow()]], + ValueType.UNIX_TIMESTAMP_LIST: [[_utc_now()]], } feature_dict = {} diff --git a/sdk/python/feast/utils.py b/sdk/python/feast/utils.py index a6c893c954c..1a1d757fc16 100644 --- a/sdk/python/feast/utils.py +++ b/sdk/python/feast/utils.py @@ -1052,3 +1052,7 @@ def tags_str_to_dict(tags: str = "") -> dict[str, str]: cast(tuple[str, str], tag.split(":", 1)) for tag in tags_list if ":" in tag ).items() } + + +def _utc_now() -> datetime: + return datetime.utcnow() diff --git a/sdk/python/tests/conftest.py b/sdk/python/tests/conftest.py index fb6b7e56085..1fd510d1048 100644 --- a/sdk/python/tests/conftest.py +++ b/sdk/python/tests/conftest.py @@ -15,7 +15,7 @@ import multiprocessing import os import random -from datetime import datetime, timedelta +from datetime import timedelta from multiprocessing import Process from sys import platform from typing import Any, Dict, List, Tuple, no_type_check @@ -27,6 +27,7 @@ from feast.data_source import DataSource from feast.feature_store import FeatureStore # noqa: E402 +from feast.utils import _utc_now from feast.wait import wait_retry_backoff # noqa: E402 from tests.data.data_creator import ( # noqa: E402 create_basic_driver_dataset, @@ -133,7 +134,7 @@ def pytest_collection_modifyitems(config, items: List[Item]): @pytest.fixture def simple_dataset_1() -> pd.DataFrame: - now = datetime.utcnow() + now = _utc_now() ts = pd.Timestamp(now).round("ms") data = { "id_join_key": [1, 2, 1, 3, 3], @@ -153,7 +154,7 @@ def simple_dataset_1() -> pd.DataFrame: @pytest.fixture def simple_dataset_2() -> pd.DataFrame: - now = datetime.utcnow() + now = _utc_now() ts = pd.Timestamp(now).round("ms") data = { "id_join_key": ["a", "b", "c", "d", "e"], @@ -391,8 +392,8 @@ def fake_ingest_data(): "conv_rate": [0.5], "acc_rate": [0.6], "avg_daily_trips": [4], - "event_timestamp": [pd.Timestamp(datetime.utcnow()).round("ms")], - "created": [pd.Timestamp(datetime.utcnow()).round("ms")], + "event_timestamp": [pd.Timestamp(_utc_now()).round("ms")], + "created": [pd.Timestamp(_utc_now()).round("ms")], } return pd.DataFrame(data) diff --git a/sdk/python/tests/data/data_creator.py b/sdk/python/tests/data/data_creator.py index 1be96f753a7..15d09c5a40a 100644 --- a/sdk/python/tests/data/data_creator.py +++ b/sdk/python/tests/data/data_creator.py @@ -5,6 +5,7 @@ from pytz import timezone, utc from feast.types import FeastType, Float32, Int32, Int64, String +from feast.utils import _utc_now def create_basic_driver_dataset( @@ -13,7 +14,7 @@ def create_basic_driver_dataset( feature_is_list: bool = False, list_has_empty_list: bool = False, ) -> pd.DataFrame: - now = datetime.utcnow().replace(microsecond=0, second=0, minute=0) + now = _utc_now().replace(microsecond=0, second=0, minute=0) ts = pd.Timestamp(now).round("ms") data = { "driver_id": get_entities_for_feast_type(entity_type), @@ -86,14 +87,14 @@ def create_document_dataset() -> pd.DataFrame: "embedding_float": [[4.0, 5.0], [1.0, 2.0], [3.0, 4.0]], "embedding_double": [[4.0, 5.0], [1.0, 2.0], [3.0, 4.0]], "ts": [ - pd.Timestamp(datetime.utcnow()).round("ms"), - pd.Timestamp(datetime.utcnow()).round("ms"), - pd.Timestamp(datetime.utcnow()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), ], "created_ts": [ - pd.Timestamp(datetime.utcnow()).round("ms"), - pd.Timestamp(datetime.utcnow()).round("ms"), - pd.Timestamp(datetime.utcnow()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), + pd.Timestamp(_utc_now()).round("ms"), ], } return pd.DataFrame(data) diff --git a/sdk/python/tests/doctest/test_all.py b/sdk/python/tests/doctest/test_all.py index 814a7ca7985..52348e7da4e 100644 --- a/sdk/python/tests/doctest/test_all.py +++ b/sdk/python/tests/doctest/test_all.py @@ -6,13 +6,14 @@ import unittest import feast +from feast.utils import _utc_now FILES_TO_IGNORE = {"app"} def setup_feature_store(): """Prepares the local environment for a FeatureStore docstring test.""" - from datetime import datetime, timedelta + from datetime import timedelta from feast import Entity, FeatureStore, FeatureView, Field, FileSource from feast.repo_operations import init_repo @@ -42,8 +43,8 @@ def setup_feature_store(): ) fs.apply([driver_hourly_stats_view, driver]) fs.materialize( - start_date=datetime.utcnow() - timedelta(hours=3), - end_date=datetime.utcnow() - timedelta(minutes=10), + start_date=_utc_now() - timedelta(hours=3), + end_date=_utc_now() - timedelta(minutes=10), ) diff --git a/sdk/python/tests/integration/feature_repos/repo_configuration.py b/sdk/python/tests/integration/feature_repos/repo_configuration.py index 9e3c02b9c01..48f5070f1e2 100644 --- a/sdk/python/tests/integration/feature_repos/repo_configuration.py +++ b/sdk/python/tests/integration/feature_repos/repo_configuration.py @@ -21,6 +21,7 @@ ) from feast.infra.feature_servers.local_process.config import LocalFeatureServerConfig from feast.repo_config import RegistryConfig, RepoConfig +from feast.utils import _utc_now from tests.integration.feature_repos.integration_test_repo_config import ( IntegrationTestRepoConfig, RegistryLocation, @@ -412,7 +413,7 @@ class Environment: fixture_request: Optional[pytest.FixtureRequest] = None def __post_init__(self): - self.end_date = datetime.utcnow().replace(microsecond=0, second=0, minute=0) + self.end_date = _utc_now().replace(microsecond=0, second=0, minute=0) self.start_date: datetime = self.end_date - timedelta(days=3) def setup(self): diff --git a/sdk/python/tests/integration/materialization/test_snowflake.py b/sdk/python/tests/integration/materialization/test_snowflake.py index adb2bd7e7df..f12191363b4 100644 --- a/sdk/python/tests/integration/materialization/test_snowflake.py +++ b/sdk/python/tests/integration/materialization/test_snowflake.py @@ -8,6 +8,7 @@ from feast.entity import Entity from feast.feature_view import FeatureView from feast.types import Array, Bool, Bytes, Float64, Int32, Int64, String, UnixTimestamp +from feast.utils import _utc_now from tests.data.data_creator import create_basic_driver_dataset from tests.integration.feature_repos.integration_test_repo_config import ( IntegrationTestRepoConfig, @@ -146,7 +147,7 @@ def test_snowflake_materialization_consistency_internal_with_lists( split_dt = df["ts_1"][4].to_pydatetime() - timedelta(seconds=1) print(f"Split datetime: {split_dt}") - now = datetime.utcnow() + now = _utc_now() full_feature_names = True start_date = (now - timedelta(hours=5)).replace(tzinfo=utc) @@ -231,7 +232,7 @@ def test_snowflake_materialization_entityless_fv(): print(f"Split datetime: {split_dt}") - now = datetime.utcnow() + now = _utc_now() start_date = (now - timedelta(hours=5)).replace(tzinfo=utc) end_date = split_dt diff --git a/sdk/python/tests/integration/offline_store/test_offline_write.py b/sdk/python/tests/integration/offline_store/test_offline_write.py index b8c465946df..63bdc4755ac 100644 --- a/sdk/python/tests/integration/offline_store/test_offline_write.py +++ b/sdk/python/tests/integration/offline_store/test_offline_write.py @@ -1,5 +1,5 @@ import random -from datetime import datetime, timedelta +from datetime import timedelta import numpy as np import pandas as pd @@ -7,6 +7,7 @@ from feast import FeatureView, Field from feast.types import Float32, Int32 +from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, ) @@ -23,7 +24,7 @@ def test_reorder_columns(environment, universal_data_sources): driver_fv = feature_views.driver store.apply([driver(), driver_fv]) - now = datetime.utcnow() + now = _utc_now() ts = pd.Timestamp(now).round("ms") # This dataframe has columns in the wrong order. @@ -53,7 +54,7 @@ def test_writing_incorrect_schema_fails(environment, universal_data_sources): driver_fv = feature_views.driver store.apply([driver(), driver_fv]) - now = datetime.utcnow() + now = _utc_now() ts = pd.Timestamp(now).round("ms") expected_df = pd.DataFrame.from_dict( @@ -91,7 +92,7 @@ def test_writing_consecutively_to_offline_store(environment, universal_data_sour ), # This is to make sure all offline store data is out of date since get_historical_features() only searches backwards for a ttl window. ) - now = datetime.utcnow() + now = _utc_now() ts = pd.Timestamp(now, unit="ns") entity_df = pd.DataFrame.from_dict( diff --git a/sdk/python/tests/integration/offline_store/test_push_features_to_offline_store.py b/sdk/python/tests/integration/offline_store/test_push_features_to_offline_store.py index 0b1db9011a5..5e3d72e671b 100644 --- a/sdk/python/tests/integration/offline_store/test_push_features_to_offline_store.py +++ b/sdk/python/tests/integration/offline_store/test_push_features_to_offline_store.py @@ -1,10 +1,9 @@ -import datetime - import numpy as np import pandas as pd import pytest from feast.data_source import PushMode +from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, ) @@ -20,7 +19,7 @@ def test_push_features_and_read(environment, universal_data_sources): location_fv = feature_views.pushed_locations store.apply([location(), location_fv]) - now = pd.Timestamp(datetime.datetime.utcnow()).round("ms") + now = pd.Timestamp(_utc_now()).round("ms") entity_df = pd.DataFrame.from_dict({"location_id": [1], "event_timestamp": [now]}) before_df = store.get_historical_features( diff --git a/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py b/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py index bfb8a56200a..ecaa5f40db6 100644 --- a/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py +++ b/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py @@ -15,6 +15,7 @@ DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL, ) from feast.types import Float32, Int32 +from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, table_name_from_data_source, @@ -144,11 +145,11 @@ def test_historical_features_main( files = job_from_df.to_remote_storage() assert len(files) # 0 # This test should be way more detailed - start_time = datetime.utcnow() + start_time = _utc_now() actual_df_from_df_entities = job_from_df.to_df() print(f"actual_df_from_df_entities shape: {actual_df_from_df_entities.shape}") - end_time = datetime.utcnow() + end_time = _utc_now() print(str(f"Time to execute job_from_df.to_df() = '{(end_time - start_time)}'\n")) assert sorted(expected_df.columns) == sorted(actual_df_from_df_entities.columns) @@ -303,9 +304,9 @@ def test_historical_features_with_entities_from_query( full_feature_names=full_feature_names, ) - start_time = datetime.utcnow() + start_time = _utc_now() actual_df_from_sql_entities = job_from_sql.to_df() - end_time = datetime.utcnow() + end_time = _utc_now() print(str(f"\nTime to execute job_from_sql.to_df() = '{(end_time - start_time)}'")) event_timestamp = ( @@ -618,11 +619,11 @@ def test_historical_features_containing_backfills(environment): full_feature_names=False, ) - start_time = datetime.utcnow() + start_time = _utc_now() actual_df = offline_job.to_df() print(f"actual_df shape: {actual_df.shape}") - end_time = datetime.utcnow() + end_time = _utc_now() print(str(f"Time to execute job_from_df.to_df() = '{(end_time - start_time)}'\n")) assert sorted(expected_df.columns) == sorted(actual_df.columns) diff --git a/sdk/python/tests/integration/offline_store/test_validation.py b/sdk/python/tests/integration/offline_store/test_validation.py index 1731f823c89..6f0496e8c8d 100644 --- a/sdk/python/tests/integration/offline_store/test_validation.py +++ b/sdk/python/tests/integration/offline_store/test_validation.py @@ -16,7 +16,7 @@ LoggingConfig, ) from feast.protos.feast.serving.ServingService_pb2 import FieldStatus -from feast.utils import make_tzaware +from feast.utils import _utc_now, make_tzaware from feast.wait import wait_retry_backoff from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, @@ -316,8 +316,7 @@ def test_e2e_validation_via_cli(environment, universal_data_sources): "avg_passenger_count": [0], "lifetime_trip_count": [0], "event_timestamp": [ - make_tzaware(datetime.datetime.utcnow()) - - datetime.timedelta(hours=1) + make_tzaware(_utc_now()) - datetime.timedelta(hours=1) ], } ) diff --git a/sdk/python/tests/integration/online_store/test_push_features_to_online_store.py b/sdk/python/tests/integration/online_store/test_push_features_to_online_store.py index 42561563f99..98fe3ab1ec0 100644 --- a/sdk/python/tests/integration/online_store/test_push_features_to_online_store.py +++ b/sdk/python/tests/integration/online_store/test_push_features_to_online_store.py @@ -1,8 +1,7 @@ -import datetime - import pandas as pd import pytest +from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, ) @@ -21,8 +20,8 @@ def test_push_features_and_read(environment, universal_data_sources): data = { "location_id": [1], "temperature": [4], - "event_timestamp": [pd.Timestamp(datetime.datetime.utcnow()).round("ms")], - "created": [pd.Timestamp(datetime.datetime.utcnow()).round("ms")], + "event_timestamp": [pd.Timestamp(_utc_now()).round("ms")], + "created": [pd.Timestamp(_utc_now()).round("ms")], } df_ingest = pd.DataFrame(data) diff --git a/sdk/python/tests/integration/online_store/test_python_feature_server.py b/sdk/python/tests/integration/online_store/test_python_feature_server.py index 089efd7a562..1010e731788 100644 --- a/sdk/python/tests/integration/online_store/test_python_feature_server.py +++ b/sdk/python/tests/integration/online_store/test_python_feature_server.py @@ -1,5 +1,4 @@ import json -from datetime import datetime from typing import List import pytest @@ -7,6 +6,7 @@ from feast.feast_object import FeastObject from feast.feature_server import get_app +from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, ) @@ -67,8 +67,8 @@ def test_push(python_fs_client): "df": { "location_id": [1], "temperature": [initial_temp * 100], - "event_timestamp": [str(datetime.utcnow())], - "created": [str(datetime.utcnow())], + "event_timestamp": [str(_utc_now())], + "created": [str(_utc_now())], }, } ) @@ -98,8 +98,8 @@ def test_push_source_does_not_exist(python_fs_client): "df": { "location_id": [1], "temperature": [initial_temp * 100], - "event_timestamp": [str(datetime.utcnow())], - "created": [str(datetime.utcnow())], + "event_timestamp": [str(_utc_now())], + "created": [str(_utc_now())], }, } ), diff --git a/sdk/python/tests/integration/online_store/test_remote_online_store.py b/sdk/python/tests/integration/online_store/test_remote_online_store.py index 759a9c7a87b..1d5dd0fca0a 100644 --- a/sdk/python/tests/integration/online_store/test_remote_online_store.py +++ b/sdk/python/tests/integration/online_store/test_remote_online_store.py @@ -1,12 +1,12 @@ import os import subprocess import tempfile -from datetime import datetime from textwrap import dedent import pytest from feast.feature_store import FeatureStore +from feast.utils import _utc_now from feast.wait import wait_retry_backoff from tests.utils.cli_repo_creator import CliRunner from tests.utils.http_server import check_port_open, free_port @@ -150,7 +150,7 @@ def _default_store(temp_dir, project_name) -> FeatureStore: fs = FeatureStore(repo_path=repo_path) fs.materialize_incremental( - end_date=datetime.utcnow(), feature_views=["driver_hourly_stats"] + end_date=_utc_now(), feature_views=["driver_hourly_stats"] ) return fs diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index c6b034e2aae..38656b90a9c 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -22,6 +22,7 @@ from feast.infra.utils.postgres.postgres_config import ConnectionType from feast.online_response import TIMESTAMP_POSTFIX from feast.types import Float32, Int32, String +from feast.utils import _utc_now from feast.wait import wait_retry_backoff from tests.integration.feature_repos.repo_configuration import ( Environment, @@ -136,9 +137,9 @@ def test_write_to_online_store_event_check(environment): fs = environment.feature_store # write same data points 3 with different timestamps - now = pd.Timestamp(datetime.datetime.utcnow()).round("ms") - hour_ago = pd.Timestamp(datetime.datetime.utcnow() - timedelta(hours=1)).round("ms") - latest = pd.Timestamp(datetime.datetime.utcnow() + timedelta(seconds=1)).round("ms") + now = pd.Timestamp(_utc_now()).round("ms") + hour_ago = pd.Timestamp(_utc_now() - timedelta(hours=1)).round("ms") + latest = pd.Timestamp(_utc_now() + timedelta(seconds=1)).round("ms") data = { "id": [123, 567, 890], @@ -221,7 +222,7 @@ def test_write_to_online_store_event_check(environment): # writes to online store via datasource (dataframe_source) materialization fs.materialize( start_date=datetime.datetime.now() - timedelta(hours=12), - end_date=datetime.datetime.utcnow(), + end_date=_utc_now(), ) df = fs.get_online_features( @@ -250,8 +251,8 @@ def test_write_to_online_store(environment, universal_data_sources): "conv_rate": [0.85], "acc_rate": [0.91], "avg_daily_trips": [14], - "event_timestamp": [pd.Timestamp(datetime.datetime.utcnow()).round("ms")], - "created": [pd.Timestamp(datetime.datetime.utcnow()).round("ms")], + "event_timestamp": [pd.Timestamp(_utc_now()).round("ms")], + "created": [pd.Timestamp(_utc_now()).round("ms")], } df_data = pd.DataFrame(data) diff --git a/sdk/python/tests/integration/registration/test_universal_odfv_feature_inference.py b/sdk/python/tests/integration/registration/test_universal_odfv_feature_inference.py index ce960b9c358..151f629289f 100644 --- a/sdk/python/tests/integration/registration/test_universal_odfv_feature_inference.py +++ b/sdk/python/tests/integration/registration/test_universal_odfv_feature_inference.py @@ -1,5 +1,3 @@ -from datetime import datetime - import pandas as pd import pytest @@ -7,6 +5,7 @@ from feast.errors import SpecifiedFeaturesNotPresentError from feast.infra.offline_stores.file_source import FileSource from feast.types import Float64 +from feast.utils import _utc_now from tests.integration.feature_repos.universal.entities import customer, driver, item from tests.integration.feature_repos.universal.feature_views import ( conv_rate_plus_100_feature_view, @@ -50,8 +49,8 @@ def test_infer_odfv_list_features(environment, infer_features, tmp_path): "item_id": [0], "embedding_float": [fake_embedding], "embedding_double": [fake_embedding], - "event_timestamp": [pd.Timestamp(datetime.utcnow())], - "created": [pd.Timestamp(datetime.utcnow())], + "event_timestamp": [pd.Timestamp(_utc_now())], + "created": [pd.Timestamp(_utc_now())], } ) output_path = f"{tmp_path}/items.parquet" diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index c119ae800a2..c06ccf2d4d5 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -14,7 +14,7 @@ import logging import os import time -from datetime import datetime, timedelta +from datetime import timedelta from tempfile import mkstemp from unittest import mock @@ -46,6 +46,7 @@ from feast.repo_config import RegistryConfig from feast.stream_feature_view import Aggregation, StreamFeatureView from feast.types import Array, Bytes, Float32, Int32, Int64, String +from feast.utils import _utc_now from feast.value_type import ValueType from tests.integration.feature_repos.universal.entities import driver @@ -745,7 +746,7 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: ) # Simulate materialization - current_date = datetime.utcnow() + current_date = _utc_now() end_date = current_date.replace(tzinfo=utc) start_date = (current_date - timedelta(days=1)).replace(tzinfo=utc) test_registry.apply_materialization(feature_view, project, start_date, end_date) @@ -814,7 +815,7 @@ def odfv1(feature_df: pd.DataFrame) -> pd.DataFrame: ) # Simulate materialization a second time - current_date = datetime.utcnow() + current_date = _utc_now() end_date_1 = current_date.replace(tzinfo=utc) start_date_1 = (current_date - timedelta(days=1)).replace(tzinfo=utc) test_registry.apply_materialization( diff --git a/sdk/python/tests/integration/registration/test_universal_types.py b/sdk/python/tests/integration/registration/test_universal_types.py index ca15681c9b2..928d05ad31e 100644 --- a/sdk/python/tests/integration/registration/test_universal_types.py +++ b/sdk/python/tests/integration/registration/test_universal_types.py @@ -20,6 +20,7 @@ String, UnixTimestamp, ) +from feast.utils import _utc_now from tests.data.data_creator import create_basic_driver_dataset from tests.integration.feature_repos.universal.entities import driver from tests.integration.feature_repos.universal.feature_views import driver_feature_view @@ -93,7 +94,7 @@ def test_feature_get_historical_features_types_match( entity_df = pd.DataFrame() entity_df["driver_id"] = [1, 3] - ts = pd.Timestamp(datetime.utcnow()).round("ms") + ts = pd.Timestamp(_utc_now()).round("ms") entity_df["ts"] = [ ts - timedelta(hours=4), ts - timedelta(hours=2), diff --git a/sdk/python/tests/unit/cli/test_cli_chdir.py b/sdk/python/tests/unit/cli/test_cli_chdir.py index 12ca8f6b084..dd592db0743 100644 --- a/sdk/python/tests/unit/cli/test_cli_chdir.py +++ b/sdk/python/tests/unit/cli/test_cli_chdir.py @@ -1,7 +1,8 @@ import tempfile -from datetime import datetime, timedelta +from datetime import timedelta from pathlib import Path +from feast.utils import _utc_now from tests.utils.cli_repo_creator import CliRunner @@ -29,7 +30,7 @@ def test_cli_chdir() -> None: ) assert result.returncode == 0 - end_date = datetime.utcnow() + end_date = _utc_now() start_date = end_date - timedelta(days=100) result = runner.run( [ diff --git a/sdk/python/tests/unit/local_feast_tests/test_init.py b/sdk/python/tests/unit/local_feast_tests/test_init.py index c5d3cbe57d4..4543a239796 100644 --- a/sdk/python/tests/unit/local_feast_tests/test_init.py +++ b/sdk/python/tests/unit/local_feast_tests/test_init.py @@ -1,8 +1,9 @@ import tempfile -from datetime import datetime, timedelta +from datetime import timedelta from pathlib import Path from textwrap import dedent +from feast.utils import _utc_now from tests.utils.cli_repo_creator import CliRunner @@ -20,7 +21,7 @@ def test_repo_init() -> None: result = runner.run(["apply"], cwd=repo_path) assert result.returncode == 0 - end_date = datetime.utcnow() + end_date = _utc_now() start_date = end_date - timedelta(days=100) result = runner.run( ["materialize", start_date.isoformat(), end_date.isoformat()], cwd=repo_path diff --git a/sdk/python/tests/unit/online_store/test_online_retrieval.py b/sdk/python/tests/unit/online_store/test_online_retrieval.py index 1e8cf45dcc6..0b552c04531 100644 --- a/sdk/python/tests/unit/online_store/test_online_retrieval.py +++ b/sdk/python/tests/unit/online_store/test_online_retrieval.py @@ -3,7 +3,6 @@ import sqlite3 import sys import time -from datetime import datetime import numpy as np import pandas as pd @@ -17,6 +16,7 @@ from feast.protos.feast.types.Value_pb2 import FloatList as FloatListProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import RegistryConfig +from feast.utils import _utc_now from tests.integration.feature_repos.universal.feature_views import TAGS from tests.utils.cli_repo_creator import CliRunner, get_example_repo @@ -51,8 +51,8 @@ def test_get_online_features() -> None: "lat": ValueProto(double_val=0.1), "lon": ValueProto(string_val="1.0"), }, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -72,8 +72,8 @@ def test_get_online_features() -> None: "name": ValueProto(string_val="John"), "age": ValueProto(int64_val=3), }, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -90,8 +90,8 @@ def test_get_online_features() -> None: ( customer_key, {"trips": ValueProto(int64_val=7)}, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -318,8 +318,8 @@ def test_online_to_df(): "lat": ValueProto(double_val=d * lat_multiply), "lon": ValueProto(string_val=str(d * lon_multiply)), }, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -348,8 +348,8 @@ def test_online_to_df(): "name": ValueProto(string_val=name + str(c)), "age": ValueProto(int64_val=c * age_multiply), }, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -372,8 +372,8 @@ def test_online_to_df(): ( combo_keys, {"trips": ValueProto(int64_val=c * d)}, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ], progress=None, @@ -468,8 +468,8 @@ def test_sqlite_get_online_documents() -> None: ) ) }, - datetime.utcnow(), - datetime.utcnow(), + _utc_now(), + _utc_now(), ) ) @@ -488,7 +488,7 @@ def test_sqlite_get_online_documents() -> None: ) for i in range(n) ], - "event_timestamp": [datetime.utcnow() for _ in range(n)], + "event_timestamp": [_utc_now() for _ in range(n)], } ) diff --git a/sdk/python/tests/unit/test_datetime.py b/sdk/python/tests/unit/test_datetime.py new file mode 100644 index 00000000000..aaab507ed0b --- /dev/null +++ b/sdk/python/tests/unit/test_datetime.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + + +""" +Test the retirement of datetime.utcnow() function. +""" diff --git a/sdk/python/tests/unit/test_feature_views.py b/sdk/python/tests/unit/test_feature_views.py index b387f55d8b0..981968df0de 100644 --- a/sdk/python/tests/unit/test_feature_views.py +++ b/sdk/python/tests/unit/test_feature_views.py @@ -1,9 +1,8 @@ -from datetime import datetime, timedelta +from datetime import timedelta import pytest from typeguard import TypeCheckError -from feast import utils from feast.batch_feature_view import BatchFeatureView from feast.data_format import AvroFormat from feast.data_source import KafkaSource @@ -13,6 +12,7 @@ from feast.infra.offline_stores.file_source import FileSource from feast.protos.feast.types.Value_pb2 import ValueType from feast.types import Float32 +from feast.utils import _utc_now, make_tzaware def test_create_feature_view_with_conflicting_entities(): @@ -143,9 +143,9 @@ def test_update_materialization_intervals(): ) assert len(updated_feature_view.materialization_intervals) == 0 - current_time = datetime.utcnow() - start_date = utils.make_tzaware(current_time - timedelta(days=1)) - end_date = utils.make_tzaware(current_time) + current_time = _utc_now() + start_date = make_tzaware(current_time - timedelta(days=1)) + end_date = make_tzaware(current_time) updated_feature_view.materialization_intervals.append((start_date, end_date)) # Update the Feature View, i.e. simply update the name diff --git a/sdk/python/tests/unit/test_stream_feature_view.py b/sdk/python/tests/unit/test_stream_feature_view.py index 77431666c30..4f93691028e 100644 --- a/sdk/python/tests/unit/test_stream_feature_view.py +++ b/sdk/python/tests/unit/test_stream_feature_view.py @@ -1,9 +1,8 @@ import copy -from datetime import datetime, timedelta +from datetime import timedelta import pytest -from feast import utils from feast.aggregation import Aggregation from feast.batch_feature_view import BatchFeatureView from feast.data_format import AvroFormat @@ -16,6 +15,7 @@ ) from feast.stream_feature_view import StreamFeatureView, stream_feature_view from feast.types import Float32 +from feast.utils import _utc_now, make_tzaware def test_create_batch_feature_view(): @@ -286,9 +286,9 @@ def test_update_materialization_intervals(): udf=simple_udf, tags={}, ) - current_time = datetime.utcnow() - start_date = utils.make_tzaware(current_time - timedelta(days=1)) - end_date = utils.make_tzaware(current_time) + current_time = _utc_now() + start_date = make_tzaware(current_time - timedelta(days=1)) + end_date = make_tzaware(current_time) stored_stream_feature_view.materialization_intervals.append((start_date, end_date)) # Update the stream feature view i.e. here it's simply the name diff --git a/sdk/python/tests/utils/basic_read_write_test.py b/sdk/python/tests/utils/basic_read_write_test.py index 5a93a05a1f5..c09a94083f3 100644 --- a/sdk/python/tests/utils/basic_read_write_test.py +++ b/sdk/python/tests/utils/basic_read_write_test.py @@ -1,9 +1,10 @@ -from datetime import datetime, timedelta +from datetime import timedelta from typing import Optional from feast.feature_store import FeatureStore from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto +from feast.utils import _utc_now def basic_rw_test( @@ -65,13 +66,13 @@ def _driver_rw_test(event_ts, created_ts, write, expect_read): """ 1. Basic test: write value, read it back """ - time_1 = datetime.utcnow() + time_1 = _utc_now() _driver_rw_test( event_ts=time_1, created_ts=time_1, write=(1.1, "3.1"), expect_read=(1.1, "3.1") ) """ Values with an new event_ts should overwrite older ones """ - time_3 = datetime.utcnow() + time_3 = _utc_now() _driver_rw_test( event_ts=time_1 + timedelta(hours=1), created_ts=time_3, diff --git a/sdk/python/tests/utils/dynamo_table_creator.py b/sdk/python/tests/utils/dynamo_table_creator.py index 20bac122b37..0ebc939dc11 100644 --- a/sdk/python/tests/utils/dynamo_table_creator.py +++ b/sdk/python/tests/utils/dynamo_table_creator.py @@ -1,11 +1,10 @@ -from datetime import datetime - import boto3 from feast import utils from feast.infra.online_stores.helpers import compute_entity_id from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto +from feast.utils import _utc_now def create_n_customer_test_samples(n=10): @@ -19,7 +18,7 @@ def create_n_customer_test_samples(n=10): "name": ValueProto(string_val="John"), "age": ValueProto(int64_val=3), }, - datetime.utcnow(), + _utc_now(), None, ) for i in range(n) diff --git a/sdk/python/tests/utils/e2e_test_validation.py b/sdk/python/tests/utils/e2e_test_validation.py index d9104bae420..1a8bedc7968 100644 --- a/sdk/python/tests/utils/e2e_test_validation.py +++ b/sdk/python/tests/utils/e2e_test_validation.py @@ -10,6 +10,7 @@ from pytz import utc from feast import FeatureStore, FeatureView, RepoConfig +from feast.utils import _utc_now from tests.integration.feature_repos.integration_test_repo_config import ( IntegrationTestRepoConfig, ) @@ -31,7 +32,7 @@ def validate_offline_online_store_consistency( fs: FeatureStore, fv: FeatureView, split_dt: datetime ) -> None: - now = datetime.utcnow() + now = _utc_now() full_feature_names = True check_offline_store: bool = True diff --git a/sdk/python/tests/utils/online_write_benchmark.py b/sdk/python/tests/utils/online_write_benchmark.py index 8a138f41dbc..9b1a4eb0b27 100644 --- a/sdk/python/tests/utils/online_write_benchmark.py +++ b/sdk/python/tests/utils/online_write_benchmark.py @@ -2,7 +2,7 @@ import random import string import tempfile -from datetime import datetime, timedelta +from datetime import timedelta import click import pyarrow as pa @@ -16,7 +16,7 @@ from feast.field import Field from feast.repo_config import RepoConfig from feast.types import Float32, Int32 -from feast.utils import _convert_arrow_to_proto +from feast.utils import _convert_arrow_to_proto, _utc_now def create_driver_hourly_stats_feature_view(source): @@ -69,7 +69,7 @@ def benchmark_writes(): provider = store._get_provider() - end_date = datetime.utcnow() + end_date = _utc_now() start_date = end_date - timedelta(days=14) customers = list(range(100)) data = create_driver_hourly_stats_df(customers, start_date, end_date) diff --git a/sdk/python/tests/utils/test_log_creator.py b/sdk/python/tests/utils/test_log_creator.py index ec0d92814cb..f072f4c8864 100644 --- a/sdk/python/tests/utils/test_log_creator.py +++ b/sdk/python/tests/utils/test_log_creator.py @@ -8,12 +8,12 @@ import numpy as np import pandas as pd import pyarrow -import pytz from feast import FeatureService, FeatureStore, FeatureView from feast.errors import FeatureViewNotFoundException from feast.feature_logging import LOG_DATE_FIELD, LOG_TIMESTAMP_FIELD, REQUEST_ID_FIELD from feast.protos.feast.serving.ServingService_pb2 import FieldStatus +from feast.utils import _utc_now def get_latest_rows( @@ -64,9 +64,7 @@ def generate_expected_logs( logs[f"{col}__status"] = FieldStatus.PRESENT if feature_view.ttl: logs[f"{col}__status"] = logs[f"{col}__status"].mask( - df[timestamp_column] - < datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) - - feature_view.ttl, + df[timestamp_column] < _utc_now() - feature_view.ttl, FieldStatus.OUTSIDE_MAX_AGE, ) @@ -119,7 +117,7 @@ def prepare_logs( f"{destination_field}__status" ].mask( logs_df[f"{destination_field}__timestamp"] - < (datetime.datetime.utcnow() - view.ttl), + < (_utc_now() - view.ttl), FieldStatus.OUTSIDE_MAX_AGE, ) From b0dc6832ff446429390a916aa9e0e61066cbde1d Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Thu, 4 Jul 2024 12:39:46 -0400 Subject: [PATCH 16/44] chore: Upgrading sqlite_vec to latest package (#4332) Signed-off-by: Francisco Javier Arceo Co-authored-by: Francisco Javier Arceo --- infra/scripts/pixi/pixi.lock | 306 ++++++++++++++++++ infra/scripts/pixi/pixi.toml | 2 +- .../requirements/py3.10-ci-requirements.txt | 2 + .../requirements/py3.10-requirements.txt | 2 + .../requirements/py3.11-ci-requirements.txt | 2 + .../requirements/py3.11-requirements.txt | 2 + .../requirements/py3.9-ci-requirements.txt | 2 + .../requirements/py3.9-requirements.txt | 2 + 8 files changed, 319 insertions(+), 1 deletion(-) diff --git a/infra/scripts/pixi/pixi.lock b/infra/scripts/pixi/pixi.lock index f1ce2d26585..1ca8742026c 100644 --- a/infra/scripts/pixi/pixi.lock +++ b/infra/scripts/pixi/pixi.lock @@ -11,6 +11,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_5.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-h95c4c6d_6.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.1.39-h0ea3d13_0.conda + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-heb59cac_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/uv-0.1.45-h4e38c46_0.conda osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-17.0.6-h5f092b4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.1.45-hc069d6b_0.conda @@ -41,6 +44,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.1.39-h0ea3d13_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-heb59cac_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.10.14-h00d2728_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/uv-0.1.45-h4e38c46_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda @@ -84,6 +102,22 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.1.39-h0ea3d13_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-heb59cac_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.9-h657bba9_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/uv-0.1.45-h4e38c46_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda @@ -127,6 +161,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.1.39-h0ea3d13_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-heb59cac_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.9.19-h7a9c478_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/uv-0.1.45-h4e38c46_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h93a5062_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.2.2-hf0a4a13_0.conda @@ -172,6 +221,19 @@ packages: license_family: BSD size: 23621 timestamp: 1650670423406 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h10d778d_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda + sha256: 61fb2b488928a54d9472113e1280b468a309561caa54f33825a3593da390b242 + md5: 6097a6ca9ada32699b5fc4312dd6ef18 + license: bzip2-1.0.6 + license_family: BSD + size: 127885 + timestamp: 1699280178474 - kind: conda name: bzip2 version: 1.0.8 @@ -222,6 +284,17 @@ packages: license: ISC size: 155725 timestamp: 1706844034242 +- kind: conda + name: ca-certificates + version: 2024.7.4 + build: h8857fd0_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda + sha256: d16f46c489cb3192305c7d25b795333c5fc17bb0986de20598ed519f8c9cc9e4 + md5: 7df874a4b05b2d2b82826190170eaa0f + license: ISC + size: 154473 + timestamp: 1720077510541 - kind: conda name: ld_impl_linux-64 version: '2.40' @@ -264,6 +337,21 @@ packages: license_family: Apache size: 1248885 timestamp: 1715020154867 +- kind: conda + name: libcxx + version: 17.0.6 + build: heb59cac_3 + build_number: 3 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-heb59cac_3.conda + sha256: 9df841c64b19a3843869467ff8ff2eb3f6c5491ebaac8fd94fb8029a5b00dcbf + md5: ef15f182e353155497e13726b915bfc4 + depends: + - __osx >=10.13 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 1250659 + timestamp: 1720040263499 - kind: conda name: libexpat version: 2.6.2 @@ -280,6 +368,20 @@ packages: license_family: MIT size: 73730 timestamp: 1710362120304 +- kind: conda + name: libexpat + version: 2.6.2 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + sha256: a188a77b275d61159a32ab547f7d17892226e7dac4518d2c6ac3ac8fc8dfde92 + md5: 3d1d51c8f716d97c864d12f7af329526 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + size: 69246 + timestamp: 1710362566073 - kind: conda name: libexpat version: 2.6.2 @@ -294,6 +396,19 @@ packages: license_family: MIT size: 63655 timestamp: 1710362424980 +- kind: conda + name: libffi + version: 3.4.2 + build: h0d85af4_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + sha256: 7a2d27a936ceee6942ea4d397f9c7d136f12549d86f7617e8b6bad51e01a941f + md5: ccb34fb14960ad8b125962d3d79b31a9 + license: MIT + license_family: MIT + size: 51348 + timestamp: 1636488394370 - kind: conda name: libffi version: 3.4.2 @@ -429,6 +544,20 @@ packages: license: Unlicense size: 859858 timestamp: 1713367435849 +- kind: conda + name: libsqlite + version: 3.46.0 + build: h1b8f9f3_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda + sha256: 63af1a9e3284c7e4952364bafe7267e41e2d9d8bcc0e85a4ea4b0ec02d3693f6 + md5: 5dadfbc1a567fe6e475df4ce3148be09 + depends: + - __osx >=10.13 + - libzlib >=1.2.13,<2.0a0 + license: Unlicense + size: 908643 + timestamp: 1718050720117 - kind: conda name: libstdcxx-ng version: 13.2.0 @@ -504,6 +633,23 @@ packages: license_family: Other size: 46768 timestamp: 1716874151980 +- kind: conda + name: libzlib + version: 1.3.1 + build: h87427d6_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + sha256: 80a62db652b1da0ccc100812a1d86e94f75028968991bfb17f9536f3aa72d91d + md5: b7575b5aa92108dcc9aaab0f05f2dbce + depends: + - __osx >=10.13 + constrains: + - zlib 1.3.1 *_1 + license: Zlib + license_family: Other + size: 57372 + timestamp: 1716874211519 - kind: conda name: ncurses version: 6.4.20240210 @@ -517,6 +663,17 @@ packages: license: X11 AND BSD-3-Clause size: 895669 timestamp: 1710866638986 +- kind: conda + name: ncurses + version: '6.5' + build: h5846eda_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda + sha256: 6ecc73db0e49143092c0934355ac41583a5d5a48c6914c5f6ca48e562d3a4b79 + md5: 02a888433d165c99bf09784a7b14d900 + license: X11 AND BSD-3-Clause + size: 823601 + timestamp: 1715195267791 - kind: conda name: ncurses version: '6.5' @@ -581,6 +738,24 @@ packages: license_family: Apache size: 2893954 timestamp: 1716468329572 +- kind: conda + name: openssl + version: 3.3.1 + build: h87427d6_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_1.conda + sha256: 60eed5d771207bcef05e0547c8f93a61d0ad1dcf75e19f8f8d9ded8094d78477 + md5: d838ffe9ec3c6d971f110e04487466ff + depends: + - __osx >=10.13 + - ca-certificates + constrains: + - pyopenssl >=22.1 + license: Apache-2.0 + license_family: Apache + size: 2551950 + timestamp: 1719364820943 - kind: conda name: python version: 3.9.19 @@ -610,6 +785,30 @@ packages: license: Python-2.0 size: 23800555 timestamp: 1710940120866 +- kind: conda + name: python + version: 3.9.19 + build: h7a9c478_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.9.19-h7a9c478_0_cpython.conda + sha256: 58b76be84683bc03112b3ed7e377e99af24844ebf7d7568f6466a2dae7a887fe + md5: 7d53d366acd9dbfb498c69326ccb520a + depends: + - bzip2 >=1.0.8,<2.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.45.2,<4.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - ncurses >=6.4.20240210,<7.0a0 + - openssl >=3.2.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.9.* *_cp39 + license: Python-2.0 + size: 12372436 + timestamp: 1710940037648 - kind: conda name: python version: 3.9.19 @@ -634,6 +833,30 @@ packages: license: Python-2.0 size: 11847835 timestamp: 1710939779164 +- kind: conda + name: python + version: 3.10.14 + build: h00d2728_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.10.14-h00d2728_0_cpython.conda + sha256: 00c1de2d46ede26609ef4e84a44b83be7876ba6a0215b7c83bff41a0656bf694 + md5: 0a1cddc4382c5c171e791c70740546dd + depends: + - bzip2 >=1.0.8,<2.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.45.2,<4.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - ncurses >=6.4.20240210,<7.0a0 + - openssl >=3.2.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.10.* *_cp310 + license: Python-2.0 + size: 11890228 + timestamp: 1710940046031 - kind: conda name: python version: 3.10.14 @@ -687,6 +910,32 @@ packages: license: Python-2.0 size: 25517742 timestamp: 1710939725109 +- kind: conda + name: python + version: 3.11.9 + build: h657bba9_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.9-h657bba9_0_cpython.conda + sha256: 3b50a5abb3b812875beaa9ab792dbd1bf44f335c64e9f9fedcf92d953995651c + md5: 612763bc5ede9552e4233ec518b9c9fb + depends: + - __osx >=10.9 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.6.2,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.45.3,<4.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - ncurses >=6.4.20240210,<7.0a0 + - openssl >=3.2.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + size: 15503226 + timestamp: 1713553747073 - kind: conda name: python version: 3.11.9 @@ -774,6 +1023,36 @@ packages: license_family: GPL size: 250351 timestamp: 1679532511311 +- kind: conda + name: readline + version: '8.2' + build: h9e318b2_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + sha256: 41e7d30a097d9b060037f0c6a2b1d4c4ae7e942c06c943d23f9d481548478568 + md5: f17f77f2acf4d344734bda76829ce14e + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 255870 + timestamp: 1679532707590 +- kind: conda + name: tk + version: 8.6.13 + build: h1abcd95_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + sha256: 30412b2e9de4ff82d8c2a7e5d06a15f4f4fef1809a72138b6ccb53a33b26faf5 + md5: bf830ba5afc507c6232d4ef0fb1a882d + depends: + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + size: 3270220 + timestamp: 1699202389792 - kind: conda name: tk version: 8.6.13 @@ -831,6 +1110,22 @@ packages: license: Apache-2.0 OR MIT size: 11891252 timestamp: 1714233659570 +- kind: conda + name: uv + version: 0.1.45 + build: h4e38c46_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/uv-0.1.45-h4e38c46_0.conda + sha256: 8c11774ca1940dcd90187ce240afea26b76e2942f9b18d65f6d4b483534193fd + md5: 754ce8a22c94a30c7bbd42274c7fae31 + depends: + - __osx >=10.13 + - libcxx >=16 + constrains: + - __osx >=10.12 + license: Apache-2.0 OR MIT + size: 8937335 + timestamp: 1716265195083 - kind: conda name: uv version: 0.1.45 @@ -871,3 +1166,14 @@ packages: license: LGPL-2.1 and GPL-2.0 size: 235693 timestamp: 1660346961024 +- kind: conda + name: xz + version: 5.2.6 + build: h775f41a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + sha256: eb09823f34cc2dd663c0ec4ab13f246f45dcd52e5b8c47b9864361de5204a1c8 + md5: a72f9d4ea13d55d745ff1ed594747f10 + license: LGPL-2.1 and GPL-2.0 + size: 238119 + timestamp: 1660346964847 diff --git a/infra/scripts/pixi/pixi.toml b/infra/scripts/pixi/pixi.toml index 10179339f70..487c6f7def1 100644 --- a/infra/scripts/pixi/pixi.toml +++ b/infra/scripts/pixi/pixi.toml @@ -1,7 +1,7 @@ [project] name = "pixi-feast" channels = ["conda-forge"] -platforms = ["linux-64", "osx-arm64"] +platforms = ["linux-64", "osx-arm64", "osx-64"] [tasks] diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 3aa7130ccf2..0eaababc0d9 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -278,6 +278,8 @@ googleapis-common-protos[grpc]==1.63.2 # grpcio-status great-expectations==0.18.16 # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 72124636b63..308123600c3 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -50,6 +50,8 @@ fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 # via feast (setup.py) h11==0.14.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index 673047b5c78..d0663a2beaf 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -269,6 +269,8 @@ googleapis-common-protos[grpc]==1.63.2 # grpcio-status great-expectations==0.18.16 # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index 408f3925150..f21afdb5b1c 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -48,6 +48,8 @@ fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 # via feast (setup.py) h11==0.14.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 83009f8730d..f09c666f42f 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -278,6 +278,8 @@ googleapis-common-protos[grpc]==1.63.2 # grpcio-status great-expectations==0.18.16 # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 3c833438de9..52ff8a0f4ff 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -50,6 +50,8 @@ fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 # via feast (setup.py) h11==0.14.0 From 0d89d1519fc6b8ddd05a2588138e2e85f5a921b1 Mon Sep 17 00:00:00 2001 From: Tom Steenbergen <41334387+TomSteenbergen@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:27:39 +0200 Subject: [PATCH 17/44] fix: Remove redundant batching in PostgreSQLOnlineStore.online_write_batch and fix progress bar (#4331) * Remove batching and fix tqdm progress bar Signed-off-by: TomSteenbergen * Comment Signed-off-by: TomSteenbergen * Remove test changes Signed-off-by: TomSteenbergen * Update comment Signed-off-by: TomSteenbergen --------- Signed-off-by: TomSteenbergen --- .../feast/infra/online_stores/contrib/postgres.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/contrib/postgres.py b/sdk/python/feast/infra/online_stores/contrib/postgres.py index 8715f0f65bb..330b50bc785 100644 --- a/sdk/python/feast/infra/online_stores/contrib/postgres.py +++ b/sdk/python/feast/infra/online_stores/contrib/postgres.py @@ -75,7 +75,6 @@ def online_write_batch( Tuple[EntityKeyProto, Dict[str, ValueProto], datetime, Optional[datetime]] ], progress: Optional[Callable[[int], Any]], - batch_size: int = 5000, ) -> None: # Format insert values insert_values = [] @@ -118,15 +117,13 @@ def online_write_batch( """ ).format(sql.Identifier(_table_id(config.project, table))) - # Push data in batches to online store + # Push data into the online store with self._get_conn(config) as conn, conn.cursor() as cur: - for i in range(0, len(insert_values), batch_size): - cur_batch = insert_values[i : i + batch_size] - cur.executemany(sql_query, cur_batch) - conn.commit() + cur.executemany(sql_query, insert_values) + conn.commit() - if progress: - progress(len(cur_batch)) + if progress: + progress(len(data)) def online_read( self, From cea52e9fb02cb9e0b8f48206278474f5a5fa167e Mon Sep 17 00:00:00 2001 From: Tom Steenbergen <41334387+TomSteenbergen@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:29:43 +0200 Subject: [PATCH 18/44] feat: Add async feature retrieval for Postgres Online Store (#4327) * Add async retrieval for postgres Signed-off-by: TomSteenbergen * Format Signed-off-by: TomSteenbergen * Update _prepare_keys method Signed-off-by: TomSteenbergen * Fix typo Signed-off-by: TomSteenbergen --------- Signed-off-by: TomSteenbergen --- .../infra/online_stores/contrib/postgres.py | 186 ++++++++++++------ .../infra/utils/postgres/connection_utils.py | 25 ++- .../online_store/test_universal_online.py | 2 +- 3 files changed, 150 insertions(+), 63 deletions(-) diff --git a/sdk/python/feast/infra/online_stores/contrib/postgres.py b/sdk/python/feast/infra/online_stores/contrib/postgres.py index 330b50bc785..ff73a4a3473 100644 --- a/sdk/python/feast/infra/online_stores/contrib/postgres.py +++ b/sdk/python/feast/infra/online_stores/contrib/postgres.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import ( Any, + AsyncGenerator, Callable, Dict, Generator, @@ -12,18 +13,24 @@ Optional, Sequence, Tuple, + Union, ) import pytz -from psycopg import sql +from psycopg import AsyncConnection, sql from psycopg.connection import Connection -from psycopg_pool import ConnectionPool +from psycopg_pool import AsyncConnectionPool, ConnectionPool from feast import Entity from feast.feature_view import FeatureView from feast.infra.key_encoding_utils import get_list_val_str, serialize_entity_key from feast.infra.online_stores.online_store import OnlineStore -from feast.infra.utils.postgres.connection_utils import _get_conn, _get_connection_pool +from feast.infra.utils.postgres.connection_utils import ( + _get_conn, + _get_conn_async, + _get_connection_pool, + _get_connection_pool_async, +) from feast.infra.utils.postgres.postgres_config import ConnectionType, PostgreSQLConfig from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto @@ -51,6 +58,9 @@ class PostgreSQLOnlineStore(OnlineStore): _conn: Optional[Connection] = None _conn_pool: Optional[ConnectionPool] = None + _conn_async: Optional[AsyncConnection] = None + _conn_pool_async: Optional[AsyncConnectionPool] = None + @contextlib.contextmanager def _get_conn(self, config: RepoConfig) -> Generator[Connection, Any, Any]: assert config.online_store.type == "postgres" @@ -67,6 +77,24 @@ def _get_conn(self, config: RepoConfig) -> Generator[Connection, Any, Any]: self._conn = _get_conn(config.online_store) yield self._conn + @contextlib.asynccontextmanager + async def _get_conn_async( + self, config: RepoConfig + ) -> AsyncGenerator[AsyncConnection, Any]: + if config.online_store.conn_type == ConnectionType.pool: + if not self._conn_pool_async: + self._conn_pool_async = await _get_connection_pool_async( + config.online_store + ) + await self._conn_pool_async.open() + connection = await self._conn_pool_async.getconn() + yield connection + await self._conn_pool_async.putconn(connection) + else: + if not self._conn_async: + self._conn_async = await _get_conn_async(config.online_store) + yield self._conn_async + def online_write_batch( self, config: RepoConfig, @@ -132,69 +160,107 @@ def online_read( entity_keys: List[EntityKeyProto], requested_features: Optional[List[str]] = None, ) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]: - result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = [] + keys = self._prepare_keys(entity_keys, config.entity_key_serialization_version) + query, params = self._construct_query_and_params( + config, table, keys, requested_features + ) - project = config.project with self._get_conn(config) as conn, conn.cursor() as cur: - # Collecting all the keys to a list allows us to make fewer round trips - # to PostgreSQL - keys = [] - for entity_key in entity_keys: - keys.append( - serialize_entity_key( - entity_key, - entity_key_serialization_version=config.entity_key_serialization_version, - ) - ) + cur.execute(query, params) + rows = cur.fetchall() - if not requested_features: - cur.execute( - sql.SQL( - """ - SELECT entity_key, feature_name, value, event_ts - FROM {} WHERE entity_key = ANY(%s); - """ - ).format( - sql.Identifier(_table_id(project, table)), - ), - (keys,), - ) - else: - cur.execute( - sql.SQL( - """ - SELECT entity_key, feature_name, value, event_ts - FROM {} WHERE entity_key = ANY(%s) and feature_name = ANY(%s); - """ - ).format( - sql.Identifier(_table_id(project, table)), - ), - (keys, requested_features), - ) + return self._process_rows(keys, rows) - rows = cur.fetchall() + async def online_read_async( + self, + config: RepoConfig, + table: FeatureView, + entity_keys: List[EntityKeyProto], + requested_features: Optional[List[str]] = None, + ) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]: + keys = self._prepare_keys(entity_keys, config.entity_key_serialization_version) + query, params = self._construct_query_and_params( + config, table, keys, requested_features + ) - # Since we don't know the order returned from PostgreSQL we'll need - # to construct a dict to be able to quickly look up the correct row - # when we iterate through the keys since they are in the correct order - values_dict = defaultdict(list) - for row in rows if rows is not None else []: - values_dict[ - row[0] if isinstance(row[0], bytes) else row[0].tobytes() - ].append(row[1:]) - - for key in keys: - if key in values_dict: - value = values_dict[key] - res = {} - for feature_name, value_bin, event_ts in value: - val = ValueProto() - val.ParseFromString(bytes(value_bin)) - res[feature_name] = val - result.append((event_ts, res)) - else: - result.append((None, None)) + async with self._get_conn_async(config) as conn: + async with conn.cursor() as cur: + await cur.execute(query, params) + rows = await cur.fetchall() + + return self._process_rows(keys, rows) + + @staticmethod + def _construct_query_and_params( + config: RepoConfig, + table: FeatureView, + keys: List[bytes], + requested_features: Optional[List[str]] = None, + ) -> Tuple[sql.Composed, Union[Tuple[List[bytes], List[str]], Tuple[List[bytes]]]]: + """Construct the SQL query based on the given parameters.""" + if requested_features: + query = sql.SQL( + """ + SELECT entity_key, feature_name, value, event_ts + FROM {} WHERE entity_key = ANY(%s) AND feature_name = ANY(%s); + """ + ).format( + sql.Identifier(_table_id(config.project, table)), + ) + params = (keys, requested_features) + else: + query = sql.SQL( + """ + SELECT entity_key, feature_name, value, event_ts + FROM {} WHERE entity_key = ANY(%s); + """ + ).format( + sql.Identifier(_table_id(config.project, table)), + ) + params = (keys, []) + return query, params + + @staticmethod + def _prepare_keys( + entity_keys: List[EntityKeyProto], entity_key_serialization_version: int + ) -> List[bytes]: + """Prepare all keys in a list to make fewer round trips to the database.""" + return [ + serialize_entity_key( + entity_key, + entity_key_serialization_version=entity_key_serialization_version, + ) + for entity_key in entity_keys + ] + + @staticmethod + def _process_rows( + keys: List[bytes], rows: List[Tuple] + ) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]: + """Transform the retrieved rows in the desired output. + PostgreSQL may return rows in an unpredictable order. Therefore, `values_dict` + is created to quickly look up the correct row using the keys, since these are + actually in the correct order. + """ + values_dict = defaultdict(list) + for row in rows if rows is not None else []: + values_dict[ + row[0] if isinstance(row[0], bytes) else row[0].tobytes() + ].append(row[1:]) + + result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = [] + for key in keys: + if key in values_dict: + value = values_dict[key] + res = {} + for feature_name, value_bin, event_ts in value: + val = ValueProto() + val.ParseFromString(bytes(value_bin)) + res[feature_name] = val + result.append((event_ts, res)) + else: + result.append((None, None)) return result def update( diff --git a/sdk/python/feast/infra/utils/postgres/connection_utils.py b/sdk/python/feast/infra/utils/postgres/connection_utils.py index e0599019b96..7b37ea981f4 100644 --- a/sdk/python/feast/infra/utils/postgres/connection_utils.py +++ b/sdk/python/feast/infra/utils/postgres/connection_utils.py @@ -4,8 +4,8 @@ import pandas as pd import psycopg import pyarrow as pa -from psycopg.connection import Connection -from psycopg_pool import ConnectionPool +from psycopg import AsyncConnection, Connection +from psycopg_pool import AsyncConnectionPool, ConnectionPool from feast.infra.utils.postgres.postgres_config import PostgreSQLConfig from feast.type_map import arrow_to_pg_type @@ -21,6 +21,16 @@ def _get_conn(config: PostgreSQLConfig) -> Connection: return conn +async def _get_conn_async(config: PostgreSQLConfig) -> AsyncConnection: + """Get a psycopg `AsyncConnection`.""" + conn = await psycopg.AsyncConnection.connect( + conninfo=_get_conninfo(config), + keepalives_idle=config.keepalives_idle, + **_get_conn_kwargs(config), + ) + return conn + + def _get_connection_pool(config: PostgreSQLConfig) -> ConnectionPool: """Get a psycopg `ConnectionPool`.""" return ConnectionPool( @@ -32,6 +42,17 @@ def _get_connection_pool(config: PostgreSQLConfig) -> ConnectionPool: ) +async def _get_connection_pool_async(config: PostgreSQLConfig) -> AsyncConnectionPool: + """Get a psycopg `AsyncConnectionPool`.""" + return AsyncConnectionPool( + conninfo=_get_conninfo(config), + min_size=config.min_conn, + max_size=config.max_conn, + open=False, + kwargs=_get_conn_kwargs(config), + ) + + def _get_conninfo(config: PostgreSQLConfig) -> str: """Get the `conninfo` argument required for connection objects.""" return ( diff --git a/sdk/python/tests/integration/online_store/test_universal_online.py b/sdk/python/tests/integration/online_store/test_universal_online.py index 38656b90a9c..2ffe869ef50 100644 --- a/sdk/python/tests/integration/online_store/test_universal_online.py +++ b/sdk/python/tests/integration/online_store/test_universal_online.py @@ -488,7 +488,7 @@ def test_online_retrieval_with_event_timestamps(environment, universal_data_sour @pytest.mark.integration -@pytest.mark.universal_online_stores(only=["redis", "dynamodb"]) +@pytest.mark.universal_online_stores(only=["redis", "dynamodb", "postgres"]) def test_async_online_retrieval_with_event_timestamps( environment, universal_data_sources ): From f5697863669a6bb9dbd491f79192e8ddd0073388 Mon Sep 17 00:00:00 2001 From: stanconia <36575269+stanconia@users.noreply.github.com> Date: Tue, 9 Jul 2024 05:25:29 -0400 Subject: [PATCH 19/44] feat: Add Async refresh to Sql Registry (#4251) * Add sql registry async refresh Signed-off-by: Stanley Opara * make refresh code a daemon thread Signed-off-by: Stanley Opara * Change RegistryConfig to cacheMode Signed-off-by: Stanley Opara * Only run async when ttl > 0 Signed-off-by: Stanley Opara * make refresh async run in a loop Signed-off-by: Stanley Opara * make refresh async run in a loop Signed-off-by: Stanley Opara * Reorder async refresh call Signed-off-by: Stanley Opara * Add documentation Signed-off-by: Stanley Opara * Update test_universal_registry.py Signed-off-by: Stanley Opara * Force rerun of tests Signed-off-by: Stanley Opara * Force rerun of tests Signed-off-by: Stanley Opara * Format repo config file Signed-off-by: Stanley Opara --------- Signed-off-by: Stanley Opara Co-authored-by: Stanley Opara --- .../feast/infra/registry/caching_registry.py | 57 +++++---- sdk/python/feast/infra/registry/sql.py | 4 +- sdk/python/feast/repo_config.py | 3 + .../registration/test_universal_registry.py | 113 ++++++++++++++++-- 4 files changed, 145 insertions(+), 32 deletions(-) diff --git a/sdk/python/feast/infra/registry/caching_registry.py b/sdk/python/feast/infra/registry/caching_registry.py index f7eab7d70a5..298639028d5 100644 --- a/sdk/python/feast/infra/registry/caching_registry.py +++ b/sdk/python/feast/infra/registry/caching_registry.py @@ -1,4 +1,6 @@ +import atexit import logging +import threading from abc import abstractmethod from datetime import timedelta from threading import Lock @@ -21,11 +23,7 @@ class CachingRegistry(BaseRegistry): - def __init__( - self, - project: str, - cache_ttl_seconds: int, - ): + def __init__(self, project: str, cache_ttl_seconds: int, cache_mode: str): self.cached_registry_proto = self.proto() proto_registry_utils.init_project_metadata(self.cached_registry_proto, project) self.cached_registry_proto_created = _utc_now() @@ -33,6 +31,10 @@ def __init__( self.cached_registry_proto_ttl = timedelta( seconds=cache_ttl_seconds if cache_ttl_seconds is not None else 0 ) + self.cache_mode = cache_mode + if cache_mode == "thread": + self._start_thread_async_refresh(cache_ttl_seconds) + atexit.register(self._exit_handler) @abstractmethod def _get_data_source(self, name: str, project: str) -> DataSource: @@ -322,22 +324,35 @@ def refresh(self, project: Optional[str] = None): self.cached_registry_proto_created = _utc_now() def _refresh_cached_registry_if_necessary(self): - with self._refresh_lock: - expired = ( - self.cached_registry_proto is None - or self.cached_registry_proto_created is None - ) or ( - self.cached_registry_proto_ttl.total_seconds() - > 0 # 0 ttl means infinity - and ( - _utc_now() - > ( - self.cached_registry_proto_created - + self.cached_registry_proto_ttl + if self.cache_mode == "sync": + with self._refresh_lock: + expired = ( + self.cached_registry_proto is None + or self.cached_registry_proto_created is None + ) or ( + self.cached_registry_proto_ttl.total_seconds() + > 0 # 0 ttl means infinity + and ( + _utc_now() + > ( + self.cached_registry_proto_created + + self.cached_registry_proto_ttl + ) ) ) - ) + if expired: + logger.info("Registry cache expired, so refreshing") + self.refresh() + + def _start_thread_async_refresh(self, cache_ttl_seconds): + self.refresh() + if cache_ttl_seconds <= 0: + return + self.registry_refresh_thread = threading.Timer( + cache_ttl_seconds, self._start_thread_async_refresh, [cache_ttl_seconds] + ) + self.registry_refresh_thread.setDaemon(True) + self.registry_refresh_thread.start() - if expired: - logger.info("Registry cache expired, so refreshing") - self.refresh() + def _exit_handler(self): + self.registry_refresh_thread.cancel() diff --git a/sdk/python/feast/infra/registry/sql.py b/sdk/python/feast/infra/registry/sql.py index 6ef08989b76..a2b16a3a091 100644 --- a/sdk/python/feast/infra/registry/sql.py +++ b/sdk/python/feast/infra/registry/sql.py @@ -193,7 +193,9 @@ def __init__( ) metadata.create_all(self.engine) super().__init__( - project=project, cache_ttl_seconds=registry_config.cache_ttl_seconds + project=project, + cache_ttl_seconds=registry_config.cache_ttl_seconds, + cache_mode=registry_config.cache_mode, ) def teardown(self): diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 8d6bff28187..137023ef226 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -124,6 +124,9 @@ class RegistryConfig(FeastBaseModel): sqlalchemy_config_kwargs: Dict[str, Any] = {} """ Dict[str, Any]: Extra arguments to pass to SQLAlchemy.create_engine. """ + cache_mode: StrictStr = "sync" + """ str: Cache mode type, Possible options are sync and thread(asynchronous caching using threading library)""" + @field_validator("path") def validate_path(cls, path: str, values: ValidationInfo) -> str: if values.data.get("registry_type") == "sql": diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index c06ccf2d4d5..b0738c84190 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -125,7 +125,7 @@ def minio_registry() -> Registry: logger = logging.getLogger(__name__) -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def pg_registry(): container = ( DockerContainer("postgres:latest") @@ -137,6 +137,35 @@ def pg_registry(): container.start() + registry_config = _given_registry_config_for_pg_sql(container) + + yield SqlRegistry(registry_config, "project", None) + + container.stop() + + +@pytest.fixture(scope="function") +def pg_registry_async(): + container = ( + DockerContainer("postgres:latest") + .with_exposed_ports(5432) + .with_env("POSTGRES_USER", POSTGRES_USER) + .with_env("POSTGRES_PASSWORD", POSTGRES_PASSWORD) + .with_env("POSTGRES_DB", POSTGRES_DB) + ) + + container.start() + + registry_config = _given_registry_config_for_pg_sql(container, 2, "thread") + + yield SqlRegistry(registry_config, "project", None) + + container.stop() + + +def _given_registry_config_for_pg_sql( + container, cache_ttl_seconds=2, cache_mode="sync" +): log_string_to_wait_for = "database system is ready to accept connections" waited = wait_for_logs( container=container, @@ -148,25 +177,42 @@ def pg_registry(): container_port = container.get_exposed_port(5432) container_host = container.get_container_host_ip() - registry_config = RegistryConfig( + return RegistryConfig( registry_type="sql", + cache_ttl_seconds=cache_ttl_seconds, + cache_mode=cache_mode, # The `path` must include `+psycopg` in order for `sqlalchemy.create_engine()` # to understand that we are using psycopg3. path=f"postgresql+psycopg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{container_host}:{container_port}/{POSTGRES_DB}", sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, ) + +@pytest.fixture(scope="function") +def mysql_registry(): + container = MySqlContainer("mysql:latest") + container.start() + + registry_config = _given_registry_config_for_mysql(container) + yield SqlRegistry(registry_config, "project", None) container.stop() -@pytest.fixture(scope="session") -def mysql_registry(): +@pytest.fixture(scope="function") +def mysql_registry_async(): container = MySqlContainer("mysql:latest") container.start() - # testing for the database to exist and ready to connect and start testing. + registry_config = _given_registry_config_for_mysql(container, 2, "thread") + + yield SqlRegistry(registry_config, "project", None) + + container.stop() + + +def _given_registry_config_for_mysql(container, cache_ttl_seconds=2, cache_mode="sync"): import sqlalchemy engine = sqlalchemy.create_engine( @@ -174,16 +220,14 @@ def mysql_registry(): ) engine.connect() - registry_config = RegistryConfig( + return RegistryConfig( registry_type="sql", path=container.get_connection_url(), + cache_ttl_seconds=cache_ttl_seconds, + cache_mode=cache_mode, sqlalchemy_config_kwargs={"echo": False, "pool_pre_ping": True}, ) - yield SqlRegistry(registry_config, "project", None) - - container.stop() - @pytest.fixture(scope="session") def sqlite_registry(): @@ -269,6 +313,17 @@ def mock_remote_registry(): lazy_fixture("sqlite_registry"), ] +async_sql_fixtures = [ + pytest.param( + lazy_fixture("pg_registry_async"), + marks=pytest.mark.xdist_group(name="pg_registry_async"), + ), + pytest.param( + lazy_fixture("mysql_registry_async"), + marks=pytest.mark.xdist_group(name="mysql_registry_async"), + ), +] + @pytest.mark.integration @pytest.mark.parametrize("test_registry", all_fixtures) @@ -999,6 +1054,44 @@ def test_registry_cache(test_registry): test_registry.teardown() +@pytest.mark.integration +@pytest.mark.parametrize( + "test_registry", + async_sql_fixtures, +) +def test_registry_cache_thread_async(test_registry): + # Create Feature View + batch_source = FileSource( + name="test_source", + file_format=ParquetFormat(), + path="file://feast/*", + timestamp_field="ts_col", + created_timestamp_column="timestamp", + ) + + project = "project" + + # Register data source + test_registry.apply_data_source(batch_source, project) + registry_data_sources_cached = test_registry.list_data_sources( + project, allow_cache=True + ) + # async ttl yet to expire, so there will be a cache miss + assert len(registry_data_sources_cached) == 0 + + # Wait for cache to be refreshed + time.sleep(4) + # Now objects exist + registry_data_sources_cached = test_registry.list_data_sources( + project, allow_cache=True + ) + assert len(registry_data_sources_cached) == 1 + registry_data_source = registry_data_sources_cached[0] + assert registry_data_source == batch_source + + test_registry.teardown() + + @pytest.mark.integration @pytest.mark.parametrize( "test_registry", From 8e8c1f2ff9a77738e71542cbaab9531f321842a4 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Wed, 10 Jul 2024 09:20:41 -0400 Subject: [PATCH 20/44] feat: Add Tornike to maintainers.md (#4339) * Update maintainers.md * Update maintainers.md * Update maintainers.md --- community/maintainers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/community/maintainers.md b/community/maintainers.md index 0b3d4ab6480..5ccd347be00 100644 --- a/community/maintainers.md +++ b/community/maintainers.md @@ -16,6 +16,7 @@ In alphabetical order | Shuchu Han | `shuchu` | shuchu.han@gmail.com | Independent | | Willem Pienaar | `woop` | will.pienaar@gmail.com | Cleric | | Zhiling Chen | `zhilingc` | chnzhlng@gmail.com | GetGround | +| Tornike Gurgenidze | `tokoko` | togurgenidze@gmail.com | Bank of Georgia | ## Emeritus Maintainers From 71afd1c31d2a0ebf033f72aaf27eba8b9d66d4db Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Wed, 10 Jul 2024 10:10:40 -0400 Subject: [PATCH 21/44] docs: Update SUMMARY.md (#4340) Update SUMMARY.md --- docs/SUMMARY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index a40c60d97c0..5a82a190fe7 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -121,7 +121,8 @@ * [\[Alpha\] Go feature server](reference/feature-servers/go-feature-server.md) * [Offline Feature Server](reference/feature-servers/offline-feature-server) * [\[Beta\] Web UI](reference/alpha-web-ui.md) -* [\[Alpha\] On demand feature view](reference/alpha-on-demand-feature-view.md) +* [\[Beta\] On demand feature view](reference/beta-on-demand-feature-view.md) +* [\[Alpha\] Vector Database](reference/alpha-vector-database.md) * [\[Alpha\] Data quality monitoring](reference/dqm.md) * [Feast CLI reference](reference/feast-cli-commands.md) * [Python API reference](http://rtd.feast.dev) From 96613c108ad3f42ca38f72c25130319eef2568c6 Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Wed, 10 Jul 2024 19:19:55 +0400 Subject: [PATCH 22/44] chore: Upgrade ibis to 9.0 (#4330) upgrade ibis version Signed-off-by: tokoko --- sdk/python/feast/infra/offline_stores/ibis.py | 7 +- .../requirements/py3.10-ci-requirements.txt | 167 ++---------------- .../requirements/py3.10-requirements.txt | 46 +---- .../requirements/py3.11-ci-requirements.txt | 167 ++---------------- .../requirements/py3.11-requirements.txt | 46 +---- .../requirements/py3.9-ci-requirements.txt | 167 ++---------------- .../requirements/py3.9-requirements.txt | 46 +---- setup.py | 6 +- 8 files changed, 80 insertions(+), 572 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/ibis.py b/sdk/python/feast/infra/offline_stores/ibis.py index 6cc1606a458..dd81fd1d4e3 100644 --- a/sdk/python/feast/infra/offline_stores/ibis.py +++ b/sdk/python/feast/infra/offline_stores/ibis.py @@ -335,11 +335,8 @@ def deduplicate( if created_timestamp_col: order_by_fields.append(ibis.desc(table[created_timestamp_col])) - table = ( - table.group_by(by=group_by_cols) - .order_by(order_by_fields) - .mutate(rn=ibis.row_number()) - ) + window = ibis.window(group_by=group_by_cols, order_by=order_by_fields, following=0) + table = table.mutate(rn=ibis.row_number().over(window)) return table.filter(table["rn"] == ibis.literal(0)).drop("rn") diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 0eaababc0d9..e6e66ac2ee6 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -52,9 +48,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -66,9 +60,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -77,7 +69,6 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -85,7 +76,6 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.6.2 # via # elastic-transport @@ -108,7 +98,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -118,9 +107,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -129,7 +116,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -141,9 +127,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -155,9 +139,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -167,15 +149,10 @@ docker==7.1.0 docutils==0.19 # via sphinx duckdb==0.10.3 - # via - # duckdb-engine - # ibis-framework -duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -190,7 +167,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -200,7 +176,6 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -208,16 +183,13 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -242,11 +214,8 @@ google-auth==2.30.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -255,13 +224,10 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin + # via firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -272,19 +238,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) greenlet==3.0.3 # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -295,27 +258,19 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -326,15 +281,11 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab -ibis-framework[duckdb]==8.0.0 - # via - # feast (setup.py) - # ibis-substrait -ibis-substrait==3.2.0 - # via feast (setup.py) +ibis-framework[duckdb]==9.1.0 + # via ibis-substrait +ibis-substrait==4.0.0 identify==2.5.36 # via pre-commit idna==3.7 @@ -369,7 +320,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -393,7 +343,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -439,7 +388,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -460,17 +408,13 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -483,16 +427,11 @@ multidict==6.0.5 # via # aiohttp # yarl -multipledispatch==1.0.0 - # via ibis-framework mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -515,7 +454,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -535,7 +473,6 @@ packaging==24.1 # build # dask # db-dtypes - # duckdb-engine # google-cloud-bigquery # great-expectations # gunicorn @@ -551,7 +488,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -577,7 +513,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -590,7 +525,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -605,7 +539,6 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -623,11 +556,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.19 - # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -639,14 +569,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -664,19 +592,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -687,11 +612,8 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -703,10 +625,8 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -716,21 +636,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -759,7 +671,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -773,19 +684,15 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core # cachecontrol # docker @@ -820,7 +727,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -830,7 +736,6 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -847,7 +752,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -868,13 +772,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -888,17 +790,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via - # feast (setup.py) - # duckdb-engine - # ibis-framework - # sqlalchemy-views -sqlalchemy-views==0.3.2 - # via ibis-framework -sqlglot==20.11.0 +sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -908,21 +802,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via # build @@ -950,9 +840,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -969,39 +857,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -1040,7 +914,6 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1052,15 +925,11 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 308123600c3..99c9bfc3fee 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -20,22 +20,17 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -43,9 +38,7 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 @@ -53,7 +46,6 @@ fsspec==2024.3.1 greenlet==3.0.3 # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -73,11 +65,8 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -89,16 +78,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -110,29 +96,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -143,7 +120,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -151,7 +127,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -167,15 +142,11 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -183,9 +154,7 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -209,7 +178,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index d0663a2beaf..fa1f24a586c 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -48,9 +44,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -62,9 +56,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -73,7 +65,6 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -81,7 +72,6 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.6.2 # via # elastic-transport @@ -104,7 +94,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -114,9 +103,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -125,7 +112,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -137,9 +123,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -151,9 +135,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -163,15 +145,10 @@ docker==7.1.0 docutils==0.19 # via sphinx duckdb==0.10.3 - # via - # duckdb-engine - # ibis-framework -duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -181,7 +158,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -191,7 +167,6 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -199,16 +174,13 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -233,11 +205,8 @@ google-auth==2.30.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -246,13 +215,10 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin + # via firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -263,19 +229,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) greenlet==3.0.3 # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -286,27 +249,19 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -317,15 +272,11 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab -ibis-framework[duckdb]==8.0.0 - # via - # feast (setup.py) - # ibis-substrait -ibis-substrait==3.2.0 - # via feast (setup.py) +ibis-framework[duckdb]==9.1.0 + # via ibis-substrait +ibis-substrait==4.0.0 identify==2.5.36 # via pre-commit idna==3.7 @@ -360,7 +311,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -384,7 +334,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -430,7 +379,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -451,17 +399,13 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -474,16 +418,11 @@ multidict==6.0.5 # via # aiohttp # yarl -multipledispatch==1.0.0 - # via ibis-framework mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -506,7 +445,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -526,7 +464,6 @@ packaging==24.1 # build # dask # db-dtypes - # duckdb-engine # google-cloud-bigquery # great-expectations # gunicorn @@ -542,7 +479,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -568,7 +504,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -581,7 +516,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -596,7 +530,6 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -614,11 +547,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.19 - # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -630,14 +560,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -655,19 +583,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -678,11 +603,8 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -694,10 +616,8 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -707,21 +627,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -750,7 +662,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -764,19 +675,15 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core # cachecontrol # docker @@ -811,7 +718,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -821,7 +727,6 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -838,7 +743,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -859,13 +763,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -879,17 +781,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via - # feast (setup.py) - # duckdb-engine - # ibis-framework - # sqlalchemy-views -sqlalchemy-views==0.3.2 - # via ibis-framework -sqlglot==20.11.0 +sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -899,21 +793,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -931,9 +821,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -950,39 +838,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -1018,7 +892,6 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1030,15 +903,11 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index f21afdb5b1c..c34b610d14c 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -20,30 +20,23 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 # via fastapi fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 @@ -51,7 +44,6 @@ fsspec==2024.3.1 greenlet==3.0.3 # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -71,11 +63,8 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -87,16 +76,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -108,29 +94,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -141,7 +118,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -149,7 +125,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -165,23 +140,17 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) toolz==0.12.1 # via # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -203,7 +172,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index f09c666f42f..ba4c6c989d8 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -52,9 +48,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -66,9 +60,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -77,7 +69,6 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -85,7 +76,6 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.6.2 # via # elastic-transport @@ -108,7 +98,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -118,9 +107,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -129,7 +116,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -141,9 +127,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -155,9 +139,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -167,15 +149,10 @@ docker==7.1.0 docutils==0.19 # via sphinx duckdb==0.10.3 - # via - # duckdb-engine - # ibis-framework -duckdb-engine==0.13.0 # via ibis-framework elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -190,7 +167,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -200,7 +176,6 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -208,16 +183,13 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -242,11 +214,8 @@ google-auth==2.30.0 google-auth-httplib2==0.2.0 # via google-api-python-client google-cloud-bigquery[pandas]==3.12.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -255,13 +224,10 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin + # via firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -272,19 +238,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) greenlet==3.0.3 # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -295,27 +258,19 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -326,15 +281,11 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab -ibis-framework[duckdb]==8.0.0 - # via - # feast (setup.py) - # ibis-substrait -ibis-substrait==3.2.0 - # via feast (setup.py) +ibis-framework[duckdb]==9.0.0 + # via ibis-substrait +ibis-substrait==4.0.0 identify==2.5.36 # via pre-commit idna==3.7 @@ -378,7 +329,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -402,7 +352,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -448,7 +397,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -469,17 +417,13 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -492,16 +436,11 @@ multidict==6.0.5 # via # aiohttp # yarl -multipledispatch==1.0.0 - # via ibis-framework mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -524,7 +463,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -544,7 +482,6 @@ packaging==24.1 # build # dask # db-dtypes - # duckdb-engine # google-cloud-bigquery # great-expectations # gunicorn @@ -560,7 +497,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -586,7 +522,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -599,7 +534,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -614,7 +548,6 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -632,11 +565,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.18 - # via feast (setup.py) psycopg-binary==3.1.18 # via psycopg psycopg-pool==3.2.2 @@ -648,14 +578,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -673,19 +601,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -696,11 +621,8 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -712,10 +634,8 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -725,21 +645,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -768,7 +680,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -782,19 +693,15 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core # cachecontrol # docker @@ -829,7 +736,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -841,7 +747,6 @@ ruamel-yaml==0.17.17 ruamel-yaml-clib==0.2.8 # via ruamel-yaml ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.13.1 @@ -858,7 +763,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -879,13 +783,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -899,17 +801,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via - # feast (setup.py) - # duckdb-engine - # ibis-framework - # sqlalchemy-views -sqlalchemy-views==0.3.2 - # via ibis-framework -sqlglot==20.11.0 +sqlglot==23.12.2 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -919,21 +813,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via # build @@ -961,9 +851,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -980,39 +868,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -1053,7 +927,6 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1066,15 +939,11 @@ urllib3==1.26.19 # snowflake-connector-python # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 52ff8a0f4ff..149a96626ef 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -20,22 +20,17 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -43,9 +38,7 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 @@ -53,7 +46,6 @@ fsspec==2024.3.1 greenlet==3.0.3 # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -75,11 +67,8 @@ importlib-metadata==7.1.0 # dask # typeguard jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -91,16 +80,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -112,29 +98,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -145,7 +122,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -153,7 +129,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -169,15 +144,11 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -185,9 +156,7 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -212,7 +181,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/setup.py b/setup.py index 958e93799d9..f7fa610fbdd 100644 --- a/setup.py +++ b/setup.py @@ -138,8 +138,8 @@ ] IBIS_REQUIRED = [ - "ibis-framework>=8.0.0,<9", - "ibis-substrait<=3.2.0", + "ibis-framework>=9.0.0,<10", + "ibis-substrait>=4.0.0", ] GRPCIO_REQUIRED = [ @@ -149,7 +149,7 @@ "grpcio-health-checking>=1.56.2,<2", ] -DUCKDB_REQUIRED = ["ibis-framework[duckdb]>=8.0.0,<9"] +DUCKDB_REQUIRED = ["ibis-framework[duckdb]>=9.0.0,<10"] DELTA_REQUIRED = ["deltalake"] From a639d617c047030f75c6950e9bfa6e5cfe63daaa Mon Sep 17 00:00:00 2001 From: Hao Xu Date: Wed, 10 Jul 2024 08:22:13 -0700 Subject: [PATCH 23/44] fix: Update dask version to support pandas 1.x (#4326) * update dask version to support pandas 1.x Signed-off-by: cmuhao * update dask version to support pandas 1.x Signed-off-by: cmuhao * update dask version to support pandas 1.x Signed-off-by: cmuhao --------- Signed-off-by: cmuhao --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f7fa610fbdd..a5432628210 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ "fastapi>=0.68.0", "uvicorn[standard]>=0.14.0,<1", "gunicorn; platform_system != 'Windows'", - "dask[dataframe]>=2024.4.2", + "dask[dataframe]>=2024.2.1", ] GCP_REQUIRED = [ From c45ff72f821404c595477e696ab4be1b888090cc Mon Sep 17 00:00:00 2001 From: Alex Mirrington <34053287+alexmirrington@users.noreply.github.com> Date: Thu, 11 Jul 2024 03:18:28 +1000 Subject: [PATCH 24/44] fix: OnDemandFeatureView type inference for array types (#4310) Fix OnDemandFeatureView type inference for array types Signed-off-by: Alex Mirrington --- .../transformation/pandas_transformation.py | 28 +- .../transformation/python_transformation.py | 27 +- .../substrait_transformation.py | 32 ++- sdk/python/feast/type_map.py | 1 + .../test_on_demand_pandas_transformation.py | 254 +++++++++++++++++- .../test_on_demand_python_transformation.py | 243 ++++++++++++++++- 6 files changed, 555 insertions(+), 30 deletions(-) diff --git a/sdk/python/feast/transformation/pandas_transformation.py b/sdk/python/feast/transformation/pandas_transformation.py index e9dab721608..41e437fb6b7 100644 --- a/sdk/python/feast/transformation/pandas_transformation.py +++ b/sdk/python/feast/transformation/pandas_transformation.py @@ -40,15 +40,27 @@ def infer_features(self, random_input: dict[str, list[Any]]) -> list[Field]: df = pd.DataFrame.from_dict(random_input) output_df: pd.DataFrame = self.transform(df) - return [ - Field( - name=f, - dtype=from_value_type( - python_type_to_feast_value_type(f, type_name=str(dt)) - ), + fields = [] + for feature_name, feature_type in zip(output_df.columns, output_df.dtypes): + feature_value = output_df[feature_name].tolist() + if len(feature_value) <= 0: + raise TypeError( + f"Failed to infer type for feature '{feature_name}' with value " + + f"'{feature_value}' since no items were returned by the UDF." + ) + fields.append( + Field( + name=feature_name, + dtype=from_value_type( + python_type_to_feast_value_type( + feature_name, + value=feature_value[0], + type_name=str(feature_type), + ) + ), + ) ) - for f, dt in zip(output_df.columns, output_df.dtypes) - ] + return fields def __eq__(self, other): if not isinstance(other, PandasTransformation): diff --git a/sdk/python/feast/transformation/python_transformation.py b/sdk/python/feast/transformation/python_transformation.py index 2a9c7db8763..d828890b1e1 100644 --- a/sdk/python/feast/transformation/python_transformation.py +++ b/sdk/python/feast/transformation/python_transformation.py @@ -40,15 +40,26 @@ def transform(self, input_dict: dict) -> dict: def infer_features(self, random_input: dict[str, list[Any]]) -> list[Field]: output_dict: dict[str, list[Any]] = self.transform(random_input) - return [ - Field( - name=f, - dtype=from_value_type( - python_type_to_feast_value_type(f, type_name=type(dt[0]).__name__) - ), + fields = [] + for feature_name, feature_value in output_dict.items(): + if len(feature_value) <= 0: + raise TypeError( + f"Failed to infer type for feature '{feature_name}' with value " + + f"'{feature_value}' since no items were returned by the UDF." + ) + fields.append( + Field( + name=feature_name, + dtype=from_value_type( + python_type_to_feast_value_type( + feature_name, + value=feature_value[0], + type_name=type(feature_value[0]).__name__, + ) + ), + ) ) - for f, dt in output_dict.items() - ] + return fields def __eq__(self, other): if not isinstance(other, PythonTransformation): diff --git a/sdk/python/feast/transformation/substrait_transformation.py b/sdk/python/feast/transformation/substrait_transformation.py index 17c40cf0a16..1de60aed00a 100644 --- a/sdk/python/feast/transformation/substrait_transformation.py +++ b/sdk/python/feast/transformation/substrait_transformation.py @@ -60,16 +60,28 @@ def infer_features(self, random_input: dict[str, list[Any]]) -> list[Field]: df = pd.DataFrame.from_dict(random_input) output_df: pd.DataFrame = self.transform(df) - return [ - Field( - name=f, - dtype=from_value_type( - python_type_to_feast_value_type(f, type_name=str(dt)) - ), - ) - for f, dt in zip(output_df.columns, output_df.dtypes) - if f not in random_input - ] + fields = [] + for feature_name, feature_type in zip(output_df.columns, output_df.dtypes): + feature_value = output_df[feature_name].tolist() + if len(feature_value) <= 0: + raise TypeError( + f"Failed to infer type for feature '{feature_name}' with value " + + f"'{feature_value}' since no items were returned by the UDF." + ) + if feature_name not in random_input: + fields.append( + Field( + name=feature_name, + dtype=from_value_type( + python_type_to_feast_value_type( + feature_name, + value=feature_value[0], + type_name=str(feature_type), + ) + ), + ) + ) + return fields def __eq__(self, other): if not isinstance(other, SubstraitTransformation): diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index a0859f2f7ad..6ba61fc8c5f 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -155,6 +155,7 @@ def python_type_to_feast_value_type( "uint16": ValueType.INT32, "uint8": ValueType.INT32, "int8": ValueType.INT32, + "bool_": ValueType.BOOL, # np.bool_ "bool": ValueType.BOOL, "boolean": ValueType.BOOL, "timedelta": ValueType.UNIX_TIMESTAMP, diff --git a/sdk/python/tests/unit/test_on_demand_pandas_transformation.py b/sdk/python/tests/unit/test_on_demand_pandas_transformation.py index c5f066dd83d..1a04a466fbc 100644 --- a/sdk/python/tests/unit/test_on_demand_pandas_transformation.py +++ b/sdk/python/tests/unit/test_on_demand_pandas_transformation.py @@ -1,15 +1,31 @@ import os +import re import tempfile from datetime import datetime, timedelta import pandas as pd +import pytest -from feast import Entity, FeatureStore, FeatureView, FileSource, RepoConfig +from feast import ( + Entity, + FeatureStore, + FeatureView, + FileSource, + RepoConfig, + RequestSource, +) from feast.driver_test_data import create_driver_hourly_stats_df from feast.field import Field from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig from feast.on_demand_feature_view import on_demand_feature_view -from feast.types import Float32, Float64, Int64 +from feast.types import ( + Array, + Bool, + Float32, + Float64, + Int64, + String, +) def test_pandas_transformation(): @@ -91,3 +107,237 @@ def pandas_view(inputs: pd.DataFrame) -> pd.DataFrame: assert online_response["conv_rate_plus_acc"].equals( online_response["conv_rate"] + online_response["acc_rate"] ) + + +def test_pandas_transformation_returning_all_data_types(): + with tempfile.TemporaryDirectory() as data_dir: + store = FeatureStore( + config=RepoConfig( + project="test_on_demand_python_transformation", + registry=os.path.join(data_dir, "registry.db"), + provider="local", + entity_key_serialization_version=2, + online_store=SqliteOnlineStoreConfig( + path=os.path.join(data_dir, "online.db") + ), + ) + ) + + # Generate test data. + end_date = datetime.now().replace(microsecond=0, second=0, minute=0) + start_date = end_date - timedelta(days=15) + + driver_entities = [1001, 1002, 1003, 1004, 1005] + driver_df = create_driver_hourly_stats_df(driver_entities, start_date, end_date) + driver_stats_path = os.path.join(data_dir, "driver_stats.parquet") + driver_df.to_parquet(path=driver_stats_path, allow_truncated_timestamps=True) + + driver = Entity(name="driver", join_keys=["driver_id"]) + + driver_stats_source = FileSource( + name="driver_hourly_stats_source", + path=driver_stats_path, + timestamp_field="event_timestamp", + created_timestamp_column="created", + ) + + driver_stats_fv = FeatureView( + name="driver_hourly_stats", + entities=[driver], + ttl=timedelta(days=0), + schema=[ + Field(name="conv_rate", dtype=Float32), + Field(name="acc_rate", dtype=Float32), + Field(name="avg_daily_trips", dtype=Int64), + ], + online=True, + source=driver_stats_source, + ) + + request_source = RequestSource( + name="request_source", + schema=[ + Field(name="avg_daily_trip_rank_thresholds", dtype=Array(Int64)), + Field(name="avg_daily_trip_rank_names", dtype=Array(String)), + ], + ) + + @on_demand_feature_view( + sources=[request_source, driver_stats_fv], + schema=[ + Field(name="highest_achieved_rank", dtype=String), + Field(name="avg_daily_trips_plus_one", dtype=Int64), + Field(name="conv_rate_plus_acc", dtype=Float64), + Field(name="is_highest_rank", dtype=Bool), + Field(name="achieved_ranks", dtype=Array(String)), + Field(name="trips_until_next_rank_int", dtype=Array(Int64)), + Field(name="trips_until_next_rank_float", dtype=Array(Float64)), + Field(name="achieved_ranks_mask", dtype=Array(Bool)), + ], + mode="pandas", + ) + def pandas_view(inputs: pd.DataFrame) -> pd.DataFrame: + df = pd.DataFrame() + df["conv_rate_plus_acc"] = inputs["conv_rate"] + inputs["acc_rate"] + df["avg_daily_trips_plus_one"] = inputs["avg_daily_trips"] + 1 + + df["trips_until_next_rank_int"] = inputs[ + ["avg_daily_trips", "avg_daily_trip_rank_thresholds"] + ].apply( + lambda x: [max(threshold - x.iloc[0], 0) for threshold in x.iloc[1]], + axis=1, + ) + df["trips_until_next_rank_float"] = df["trips_until_next_rank_int"].map( + lambda values: [float(value) for value in values] + ) + df["achieved_ranks_mask"] = df["trips_until_next_rank_int"].map( + lambda values: [value <= 0 for value in values] + ) + + temp = pd.concat( + [df[["achieved_ranks_mask"]], inputs[["avg_daily_trip_rank_names"]]], + axis=1, + ) + df["achieved_ranks"] = temp.apply( + lambda x: [ + rank if achieved else "Locked" + for achieved, rank in zip(x.iloc[0], x.iloc[1]) + ], + axis=1, + ) + df["highest_achieved_rank"] = ( + df["achieved_ranks"] + .map( + lambda ranks: str( + ([rank for rank in ranks if rank != "Locked"][-1:] or ["None"])[ + 0 + ] + ) + ) + .astype("string") + ) + df["is_highest_rank"] = df["achieved_ranks"].map( + lambda ranks: ranks[-1] != "Locked" + ) + return df + + store.apply([driver, driver_stats_source, driver_stats_fv, pandas_view]) + + entity_rows = [ + { + "driver_id": 1001, + "avg_daily_trip_rank_thresholds": [100, 250, 500, 1000], + "avg_daily_trip_rank_names": ["Bronze", "Silver", "Gold", "Platinum"], + } + ] + store.write_to_online_store( + feature_view_name="driver_hourly_stats", df=driver_df + ) + + online_response = store.get_online_features( + entity_rows=entity_rows, + features=[ + "driver_hourly_stats:conv_rate", + "driver_hourly_stats:acc_rate", + "driver_hourly_stats:avg_daily_trips", + "pandas_view:avg_daily_trips_plus_one", + "pandas_view:conv_rate_plus_acc", + "pandas_view:trips_until_next_rank_int", + "pandas_view:trips_until_next_rank_float", + "pandas_view:achieved_ranks_mask", + "pandas_view:achieved_ranks", + "pandas_view:highest_achieved_rank", + "pandas_view:is_highest_rank", + ], + ).to_df() + # We use to_df here to ensure we use the pandas backend, but convert to a dict for comparisons + result = online_response.to_dict(orient="records")[0] + + # Type assertions + # Materialized view + assert type(result["conv_rate"]) == float + assert type(result["acc_rate"]) == float + assert type(result["avg_daily_trips"]) == int + # On-demand view + assert type(result["avg_daily_trips_plus_one"]) == int + assert type(result["conv_rate_plus_acc"]) == float + assert type(result["highest_achieved_rank"]) == str + assert type(result["is_highest_rank"]) == bool + + assert type(result["trips_until_next_rank_int"]) == list + assert all([type(e) == int for e in result["trips_until_next_rank_int"]]) + + assert type(result["trips_until_next_rank_float"]) == list + assert all([type(e) == float for e in result["trips_until_next_rank_float"]]) + + assert type(result["achieved_ranks"]) == list + assert all([type(e) == str for e in result["achieved_ranks"]]) + + assert type(result["achieved_ranks_mask"]) == list + assert all([type(e) == bool for e in result["achieved_ranks_mask"]]) + + # Value assertions + expected_trips_until_next_rank = [ + max(threshold - result["avg_daily_trips"], 0) + for threshold in entity_rows[0]["avg_daily_trip_rank_thresholds"] + ] + expected_mask = [value <= 0 for value in expected_trips_until_next_rank] + expected_ranks = [ + rank if achieved else "Locked" + for achieved, rank in zip( + expected_mask, entity_rows[0]["avg_daily_trip_rank_names"] + ) + ] + highest_rank = ( + [rank for rank in expected_ranks if rank != "Locked"][-1:] or ["None"] + )[0] + + assert result["conv_rate_plus_acc"] == result["conv_rate"] + result["acc_rate"] + assert result["avg_daily_trips_plus_one"] == result["avg_daily_trips"] + 1 + assert result["highest_achieved_rank"] == highest_rank + assert result["is_highest_rank"] == (expected_ranks[-1] != "Locked") + + assert result["trips_until_next_rank_int"] == expected_trips_until_next_rank + assert result["trips_until_next_rank_float"] == [ + float(value) for value in expected_trips_until_next_rank + ] + assert result["achieved_ranks_mask"] == expected_mask + assert result["achieved_ranks"] == expected_ranks + + +def test_invalid_pandas_transformation_raises_type_error_on_apply(): + with tempfile.TemporaryDirectory() as data_dir: + store = FeatureStore( + config=RepoConfig( + project="test_on_demand_python_transformation", + registry=os.path.join(data_dir, "registry.db"), + provider="local", + entity_key_serialization_version=2, + online_store=SqliteOnlineStoreConfig( + path=os.path.join(data_dir, "online.db") + ), + ) + ) + + request_source = RequestSource( + name="request_source", + schema=[ + Field(name="driver_name", dtype=String), + ], + ) + + @on_demand_feature_view( + sources=[request_source], + schema=[Field(name="driver_name_lower", dtype=String)], + mode="pandas", + ) + def pandas_view(inputs: pd.DataFrame) -> pd.DataFrame: + return pd.DataFrame({"driver_name_lower": []}) + + with pytest.raises( + TypeError, + match=re.escape( + "Failed to infer type for feature 'driver_name_lower' with value '[]' since no items were returned by the UDF." + ), + ): + store.apply([request_source, pandas_view]) diff --git a/sdk/python/tests/unit/test_on_demand_python_transformation.py b/sdk/python/tests/unit/test_on_demand_python_transformation.py index 72e9b53a101..c5bd68d6a8f 100644 --- a/sdk/python/tests/unit/test_on_demand_python_transformation.py +++ b/sdk/python/tests/unit/test_on_demand_python_transformation.py @@ -1,4 +1,5 @@ import os +import re import tempfile import unittest from datetime import datetime, timedelta @@ -7,12 +8,19 @@ import pandas as pd import pytest -from feast import Entity, FeatureStore, FeatureView, FileSource, RepoConfig +from feast import ( + Entity, + FeatureStore, + FeatureView, + FileSource, + RepoConfig, + RequestSource, +) from feast.driver_test_data import create_driver_hourly_stats_df from feast.field import Field from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig from feast.on_demand_feature_view import on_demand_feature_view -from feast.types import Float32, Float64, Int64 +from feast.types import Array, Bool, Float32, Float64, Int64, String class TestOnDemandPythonTransformation(unittest.TestCase): @@ -248,3 +256,234 @@ def test_python_docs_demo(self): + online_python_response["acc_rate"][0] == online_python_response["conv_rate_plus_val2_python"][0] ) + + +class TestOnDemandPythonTransformationAllDataTypes(unittest.TestCase): + def setUp(self): + with tempfile.TemporaryDirectory() as data_dir: + self.store = FeatureStore( + config=RepoConfig( + project="test_on_demand_python_transformation", + registry=os.path.join(data_dir, "registry.db"), + provider="local", + entity_key_serialization_version=2, + online_store=SqliteOnlineStoreConfig( + path=os.path.join(data_dir, "online.db") + ), + ) + ) + + # Generate test data. + end_date = datetime.now().replace(microsecond=0, second=0, minute=0) + start_date = end_date - timedelta(days=15) + + driver_entities = [1001, 1002, 1003, 1004, 1005] + driver_df = create_driver_hourly_stats_df( + driver_entities, start_date, end_date + ) + driver_stats_path = os.path.join(data_dir, "driver_stats.parquet") + driver_df.to_parquet( + path=driver_stats_path, allow_truncated_timestamps=True + ) + + driver = Entity(name="driver", join_keys=["driver_id"]) + + driver_stats_source = FileSource( + name="driver_hourly_stats_source", + path=driver_stats_path, + timestamp_field="event_timestamp", + created_timestamp_column="created", + ) + + driver_stats_fv = FeatureView( + name="driver_hourly_stats", + entities=[driver], + ttl=timedelta(days=0), + schema=[ + Field(name="conv_rate", dtype=Float32), + Field(name="acc_rate", dtype=Float32), + Field(name="avg_daily_trips", dtype=Int64), + ], + online=True, + source=driver_stats_source, + ) + + request_source = RequestSource( + name="request_source", + schema=[ + Field(name="avg_daily_trip_rank_thresholds", dtype=Array(Int64)), + Field(name="avg_daily_trip_rank_names", dtype=Array(String)), + ], + ) + + @on_demand_feature_view( + sources=[request_source, driver_stats_fv], + schema=[ + Field(name="highest_achieved_rank", dtype=String), + Field(name="avg_daily_trips_plus_one", dtype=Int64), + Field(name="conv_rate_plus_acc", dtype=Float64), + Field(name="is_highest_rank", dtype=Bool), + Field(name="achieved_ranks", dtype=Array(String)), + Field(name="trips_until_next_rank_int", dtype=Array(Int64)), + Field(name="trips_until_next_rank_float", dtype=Array(Float64)), + Field(name="achieved_ranks_mask", dtype=Array(Bool)), + ], + mode="python", + ) + def python_view(inputs: dict[str, Any]) -> dict[str, Any]: + output = {} + trips_until_next_rank = [ + [max(threshold - row[1], 0) for threshold in row[0]] + for row in zip( + inputs["avg_daily_trip_rank_thresholds"], + inputs["avg_daily_trips"], + ) + ] + mask = [[value <= 0 for value in row] for row in trips_until_next_rank] + ranks = [ + [rank if mask else "Locked" for mask, rank in zip(*row)] + for row in zip(mask, inputs["avg_daily_trip_rank_names"]) + ] + highest_rank = [ + ([rank for rank in row if rank != "Locked"][-1:] or ["None"])[0] + for row in ranks + ] + + output["conv_rate_plus_acc"] = [ + sum(row) for row in zip(inputs["conv_rate"], inputs["acc_rate"]) + ] + output["avg_daily_trips_plus_one"] = [ + row + 1 for row in inputs["avg_daily_trips"] + ] + output["highest_achieved_rank"] = highest_rank + output["is_highest_rank"] = [row[-1] != "Locked" for row in ranks] + + output["trips_until_next_rank_int"] = trips_until_next_rank + output["trips_until_next_rank_float"] = [ + [float(value) for value in row] for row in trips_until_next_rank + ] + output["achieved_ranks_mask"] = mask + output["achieved_ranks"] = ranks + return output + + self.store.apply( + [driver, driver_stats_source, driver_stats_fv, python_view] + ) + self.store.write_to_online_store( + feature_view_name="driver_hourly_stats", df=driver_df + ) + + def test_python_transformation_returning_all_data_types(self): + entity_rows = [ + { + "driver_id": 1001, + "avg_daily_trip_rank_thresholds": [100, 250, 500, 1000], + "avg_daily_trip_rank_names": ["Bronze", "Silver", "Gold", "Platinum"], + } + ] + online_response = self.store.get_online_features( + entity_rows=entity_rows, + features=[ + "driver_hourly_stats:conv_rate", + "driver_hourly_stats:acc_rate", + "driver_hourly_stats:avg_daily_trips", + "python_view:avg_daily_trips_plus_one", + "python_view:conv_rate_plus_acc", + "python_view:trips_until_next_rank_int", + "python_view:trips_until_next_rank_float", + "python_view:achieved_ranks_mask", + "python_view:achieved_ranks", + "python_view:highest_achieved_rank", + "python_view:is_highest_rank", + ], + ).to_dict() + result = {name: value[0] for name, value in online_response.items()} + + # Type assertions + # Materialized view + assert type(result["conv_rate"]) == float + assert type(result["acc_rate"]) == float + assert type(result["avg_daily_trips"]) == int + # On-demand view + assert type(result["avg_daily_trips_plus_one"]) == int + assert type(result["conv_rate_plus_acc"]) == float + assert type(result["highest_achieved_rank"]) == str + assert type(result["is_highest_rank"]) == bool + + assert type(result["trips_until_next_rank_int"]) == list + assert all([type(e) == int for e in result["trips_until_next_rank_int"]]) + + assert type(result["trips_until_next_rank_float"]) == list + assert all([type(e) == float for e in result["trips_until_next_rank_float"]]) + + assert type(result["achieved_ranks"]) == list + assert all([type(e) == str for e in result["achieved_ranks"]]) + + assert type(result["achieved_ranks_mask"]) == list + assert all([type(e) == bool for e in result["achieved_ranks_mask"]]) + + # Value assertions + expected_trips_until_next_rank = [ + max(threshold - result["avg_daily_trips"], 0) + for threshold in entity_rows[0]["avg_daily_trip_rank_thresholds"] + ] + expected_mask = [value <= 0 for value in expected_trips_until_next_rank] + expected_ranks = [ + rank if achieved else "Locked" + for achieved, rank in zip( + expected_mask, entity_rows[0]["avg_daily_trip_rank_names"] + ) + ] + highest_rank = ( + [rank for rank in expected_ranks if rank != "Locked"][-1:] or ["None"] + )[0] + + assert result["conv_rate_plus_acc"] == result["conv_rate"] + result["acc_rate"] + assert result["avg_daily_trips_plus_one"] == result["avg_daily_trips"] + 1 + assert result["highest_achieved_rank"] == highest_rank + assert result["is_highest_rank"] == (expected_ranks[-1] != "Locked") + + assert result["trips_until_next_rank_int"] == expected_trips_until_next_rank + assert result["trips_until_next_rank_float"] == [ + float(value) for value in expected_trips_until_next_rank + ] + assert result["achieved_ranks_mask"] == expected_mask + assert result["achieved_ranks"] == expected_ranks + + +def test_invalid_python_transformation_raises_type_error_on_apply(): + with tempfile.TemporaryDirectory() as data_dir: + store = FeatureStore( + config=RepoConfig( + project="test_on_demand_python_transformation", + registry=os.path.join(data_dir, "registry.db"), + provider="local", + entity_key_serialization_version=2, + online_store=SqliteOnlineStoreConfig( + path=os.path.join(data_dir, "online.db") + ), + ) + ) + + request_source = RequestSource( + name="request_source", + schema=[ + Field(name="driver_name", dtype=String), + ], + ) + + @on_demand_feature_view( + sources=[request_source], + schema=[Field(name="driver_name_lower", dtype=String)], + mode="python", + ) + def python_view(inputs: dict[str, Any]) -> dict[str, Any]: + return {"driver_name_lower": []} + + with pytest.raises( + TypeError, + match=re.escape( + "Failed to infer type for feature 'driver_name_lower' with value '[]' since no items were returned by the UDF." + ), + ): + store.apply([request_source, python_view]) From 5c07bd80ce729c5aef90e2b07710cd06c0334b1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:19:13 +0400 Subject: [PATCH 25/44] chore: Bump zipp from 3.18.1 to 3.19.1 in /sdk/python/requirements (#4337) Bumps [zipp](https://github.com/jaraco/zipp) from 3.18.1 to 3.19.1. - [Release notes](https://github.com/jaraco/zipp/releases) - [Changelog](https://github.com/jaraco/zipp/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/zipp/compare/v3.18.1...v3.19.1) --- updated-dependencies: - dependency-name: zipp 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.11-ci-requirements.txt | 2 +- sdk/python/requirements/py3.11-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 e6e66ac2ee6..a9ac50711aa 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -962,5 +962,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.19.2 +zipp==3.19.1 # via importlib-metadata diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 99c9bfc3fee..b9d913c48ae 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -186,5 +186,5 @@ watchfiles==0.21.0 # via uvicorn websockets==12.0 # via uvicorn -zipp==3.18.1 +zipp==3.19.1 # via importlib-metadata diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index fa1f24a586c..7f85ceb5477 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -940,5 +940,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.19.2 +zipp==3.19.1 # via importlib-metadata diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index c34b610d14c..2bf521cda99 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -180,5 +180,5 @@ watchfiles==0.21.0 # via uvicorn websockets==12.0 # via uvicorn -zipp==3.18.1 +zipp==3.19.1 # via importlib-metadata diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index ba4c6c989d8..bbeb7367e7b 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -976,5 +976,5 @@ xmltodict==0.13.0 # via moto yarl==1.9.4 # via aiohttp -zipp==3.19.2 +zipp==3.19.1 # via importlib-metadata diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 149a96626ef..9c4450cb454 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -189,5 +189,5 @@ watchfiles==0.21.0 # via uvicorn websockets==12.0 # via uvicorn -zipp==3.18.1 +zipp==3.19.1 # via importlib-metadata From aba317cf33b1c17bd94c8724522abaf15e5683ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:27:54 -0400 Subject: [PATCH 26/44] chore: Bump braces from 3.0.2 to 3.0.3 in /sdk/python/feast/ui (#4287) --- sdk/python/feast/ui/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index 005035db2d1..7c01b6c1e86 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -3560,11 +3560,11 @@ brace-expansion@^2.0.1: balanced-match "^1.0.0" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" broadcast-channel@^3.4.1: version "3.7.0" @@ -5400,10 +5400,10 @@ filesize@^8.0.6: resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" From dc363472db75c2971c7711147f824257235c6673 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:28:42 -0400 Subject: [PATCH 27/44] chore: Bump ws from 7.5.6 to 7.5.10 in /ui (#4292) --- ui/yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/yarn.lock b/ui/yarn.lock index 89107de0b89..26c833fa11f 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -11640,14 +11640,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.1.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" - integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml-name-validator@^3.0.0: version "3.0.0" From 660df6ec9b1191d1ea1b673c8a4fd5845c05f717 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Fri, 12 Jul 2024 01:26:03 -0400 Subject: [PATCH 28/44] chore: Updating docs (#4346) --- docs/README.md | 8 ++++---- ui/feature_repo/features.py | 22 +++++++++++----------- ui/package.json | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/README.md b/docs/README.md index 66c7548440b..eea372ded0e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,10 +39,10 @@ Feast is likely **not** the right tool if you ### Feast does not _fully_ solve * **reproducible model training / model backtesting / experiment management**: Feast captures feature and model metadata, but does not version-control datasets / labels or manage train / test splits. Other tools like [DVC](https://dvc.org/), [MLflow](https://www.mlflow.org/), and [Kubeflow](https://www.kubeflow.org/) are better suited for this. -* **batch + streaming feature engineering**: Feast primarily processes already transformed feature values (though it offers experimental light-weight transformations). Users usually integrate Feast with upstream systems (e.g. existing ETL/ELT pipelines). [Tecton](http://tecton.ai/) is a more fully featured feature platform which addresses these needs. -* **native streaming feature integration:** Feast enables users to push streaming features, but does not pull from streaming sources or manage streaming pipelines. [Tecton](http://tecton.ai/) is a more fully featured feature platform which orchestrates end to end streaming pipelines. -* **feature sharing**: Feast has experimental functionality to enable discovery and cataloguing of feature metadata with a [Feast web UI (alpha)](https://docs.feast.dev/reference/alpha-web-ui). Feast also has community contributed plugins with [DataHub](https://datahubproject.io/docs/generated/ingestion/sources/feast/) and [Amundsen](https://github.com/amundsen-io/amundsen/blob/4a9d60176767c4d68d1cad5b093320ea22e26a49/databuilder/databuilder/extractor/feast\_extractor.py). [Tecton](http://tecton.ai/) also more robustly addresses these needs. -* **lineage:** Feast helps tie feature values to model versions, but is not a complete solution for capturing end-to-end lineage from raw data sources to model versions. Feast also has community contributed plugins with [DataHub](https://datahubproject.io/docs/generated/ingestion/sources/feast/) and [Amundsen](https://github.com/amundsen-io/amundsen/blob/4a9d60176767c4d68d1cad5b093320ea22e26a49/databuilder/databuilder/extractor/feast\_extractor.py). [Tecton](http://tecton.ai/) captures more end-to-end lineage by also managing feature transformations. +* **batch + streaming feature engineering**: Feast primarily processes already transformed feature values but is investing in supporting batch and streaming transformations. +* **native streaming feature integration:** Feast enables users to push streaming features, but does not pull from streaming sources or manage streaming pipelines. +* **feature sharing**: Feast has experimental functionality to enable discovery and cataloguing of feature metadata with a [Feast web UI (alpha)](https://docs.feast.dev/reference/alpha-web-ui). Feast also has community contributed plugins with [DataHub](https://datahubproject.io/docs/generated/ingestion/sources/feast/) and [Amundsen](https://github.com/amundsen-io/amundsen/blob/4a9d60176767c4d68d1cad5b093320ea22e26a49/databuilder/databuilder/extractor/feast\_extractor.py). +* **lineage:** Feast helps tie feature values to model versions, but is not a complete solution for capturing end-to-end lineage from raw data sources to model versions. Feast also has community contributed plugins with [DataHub](https://datahubproject.io/docs/generated/ingestion/sources/feast/) and [Amundsen](https://github.com/amundsen-io/amundsen/blob/4a9d60176767c4d68d1cad5b093320ea22e26a49/databuilder/databuilder/extractor/feast\_extractor.py). * **data quality / drift detection**: Feast has experimental integrations with [Great Expectations](https://greatexpectations.io/), but is not purpose built to solve data drift / data quality issues. This requires more sophisticated monitoring across data pipelines, served feature values, labels, and model versions. ## Example use cases diff --git a/ui/feature_repo/features.py b/ui/feature_repo/features.py index e02bb3de5d0..40a42a9e99e 100644 --- a/ui/feature_repo/features.py +++ b/ui/feature_repo/features.py @@ -11,7 +11,7 @@ name="zipcode", description="A zipcode", tags={ - "owner": "danny@tecton.ai", + "owner": "danny@feast.ai", "team": "hack week", }, ) @@ -40,7 +40,7 @@ tags={ "date_added": "2022-02-7", "experiments": "experiment-A,experiment-B,experiment-C", - "access_group": "feast-team@tecton.ai", + "access_group": "feast-team@feast.ai", }, online=True, ) @@ -62,7 +62,7 @@ tags={ "date_added": "2022-02-7", "experiments": "experiment-A,experiment-B,experiment-C", - "access_group": "feast-team@tecton.ai", + "access_group": "feast-team@feast.ai", }, online=True, ) @@ -80,7 +80,7 @@ tags={ "date_added": "2022-02-7", "experiments": "experiment-A,experiment-B,experiment-C", - "access_group": "feast-team@tecton.ai", + "access_group": "feast-team@feast.ai", }, online=True, ) @@ -89,7 +89,7 @@ name="dob_ssn", description="Date of birth and last four digits of social security number", tags={ - "owner": "tony@tecton.ai", + "owner": "tony@feast.ai", "team": "hack week", }, ) @@ -121,7 +121,7 @@ tags={ "date_added": "2022-02-6", "experiments": "experiment-A", - "access_group": "feast-team@tecton.ai", + "access_group": "feast-team@feast.ai", }, online=True, ) @@ -157,7 +157,7 @@ def transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame: credit_history[["credit_card_due", "missed_payments_1y"]], zipcode_features, ], - tags={"owner": "tony@tecton.ai", "stage": "staging"}, + tags={"owner": "tony@feast.ai", "stage": "staging"}, description="Credit scoring model", ) @@ -167,7 +167,7 @@ def transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame: credit_history[["mortgage_due", "credit_card_due", "missed_payments_1y"]], zipcode_features, ], - tags={"owner": "tony@tecton.ai", "stage": "prod"}, + tags={"owner": "tony@feast.ai", "stage": "prod"}, description="Credit scoring model", ) @@ -178,7 +178,7 @@ def transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame: zipcode_features, transaction_gt_last_credit_card_due, ], - tags={"owner": "tony@tecton.ai", "stage": "dev"}, + tags={"owner": "tony@feast.ai", "stage": "dev"}, description="Credit scoring model", ) @@ -187,7 +187,7 @@ def transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame: features=[ zipcode_features, ], - tags={"owner": "amanda@tecton.ai", "stage": "dev"}, + tags={"owner": "amanda@feast.ai", "stage": "dev"}, description="Location model", ) @@ -196,6 +196,6 @@ def transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame: features=[ zipcode_money_features, ], - tags={"owner": "amanda@tecton.ai", "stage": "dev"}, + tags={"owner": "amanda@feast.ai", "stage": "dev"}, description="Location model", ) diff --git a/ui/package.json b/ui/package.json index de37f4394a0..a380c65cfc9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -118,7 +118,7 @@ "Feature", "Store" ], - "author": "tony@tecton.ai", + "author": "tony@feast.ai", "license": "Apache-2.0", "bugs": { "url": "https://github.com/feast-dev/feast/issues" From 1ce65bc1f5528a8bfa33e36e8852f4fd198d3ce7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 14:11:39 +0400 Subject: [PATCH 29/44] chore: Bump certifi from 2024.2.2 to 2024.7.4 in /sdk/python/requirements (#4334) chore: Bump certifi in /sdk/python/requirements Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04) --- updated-dependencies: - dependency-name: certifi 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.11-ci-requirements.txt | 2 +- sdk/python/requirements/py3.11-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 a9ac50711aa..33709a1ef0d 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -76,7 +76,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.6.2 +certifi==2024.7.4 # via # elastic-transport # httpcore diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index b9d913c48ae..0cca1068634 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -11,7 +11,7 @@ attrs==23.2.0 # via # jsonschema # referencing -certifi==2024.2.2 +certifi==2024.7.4 # via # httpcore # httpx diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index 7f85ceb5477..09e9e8eeeaa 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -72,7 +72,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.6.2 +certifi==2024.7.4 # via # elastic-transport # httpcore diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index 2bf521cda99..687e4bfe52e 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -11,7 +11,7 @@ attrs==23.2.0 # via # jsonschema # referencing -certifi==2024.2.2 +certifi==2024.7.4 # via # httpcore # httpx diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index bbeb7367e7b..6f5d0220bc1 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -76,7 +76,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 -certifi==2024.6.2 +certifi==2024.7.4 # via # elastic-transport # httpcore diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 9c4450cb454..096f54ab1fa 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -11,7 +11,7 @@ attrs==23.2.0 # via # jsonschema # referencing -certifi==2024.2.2 +certifi==2024.7.4 # via # httpcore # httpx From 92d17def8cdff2bebfa622a4b3846d5bdc3e58d8 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Sat, 13 Jul 2024 22:19:12 -0400 Subject: [PATCH 30/44] fix: Remove typo. (#4351) --- docs/reference/feature-servers/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/feature-servers/README.md b/docs/reference/feature-servers/README.md index 124834f8a73..2ceaf5807f3 100644 --- a/docs/reference/feature-servers/README.md +++ b/docs/reference/feature-servers/README.md @@ -8,7 +8,6 @@ Feast users can choose to retrieve features from a feature server, as opposed to {% content-ref url="go-feature-server.md" %} [go-feature-server.md](go-feature-server.md) -======= {% endcontent-ref %} {% content-ref url="offline-feature-server.md" %} From b9696efb128b9591ca5b2a41e7a9a26e196ebac4 Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Mon, 15 Jul 2024 02:47:01 +0400 Subject: [PATCH 31/44] chore: Rename FileOfflineStore to DaskOfflineStore (#4349) rename file offline store to dask Signed-off-by: tokoko --- docs/SUMMARY.md | 4 +-- .../offline-stores/{file.md => dask.md} | 17 ++++++----- docs/reference/offline-stores/overview.md | 6 ++-- .../docs/source/feast.infra.contrib.rst | 8 ------ .../source/feast.infra.feature_servers.rst | 2 -- .../source/feast.infra.offline_stores.rst | 12 ++++---- .../source/feast.infra.registry.contrib.rst | 1 - sdk/python/docs/source/feast.infra.rst | 24 ---------------- .../infra/offline_stores/{file.py => dask.py} | 28 +++++++++---------- sdk/python/feast/repo_config.py | 7 +++-- .../universal/data_sources/file.py | 6 ++-- .../offline_stores/test_offline_store.py | 8 +++--- .../test_dynamodb_online_store.py | 4 +-- 13 files changed, 46 insertions(+), 81 deletions(-) rename docs/reference/offline-stores/{file.md => dask.md} (87%) rename sdk/python/feast/infra/offline_stores/{file.py => dask.py} (97%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5a82a190fe7..1173a693efb 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -76,7 +76,7 @@ * [Azure Synapse + Azure SQL (contrib)](reference/data-sources/mssql.md) * [Offline stores](reference/offline-stores/README.md) * [Overview](reference/offline-stores/overview.md) - * [File](reference/offline-stores/file.md) + * [Dask](reference/offline-stores/dask.md) * [Snowflake](reference/offline-stores/snowflake.md) * [BigQuery](reference/offline-stores/bigquery.md) * [Redshift](reference/offline-stores/redshift.md) @@ -119,7 +119,7 @@ * [Feature servers](reference/feature-servers/README.md) * [Python feature server](reference/feature-servers/python-feature-server.md) * [\[Alpha\] Go feature server](reference/feature-servers/go-feature-server.md) - * [Offline Feature Server](reference/feature-servers/offline-feature-server) + * [Offline Feature Server](reference/feature-servers/offline-feature-server.md) * [\[Beta\] Web UI](reference/alpha-web-ui.md) * [\[Beta\] On demand feature view](reference/beta-on-demand-feature-view.md) * [\[Alpha\] Vector Database](reference/alpha-vector-database.md) diff --git a/docs/reference/offline-stores/file.md b/docs/reference/offline-stores/dask.md similarity index 87% rename from docs/reference/offline-stores/file.md rename to docs/reference/offline-stores/dask.md index 4b76d9af904..d8698ba544b 100644 --- a/docs/reference/offline-stores/file.md +++ b/docs/reference/offline-stores/dask.md @@ -1,9 +1,8 @@ -# File offline store +# Dask offline store ## Description -The file offline store provides support for reading [FileSources](../data-sources/file.md). -It uses Dask as the compute engine. +The Dask offline store provides support for reading [FileSources](../data-sources/file.md). {% hint style="warning" %} All data is downloaded and joined using Python and therefore may not scale to production workloads. @@ -17,18 +16,18 @@ project: my_feature_repo registry: data/registry.db provider: local offline_store: - type: file + type: dask ``` {% endcode %} -The full set of configuration options is available in [FileOfflineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.offline_stores.file.FileOfflineStoreConfig). +The full set of configuration options is available in [DaskOfflineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.offline_stores.dask.DaskOfflineStoreConfig). ## Functionality Matrix The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the file offline store. +Below is a matrix indicating which functionality is supported by the dask offline store. -| | File | +| | Dask | | :-------------------------------- | :-- | | `get_historical_features` (point-in-time correct join) | yes | | `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | @@ -36,9 +35,9 @@ Below is a matrix indicating which functionality is supported by the file offlin | `offline_write_batch` (persist dataframes to offline store) | yes | | `write_logged_features` (persist logged features to offline store) | yes | -Below is a matrix indicating which functionality is supported by `FileRetrievalJob`. +Below is a matrix indicating which functionality is supported by `DaskRetrievalJob`. -| | File | +| | Dask | | --------------------------------- | --- | | export to dataframe | yes | | export to arrow table | yes | diff --git a/docs/reference/offline-stores/overview.md b/docs/reference/offline-stores/overview.md index 4d7681e38c8..182eac65864 100644 --- a/docs/reference/offline-stores/overview.md +++ b/docs/reference/offline-stores/overview.md @@ -25,13 +25,13 @@ The first three of these methods all return a `RetrievalJob` specific to an offl ## Functionality Matrix -There are currently four core offline store implementations: `FileOfflineStore`, `BigQueryOfflineStore`, `SnowflakeOfflineStore`, and `RedshiftOfflineStore`. +There are currently four core offline store implementations: `DaskOfflineStore`, `BigQueryOfflineStore`, `SnowflakeOfflineStore`, and `RedshiftOfflineStore`. There are several additional implementations contributed by the Feast community (`PostgreSQLOfflineStore`, `SparkOfflineStore`, and `TrinoOfflineStore`), which are not guaranteed to be stable or to match the functionality of the core implementations. Details for each specific offline store, such as how to configure it in a `feature_store.yaml`, can be found [here](README.md). Below is a matrix indicating which offline stores support which methods. -| | File | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | +| | Dask | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | | :-------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | `get_historical_features` | yes | yes | yes | yes | yes | yes | yes | | `pull_latest_from_table_or_query` | yes | yes | yes | yes | yes | yes | yes | @@ -42,7 +42,7 @@ Below is a matrix indicating which offline stores support which methods. Below is a matrix indicating which `RetrievalJob`s support what functionality. -| | File | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | DuckDB | +| | Dask | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | DuckDB | | --------------------------------- | --- | --- | --- | --- | --- | --- | --- | --- | | export to dataframe | yes | yes | yes | yes | yes | yes | yes | yes | | export to arrow table | yes | yes | yes | yes | yes | yes | yes | yes | diff --git a/sdk/python/docs/source/feast.infra.contrib.rst b/sdk/python/docs/source/feast.infra.contrib.rst index 7b2fa3cc9c5..1f46ff0abf8 100644 --- a/sdk/python/docs/source/feast.infra.contrib.rst +++ b/sdk/python/docs/source/feast.infra.contrib.rst @@ -4,14 +4,6 @@ feast.infra.contrib package Submodules ---------- -feast.infra.contrib.azure\_provider module ------------------------------------------- - -.. automodule:: feast.infra.contrib.azure_provider - :members: - :undoc-members: - :show-inheritance: - feast.infra.contrib.grpc\_server module --------------------------------------- diff --git a/sdk/python/docs/source/feast.infra.feature_servers.rst b/sdk/python/docs/source/feast.infra.feature_servers.rst index 334b5859053..ca5203504df 100644 --- a/sdk/python/docs/source/feast.infra.feature_servers.rst +++ b/sdk/python/docs/source/feast.infra.feature_servers.rst @@ -7,8 +7,6 @@ Subpackages .. toctree:: :maxdepth: 4 - feast.infra.feature_servers.aws_lambda - feast.infra.feature_servers.gcp_cloudrun feast.infra.feature_servers.local_process feast.infra.feature_servers.multicloud diff --git a/sdk/python/docs/source/feast.infra.offline_stores.rst b/sdk/python/docs/source/feast.infra.offline_stores.rst index 052a114cfb3..c770e5c13b0 100644 --- a/sdk/python/docs/source/feast.infra.offline_stores.rst +++ b/sdk/python/docs/source/feast.infra.offline_stores.rst @@ -28,18 +28,18 @@ feast.infra.offline\_stores.bigquery\_source module :undoc-members: :show-inheritance: -feast.infra.offline\_stores.duckdb module ------------------------------------------ +feast.infra.offline\_stores.dask module +--------------------------------------- -.. automodule:: feast.infra.offline_stores.duckdb +.. automodule:: feast.infra.offline_stores.dask :members: :undoc-members: :show-inheritance: -feast.infra.offline\_stores.file module ---------------------------------------- +feast.infra.offline\_stores.duckdb module +----------------------------------------- -.. automodule:: feast.infra.offline_stores.file +.. automodule:: feast.infra.offline_stores.duckdb :members: :undoc-members: :show-inheritance: diff --git a/sdk/python/docs/source/feast.infra.registry.contrib.rst b/sdk/python/docs/source/feast.infra.registry.contrib.rst index 44b89736adb..83417109b86 100644 --- a/sdk/python/docs/source/feast.infra.registry.contrib.rst +++ b/sdk/python/docs/source/feast.infra.registry.contrib.rst @@ -8,7 +8,6 @@ Subpackages :maxdepth: 4 feast.infra.registry.contrib.azure - feast.infra.registry.contrib.postgres Module contents --------------- diff --git a/sdk/python/docs/source/feast.infra.rst b/sdk/python/docs/source/feast.infra.rst index a1dfc864926..b0046a2719e 100644 --- a/sdk/python/docs/source/feast.infra.rst +++ b/sdk/python/docs/source/feast.infra.rst @@ -19,22 +19,6 @@ Subpackages Submodules ---------- -feast.infra.aws module ----------------------- - -.. automodule:: feast.infra.aws - :members: - :undoc-members: - :show-inheritance: - -feast.infra.gcp module ----------------------- - -.. automodule:: feast.infra.gcp - :members: - :undoc-members: - :show-inheritance: - feast.infra.infra\_object module -------------------------------- @@ -51,14 +35,6 @@ feast.infra.key\_encoding\_utils module :undoc-members: :show-inheritance: -feast.infra.local module ------------------------- - -.. automodule:: feast.infra.local - :members: - :undoc-members: - :show-inheritance: - feast.infra.passthrough\_provider module ---------------------------------------- diff --git a/sdk/python/feast/infra/offline_stores/file.py b/sdk/python/feast/infra/offline_stores/dask.py similarity index 97% rename from sdk/python/feast/infra/offline_stores/file.py rename to sdk/python/feast/infra/offline_stores/dask.py index af2570ebc08..4a63baf6467 100644 --- a/sdk/python/feast/infra/offline_stores/file.py +++ b/sdk/python/feast/infra/offline_stores/dask.py @@ -39,20 +39,20 @@ from feast.saved_dataset import SavedDatasetStorage from feast.utils import _get_requested_feature_views_to_features_dict -# FileRetrievalJob will cast string objects to string[pyarrow] from dask version 2023.7.1 +# DaskRetrievalJob will cast string objects to string[pyarrow] from dask version 2023.7.1 # This is not the desired behavior for our use case, so we set the convert-string option to False # See (https://github.com/dask/dask/issues/10881#issuecomment-1923327936) dask.config.set({"dataframe.convert-string": False}) -class FileOfflineStoreConfig(FeastConfigBaseModel): - """Offline store config for local (file-based) store""" +class DaskOfflineStoreConfig(FeastConfigBaseModel): + """Offline store config for dask store""" - type: Literal["file"] = "file" + type: Union[Literal["dask"], Literal["file"]] = "dask" """ Offline store type selector""" -class FileRetrievalJob(RetrievalJob): +class DaskRetrievalJob(RetrievalJob): def __init__( self, evaluation_function: Callable, @@ -122,7 +122,7 @@ def supports_remote_storage_export(self) -> bool: return False -class FileOfflineStore(OfflineStore): +class DaskOfflineStore(OfflineStore): @staticmethod def get_historical_features( config: RepoConfig, @@ -133,7 +133,7 @@ def get_historical_features( project: str, full_feature_names: bool = False, ) -> RetrievalJob: - assert isinstance(config.offline_store, FileOfflineStoreConfig) + assert isinstance(config.offline_store, DaskOfflineStoreConfig) for fv in feature_views: assert isinstance(fv.batch_source, FileSource) @@ -283,7 +283,7 @@ def evaluate_historical_retrieval(): return entity_df_with_features.persist() - job = FileRetrievalJob( + job = DaskRetrievalJob( evaluation_function=evaluate_historical_retrieval, full_feature_names=full_feature_names, on_demand_feature_views=OnDemandFeatureView.get_requested_odfvs( @@ -309,7 +309,7 @@ def pull_latest_from_table_or_query( start_date: datetime, end_date: datetime, ) -> RetrievalJob: - assert isinstance(config.offline_store, FileOfflineStoreConfig) + assert isinstance(config.offline_store, DaskOfflineStoreConfig) assert isinstance(data_source, FileSource) # Create lazy function that is only called from the RetrievalJob object @@ -372,7 +372,7 @@ def evaluate_offline_job(): return source_df[list(columns_to_extract)].persist() # When materializing a single feature view, we don't need full feature names. On demand transforms aren't materialized - return FileRetrievalJob( + return DaskRetrievalJob( evaluation_function=evaluate_offline_job, full_feature_names=False, ) @@ -387,10 +387,10 @@ def pull_all_from_table_or_query( start_date: datetime, end_date: datetime, ) -> RetrievalJob: - assert isinstance(config.offline_store, FileOfflineStoreConfig) + assert isinstance(config.offline_store, DaskOfflineStoreConfig) assert isinstance(data_source, FileSource) - return FileOfflineStore.pull_latest_from_table_or_query( + return DaskOfflineStore.pull_latest_from_table_or_query( config=config, data_source=data_source, join_key_columns=join_key_columns @@ -410,7 +410,7 @@ def write_logged_features( logging_config: LoggingConfig, registry: BaseRegistry, ): - assert isinstance(config.offline_store, FileOfflineStoreConfig) + assert isinstance(config.offline_store, DaskOfflineStoreConfig) destination = logging_config.destination assert isinstance(destination, FileLoggingDestination) @@ -441,7 +441,7 @@ def offline_write_batch( table: pyarrow.Table, progress: Optional[Callable[[int], Any]], ): - assert isinstance(config.offline_store, FileOfflineStoreConfig) + assert isinstance(config.offline_store, DaskOfflineStoreConfig) assert isinstance(feature_view.batch_source, FileSource) pa_schema, column_names = get_pyarrow_schema_from_batch_source( diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 137023ef226..fc2792e3237 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -68,7 +68,8 @@ } OFFLINE_STORE_CLASS_FOR_TYPE = { - "file": "feast.infra.offline_stores.file.FileOfflineStore", + "file": "feast.infra.offline_stores.dask.DaskOfflineStore", + "dask": "feast.infra.offline_stores.dask.DaskOfflineStore", "bigquery": "feast.infra.offline_stores.bigquery.BigQueryOfflineStore", "redshift": "feast.infra.offline_stores.redshift.RedshiftOfflineStore", "snowflake.offline": "feast.infra.offline_stores.snowflake.SnowflakeOfflineStore", @@ -205,7 +206,7 @@ def __init__(self, **data: Any): self.registry_config = data["registry"] self._offline_store = None - self.offline_config = data.get("offline_store", "file") + self.offline_config = data.get("offline_store", "dask") self._online_store = None self.online_config = data.get("online_store", "sqlite") @@ -348,7 +349,7 @@ def _validate_offline_store_config(cls, values: Any) -> Any: # Set the default type if "type" not in values["offline_store"]: - values["offline_store"]["type"] = "file" + values["offline_store"]["type"] = "dask" offline_store_type = values["offline_store"]["type"] diff --git a/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py b/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py index 4a4a7360d8c..5174e160465 100644 --- a/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py +++ b/sdk/python/tests/integration/feature_repos/universal/data_sources/file.py @@ -20,8 +20,8 @@ from feast.data_format import DeltaFormat, ParquetFormat from feast.data_source import DataSource from feast.feature_logging import LoggingDestination +from feast.infra.offline_stores.dask import DaskOfflineStoreConfig from feast.infra.offline_stores.duckdb import DuckDBOfflineStoreConfig -from feast.infra.offline_stores.file import FileOfflineStoreConfig from feast.infra.offline_stores.file_source import ( FileLoggingDestination, SavedDatasetFileStorage, @@ -84,7 +84,7 @@ def get_prefixed_table_name(self, suffix: str) -> str: return f"{self.project_name}.{suffix}" def create_offline_store_config(self) -> FeastConfigBaseModel: - return FileOfflineStoreConfig() + return DaskOfflineStoreConfig() def create_logged_features_destination(self) -> LoggingDestination: d = tempfile.mkdtemp(prefix=self.project_name) @@ -334,7 +334,7 @@ def get_prefixed_table_name(self, suffix: str) -> str: return f"{suffix}" def create_offline_store_config(self) -> FeastConfigBaseModel: - return FileOfflineStoreConfig() + return DaskOfflineStoreConfig() def teardown(self): self.minio.stop() diff --git a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py index 3589c8a3fad..50f048928dc 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py @@ -23,7 +23,7 @@ from feast.infra.offline_stores.contrib.trino_offline_store.trino import ( TrinoRetrievalJob, ) -from feast.infra.offline_stores.file import FileRetrievalJob +from feast.infra.offline_stores.dask import DaskRetrievalJob from feast.infra.offline_stores.offline_store import RetrievalJob, RetrievalMetadata from feast.infra.offline_stores.redshift import ( RedshiftOfflineStoreConfig, @@ -100,7 +100,7 @@ def metadata(self) -> Optional[RetrievalMetadata]: @pytest.fixture( params=[ MockRetrievalJob, - FileRetrievalJob, + DaskRetrievalJob, RedshiftRetrievalJob, SnowflakeRetrievalJob, AthenaRetrievalJob, @@ -112,8 +112,8 @@ def metadata(self) -> Optional[RetrievalMetadata]: ] ) def retrieval_job(request, environment): - if request.param is FileRetrievalJob: - return FileRetrievalJob(lambda: 1, full_feature_names=False) + if request.param is DaskRetrievalJob: + return DaskRetrievalJob(lambda: 1, full_feature_names=False) elif request.param is RedshiftRetrievalJob: offline_store_config = RedshiftOfflineStoreConfig( cluster_id="feast-int-bucket", diff --git a/sdk/python/tests/unit/infra/online_store/test_dynamodb_online_store.py b/sdk/python/tests/unit/infra/online_store/test_dynamodb_online_store.py index 6045dbc6ce0..6ff7b3c3605 100644 --- a/sdk/python/tests/unit/infra/online_store/test_dynamodb_online_store.py +++ b/sdk/python/tests/unit/infra/online_store/test_dynamodb_online_store.py @@ -5,7 +5,7 @@ import pytest from moto import mock_dynamodb -from feast.infra.offline_stores.file import FileOfflineStoreConfig +from feast.infra.offline_stores.dask import DaskOfflineStoreConfig from feast.infra.online_stores.dynamodb import ( DynamoDBOnlineStore, DynamoDBOnlineStoreConfig, @@ -40,7 +40,7 @@ def repo_config(): provider=PROVIDER, online_store=DynamoDBOnlineStoreConfig(region=REGION), # online_store={"type": "dynamodb", "region": REGION}, - offline_store=FileOfflineStoreConfig(), + offline_store=DaskOfflineStoreConfig(), entity_key_serialization_version=2, ) From 40270e754660d0a8f57cc8a3bbfb1e1e346c3d86 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Mon, 15 Jul 2024 03:47:12 -0400 Subject: [PATCH 32/44] fix: Avoid XSS attack from Jinjin2's Environment(). (#4355) Signed-off-by: Shuchu Han --- .../offline_stores/contrib/postgres_offline_store/postgres.py | 4 +++- sdk/python/feast/infra/offline_stores/offline_utils.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py index c4740a960ef..c3bbfd97bc7 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py +++ b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py @@ -365,7 +365,9 @@ def build_point_in_time_query( full_feature_names: bool = False, ) -> str: """Build point-in-time query between each feature view table and the entity dataframe for PostgreSQL""" - template = Environment(loader=BaseLoader()).from_string(source=query_template) + template = Environment(autoescape=True, loader=BaseLoader()).from_string( + source=query_template + ) final_output_feature_names = list(entity_df_columns) final_output_feature_names.extend( diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index 2d4fa268e40..6036ba54729 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -186,7 +186,9 @@ def build_point_in_time_query( full_feature_names: bool = False, ) -> str: """Build point-in-time query between each feature view table and the entity dataframe for Bigquery and Redshift""" - template = Environment(loader=BaseLoader()).from_string(source=query_template) + template = Environment(autoescape=True, loader=BaseLoader()).from_string( + source=query_template + ) final_output_feature_names = list(entity_df_columns) final_output_feature_names.extend( From 38cae164000e116d08bb5b403d573efd03e34b6f Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Tue, 16 Jul 2024 09:05:34 +0400 Subject: [PATCH 33/44] chore: Bump google-cloud-datastore lower bound (#4348) bump google-cloud-datastore lower bound Signed-off-by: tokoko --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5432628210..b8362000055 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ "googleapis-common-protos>=1.52.0,<2", "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-datastore>=2.16.0,<3", "google-cloud-storage>=1.34.0,<3", "google-cloud-bigtable>=2.11.0,<3", "fsspec<=2024.1.0", From cdeab486970ccb8c716499610f927a6e8eb14457 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Tue, 16 Jul 2024 14:47:00 -0400 Subject: [PATCH 34/44] revert: Revert "fix: Avoid XSS attack from Jinjin2's Environment()." (#4357) Revert "fix: Avoid XSS attack from Jinjin2's Environment(). (#4355)" This reverts commit 40270e754660d0a8f57cc8a3bbfb1e1e346c3d86. --- .../offline_stores/contrib/postgres_offline_store/postgres.py | 4 +--- sdk/python/feast/infra/offline_stores/offline_utils.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py index c3bbfd97bc7..c4740a960ef 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py +++ b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py @@ -365,9 +365,7 @@ def build_point_in_time_query( full_feature_names: bool = False, ) -> str: """Build point-in-time query between each feature view table and the entity dataframe for PostgreSQL""" - template = Environment(autoescape=True, loader=BaseLoader()).from_string( - source=query_template - ) + template = Environment(loader=BaseLoader()).from_string(source=query_template) final_output_feature_names = list(entity_df_columns) final_output_feature_names.extend( diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index 6036ba54729..2d4fa268e40 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -186,9 +186,7 @@ def build_point_in_time_query( full_feature_names: bool = False, ) -> str: """Build point-in-time query between each feature view table and the entity dataframe for Bigquery and Redshift""" - template = Environment(autoescape=True, loader=BaseLoader()).from_string( - source=query_template - ) + template = Environment(loader=BaseLoader()).from_string(source=query_template) final_output_feature_names = list(entity_df_columns) final_output_feature_names.extend( From ce4f09b9d21f0b9315f1b8b79772901d2081813d Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Tue, 16 Jul 2024 23:05:40 +0400 Subject: [PATCH 35/44] chore: Remove distutils (#4356) * remove distutils Signed-off-by: tokoko * fix formatting Signed-off-by: tokoko --------- Signed-off-by: tokoko --- sdk/python/feast/repo_operations.py | 4 ++-- setup.py | 19 ++++++------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/sdk/python/feast/repo_operations.py b/sdk/python/feast/repo_operations.py index 05a7d05e235..a3100ca9d7e 100644 --- a/sdk/python/feast/repo_operations.py +++ b/sdk/python/feast/repo_operations.py @@ -384,8 +384,8 @@ def cli_check_repo(repo_path: Path, fs_yaml_file: Path): def init_repo(repo_name: str, template: str): import os - from distutils.dir_util import copy_tree from pathlib import Path + from shutil import copytree from colorama import Fore, Style @@ -412,7 +412,7 @@ def init_repo(repo_name: str, template: str): template_path = str(Path(Path(__file__).parent / "templates" / template).absolute()) if not os.path.exists(template_path): raise IOError(f"Could not find template {template}") - copy_tree(template_path, str(repo_path)) + copytree(template_path, str(repo_path), dirs_exist_ok=True) # Seed the repository bootstrap_path = repo_path / "bootstrap.py" diff --git a/setup.py b/setup.py index b8362000055..4ac492b3bfc 100644 --- a/setup.py +++ b/setup.py @@ -18,21 +18,14 @@ import shutil import subprocess import sys -from distutils.cmd import Command -from pathlib import Path - -from setuptools import find_packages -try: - from setuptools import setup - from setuptools.command.build_ext import build_ext as _build_ext - from setuptools.command.build_py import build_py - from setuptools.command.develop import develop - from setuptools.command.install import install +from pathlib import Path -except ImportError: - from distutils.command.build_py import build_py - from distutils.core import setup +from setuptools import find_packages, setup, Command +from setuptools.command.build_ext import build_ext as _build_ext +from setuptools.command.build_py import build_py +from setuptools.command.develop import develop +from setuptools.command.install import install NAME = "feast" DESCRIPTION = "Python SDK for Feast" From a8bc696010fa94fa0be44fba2570bee0eab83ba2 Mon Sep 17 00:00:00 2001 From: Shuchu Han Date: Tue, 16 Jul 2024 15:22:30 -0400 Subject: [PATCH 36/44] fix: Retire the datetime.utcnow(). (#4352) * fix: Retire the datetime.utcnow(). Signed-off-by: Shuchu Han * fix: Remove unnecessary unit test. Signed-off-by: Shuchu Han --------- Signed-off-by: Shuchu Han --- sdk/python/feast/driver_test_data.py | 43 ++++++++++++++++------ sdk/python/feast/type_map.py | 3 +- sdk/python/feast/utils.py | 4 +- sdk/python/tests/unit/test_datetime.py | 6 --- sdk/python/tests/utils/feature_records.py | 4 +- sdk/python/tests/utils/test_log_creator.py | 2 +- 6 files changed, 38 insertions(+), 24 deletions(-) delete mode 100644 sdk/python/tests/unit/test_datetime.py diff --git a/sdk/python/feast/driver_test_data.py b/sdk/python/feast/driver_test_data.py index 7959046e6eb..defeb404a3a 100644 --- a/sdk/python/feast/driver_test_data.py +++ b/sdk/python/feast/driver_test_data.py @@ -61,11 +61,11 @@ def create_orders_df( df["order_is_success"] = np.random.randint(0, 2, size=order_count).astype(np.int32) df[DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL] = [ _convert_event_timestamp( - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms"), + pd.Timestamp(dt, unit="ms").round("ms"), EventTimestampType(idx % 4), ) for idx, dt in enumerate( - pd.date_range(start=start_date, end=end_date, periods=order_count) + pd.date_range(start=start_date, end=end_date, periods=order_count, tz="UTC") ) ] df.sort_values( @@ -101,9 +101,13 @@ def create_driver_hourly_stats_df(drivers, start_date, end_date) -> pd.DataFrame df_hourly = pd.DataFrame( { "event_timestamp": [ - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") + pd.Timestamp(dt, unit="ms").round("ms") for dt in pd.date_range( - start=start_date, end=end_date, freq="1h", inclusive="left" + start=start_date, + end=end_date, + freq="1h", + inclusive="left", + tz="UTC", ) ] # include a fixed timestamp for get_historical_features in the quickstart @@ -162,9 +166,13 @@ def create_customer_daily_profile_df(customers, start_date, end_date) -> pd.Data df_daily = pd.DataFrame( { "event_timestamp": [ - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") + pd.Timestamp(dt, unit="ms").round("ms") for dt in pd.date_range( - start=start_date, end=end_date, freq="1D", inclusive="left" + start=start_date, + end=end_date, + freq="1D", + inclusive="left", + tz="UTC", ) ] } @@ -207,9 +215,13 @@ def create_location_stats_df(locations, start_date, end_date) -> pd.DataFrame: df_hourly = pd.DataFrame( { "event_timestamp": [ - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") + pd.Timestamp(dt, unit="ms").round("ms") for dt in pd.date_range( - start=start_date, end=end_date, freq="1h", inclusive="left" + start=start_date, + end=end_date, + freq="1h", + inclusive="left", + tz="UTC", ) ] } @@ -254,9 +266,16 @@ def create_global_daily_stats_df(start_date, end_date) -> pd.DataFrame: df_daily = pd.DataFrame( { "event_timestamp": [ - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") + pd.Timestamp( + dt, + unit="ms", + ).round("ms") for dt in pd.date_range( - start=start_date, end=end_date, freq="1D", inclusive="left" + start=start_date, + end=end_date, + freq="1D", + inclusive="left", + tz="UTC", ) ] } @@ -286,11 +305,11 @@ def create_field_mapping_df(start_date, end_date) -> pd.DataFrame: df["column_name"] = np.random.randint(1, 100, size=size).astype(np.int32) df[DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL] = [ _convert_event_timestamp( - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms"), + pd.Timestamp(dt, unit="ms").round("ms"), EventTimestampType(idx % 4), ) for idx, dt in enumerate( - pd.date_range(start=start_date, end=end_date, periods=size) + pd.date_range(start=start_date, end=end_date, periods=size, tz="UTC") ) ] df["created"] = pd.to_datetime(pd.Timestamp.now(tz=None).round("ms")) diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index 6ba61fc8c5f..703c1dc7c50 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -162,7 +162,8 @@ def python_type_to_feast_value_type( "timestamp": ValueType.UNIX_TIMESTAMP, "datetime": ValueType.UNIX_TIMESTAMP, "datetime64[ns]": ValueType.UNIX_TIMESTAMP, - "datetime64[ns, tz]": ValueType.UNIX_TIMESTAMP, + "datetime64[ns, tz]": ValueType.UNIX_TIMESTAMP, # special dtype of pandas + "datetime64[ns, utc]": ValueType.UNIX_TIMESTAMP, "category": ValueType.STRING, } diff --git a/sdk/python/feast/utils.py b/sdk/python/feast/utils.py index 1a1d757fc16..0467393aa2e 100644 --- a/sdk/python/feast/utils.py +++ b/sdk/python/feast/utils.py @@ -5,7 +5,7 @@ import typing import warnings from collections import Counter, defaultdict -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import ( Any, @@ -1055,4 +1055,4 @@ def tags_str_to_dict(tags: str = "") -> dict[str, str]: def _utc_now() -> datetime: - return datetime.utcnow() + return datetime.now(tz=timezone.utc) diff --git a/sdk/python/tests/unit/test_datetime.py b/sdk/python/tests/unit/test_datetime.py deleted file mode 100644 index aaab507ed0b..00000000000 --- a/sdk/python/tests/unit/test_datetime.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - - -""" -Test the retirement of datetime.utcnow() function. -""" diff --git a/sdk/python/tests/utils/feature_records.py b/sdk/python/tests/utils/feature_records.py index 2c26f3c0000..bd3567c9eeb 100644 --- a/sdk/python/tests/utils/feature_records.py +++ b/sdk/python/tests/utils/feature_records.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional import numpy as np @@ -520,7 +520,7 @@ def get_last_feature_row(df: pd.DataFrame, driver_id, max_date: datetime): """Manually extract last feature value from a dataframe for a given driver_id with up to `max_date` date""" filtered = df[ (df["driver_id"] == driver_id) - & (df["event_timestamp"] < max_date.replace(tzinfo=utc)) + & (df["event_timestamp"] < max_date.replace(tzinfo=timezone.utc)) ] max_ts = filtered.loc[filtered["event_timestamp"].idxmax()]["event_timestamp"] filtered_by_ts = filtered[filtered["event_timestamp"] == max_ts] diff --git a/sdk/python/tests/utils/test_log_creator.py b/sdk/python/tests/utils/test_log_creator.py index f072f4c8864..987c8d77ef7 100644 --- a/sdk/python/tests/utils/test_log_creator.py +++ b/sdk/python/tests/utils/test_log_creator.py @@ -117,7 +117,7 @@ def prepare_logs( f"{destination_field}__status" ].mask( logs_df[f"{destination_field}__timestamp"] - < (_utc_now() - view.ttl), + < (_utc_now() - view.ttl).replace(tzinfo=None), FieldStatus.OUTSIDE_MAX_AGE, ) From 5a1636431d44a7e109c64688fe3176741feccc1b Mon Sep 17 00:00:00 2001 From: camenares <32527085+camenares@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:13:20 -0400 Subject: [PATCH 37/44] chore: Change arrow scalar ids usage (#4347) * Update google-cloud-storage Signed-off-by: Christopher Camenares * test tighter library restriction Signed-off-by: Christopher Camenares * fix lint Signed-off-by: Christopher Camenares * bump <4 again Signed-off-by: Christopher Camenares --------- Signed-off-by: Christopher Camenares --- .../feast/infra/offline_stores/bigquery.py | 15 +- .../requirements/py3.10-ci-requirements.txt | 152 +++++++++++++++--- .../requirements/py3.10-requirements.txt | 48 ++++-- .../requirements/py3.11-ci-requirements.txt | 152 +++++++++++++++--- .../requirements/py3.11-requirements.txt | 48 ++++-- .../requirements/py3.9-ci-requirements.txt | 152 +++++++++++++++--- .../requirements/py3.9-requirements.txt | 48 ++++-- setup.py | 2 +- 8 files changed, 532 insertions(+), 85 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 3e4a0f1b997..ef12eba442b 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -59,7 +59,6 @@ from google.auth.exceptions import DefaultCredentialsError from google.cloud import bigquery from google.cloud.bigquery import Client, SchemaField, Table - from google.cloud.bigquery._pandas_helpers import ARROW_SCALAR_IDS_TO_BQ from google.cloud.storage import Client as StorageClient except ImportError as e: @@ -67,6 +66,16 @@ raise FeastExtrasDependencyImportError("gcp", str(e)) +try: + from google.cloud.bigquery._pyarrow_helpers import _ARROW_SCALAR_IDS_TO_BQ +except ImportError: + try: + from google.cloud.bigquery._pandas_helpers import ( # type: ignore + ARROW_SCALAR_IDS_TO_BQ as _ARROW_SCALAR_IDS_TO_BQ, + ) + except ImportError as e: + raise FeastExtrasDependencyImportError("gcp", str(e)) + def get_http_client_info(): return http_client_info.ClientInfo(user_agent=get_user_agent()) @@ -794,10 +803,10 @@ def arrow_schema_to_bq_schema(arrow_schema: pyarrow.Schema) -> List[SchemaField] for field in arrow_schema: if pyarrow.types.is_list(field.type): detected_mode = "REPEATED" - detected_type = ARROW_SCALAR_IDS_TO_BQ[field.type.value_type.id] + detected_type = _ARROW_SCALAR_IDS_TO_BQ[field.type.value_type.id] else: detected_mode = "NULLABLE" - detected_type = ARROW_SCALAR_IDS_TO_BQ[field.type.id] + detected_type = _ARROW_SCALAR_IDS_TO_BQ[field.type.id] bq_schema.append( SchemaField(name=field.name, field_type=detected_type, mode=detected_mode) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index 33709a1ef0d..a7f300a0ed5 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -19,6 +20,8 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -48,7 +52,9 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -60,7 +66,9 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via moto + # via + # feast (setup.py) + # moto botocore==1.34.131 # via # aiobotocore @@ -69,6 +77,7 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -76,6 +85,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 + # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -98,6 +108,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -107,7 +118,9 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel @@ -116,6 +129,7 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -127,7 +141,9 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -139,7 +155,9 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -153,6 +171,7 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 + # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -167,6 +186,7 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 + # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -176,6 +196,7 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -183,13 +204,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -213,9 +237,12 @@ google-auth==2.30.0 # kubernetes google-auth-httplib2==0.2.0 # via google-api-python-client -google-cloud-bigquery[pandas]==3.12.0 +google-cloud-bigquery[pandas]==3.13.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 + # via feast (setup.py) google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -224,10 +251,13 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via firebase-admin + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -238,16 +268,17 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 -greenlet==3.0.3 - # via sqlalchemy + # via feast (setup.py) grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -258,19 +289,27 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -281,11 +320,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.1.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==4.0.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -320,6 +363,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -343,6 +387,7 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -388,6 +433,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -408,13 +454,17 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 + # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -428,10 +478,13 @@ multidict==6.0.5 # aiohttp # yarl mypy==1.10.1 - # via sqlalchemy + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -454,6 +507,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -488,6 +542,7 @@ packaging==24.1 # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -513,6 +568,7 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -525,6 +581,7 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -539,6 +596,7 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -556,8 +614,11 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel + # via + # feast (setup.py) + # ipykernel psycopg[binary, pool]==3.1.19 + # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -569,12 +630,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -592,16 +655,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -612,8 +678,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -625,8 +694,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -636,13 +707,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -671,6 +750,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -684,15 +764,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via parsimonious + # via + # feast (setup.py) + # parsimonious requests==2.32.3 # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -727,6 +811,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -736,6 +821,7 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 + # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -752,6 +838,7 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -772,11 +859,13 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -790,9 +879,11 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 + # via feast (setup.py) sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -802,17 +893,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 + # via feast (setup.py) tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 + # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via # build @@ -840,7 +935,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -857,25 +954,39 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 + # via feast (setup.py) typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 + # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via types-cffi + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -914,6 +1025,7 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -925,11 +1037,15 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via fastapi + # via + # feast (setup.py) + # fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit + # via + # feast (setup.py) + # pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 0cca1068634..39a278818f0 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -20,17 +20,22 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -38,14 +43,15 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -65,8 +71,11 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -78,13 +87,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -96,20 +108,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -120,6 +141,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -127,6 +149,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -142,11 +165,15 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -154,7 +181,9 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -178,6 +207,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index 09e9e8eeeaa..4c1be0a5b43 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -19,6 +20,8 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -44,7 +48,9 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -56,7 +62,9 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via moto + # via + # feast (setup.py) + # moto botocore==1.34.131 # via # aiobotocore @@ -65,6 +73,7 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -72,6 +81,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 + # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -94,6 +104,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -103,7 +114,9 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel @@ -112,6 +125,7 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -123,7 +137,9 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -135,7 +151,9 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -149,6 +167,7 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 + # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -158,6 +177,7 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 + # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -167,6 +187,7 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -174,13 +195,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -204,9 +228,12 @@ google-auth==2.30.0 # kubernetes google-auth-httplib2==0.2.0 # via google-api-python-client -google-cloud-bigquery[pandas]==3.12.0 +google-cloud-bigquery[pandas]==3.13.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 + # via feast (setup.py) google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -215,10 +242,13 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via firebase-admin + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -229,16 +259,17 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 -greenlet==3.0.3 - # via sqlalchemy + # via feast (setup.py) grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -249,19 +280,27 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -272,11 +311,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.1.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==4.0.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -311,6 +354,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -334,6 +378,7 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -379,6 +424,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -399,13 +445,17 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 + # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -419,10 +469,13 @@ multidict==6.0.5 # aiohttp # yarl mypy==1.10.1 - # via sqlalchemy + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -445,6 +498,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -479,6 +533,7 @@ packaging==24.1 # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -504,6 +559,7 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -516,6 +572,7 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -530,6 +587,7 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -547,8 +605,11 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel + # via + # feast (setup.py) + # ipykernel psycopg[binary, pool]==3.1.19 + # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -560,12 +621,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -583,16 +646,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -603,8 +669,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -616,8 +685,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -627,13 +698,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -662,6 +741,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -675,15 +755,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via parsimonious + # via + # feast (setup.py) + # parsimonious requests==2.32.3 # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -718,6 +802,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -727,6 +812,7 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 + # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -743,6 +829,7 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -763,11 +850,13 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -781,9 +870,11 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 + # via feast (setup.py) sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -793,17 +884,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 + # via feast (setup.py) tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 + # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -821,7 +916,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -838,25 +935,39 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 + # via feast (setup.py) typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 + # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via types-cffi + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -892,6 +1003,7 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -903,11 +1015,15 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via fastapi + # via + # feast (setup.py) + # fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit + # via + # feast (setup.py) + # pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index 687e4bfe52e..44e658113ae 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -20,30 +20,36 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 # via fastapi fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -63,8 +69,11 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -76,13 +85,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -94,20 +106,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -118,6 +139,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -125,6 +147,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -140,17 +163,23 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) toolz==0.12.1 # via # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -172,6 +201,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 6f5d0220bc1..25cdea7a688 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1,6 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt aiobotocore==2.13.1 + # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -19,6 +20,8 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles +appnope==0.1.4 + # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -28,6 +31,7 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 + # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -48,7 +52,9 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 + # via feast (setup.py) azure-storage-blob==12.20.0 + # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -60,7 +66,9 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via moto + # via + # feast (setup.py) + # moto botocore==1.34.131 # via # aiobotocore @@ -69,6 +77,7 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via + # feast (setup.py) # pip-tools # singlestoredb cachecontrol==0.14.0 @@ -76,6 +85,7 @@ cachecontrol==0.14.0 cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 + # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -98,6 +108,7 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via + # feast (setup.py) # dask # geomet # great-expectations @@ -107,7 +118,9 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via great-expectations + # via + # feast (setup.py) + # great-expectations comm==0.2.2 # via # ipykernel @@ -116,6 +129,7 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via + # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -127,7 +141,9 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -139,7 +155,9 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 + # via feast (setup.py) dill==0.3.8 + # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -153,6 +171,7 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 + # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -167,6 +186,7 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 + # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -176,6 +196,7 @@ filelock==3.15.4 # snowflake-connector-python # virtualenv firebase-admin==5.4.0 + # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -183,13 +204,16 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via dask + # via + # feast (setup.py) + # dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via + # feast (setup.py) # firebase-admin # google-api-python-client # google-cloud-bigquery @@ -213,9 +237,12 @@ google-auth==2.30.0 # kubernetes google-auth-httplib2==0.2.0 # via google-api-python-client -google-cloud-bigquery[pandas]==3.12.0 +google-cloud-bigquery[pandas]==3.13.0 + # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 + # via feast (setup.py) google-cloud-bigtable==2.24.0 + # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery @@ -224,10 +251,13 @@ google-cloud-core==2.4.1 # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 + # via feast (setup.py) google-cloud-firestore==2.16.0 # via firebase-admin google-cloud-storage==2.17.0 - # via firebase-admin + # via + # feast (setup.py) + # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -238,16 +268,17 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via + # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 -greenlet==3.0.3 - # via sqlalchemy + # via feast (setup.py) grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -258,19 +289,27 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 + # via feast (setup.py) grpcio-reflection==1.62.2 + # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 + # via feast (setup.py) grpcio-tools==1.62.2 + # via feast (setup.py) gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 + # via feast (setup.py) hazelcast-python-client==5.4.0 + # via feast (setup.py) hiredis==2.3.2 + # via feast (setup.py) httpcore==1.0.5 # via httpx httplib2==0.22.0 @@ -281,11 +320,15 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.0.0 - # via ibis-substrait + # via + # feast (setup.py) + # ibis-substrait ibis-substrait==4.0.0 + # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -329,6 +372,7 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via + # feast (setup.py) # altair # fastapi # great-expectations @@ -352,6 +396,7 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via + # feast (setup.py) # altair # great-expectations # jupyter-events @@ -397,6 +442,7 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 + # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -417,13 +463,17 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 + # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 + # via feast (setup.py) mock==2.0.0 + # via feast (setup.py) moto==4.2.14 + # via feast (setup.py) msal==1.29.0 # via # azure-identity @@ -437,10 +487,13 @@ multidict==6.0.5 # aiohttp # yarl mypy==1.10.1 - # via sqlalchemy + # via + # feast (setup.py) + # sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 + # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -463,6 +516,7 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via + # feast (setup.py) # altair # dask # db-dtypes @@ -497,6 +551,7 @@ packaging==24.1 # sphinx pandas==2.2.2 # via + # feast (setup.py) # altair # dask # dask-expr @@ -522,6 +577,7 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 + # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -534,6 +590,7 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 + # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -548,6 +605,7 @@ proto-plus==1.24.0 # google-cloud-firestore protobuf==4.25.3 # via + # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage @@ -565,8 +623,11 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via ipykernel + # via + # feast (setup.py) + # ipykernel psycopg[binary, pool]==3.1.18 + # via feast (setup.py) psycopg-binary==3.1.18 # via psycopg psycopg-pool==3.2.2 @@ -578,12 +639,14 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 + # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via + # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -601,16 +664,19 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 + # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via + # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via + # feast (setup.py) # ipython # nbconvert # rich @@ -621,8 +687,11 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 + # via feast (setup.py) pymysql==1.1.1 + # via feast (setup.py) pyodbc==5.1.0 + # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 @@ -634,8 +703,10 @@ pyproject-hooks==1.1.0 # build # pip-tools pyspark==3.5.1 + # via feast (setup.py) pytest==7.4.4 # via + # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -645,13 +716,21 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 + # via feast (setup.py) pytest-cov==5.0.0 + # via feast (setup.py) pytest-env==1.1.3 + # via feast (setup.py) pytest-lazy-fixture==0.6.3 + # via feast (setup.py) pytest-mock==1.10.4 + # via feast (setup.py) pytest-ordering==0.6 + # via feast (setup.py) pytest-timeout==1.4.2 + # via feast (setup.py) pytest-xdist==3.6.1 + # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -680,6 +759,7 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via + # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -693,15 +773,19 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 + # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via parsimonious + # via + # feast (setup.py) + # parsimonious requests==2.32.3 # via + # feast (setup.py) # azure-core # cachecontrol # docker @@ -736,6 +820,7 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 + # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -747,6 +832,7 @@ ruamel-yaml==0.17.17 ruamel-yaml-clib==0.2.8 # via ruamel-yaml ruff==0.4.10 + # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.13.1 @@ -763,6 +849,7 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 + # via feast (setup.py) six==1.16.0 # via # asttokens @@ -783,11 +870,13 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 + # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 + # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -801,9 +890,11 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 + # via feast (setup.py) sqlglot==23.12.2 # via ibis-framework sqlite-vec==0.0.1a10 + # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -813,17 +904,21 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 + # via feast (setup.py) tenacity==8.4.2 + # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 + # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via # build @@ -851,7 +946,9 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via great-expectations + # via + # feast (setup.py) + # great-expectations traitlets==5.14.3 # via # comm @@ -868,25 +965,39 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 + # via feast (setup.py) typeguard==4.3.0 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf types-pymysql==1.1.0.20240524 + # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via arrow + # via + # feast (setup.py) + # arrow types-pytz==2024.1.0.20240417 + # via feast (setup.py) types-pyyaml==6.0.12.20240311 + # via feast (setup.py) types-redis==4.6.0.20240425 + # via feast (setup.py) types-requests==2.30.0.0 + # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via types-cffi + # via + # feast (setup.py) + # types-cffi types-tabulate==0.9.0.20240106 + # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -927,6 +1038,7 @@ uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.19 # via + # feast (setup.py) # botocore # docker # elastic-transport @@ -939,11 +1051,15 @@ urllib3==1.26.19 # snowflake-connector-python # testcontainers uvicorn[standard]==0.30.1 - # via fastapi + # via + # feast (setup.py) + # fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via pre-commit + # via + # feast (setup.py) + # pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index 096f54ab1fa..ea553bcae2d 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -20,17 +20,22 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 + # via feast (setup.py) dask[dataframe]==2024.5.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 + # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -38,14 +43,15 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via fastapi-cli + # via + # feast (setup.py) + # fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask -greenlet==3.0.3 - # via sqlalchemy gunicorn==22.0.0 + # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -67,8 +73,11 @@ importlib-metadata==7.1.0 # dask # typeguard jinja2==3.1.4 - # via fastapi + # via + # feast (setup.py) + # fastapi jsonschema==4.22.0 + # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -80,13 +89,16 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 + # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 + # via feast (setup.py) numpy==1.26.4 # via + # feast (setup.py) # dask # pandas # pyarrow @@ -98,20 +110,29 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via + # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via mypy-protobuf + # via + # feast (setup.py) + # mypy-protobuf pyarrow==16.0.0 - # via dask-expr + # via + # feast (setup.py) + # dask-expr pydantic==2.7.1 - # via fastapi + # via + # feast (setup.py) + # fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via rich + # via + # feast (setup.py) + # rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -122,6 +143,7 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via + # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -129,6 +151,7 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 + # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -144,11 +167,15 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 + # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 + # via feast (setup.py) tenacity==8.3.0 + # via feast (setup.py) toml==0.10.2 + # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -156,7 +183,9 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 + # via feast (setup.py) typeguard==4.2.1 + # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -181,6 +210,7 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via + # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/setup.py b/setup.py index 4ac492b3bfc..2043bf1b3f4 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ GCP_REQUIRED = [ "google-api-core>=1.23.0,<3", "googleapis-common-protos>=1.52.0,<2", - "google-cloud-bigquery[pandas]>=2,<3.13.0", + "google-cloud-bigquery[pandas]>=2,<4", "google-cloud-bigquery-storage >= 2.0.0,<3", "google-cloud-datastore>=2.16.0,<3", "google-cloud-storage>=1.34.0,<3", From 7914cbdaffeade727cf3cee538cf128cbfd86e06 Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Fri, 19 Jul 2024 01:46:43 +0400 Subject: [PATCH 38/44] feat: Port mssql contrib offline store to ibis (#4360) port mssql offline store to ibis Signed-off-by: tokoko --- Makefile | 5 +- .../contrib/mssql_offline_store/mssql.py | 742 ++++-------------- .../mssql_offline_store/mssqlserver_source.py | 42 +- .../mssql_offline_store/tests/data_source.py | 38 +- sdk/python/feast/infra/offline_stores/ibis.py | 31 +- sdk/python/feast/type_map.py | 2 + .../offline_stores/test_offline_store.py | 11 - setup.py | 3 + 8 files changed, 246 insertions(+), 628 deletions(-) diff --git a/Makefile b/Makefile index d2fbb34e1f5..5e3bd0d9135 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,10 @@ test-python-universal-mssql: -k "not gcs_registry and \ not s3_registry and \ not test_lambda_materialization and \ - not test_snowflake" \ + not test_snowflake and \ + not test_historical_features_persisting and \ + not validation and \ + not test_feature_service_logging" \ sdk/python/tests diff --git a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssql.py b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssql.py index 5fe58571466..875d584568b 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssql.py +++ b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssql.py @@ -1,44 +1,109 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -import warnings -from datetime import datetime +from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Callable, Dict, List, Literal, Optional, Set, Tuple, Union +from typing import Any, Callable, Iterable, List, Literal, Optional, Tuple, Union +from urllib import parse -import numpy as np -import pandas +import ibis +import pandas as pd import pyarrow -import pyarrow as pa -import sqlalchemy -from pydantic.types import StrictStr -from sqlalchemy import create_engine -from sqlalchemy.engine import Engine -from sqlalchemy.orm import sessionmaker +from ibis.expr.types import Table +from pydantic import StrictStr -from feast import FileSource, errors from feast.data_source import DataSource -from feast.errors import InvalidEntityType from feast.feature_logging import LoggingConfig, LoggingSource from feast.feature_view import FeatureView -from feast.infra.offline_stores import offline_utils -from feast.infra.offline_stores.file_source import SavedDatasetFileStorage -from feast.infra.offline_stores.offline_store import OfflineStore, RetrievalMetadata -from feast.infra.offline_stores.offline_utils import ( - DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL, - build_point_in_time_query, - get_feature_view_query_context, +from feast.infra.offline_stores.contrib.mssql_offline_store.mssqlserver_source import ( + MsSqlServerSource, ) -from feast.infra.provider import RetrievalJob +from feast.infra.offline_stores.ibis import ( + get_historical_features_ibis, + offline_write_batch_ibis, + pull_all_from_table_or_query_ibis, + pull_latest_from_table_or_query_ibis, + write_logged_features_ibis, +) +from feast.infra.offline_stores.offline_store import OfflineStore, RetrievalJob from feast.infra.registry.base_registry import BaseRegistry -from feast.on_demand_feature_view import OnDemandFeatureView from feast.repo_config import FeastConfigBaseModel, RepoConfig -from feast.saved_dataset import SavedDatasetStorage -from feast.type_map import pa_to_mssql_type -# Make sure warning doesn't raise more than once. -warnings.simplefilter("once", RuntimeWarning) -EntitySchema = Dict[str, np.dtype] +def get_ibis_connection(config: RepoConfig): + connection_params = parse.urlparse(config.offline_store.connection_string) + additional_kwargs = dict(parse.parse_qsl(connection_params.query)) + return ibis.mssql.connect( + user=connection_params.username, + password=connection_params.password, + host=connection_params.hostname, + port=connection_params.port, + database=connection_params.path.strip("/"), + **additional_kwargs, + ) + + +def get_table_column_names_and_types( + config: RepoConfig, data_source: MsSqlServerSource +) -> Iterable[Tuple[str, str]]: + con = get_ibis_connection(config) + + # assert isinstance(config.offline_store, MsSqlServerOfflineStoreConfig) + # conn = create_engine(config.offline_store.connection_string) + # self._mssqlserver_options.connection_str = ( + # config.offline_store.connection_string + # ) + name_type_pairs = [] + if len(data_source.table_ref.split(".")) == 2: + database, table_name = data_source.table_ref.split(".") + columns_query = f""" + SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '{table_name}' and table_schema = '{database}' + """ + else: + columns_query = f""" + SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '{data_source.table_ref}' + """ + + table_schema = con.sql(columns_query).execute() + + name_type_pairs.extend( + list( + zip( + table_schema["COLUMN_NAME"].to_list(), + table_schema["DATA_TYPE"].to_list(), + ) + ) + ) + return name_type_pairs + + +def _build_data_source_reader(config: RepoConfig): + con = get_ibis_connection(config) + + def _read_data_source(data_source: DataSource) -> Table: + assert isinstance(data_source, MsSqlServerSource) + return con.table(data_source.table_ref) + + return _read_data_source + + +def _build_data_source_writer(config: RepoConfig): + con = get_ibis_connection(config) + + def _write_data_source( + table: Table, + data_source: DataSource, + mode: str = "append", + allow_overwrite: bool = False, + ): + assert isinstance(data_source, MsSqlServerSource) + con.insert(table_name=data_source.table_ref, obj=table.to_pandas()) + + return _write_data_source + + +def mssql_event_expire_timestamp_fn(timestamp_field: str, ttl: timedelta) -> str: + ttl_seconds = int(ttl.total_seconds()) + return f"DATEADD(ss, {ttl_seconds}, {timestamp_field})" class MsSqlServerOfflineStoreConfig(FeastConfigBaseModel): @@ -52,18 +117,7 @@ class MsSqlServerOfflineStoreConfig(FeastConfigBaseModel): format: SQLAlchemy connection string, e.g. mssql+pyodbc://sa:yourStrong(!)Password@localhost:1433/feast_test?driver=ODBC+Driver+17+for+SQL+Server""" -def make_engine(config: MsSqlServerOfflineStoreConfig) -> Engine: - return create_engine(config.connection_string) - - class MsSqlServerOfflineStore(OfflineStore): - """ - Microsoft SQL Server based offline store, supporting Azure Synapse or Azure SQL. - - Note: to use this, you'll need to have Microsoft ODBC 17 installed. - See https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15#17 - """ - @staticmethod def pull_latest_from_table_or_query( config: RepoConfig, @@ -75,43 +129,45 @@ def pull_latest_from_table_or_query( start_date: datetime, end_date: datetime, ) -> RetrievalJob: - warnings.warn( - "The Azure Synapse + Azure SQL offline store is an experimental feature in alpha development. " - "Some functionality may still be unstable so functionality can change in the future.", - RuntimeWarning, + return pull_latest_from_table_or_query_ibis( + config=config, + data_source=data_source, + join_key_columns=join_key_columns, + feature_name_columns=feature_name_columns, + timestamp_field=timestamp_field, + created_timestamp_column=created_timestamp_column, + start_date=start_date, + end_date=end_date, + data_source_reader=_build_data_source_reader(config), + data_source_writer=_build_data_source_writer(config), ) - assert type(data_source).__name__ == "MsSqlServerSource" - from_expression = data_source.get_table_query_string().replace("`", "") - - partition_by_join_key_string = ", ".join(join_key_columns) - if partition_by_join_key_string != "": - partition_by_join_key_string = ( - "PARTITION BY " + partition_by_join_key_string - ) - timestamps = [timestamp_field] - if created_timestamp_column: - timestamps.append(created_timestamp_column) - timestamp_desc_string = " DESC, ".join(timestamps) + " DESC" - field_string = ", ".join(join_key_columns + feature_name_columns + timestamps) - - query = f""" - SELECT {field_string} - FROM ( - SELECT {field_string}, - ROW_NUMBER() OVER({partition_by_join_key_string} ORDER BY {timestamp_desc_string}) AS _feast_row - FROM {from_expression} inner_t - WHERE {timestamp_field} BETWEEN CONVERT(DATETIMEOFFSET, '{start_date}', 120) AND CONVERT(DATETIMEOFFSET, '{end_date}', 120) - ) outer_t - WHERE outer_t._feast_row = 1 - """ - engine = make_engine(config.offline_store) - return MsSqlServerRetrievalJob( - query=query, - engine=engine, - config=config.offline_store, - full_feature_names=False, - on_demand_feature_views=None, + @staticmethod + def get_historical_features( + config: RepoConfig, + feature_views: List[FeatureView], + feature_refs: List[str], + entity_df: Union[pd.DataFrame, str], + registry: BaseRegistry, + project: str, + full_feature_names: bool = False, + ) -> RetrievalJob: + # TODO avoid this conversion + if type(entity_df) == str: + con = get_ibis_connection(config) + entity_df = con.sql(entity_df).execute() + + return get_historical_features_ibis( + config=config, + feature_views=feature_views, + feature_refs=feature_refs, + entity_df=entity_df, + registry=registry, + project=project, + full_feature_names=full_feature_names, + data_source_reader=_build_data_source_reader(config), + data_source_writer=_build_data_source_writer(config), + event_expire_timestamp_fn=mssql_event_expire_timestamp_fn, ) @staticmethod @@ -124,114 +180,32 @@ def pull_all_from_table_or_query( start_date: datetime, end_date: datetime, ) -> RetrievalJob: - assert type(data_source).__name__ == "MsSqlServerSource" - warnings.warn( - "The Azure Synapse + Azure SQL offline store is an experimental feature in alpha development. " - "Some functionality may still be unstable so functionality can change in the future.", - RuntimeWarning, - ) - from_expression = data_source.get_table_query_string().replace("`", "") - timestamps = [timestamp_field] - field_string = ", ".join(join_key_columns + feature_name_columns + timestamps) - - query = f""" - SELECT {field_string} - FROM ( - SELECT {field_string} - FROM {from_expression} - WHERE {timestamp_field} BETWEEN TIMESTAMP '{start_date}' AND TIMESTAMP '{end_date}' - ) - """ - engine = make_engine(config.offline_store) - - return MsSqlServerRetrievalJob( - query=query, - engine=engine, - config=config.offline_store, - full_feature_names=False, - on_demand_feature_views=None, + return pull_all_from_table_or_query_ibis( + config=config, + data_source=data_source, + join_key_columns=join_key_columns, + feature_name_columns=feature_name_columns, + timestamp_field=timestamp_field, + start_date=start_date, + end_date=end_date, + data_source_reader=_build_data_source_reader(config), + data_source_writer=_build_data_source_writer(config), ) @staticmethod - def get_historical_features( + def offline_write_batch( config: RepoConfig, - feature_views: List[FeatureView], - feature_refs: List[str], - entity_df: Union[pandas.DataFrame, str], - registry: BaseRegistry, - project: str, - full_feature_names: bool = False, - ) -> RetrievalJob: - warnings.warn( - "The Azure Synapse + Azure SQL offline store is an experimental feature in alpha development. " - "Some functionality may still be unstable so functionality can change in the future.", - RuntimeWarning, - ) - - expected_join_keys = _get_join_keys(project, feature_views, registry) - assert isinstance(config.offline_store, MsSqlServerOfflineStoreConfig) - engine = make_engine(config.offline_store) - if isinstance(entity_df, pandas.DataFrame): - entity_df_event_timestamp_col = ( - offline_utils.infer_event_timestamp_from_entity_df( - dict(zip(list(entity_df.columns), list(entity_df.dtypes))) - ) - ) - entity_df[entity_df_event_timestamp_col] = pandas.to_datetime( - entity_df[entity_df_event_timestamp_col], utc=True - ).fillna(pandas.Timestamp.now()) - - elif isinstance(entity_df, str): - raise ValueError( - "string entities are currently not supported in the MsSQL offline store." - ) - ( - table_schema, - table_name, - ) = _upload_entity_df_into_sqlserver_and_get_entity_schema( - engine, config, entity_df, full_feature_names=full_feature_names - ) - - _assert_expected_columns_in_sqlserver( - expected_join_keys, - entity_df_event_timestamp_col, - table_schema, - ) - - entity_df_event_timestamp_range = _get_entity_df_event_timestamp_range( - entity_df, - entity_df_event_timestamp_col, - engine, - ) - - # Build a query context containing all information required to template the SQL query - query_context = get_feature_view_query_context( - feature_refs, - feature_views, - registry, - project, - entity_df_timestamp_range=entity_df_event_timestamp_range, - ) - - # Generate the SQL query from the query context - query = build_point_in_time_query( - query_context, - left_table_query_string=table_name, - entity_df_event_timestamp_col=entity_df_event_timestamp_col, - entity_df_columns=table_schema.keys(), - full_feature_names=full_feature_names, - query_template=MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN, - ) - query = query.replace("`", "") - - job = MsSqlServerRetrievalJob( - query=query, - engine=engine, - config=config.offline_store, - full_feature_names=full_feature_names, - on_demand_feature_views=registry.list_on_demand_feature_views(project), + feature_view: FeatureView, + table: pyarrow.Table, + progress: Optional[Callable[[int], Any]], + ): + offline_write_batch_ibis( + config=config, + feature_view=feature_view, + table=table, + progress=progress, + data_source_writer=_build_data_source_writer(config), ) - return job @staticmethod def write_logged_features( @@ -241,410 +215,10 @@ def write_logged_features( logging_config: LoggingConfig, registry: BaseRegistry, ): - raise NotImplementedError() - - @staticmethod - def offline_write_batch( - config: RepoConfig, - feature_view: FeatureView, - table: pyarrow.Table, - progress: Optional[Callable[[int], Any]], - ): - raise NotImplementedError() - - -def _assert_expected_columns_in_dataframe( - join_keys: Set[str], entity_df_event_timestamp_col: str, entity_df: pandas.DataFrame -): - entity_df_columns = set(entity_df.columns.values) - expected_columns = join_keys.copy() - expected_columns.add(entity_df_event_timestamp_col) - - missing_keys = expected_columns - entity_df_columns - - if len(missing_keys) != 0: - raise errors.FeastEntityDFMissingColumnsError(expected_columns, missing_keys) - - -def _assert_expected_columns_in_sqlserver( - join_keys: Set[str], entity_df_event_timestamp_col: str, table_schema: EntitySchema -): - entity_columns = set(table_schema.keys()) - expected_columns = join_keys.copy() - expected_columns.add(entity_df_event_timestamp_col) - - missing_keys = expected_columns - entity_columns - - if len(missing_keys) != 0: - raise errors.FeastEntityDFMissingColumnsError(expected_columns, missing_keys) - - -def _get_join_keys( - project: str, feature_views: List[FeatureView], registry: BaseRegistry -) -> Set[str]: - join_keys = set() - for feature_view in feature_views: - entities = feature_view.entities - for entity_name in entities: - entity = registry.get_entity(entity_name, project) - join_keys.add(entity.join_key) - return join_keys - - -def _infer_event_timestamp_from_sqlserver_schema(table_schema) -> str: - if any( - schema_field["COLUMN_NAME"] == DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL - for schema_field in table_schema - ): - return DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL - else: - datetime_columns = list( - filter( - lambda schema_field: schema_field["DATA_TYPE"] == "DATETIMEOFFSET", - table_schema, - ) - ) - if len(datetime_columns) == 1: - print( - f"Using {datetime_columns[0]['COLUMN_NAME']} as the event timestamp. To specify a column explicitly, please name it {DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL}." - ) - return datetime_columns[0].name - else: - raise ValueError( - f"Please provide an entity_df with a column named {DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL} representing the time of events." - ) - - -class MsSqlServerRetrievalJob(RetrievalJob): - def __init__( - self, - query: str, - engine: Engine, - config: MsSqlServerOfflineStoreConfig, - full_feature_names: bool, - on_demand_feature_views: Optional[List[OnDemandFeatureView]] = None, - metadata: Optional[RetrievalMetadata] = None, - drop_columns: Optional[List[str]] = None, - ): - self.query = query - self.engine = engine - self._config = config - self._full_feature_names = full_feature_names - self._on_demand_feature_views = on_demand_feature_views or [] - self._drop_columns = drop_columns - self._metadata = metadata - - @property - def full_feature_names(self) -> bool: - return self._full_feature_names - - @property - def on_demand_feature_views(self) -> List[OnDemandFeatureView]: - return self._on_demand_feature_views - - def _to_df_internal(self, timeout: Optional[int] = None) -> pandas.DataFrame: - return pandas.read_sql(self.query, con=self.engine).fillna(value=np.nan) - - def _to_arrow_internal(self, timeout: Optional[int] = None) -> pyarrow.Table: - result = pandas.read_sql(self.query, con=self.engine).fillna(value=np.nan) - return pyarrow.Table.from_pandas(result) - - ## Implements persist in Feast 0.18 - This persists to filestorage - ## ToDo: Persist to Azure Storage - def persist( - self, - storage: SavedDatasetStorage, - allow_overwrite: Optional[bool] = False, - timeout: Optional[int] = None, - ): - assert isinstance(storage, SavedDatasetFileStorage) - - filesystem, path = FileSource.create_filesystem_and_path( - storage.file_options.uri, - storage.file_options.s3_endpoint_override, + write_logged_features_ibis( + config=config, + data=data, + source=source, + logging_config=logging_config, + registry=registry, ) - - if path.endswith(".parquet"): - pyarrow.parquet.write_table( - self.to_arrow(), where=path, filesystem=filesystem - ) - else: - # otherwise assume destination is directory - pyarrow.parquet.write_to_dataset( - self.to_arrow(), root_path=path, filesystem=filesystem - ) - - def supports_remote_storage_export(self) -> bool: - return False - - def to_remote_storage(self) -> List[str]: - raise NotImplementedError() - - @property - def metadata(self) -> Optional[RetrievalMetadata]: - return self._metadata - - -def _upload_entity_df_into_sqlserver_and_get_entity_schema( - engine: sqlalchemy.engine.Engine, - config: RepoConfig, - entity_df: Union[pandas.DataFrame, str], - full_feature_names: bool, -) -> Tuple[Dict[Any, Any], str]: - """ - Uploads a Pandas entity dataframe into a SQL Server table and constructs the - schema from the original entity_df dataframe. - """ - table_id = offline_utils.get_temp_entity_table_name() - session = sessionmaker(bind=engine)() - - if type(entity_df) is str: - # TODO: This should be a temporary table, right? - session.execute(f"SELECT * INTO {table_id} FROM ({entity_df}) t") # type: ignore - - session.commit() - - limited_entity_df = MsSqlServerRetrievalJob( - f"SELECT TOP 1 * FROM {table_id}", - engine, - config.offline_store, - full_feature_names=full_feature_names, - on_demand_feature_views=None, - ).to_df() - - entity_schema = ( - dict(zip(limited_entity_df.columns, limited_entity_df.dtypes)), - table_id, - ) - - elif isinstance(entity_df, pandas.DataFrame): - # Drop the index so that we don't have unnecessary columns - engine.execute(_df_to_create_table_sql(entity_df, table_id)) # type: ignore - entity_df.to_sql(name=table_id, con=engine, index=False, if_exists="append") - entity_schema = dict(zip(entity_df.columns, entity_df.dtypes)), table_id - - else: - raise ValueError( - f"The entity dataframe you have provided must be a SQL Server SQL query," - f" or a Pandas dataframe. But we found: {type(entity_df)} " - ) - - return entity_schema - - -def _df_to_create_table_sql(df: pandas.DataFrame, table_name: str) -> str: - pa_table = pa.Table.from_pandas(df) - - columns = [f""""{f.name}" {pa_to_mssql_type(f.type)}""" for f in pa_table.schema] - - return f""" - CREATE TABLE "{table_name}" ( - {", ".join(columns)} - ); - """ - - -def _get_entity_df_event_timestamp_range( - entity_df: Union[pandas.DataFrame, str], - entity_df_event_timestamp_col: str, - engine: Engine, -) -> Tuple[datetime, datetime]: - if isinstance(entity_df, pandas.DataFrame): - entity_df_event_timestamp = entity_df.loc[ - :, entity_df_event_timestamp_col - ].infer_objects() - if pandas.api.types.is_string_dtype(entity_df_event_timestamp): - entity_df_event_timestamp = pandas.to_datetime( - entity_df_event_timestamp, utc=True - ) - entity_df_event_timestamp_range = ( - entity_df_event_timestamp.min().to_pydatetime(), - entity_df_event_timestamp.max().to_pydatetime(), - ) - elif isinstance(entity_df, str): - # If the entity_df is a string (SQL query), determine range - # from table - df = pandas.read_sql(entity_df, con=engine).fillna(value=np.nan) - entity_df_event_timestamp = df.loc[ - :, entity_df_event_timestamp_col - ].infer_objects() - if pandas.api.types.is_string_dtype(entity_df_event_timestamp): - entity_df_event_timestamp = pandas.to_datetime( - entity_df_event_timestamp, utc=True - ) - entity_df_event_timestamp_range = ( - entity_df_event_timestamp.min().to_pydatetime(), - entity_df_event_timestamp.max().to_pydatetime(), - ) - else: - raise InvalidEntityType(type(entity_df)) - - return entity_df_event_timestamp_range - - -# TODO: Optimizations -# * Use NEWID() instead of ROW_NUMBER(), or join on entity columns directly -# * Precompute ROW_NUMBER() so that it doesn't have to be recomputed for every query on entity_dataframe -# * Create temporary tables instead of keeping all tables in memory - -MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN = """ -/* - Compute a deterministic hash for the `left_table_query_string` that will be used throughout - all the logic as the field to GROUP BY the data -*/ -WITH entity_dataframe AS ( - SELECT *, - {{entity_df_event_timestamp_col}} AS entity_timestamp - {% for featureview in featureviews %} - ,CONCAT( - {% for entity_key in unique_entity_keys %} - {{entity_key}}, - {% endfor %} - {{entity_df_event_timestamp_col}} - ) AS {{featureview.name}}__entity_row_unique_id - {% endfor %} - FROM {{ left_table_query_string }} -), - -{% for featureview in featureviews %} - -{{ featureview.name }}__entity_dataframe AS ( - SELECT - {{ featureview.entities | join(', ')}}{% if featureview.entities %},{% else %}{% endif %} - entity_timestamp, - {{featureview.name}}__entity_row_unique_id - FROM entity_dataframe - GROUP BY - {{ featureview.entities | join(', ')}}{% if featureview.entities %},{% else %}{% endif %} - entity_timestamp, - {{featureview.name}}__entity_row_unique_id -), - -/* - This query template performs the point-in-time correctness join for a single feature set table - to the provided entity table. - - 1. We first join the current feature_view to the entity dataframe that has been passed. - This JOIN has the following logic: - - For each row of the entity dataframe, only keep the rows where the timestamp_field` - is less than the one provided in the entity dataframe - - If there a TTL for the current feature_view, also keep the rows where the `timestamp_field` - is higher the the one provided minus the TTL - - For each row, Join on the entity key and retrieve the `entity_row_unique_id` that has been - computed previously - - The output of this CTE will contain all the necessary information and already filtered out most - of the data that is not relevant. -*/ - -{{ featureview.name }}__subquery AS ( - SELECT - {{ featureview.timestamp_field }} as event_timestamp, - {{ featureview.created_timestamp_column ~ ' as created_timestamp,' if featureview.created_timestamp_column else '' }} - {{ featureview.entity_selections | join(', ')}}{% if featureview.entity_selections %},{% else %}{% endif %} - {% for feature in featureview.features %} - {{ feature }} as {% if full_feature_names %}{{ featureview.name }}__{{featureview.field_mapping.get(feature, feature)}}{% else %}{{ featureview.field_mapping.get(feature, feature) }}{% endif %}{% if loop.last %}{% else %}, {% endif %} - {% endfor %} - FROM {{ featureview.table_subquery }} - WHERE {{ featureview.timestamp_field }} <= '{{ featureview.max_event_timestamp }}' - {% if featureview.ttl == 0 %}{% else %} - AND {{ featureview.timestamp_field }} >= '{{ featureview.min_event_timestamp }}' - {% endif %} -), - -{{ featureview.name }}__base AS ( - SELECT - subquery.*, - entity_dataframe.{{entity_df_event_timestamp_col}} AS entity_timestamp, - entity_dataframe.{{featureview.name}}__entity_row_unique_id - FROM {{ featureview.name }}__subquery AS subquery - INNER JOIN entity_dataframe - ON 1=1 - AND subquery.event_timestamp <= entity_dataframe.{{entity_df_event_timestamp_col}} - - {% if featureview.ttl == 0 %}{% else %} - AND {{ featureview.ttl }} > = DATEDIFF(SECOND, subquery.event_timestamp, entity_dataframe.{{entity_df_event_timestamp_col}}) - {% endif %} - - {% for entity in featureview.entities %} - AND subquery.{{ entity }} = entity_dataframe.{{ entity }} - {% endfor %} -), - -/* - 2. If the `created_timestamp_column` has been set, we need to - deduplicate the data first. This is done by calculating the - `MAX(created_at_timestamp)` for each event_timestamp. - We then join the data on the next CTE -*/ -{% if featureview.created_timestamp_column %} -{{ featureview.name }}__dedup AS ( - SELECT - {{featureview.name}}__entity_row_unique_id, - event_timestamp, - MAX(created_timestamp) as created_timestamp - FROM {{ featureview.name }}__base - GROUP BY {{featureview.name}}__entity_row_unique_id, event_timestamp -), -{% endif %} - -/* - 3. The data has been filtered during the first CTE "*__base" - Thus we only need to compute the latest timestamp of each feature. -*/ -{{ featureview.name }}__latest AS ( - SELECT - {{ featureview.name }}__base.{{ featureview.name }}__entity_row_unique_id, - MAX({{ featureview.name }}__base.event_timestamp) AS event_timestamp - {% if featureview.created_timestamp_column %} - ,MAX({{ featureview.name }}__base.created_timestamp) AS created_timestamp - {% endif %} - - FROM {{ featureview.name }}__base - {% if featureview.created_timestamp_column %} - INNER JOIN {{ featureview.name }}__dedup - ON {{ featureview.name }}__dedup.{{ featureview.name }}__entity_row_unique_id = {{ featureview.name }}__base.{{ featureview.name }}__entity_row_unique_id - AND {{ featureview.name }}__dedup.event_timestamp = {{ featureview.name }}__base.event_timestamp - AND {{ featureview.name }}__dedup.created_timestamp = {{ featureview.name }}__base.created_timestamp - {% endif %} - - GROUP BY {{ featureview.name }}__base.{{ featureview.name }}__entity_row_unique_id -), - -/* - 4. Once we know the latest value of each feature for a given timestamp, - we can join again the data back to the original "base" dataset -*/ -{{ featureview.name }}__cleaned AS ( - SELECT base.* - FROM {{ featureview.name }}__base as base - INNER JOIN {{ featureview.name }}__latest - ON base.{{ featureview.name }}__entity_row_unique_id = {{ featureview.name }}__latest.{{ featureview.name }}__entity_row_unique_id - AND base.event_timestamp = {{ featureview.name }}__latest.event_timestamp - {% if featureview.created_timestamp_column %} - AND base.created_timestamp = {{ featureview.name }}__latest.created_timestamp - {% endif %} -){% if loop.last %}{% else %}, {% endif %} - -{% endfor %} - -/* - Joins the outputs of multiple time travel joins to a single table. - The entity_dataframe dataset being our source of truth here. - */ - -SELECT {{ final_output_feature_names | join(', ')}} -FROM entity_dataframe -{% for featureview in featureviews %} -LEFT JOIN ( - SELECT - {{featureview.name}}__entity_row_unique_id - {% for feature in featureview.features %} - ,{% if full_feature_names %}{{ featureview.name }}__{{featureview.field_mapping.get(feature, feature)}}{% else %}{{ featureview.field_mapping.get(feature, feature) }}{% endif %} - {% endfor %} - FROM "{{ featureview.name }}__cleaned" -) {{ featureview.name }}__cleaned -ON -{{ featureview.name }}__cleaned.{{ featureview.name }}__entity_row_unique_id = entity_dataframe.{{ featureview.name }}__entity_row_unique_id -{% endfor %} -""" diff --git a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssqlserver_source.py b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssqlserver_source.py index 6b126fa40c0..39abd1c9e74 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssqlserver_source.py +++ b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/mssqlserver_source.py @@ -3,15 +3,10 @@ import json import warnings from typing import Callable, Dict, Iterable, Optional, Tuple - -import pandas -from sqlalchemy import create_engine +from urllib import parse from feast import type_map from feast.data_source import DataSource -from feast.infra.offline_stores.contrib.mssql_offline_store.mssql import ( - MsSqlServerOfflineStoreConfig, -) from feast.protos.feast.core.DataSource_pb2 import DataSource as DataSourceProto from feast.repo_config import RepoConfig from feast.value_type import ValueType @@ -20,6 +15,21 @@ warnings.simplefilter("once", RuntimeWarning) +def get_ibis_connection(config: RepoConfig): + import ibis + + connection_params = parse.urlparse(config.offline_store.connection_string) + additional_kwargs = dict(parse.parse_qsl(connection_params.query)) + return ibis.mssql.connect( + user=connection_params.username, + password=connection_params.password, + host=connection_params.hostname, + port=connection_params.port, + database=connection_params.path.strip("/"), + **additional_kwargs, + ) + + class MsSqlServerOptions: """ DataSource MsSQLServer options used to source features from MsSQLServer query @@ -114,11 +124,11 @@ def __init__( tags: Optional[Dict[str, str]] = None, owner: Optional[str] = None, ): - warnings.warn( - "The Azure Synapse + Azure SQL data source is an experimental feature in alpha development. " - "Some functionality may still be unstable so functionality can change in the future.", - RuntimeWarning, - ) + # warnings.warn( + # "The Azure Synapse + Azure SQL data source is an experimental feature in alpha development. " + # "Some functionality may still be unstable so functionality can change in the future.", + # RuntimeWarning, + # ) self._mssqlserver_options = MsSqlServerOptions( connection_str=connection_str, table_ref=table_ref ) @@ -222,11 +232,8 @@ def source_datatype_to_feast_value_type() -> Callable[[str], ValueType]: def get_table_column_names_and_types( self, config: RepoConfig ) -> Iterable[Tuple[str, str]]: - assert isinstance(config.offline_store, MsSqlServerOfflineStoreConfig) - conn = create_engine(config.offline_store.connection_string) - self._mssqlserver_options.connection_str = ( - config.offline_store.connection_string - ) + con = get_ibis_connection(config) + name_type_pairs = [] if len(self.table_ref.split(".")) == 2: database, table_name = self.table_ref.split(".") @@ -240,7 +247,8 @@ def get_table_column_names_and_types( WHERE TABLE_NAME = '{self.table_ref}' """ - table_schema = pandas.read_sql(columns_query, conn) + table_schema = con.sql(columns_query).execute() + name_type_pairs.extend( list( zip( diff --git a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/tests/data_source.py b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/tests/data_source.py index ccf826c068f..9c87b8d7520 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/tests/data_source.py +++ b/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/tests/data_source.py @@ -1,15 +1,15 @@ from typing import Dict, List, Optional +import ibis import pandas as pd import pytest -from sqlalchemy import create_engine from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.mssql import SqlServerContainer from feast.data_source import DataSource +from feast.feature_logging import LoggingDestination from feast.infra.offline_stores.contrib.mssql_offline_store.mssql import ( MsSqlServerOfflineStoreConfig, - _df_to_create_table_sql, ) from feast.infra.offline_stores.contrib.mssql_offline_store.mssqlserver_source import ( MsSqlServerSource, @@ -26,7 +26,7 @@ @pytest.fixture(scope="session") def mssql_container(): container = SqlServerContainer( - user=MSSQL_USER, + username=MSSQL_USER, password=MSSQL_PASSWORD, image="mcr.microsoft.com/azure-sql-edge:1.0.6", ) @@ -56,8 +56,10 @@ def __init__( ) def create_offline_store_config(self) -> MsSqlServerOfflineStoreConfig: + connection_string = self.container.get_connection_url() + connection_string += "?driver=FreeTDS" return MsSqlServerOfflineStoreConfig( - connection_string=self.container.get_connection_url(), + connection_string=connection_string, ) def create_data_source( @@ -66,7 +68,7 @@ def create_data_source( destination_name: str, created_timestamp_column="created_ts", field_mapping: Optional[Dict[str, str]] = None, - timestamp_field: Optional[str] = "ts", + timestamp_field: Optional[str] = None, ) -> DataSource: # Make sure the field mapping is correct and convert the datetime datasources. if timestamp_field in df: @@ -79,13 +81,23 @@ def create_data_source( ).fillna(pd.Timestamp.now()) connection_string = self.create_offline_store_config().connection_string - engine = create_engine(connection_string) + + con = ibis.mssql.connect( + user=self.container.username, + password=self.container.password, + host=self.container.get_container_host_ip(), + port=self.container.get_exposed_port(self.container.port), + database=self.container.dbname, + driver="FreeTDS", + ) + destination_name = self.get_prefixed_table_name(destination_name) - # Create table - engine.execute(_df_to_create_table_sql(df, destination_name)) # type: ignore - # Upload dataframe to azure table - df.to_sql(destination_name, engine, index=False, if_exists="append") + con.create_table( + name=destination_name, + schema=ibis.Schema.from_pandas(df.dtypes.to_dict().items()), + ) + con.insert(table_name=destination_name, obj=df) self.tables.append(destination_name) return MsSqlServerSource( @@ -100,8 +112,12 @@ def create_data_source( def create_saved_dataset_destination(self) -> SavedDatasetStorage: raise NotImplementedError + def create_logged_features_destination(self) -> LoggingDestination: + raise NotImplementedError + def get_prefixed_table_name(self, destination_name: str) -> str: return f"{self.project_name}_{destination_name}" def teardown(self): - raise NotImplementedError + pass + # raise NotImplementedError diff --git a/sdk/python/feast/infra/offline_stores/ibis.py b/sdk/python/feast/infra/offline_stores/ibis.py index dd81fd1d4e3..4de16cbda3c 100644 --- a/sdk/python/feast/infra/offline_stores/ibis.py +++ b/sdk/python/feast/infra/offline_stores/ibis.py @@ -1,3 +1,5 @@ +import random +import string import uuid from datetime import datetime, timedelta from pathlib import Path @@ -95,7 +97,9 @@ def _get_entity_df_event_timestamp_range( entity_df_event_timestamp = entity_df.loc[ :, entity_df_event_timestamp_col ].infer_objects() - if pd.api.types.is_string_dtype(entity_df_event_timestamp): + if pd.api.types.is_string_dtype( + entity_df_event_timestamp + ) or pd.api.types.is_object_dtype(entity_df_event_timestamp): entity_df_event_timestamp = pd.to_datetime(entity_df_event_timestamp, utc=True) entity_df_event_timestamp_range = ( entity_df_event_timestamp.min().to_pydatetime(), @@ -107,7 +111,10 @@ def _get_entity_df_event_timestamp_range( def _to_utc(entity_df: pd.DataFrame, event_timestamp_col): entity_df_event_timestamp = entity_df.loc[:, event_timestamp_col].infer_objects() - if pd.api.types.is_string_dtype(entity_df_event_timestamp): + + if pd.api.types.is_string_dtype( + entity_df_event_timestamp + ) or pd.api.types.is_object_dtype(entity_df_event_timestamp): entity_df_event_timestamp = pd.to_datetime(entity_df_event_timestamp, utc=True) entity_df[event_timestamp_col] = entity_df_event_timestamp @@ -146,6 +153,7 @@ def get_historical_features_ibis( full_feature_names: bool = False, staging_location: Optional[str] = None, staging_location_endpoint_override: Optional[str] = None, + event_expire_timestamp_fn=None, ) -> RetrievalJob: entity_schema = _get_entity_schema( entity_df=entity_df, @@ -218,6 +226,7 @@ def read_fv( for feature_view in feature_views ], event_timestamp_col=event_timestamp_col, + event_expire_timestamp_fn=event_expire_timestamp_fn, ) odfvs = OnDemandFeatureView.get_requested_odfvs(feature_refs, project, registry) @@ -345,6 +354,7 @@ def point_in_time_join( entity_table: Table, feature_tables: List[Tuple[Table, str, str, Dict[str, str], List[str], timedelta]], event_timestamp_col="event_timestamp", + event_expire_timestamp_fn=None, ): # TODO handle ttl all_entities = [event_timestamp_col] @@ -375,6 +385,19 @@ def point_in_time_join( feature_refs, ttl, ) in feature_tables: + if ttl: + if not event_expire_timestamp_fn: + feature_table = feature_table.mutate( + event_expire_timestamp=feature_table[timestamp_field] + + ibis.literal(ttl) + ) + else: + alias = "".join(random.choices(string.ascii_uppercase, k=10)) + + feature_table = feature_table.alias(alias=alias).sql( + f"SELECT *, {event_expire_timestamp_fn(timestamp_field, ttl)} AS event_expire_timestamp FROM {alias}" + ) + predicates = [ feature_table[k] == entity_table[v] for k, v in join_key_map.items() ] @@ -385,8 +408,8 @@ def point_in_time_join( if ttl: predicates.append( - feature_table[timestamp_field] - >= entity_table[event_timestamp_col] - ibis.literal(ttl) + feature_table["event_expire_timestamp"] + >= entity_table[event_timestamp_col] ) feature_table = feature_table.inner_join( diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index 703c1dc7c50..4e9b54c6316 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -600,7 +600,9 @@ def mssql_to_feast_value_type(mssql_type_as_str: str) -> ValueType: "char": ValueType.STRING, "date": ValueType.UNIX_TIMESTAMP, "datetime": ValueType.UNIX_TIMESTAMP, + "datetimeoffset": ValueType.UNIX_TIMESTAMP, "float": ValueType.FLOAT, + "int": ValueType.INT32, "nchar": ValueType.STRING, "nvarchar": ValueType.STRING, "nvarchar(max)": ValueType.STRING, diff --git a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py index 50f048928dc..6d5eeb90c71 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_offline_store.py @@ -9,9 +9,6 @@ AthenaOfflineStoreConfig, AthenaRetrievalJob, ) -from feast.infra.offline_stores.contrib.mssql_offline_store.mssql import ( - MsSqlServerRetrievalJob, -) from feast.infra.offline_stores.contrib.postgres_offline_store.postgres import ( PostgreSQLOfflineStoreConfig, PostgreSQLRetrievalJob, @@ -104,7 +101,6 @@ def metadata(self) -> Optional[RetrievalMetadata]: RedshiftRetrievalJob, SnowflakeRetrievalJob, AthenaRetrievalJob, - MsSqlServerRetrievalJob, PostgreSQLRetrievalJob, SparkRetrievalJob, TrinoRetrievalJob, @@ -173,13 +169,6 @@ def retrieval_job(request, environment): config=environment.config, full_feature_names=False, ) - elif request.param is MsSqlServerRetrievalJob: - return MsSqlServerRetrievalJob( - query="query", - engine=MagicMock(), - config=environment.config, - full_feature_names=False, - ) elif request.param is PostgreSQLRetrievalJob: offline_store_config = PostgreSQLOfflineStoreConfig( host="str", diff --git a/setup.py b/setup.py index 2043bf1b3f4..400555f0e1e 100644 --- a/setup.py +++ b/setup.py @@ -150,6 +150,8 @@ SINGLESTORE_REQUIRED = ["singlestoredb"] +MSSQL_REQUIRED = ["ibis-framework[mssql]>=9.0.0,<10"] + CI_REQUIRED = ( [ "build", @@ -369,6 +371,7 @@ def run(self): "postgres": POSTGRES_REQUIRED, "azure": AZURE_REQUIRED, "mysql": MYSQL_REQUIRED, + "mssql": MSSQL_REQUIRED, "ge": GE_REQUIRED, "hbase": HBASE_REQUIRED, "docs": DOCS_REQUIRED, From b54c1cdbb8555682bcfd3aa9f63cc59c638957ab Mon Sep 17 00:00:00 2001 From: Tornike Gurgenidze Date: Fri, 19 Jul 2024 06:26:59 +0400 Subject: [PATCH 39/44] chore: Remove firebase-admin from ci dependencies (#4359) remove firebase-admin from ci dependencies Signed-off-by: tokoko --- .../requirements/py3.10-ci-requirements.txt | 180 ++---------------- .../requirements/py3.10-requirements.txt | 48 +---- .../requirements/py3.11-ci-requirements.txt | 180 ++---------------- .../requirements/py3.11-requirements.txt | 48 +---- .../requirements/py3.9-ci-requirements.txt | 180 ++---------------- .../requirements/py3.9-requirements.txt | 48 +---- setup.py | 1 - 7 files changed, 78 insertions(+), 607 deletions(-) diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index a7f300a0ed5..a9f1d625a84 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -52,9 +48,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -66,9 +60,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -77,15 +69,11 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb -cachecontrol==0.14.0 - # via firebase-admin cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -108,7 +96,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -118,9 +105,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -129,7 +114,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -141,9 +125,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -155,9 +137,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -171,7 +151,6 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -186,7 +165,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -195,8 +173,6 @@ filelock==3.15.4 # via # snowflake-connector-python # virtualenv -firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -204,60 +180,37 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) - # firebase-admin - # google-api-python-client # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-core # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.134.0 - # via firebase-admin google-auth==2.30.0 # via # google-api-core - # google-api-python-client - # google-auth-httplib2 # google-cloud-bigquery-storage # google-cloud-core - # google-cloud-firestore # google-cloud-storage # kubernetes -google-auth-httplib2==0.2.0 - # via google-api-python-client google-cloud-bigquery[pandas]==3.13.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) -google-cloud-firestore==2.16.0 - # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -268,17 +221,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -289,46 +241,30 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx -httplib2==0.22.0 - # via - # google-api-python-client - # google-auth-httplib2 httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.1.0 - # via - # feast (setup.py) - # ibis-substrait + # via ibis-substrait ibis-substrait==4.0.0 - # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -363,7 +299,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -387,7 +322,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -433,7 +367,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -454,37 +387,28 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity # msal-extensions msal-extensions==1.2.0 # via azure-identity -msgpack==1.0.8 - # via cachecontrol multidict==6.0.5 # via # aiohttp # yarl mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -507,7 +431,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -542,7 +465,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -568,7 +490,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -581,7 +502,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -593,16 +513,13 @@ proto-plus==1.24.0 # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # googleapis-common-protos # grpc-google-iam-v1 # grpcio-health-checking @@ -614,11 +531,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.19 - # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -630,14 +544,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -655,19 +567,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -678,26 +587,19 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 - # via - # great-expectations - # httplib2 + # via great-expectations pyproject-hooks==1.1.0 # via # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -707,21 +609,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -750,7 +644,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -764,21 +657,16 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core - # cachecontrol # docker # google-api-core # google-cloud-bigquery @@ -811,7 +699,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -821,7 +708,6 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -838,7 +724,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -859,13 +744,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -879,11 +762,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via feast (setup.py) sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -893,21 +774,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via # build @@ -935,9 +812,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -954,39 +829,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -1021,11 +882,8 @@ ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema -uritemplate==4.1.1 - # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1037,15 +895,11 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 39a278818f0..0cca1068634 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -20,22 +20,17 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -43,15 +38,14 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -71,11 +65,8 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -87,16 +78,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -108,29 +96,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -141,7 +120,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -149,7 +127,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -165,15 +142,11 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -181,9 +154,7 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -207,7 +178,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index 4c1be0a5b43..ce2e50b8a7e 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -48,9 +44,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -62,9 +56,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -73,15 +65,11 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb -cachecontrol==0.14.0 - # via firebase-admin cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -104,7 +92,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -114,9 +101,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -125,7 +110,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -137,9 +121,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -151,9 +133,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -167,7 +147,6 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -177,7 +156,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -186,8 +164,6 @@ filelock==3.15.4 # via # snowflake-connector-python # virtualenv -firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -195,60 +171,37 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) - # firebase-admin - # google-api-python-client # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-core # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.134.0 - # via firebase-admin google-auth==2.30.0 # via # google-api-core - # google-api-python-client - # google-auth-httplib2 # google-cloud-bigquery-storage # google-cloud-core - # google-cloud-firestore # google-cloud-storage # kubernetes -google-auth-httplib2==0.2.0 - # via google-api-python-client google-cloud-bigquery[pandas]==3.13.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) -google-cloud-firestore==2.16.0 - # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -259,17 +212,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -280,46 +232,30 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx -httplib2==0.22.0 - # via - # google-api-python-client - # google-auth-httplib2 httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.1.0 - # via - # feast (setup.py) - # ibis-substrait + # via ibis-substrait ibis-substrait==4.0.0 - # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -354,7 +290,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -378,7 +313,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -424,7 +358,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -445,37 +378,28 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity # msal-extensions msal-extensions==1.2.0 # via azure-identity -msgpack==1.0.8 - # via cachecontrol multidict==6.0.5 # via # aiohttp # yarl mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -498,7 +422,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -533,7 +456,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -559,7 +481,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -572,7 +493,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -584,16 +504,13 @@ proto-plus==1.24.0 # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # googleapis-common-protos # grpc-google-iam-v1 # grpcio-health-checking @@ -605,11 +522,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.19 - # via feast (setup.py) psycopg-binary==3.1.19 # via psycopg psycopg-pool==3.2.2 @@ -621,14 +535,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -646,19 +558,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -669,26 +578,19 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 - # via - # great-expectations - # httplib2 + # via great-expectations pyproject-hooks==1.1.0 # via # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -698,21 +600,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -741,7 +635,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -755,21 +648,16 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core - # cachecontrol # docker # google-api-core # google-cloud-bigquery @@ -802,7 +690,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -812,7 +699,6 @@ rsa==4.9 ruamel-yaml==0.17.17 # via great-expectations ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.14.0 @@ -829,7 +715,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -850,13 +735,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -870,11 +753,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via feast (setup.py) sqlglot==25.1.0 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -884,21 +765,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomlkit==0.12.5 # via snowflake-connector-python toolz==0.12.1 @@ -916,9 +793,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -935,39 +810,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -999,11 +860,8 @@ ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema -uritemplate==4.1.1 - # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1015,15 +873,11 @@ urllib3==1.26.19 # rockset # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index 44e658113ae..687e4bfe52e 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -20,36 +20,30 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 # via fastapi fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -69,11 +63,8 @@ idna==3.7 importlib-metadata==7.1.0 # via dask jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -85,16 +76,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -106,29 +94,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -139,7 +118,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -147,7 +125,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -163,23 +140,17 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) toolz==0.12.1 # via # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -201,7 +172,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index 25cdea7a688..017c1c8920a 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1,7 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt aiobotocore==2.13.1 - # via feast (setup.py) aiohttp==3.9.5 # via aiobotocore aioitertools==0.11.0 @@ -20,8 +19,6 @@ anyio==4.4.0 # jupyter-server # starlette # watchfiles -appnope==0.1.4 - # via ipykernel argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 @@ -31,7 +28,6 @@ arrow==1.3.0 asn1crypto==1.5.1 # via snowflake-connector-python assertpy==1.1 - # via feast (setup.py) asttokens==2.4.1 # via stack-data async-lru==2.0.4 @@ -52,9 +48,7 @@ azure-core==1.30.2 # azure-identity # azure-storage-blob azure-identity==1.17.1 - # via feast (setup.py) azure-storage-blob==12.20.0 - # via feast (setup.py) babel==2.15.0 # via # jupyterlab-server @@ -66,9 +60,7 @@ bidict==0.23.1 bleach==6.1.0 # via nbconvert boto3==1.34.131 - # via - # feast (setup.py) - # moto + # via moto botocore==1.34.131 # via # aiobotocore @@ -77,15 +69,11 @@ botocore==1.34.131 # s3transfer build==1.2.1 # via - # feast (setup.py) # pip-tools # singlestoredb -cachecontrol==0.14.0 - # via firebase-admin cachetools==5.3.3 # via google-auth cassandra-driver==3.29.1 - # via feast (setup.py) certifi==2024.7.4 # via # elastic-transport @@ -108,7 +96,6 @@ charset-normalizer==3.3.2 # snowflake-connector-python click==8.1.7 # via - # feast (setup.py) # dask # geomet # great-expectations @@ -118,9 +105,7 @@ click==8.1.7 cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via - # feast (setup.py) - # great-expectations + # via great-expectations comm==0.2.2 # via # ipykernel @@ -129,7 +114,6 @@ coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via - # feast (setup.py) # azure-identity # azure-storage-blob # great-expectations @@ -141,9 +125,7 @@ cryptography==42.0.8 # types-pyopenssl # types-redis dask[dataframe]==2024.6.2 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.6 # via dask db-dtypes==1.2.0 @@ -155,9 +137,7 @@ decorator==5.1.1 defusedxml==0.7.1 # via nbconvert deltalake==0.18.1 - # via feast (setup.py) dill==0.3.8 - # via feast (setup.py) distlib==0.3.8 # via virtualenv dnspython==2.6.1 @@ -171,7 +151,6 @@ duckdb==0.10.3 elastic-transport==8.13.1 # via elasticsearch elasticsearch==8.14.0 - # via feast (setup.py) email-validator==2.2.0 # via fastapi entrypoints==0.4 @@ -186,7 +165,6 @@ execnet==2.1.1 executing==2.0.1 # via stack-data fastapi==0.111.0 - # via feast (setup.py) fastapi-cli==0.0.4 # via fastapi fastjsonschema==2.20.0 @@ -195,8 +173,6 @@ filelock==3.15.4 # via # snowflake-connector-python # virtualenv -firebase-admin==5.4.0 - # via feast (setup.py) fqdn==1.5.1 # via jsonschema frozenlist==1.4.1 @@ -204,60 +180,37 @@ frozenlist==1.4.1 # aiohttp # aiosignal fsspec==2023.12.2 - # via - # feast (setup.py) - # dask + # via dask geojson==2.5.0 # via rockset geomet==0.2.1.post1 # via cassandra-driver google-api-core[grpc]==2.19.1 # via - # feast (setup.py) - # firebase-admin - # google-api-python-client # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-core # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.134.0 - # via firebase-admin google-auth==2.30.0 # via # google-api-core - # google-api-python-client - # google-auth-httplib2 # google-cloud-bigquery-storage # google-cloud-core - # google-cloud-firestore # google-cloud-storage # kubernetes -google-auth-httplib2==0.2.0 - # via google-api-python-client google-cloud-bigquery[pandas]==3.13.0 - # via feast (setup.py) google-cloud-bigquery-storage==2.25.0 - # via feast (setup.py) google-cloud-bigtable==2.24.0 - # via feast (setup.py) google-cloud-core==2.4.1 # via # google-cloud-bigquery # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # google-cloud-storage google-cloud-datastore==2.19.0 - # via feast (setup.py) -google-cloud-firestore==2.16.0 - # via firebase-admin google-cloud-storage==2.17.0 - # via - # feast (setup.py) - # firebase-admin google-crc32c==1.5.0 # via # google-cloud-storage @@ -268,17 +221,16 @@ google-resumable-media==2.7.1 # google-cloud-storage googleapis-common-protos[grpc]==1.63.2 # via - # feast (setup.py) # google-api-core # grpc-google-iam-v1 # grpcio-status great-expectations==0.18.16 - # via feast (setup.py) +greenlet==3.0.3 + # via sqlalchemy grpc-google-iam-v1==0.13.1 # via google-cloud-bigtable grpcio==1.64.1 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # googleapis-common-protos @@ -289,46 +241,30 @@ grpcio==1.64.1 # grpcio-testing # grpcio-tools grpcio-health-checking==1.62.2 - # via feast (setup.py) grpcio-reflection==1.62.2 - # via feast (setup.py) grpcio-status==1.62.2 # via google-api-core grpcio-testing==1.62.2 - # via feast (setup.py) grpcio-tools==1.62.2 - # via feast (setup.py) gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore # uvicorn happybase==1.2.0 - # via feast (setup.py) hazelcast-python-client==5.4.0 - # via feast (setup.py) hiredis==2.3.2 - # via feast (setup.py) httpcore==1.0.5 # via httpx -httplib2==0.22.0 - # via - # google-api-python-client - # google-auth-httplib2 httptools==0.6.1 # via uvicorn httpx==0.27.0 # via - # feast (setup.py) # fastapi # jupyterlab ibis-framework[duckdb]==9.0.0 - # via - # feast (setup.py) - # ibis-substrait + # via ibis-substrait ibis-substrait==4.0.0 - # via feast (setup.py) identify==2.5.36 # via pre-commit idna==3.7 @@ -372,7 +308,6 @@ jedi==0.19.1 # via ipython jinja2==3.1.4 # via - # feast (setup.py) # altair # fastapi # great-expectations @@ -396,7 +331,6 @@ jsonpointer==3.0.0 # jsonschema jsonschema[format-nongpl]==4.22.0 # via - # feast (setup.py) # altair # great-expectations # jupyter-events @@ -442,7 +376,6 @@ jupyterlab-server==2.27.2 jupyterlab-widgets==3.0.11 # via ipywidgets kubernetes==20.13.0 - # via feast (setup.py) locket==1.0.0 # via partd makefun==1.15.2 @@ -463,37 +396,28 @@ matplotlib-inline==0.1.7 mdurl==0.1.2 # via markdown-it-py minio==7.1.0 - # via feast (setup.py) mistune==3.0.2 # via # great-expectations # nbconvert mmh3==4.1.0 - # via feast (setup.py) mock==2.0.0 - # via feast (setup.py) moto==4.2.14 - # via feast (setup.py) msal==1.29.0 # via # azure-identity # msal-extensions msal-extensions==1.2.0 # via azure-identity -msgpack==1.0.8 - # via cachecontrol multidict==6.0.5 # via # aiohttp # yarl mypy==1.10.1 - # via - # feast (setup.py) - # sqlalchemy + # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.3.0 - # via feast (setup.py) nbclient==0.10.0 # via nbconvert nbconvert==7.16.4 @@ -516,7 +440,6 @@ notebook-shim==0.2.4 # notebook numpy==1.26.4 # via - # feast (setup.py) # altair # dask # db-dtypes @@ -551,7 +474,6 @@ packaging==24.1 # sphinx pandas==2.2.2 # via - # feast (setup.py) # altair # dask # dask-expr @@ -577,7 +499,6 @@ pexpect==4.9.0 pip==24.1.1 # via pip-tools pip-tools==7.4.1 - # via feast (setup.py) platformdirs==3.11.0 # via # jupyter-core @@ -590,7 +511,6 @@ ply==3.11 portalocker==2.10.0 # via msal-extensions pre-commit==3.3.1 - # via feast (setup.py) prometheus-client==0.20.0 # via jupyter-server prompt-toolkit==3.0.47 @@ -602,16 +522,13 @@ proto-plus==1.24.0 # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore protobuf==4.25.3 # via - # feast (setup.py) # google-api-core # google-cloud-bigquery # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore - # google-cloud-firestore # googleapis-common-protos # grpc-google-iam-v1 # grpcio-health-checking @@ -623,11 +540,8 @@ protobuf==4.25.3 # proto-plus # substrait psutil==5.9.0 - # via - # feast (setup.py) - # ipykernel + # via ipykernel psycopg[binary, pool]==3.1.18 - # via feast (setup.py) psycopg-binary==3.1.18 # via psycopg psycopg-pool==3.2.2 @@ -639,14 +553,12 @@ ptyprocess==0.7.0 pure-eval==0.2.2 # via stack-data py==1.11.0 - # via feast (setup.py) py-cpuinfo==9.0.0 # via pytest-benchmark py4j==0.10.9.7 # via pyspark pyarrow==15.0.2 # via - # feast (setup.py) # dask-expr # db-dtypes # deltalake @@ -664,19 +576,16 @@ pyasn1==0.6.0 pyasn1-modules==0.4.0 # via google-auth pybindgen==0.22.1 - # via feast (setup.py) pycparser==2.22 # via cffi pydantic==2.7.4 # via - # feast (setup.py) # fastapi # great-expectations pydantic-core==2.18.4 # via pydantic pygments==2.18.0 # via - # feast (setup.py) # ipython # nbconvert # rich @@ -687,26 +596,19 @@ pyjwt[crypto]==2.8.0 # singlestoredb # snowflake-connector-python pymssql==2.3.0 - # via feast (setup.py) pymysql==1.1.1 - # via feast (setup.py) pyodbc==5.1.0 - # via feast (setup.py) pyopenssl==24.1.0 # via snowflake-connector-python pyparsing==3.1.2 - # via - # great-expectations - # httplib2 + # via great-expectations pyproject-hooks==1.1.0 # via # build # pip-tools pyspark==3.5.1 - # via feast (setup.py) pytest==7.4.4 # via - # feast (setup.py) # pytest-benchmark # pytest-cov # pytest-env @@ -716,21 +618,13 @@ pytest==7.4.4 # pytest-timeout # pytest-xdist pytest-benchmark==3.4.1 - # via feast (setup.py) pytest-cov==5.0.0 - # via feast (setup.py) pytest-env==1.1.3 - # via feast (setup.py) pytest-lazy-fixture==0.6.3 - # via feast (setup.py) pytest-mock==1.10.4 - # via feast (setup.py) pytest-ordering==0.6 - # via feast (setup.py) pytest-timeout==1.4.2 - # via feast (setup.py) pytest-xdist==3.6.1 - # via feast (setup.py) python-dateutil==2.9.0.post0 # via # arrow @@ -759,7 +653,6 @@ pytz==2024.1 # trino pyyaml==6.0.1 # via - # feast (setup.py) # dask # ibis-substrait # jupyter-events @@ -773,21 +666,16 @@ pyzmq==26.0.3 # jupyter-client # jupyter-server redis==4.6.0 - # via feast (setup.py) referencing==0.35.1 # via # jsonschema # jsonschema-specifications # jupyter-events regex==2024.5.15 - # via - # feast (setup.py) - # parsimonious + # via parsimonious requests==2.32.3 # via - # feast (setup.py) # azure-core - # cachecontrol # docker # google-api-core # google-cloud-bigquery @@ -820,7 +708,6 @@ rich==13.7.1 # ibis-framework # typer rockset==2.1.2 - # via feast (setup.py) rpds-py==0.18.1 # via # jsonschema @@ -832,7 +719,6 @@ ruamel-yaml==0.17.17 ruamel-yaml-clib==0.2.8 # via ruamel-yaml ruff==0.4.10 - # via feast (setup.py) s3transfer==0.10.2 # via boto3 scipy==1.13.1 @@ -849,7 +735,6 @@ setuptools==70.1.1 shellingham==1.5.4 # via typer singlestoredb==1.4.0 - # via feast (setup.py) six==1.16.0 # via # asttokens @@ -870,13 +755,11 @@ sniffio==1.3.1 snowballstemmer==2.2.0 # via sphinx snowflake-connector-python[pandas]==3.11.0 - # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python soupsieve==2.5 # via beautifulsoup4 sphinx==6.2.1 - # via feast (setup.py) sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -890,11 +773,9 @@ sphinxcontrib-qthelp==1.0.7 sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy[mypy]==2.0.31 - # via feast (setup.py) sqlglot==23.12.2 # via ibis-framework sqlite-vec==0.0.1a10 - # via feast (setup.py) sqlparams==6.0.1 # via singlestoredb stack-data==0.6.3 @@ -904,21 +785,17 @@ starlette==0.37.2 substrait==0.19.0 # via ibis-substrait tabulate==0.9.0 - # via feast (setup.py) tenacity==8.4.2 - # via feast (setup.py) terminado==0.18.1 # via # jupyter-server # jupyter-server-terminals testcontainers==4.4.0 - # via feast (setup.py) thriftpy2==0.5.1 # via happybase tinycss2==1.3.0 # via nbconvert toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via # build @@ -946,9 +823,7 @@ tornado==6.4.1 # notebook # terminado tqdm==4.66.4 - # via - # feast (setup.py) - # great-expectations + # via great-expectations traitlets==5.14.3 # via # comm @@ -965,39 +840,25 @@ traitlets==5.14.3 # nbconvert # nbformat trino==0.328.0 - # via feast (setup.py) typeguard==4.3.0 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-cffi==1.16.0.20240331 # via types-pyopenssl types-protobuf==3.19.22 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf types-pymysql==1.1.0.20240524 - # via feast (setup.py) types-pyopenssl==24.1.0.20240425 # via types-redis types-python-dateutil==2.9.0.20240316 - # via - # feast (setup.py) - # arrow + # via arrow types-pytz==2024.1.0.20240417 - # via feast (setup.py) types-pyyaml==6.0.12.20240311 - # via feast (setup.py) types-redis==4.6.0.20240425 - # via feast (setup.py) types-requests==2.30.0.0 - # via feast (setup.py) types-setuptools==70.1.0.20240627 - # via - # feast (setup.py) - # types-cffi + # via types-cffi types-tabulate==0.9.0.20240106 - # via feast (setup.py) types-urllib3==1.26.25.14 # via types-requests typing-extensions==4.12.2 @@ -1034,11 +895,8 @@ ujson==5.10.0 # via fastapi uri-template==1.3.0 # via jsonschema -uritemplate==4.1.1 - # via google-api-python-client urllib3==1.26.19 # via - # feast (setup.py) # botocore # docker # elastic-transport @@ -1051,15 +909,11 @@ urllib3==1.26.19 # snowflake-connector-python # testcontainers uvicorn[standard]==0.30.1 - # via - # feast (setup.py) - # fastapi + # via fastapi uvloop==0.19.0 # via uvicorn virtualenv==20.23.0 - # via - # feast (setup.py) - # pre-commit + # via pre-commit watchfiles==0.22.0 # via uvicorn wcwidth==0.2.13 diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index ea553bcae2d..096f54ab1fa 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -20,22 +20,17 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via - # feast (setup.py) # dask # typer # uvicorn cloudpickle==3.0.0 # via dask colorama==0.4.6 - # via feast (setup.py) dask[dataframe]==2024.5.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr dask-expr==1.1.0 # via dask dill==0.3.8 - # via feast (setup.py) dnspython==2.6.1 # via email-validator email-validator==2.1.1 @@ -43,15 +38,14 @@ email-validator==2.1.1 exceptiongroup==1.2.1 # via anyio fastapi==0.111.0 - # via - # feast (setup.py) - # fastapi-cli + # via fastapi-cli fastapi-cli==0.0.2 # via fastapi fsspec==2024.3.1 # via dask +greenlet==3.0.3 + # via sqlalchemy gunicorn==22.0.0 - # via feast (setup.py) h11==0.14.0 # via # httpcore @@ -73,11 +67,8 @@ importlib-metadata==7.1.0 # dask # typeguard jinja2==3.1.4 - # via - # feast (setup.py) - # fastapi + # via fastapi jsonschema==4.22.0 - # via feast (setup.py) jsonschema-specifications==2023.12.1 # via jsonschema locket==1.0.0 @@ -89,16 +80,13 @@ markupsafe==2.1.5 mdurl==0.1.2 # via markdown-it-py mmh3==4.1.0 - # via feast (setup.py) mypy==1.10.0 # via sqlalchemy mypy-extensions==1.0.0 # via mypy mypy-protobuf==3.6.0 - # via feast (setup.py) numpy==1.26.4 # via - # feast (setup.py) # dask # pandas # pyarrow @@ -110,29 +98,20 @@ packaging==24.0 # gunicorn pandas==2.2.2 # via - # feast (setup.py) # dask # dask-expr partd==1.4.2 # via dask protobuf==4.25.3 - # via - # feast (setup.py) - # mypy-protobuf + # via mypy-protobuf pyarrow==16.0.0 - # via - # feast (setup.py) - # dask-expr + # via dask-expr pydantic==2.7.1 - # via - # feast (setup.py) - # fastapi + # via fastapi pydantic-core==2.18.2 # via pydantic pygments==2.18.0 - # via - # feast (setup.py) - # rich + # via rich python-dateutil==2.9.0.post0 # via pandas python-dotenv==1.0.1 @@ -143,7 +122,6 @@ pytz==2024.1 # via pandas pyyaml==6.0.1 # via - # feast (setup.py) # dask # uvicorn referencing==0.35.1 @@ -151,7 +129,6 @@ referencing==0.35.1 # jsonschema # jsonschema-specifications requests==2.31.0 - # via feast (setup.py) rich==13.7.1 # via typer rpds-py==0.18.1 @@ -167,15 +144,11 @@ sniffio==1.3.1 # anyio # httpx sqlalchemy[mypy]==2.0.30 - # via feast (setup.py) starlette==0.37.2 # via fastapi tabulate==0.9.0 - # via feast (setup.py) tenacity==8.3.0 - # via feast (setup.py) toml==0.10.2 - # via feast (setup.py) tomli==2.0.1 # via mypy toolz==0.12.1 @@ -183,9 +156,7 @@ toolz==0.12.1 # dask # partd tqdm==4.66.4 - # via feast (setup.py) typeguard==4.2.1 - # via feast (setup.py) typer==0.12.3 # via fastapi-cli types-protobuf==5.26.0.20240422 @@ -210,7 +181,6 @@ urllib3==2.2.1 # via requests uvicorn[standard]==0.29.0 # via - # feast (setup.py) # fastapi # fastapi-cli uvloop==0.19.0 diff --git a/setup.py b/setup.py index 400555f0e1e..b983617712c 100644 --- a/setup.py +++ b/setup.py @@ -179,7 +179,6 @@ "pytest-env", "Sphinx>4.0.0,<7", "testcontainers==4.4.0", - "firebase-admin>=5.2.0,<6", "pre-commit<3.3.2", "assertpy==1.1", "pip-tools", From 56b411786af5616e2d5155df3a0076bea35dc657 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 02:37:56 +0000 Subject: [PATCH 40/44] chore: Bump ws from 7.5.7 to 7.5.10 in /sdk/python/feast/ui (#4362) Bumps [ws](https://github.com/websockets/ws) from 7.5.7 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.7...7.5.10) --- updated-dependencies: - dependency-name: ws 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 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index 7c01b6c1e86..aa176269799 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -10977,14 +10977,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.4.2: - version "8.6.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23" - integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw== + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== xml-name-validator@^3.0.0: version "3.0.0" From 9708d84a1479615d7387757f7e0edbbf33dbc31d Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Thu, 25 Jul 2024 14:51:51 -0400 Subject: [PATCH 41/44] chore: Update README.md (#4367) Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1e06774dac..13c5db443c6 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://github.com/feast-dev/feast/blob/master/LICENSE) [![GitHub Release](https://img.shields.io/github/v/release/feast-dev/feast.svg?style=flat&sort=semver&color=blue)](https://github.com/feast-dev/feast/releases) +## Join us on Slack! + +👋👋👋 [Come say hi on Slack!](https://join.slack.com/t/feastopensource/signup) + ## Overview Feast (**Fea**ture **St**ore) is an open source feature store for machine learning. Feast is the fastest path to manage existing infrastructure to productionize analytic data for model training and online inference. @@ -227,4 +231,4 @@ Thanks goes to these incredible people: - \ No newline at end of file + From 3ddb4fb90d845bb3113cc51c484938579668d2c5 Mon Sep 17 00:00:00 2001 From: Tommy Hughes IV Date: Fri, 26 Jul 2024 11:04:55 -0500 Subject: [PATCH 42/44] fix: Increment operator to v0.39.0 (#4368) increment operator to v0.39.0 Signed-off-by: Tommy Hughes --- infra/feast-operator/Makefile | 2 +- infra/feast-operator/config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/feast-operator/Makefile b/infra/feast-operator/Makefile index 1388778f9fe..f4464377513 100644 --- a/infra/feast-operator/Makefile +++ b/infra/feast-operator/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.37.0 +VERSION ?= 0.39.0 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") diff --git a/infra/feast-operator/config/manager/kustomization.yaml b/infra/feast-operator/config/manager/kustomization.yaml index 226b87118d2..1c111ac7ada 100644 --- a/infra/feast-operator/config/manager/kustomization.yaml +++ b/infra/feast-operator/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: feastdev/feast-operator - newTag: 0.37.0 + newTag: 0.39.0 From 4a135686430fc1d820e83a3dd854775ae3d85ff4 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Mon, 29 Jul 2024 11:36:38 -0400 Subject: [PATCH 43/44] docs: Update faq.md (#4371) Update faq.md --- docs/getting-started/faq.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 02f9db7d0ca..d603e12ab6b 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -70,10 +70,6 @@ Yes. See [documentation](../reference/alpha-web-ui.md). A feature view can be defined with multiple entities. Since each entity has a unique join\_key, using multiple entities will achieve the effect of a composite key. -### How does Feast compare with Tecton? - -Please see a detailed comparison of Feast vs. Tecton [here](https://www.tecton.ai/feast/). For another comparison, please see [here](https://mlops.community/learn/feature-store/). - ### What are the performance/latency characteristics of Feast? Feast is designed to work at scale and support low latency online serving. See our [benchmark blog post](https://feast.dev/blog/feast-benchmarks/) for details. From 32ec0e061a4eaf3fb968ccc10a465eec2d7d1cc1 Mon Sep 17 00:00:00 2001 From: feast-ci-bot Date: Wed, 31 Jul 2024 19:21:25 +0000 Subject: [PATCH 44/44] chore(release): release 0.40.0 # [0.40.0](https://github.com/feast-dev/feast/compare/v0.39.0...v0.40.0) (2024-07-31) ### Bug Fixes * Added missing type ([#4315](https://github.com/feast-dev/feast/issues/4315)) ([86af60a](https://github.com/feast-dev/feast/commit/86af60ad87d537b17e4ce6ec7a5eac0d637fb32d)) * Avoid XSS attack from Jinjin2's Environment(). ([#4355](https://github.com/feast-dev/feast/issues/4355)) ([40270e7](https://github.com/feast-dev/feast/commit/40270e754660d0a8f57cc8a3bbfb1e1e346c3d86)) * CGO Memory leak issue in GO Feature server ([#4291](https://github.com/feast-dev/feast/issues/4291)) ([43e198f](https://github.com/feast-dev/feast/commit/43e198f6945c5e868ade341309f2c5ca39ac563e)) * Deprecated the datetime.utcfromtimestamp(). ([#4306](https://github.com/feast-dev/feast/issues/4306)) ([21deec8](https://github.com/feast-dev/feast/commit/21deec8495a101442e78cabc9a30cb5fbee5382f)) * Fix SQLite import issue ([#4294](https://github.com/feast-dev/feast/issues/4294)) ([398ea3b](https://github.com/feast-dev/feast/commit/398ea3b86c83605963124404ff4baa95162dc1f4)) * Increment operator to v0.39.0 ([#4368](https://github.com/feast-dev/feast/issues/4368)) ([3ddb4fb](https://github.com/feast-dev/feast/commit/3ddb4fb90d845bb3113cc51c484938579668d2c5)) * Minor typo in the unit test. ([#4296](https://github.com/feast-dev/feast/issues/4296)) ([6c75e84](https://github.com/feast-dev/feast/commit/6c75e84b036f84910dcbd7f1733ebd0d8839ab6c)) * OnDemandFeatureView type inference for array types ([#4310](https://github.com/feast-dev/feast/issues/4310)) ([c45ff72](https://github.com/feast-dev/feast/commit/c45ff72f821404c595477e696ab4be1b888090cc)) * Remove redundant batching in PostgreSQLOnlineStore.online_write_batch and fix progress bar ([#4331](https://github.com/feast-dev/feast/issues/4331)) ([0d89d15](https://github.com/feast-dev/feast/commit/0d89d1519fc6b8ddd05a2588138e2e85f5a921b1)) * Remove typo. ([#4351](https://github.com/feast-dev/feast/issues/4351)) ([92d17de](https://github.com/feast-dev/feast/commit/92d17def8cdff2bebfa622a4b3846d5bdc3e58d8)) * Retire the datetime.utcnow(). ([#4352](https://github.com/feast-dev/feast/issues/4352)) ([a8bc696](https://github.com/feast-dev/feast/commit/a8bc696010fa94fa0be44fba2570bee0eab83ba2)) * Update dask version to support pandas 1.x ([#4326](https://github.com/feast-dev/feast/issues/4326)) ([a639d61](https://github.com/feast-dev/feast/commit/a639d617c047030f75c6950e9bfa6e5cfe63daaa)) * Update Feast object metadata in the registry ([#4257](https://github.com/feast-dev/feast/issues/4257)) ([8028ae0](https://github.com/feast-dev/feast/commit/8028ae0f39e706637bc2781850a3b7d8925a87f7)) * Using one single function call for utcnow(). ([#4307](https://github.com/feast-dev/feast/issues/4307)) ([98ff63c](https://github.com/feast-dev/feast/commit/98ff63cd389207998b3452ec46e5a2f0fc70485c)) ### Features * Add async feature retrieval for Postgres Online Store ([#4327](https://github.com/feast-dev/feast/issues/4327)) ([cea52e9](https://github.com/feast-dev/feast/commit/cea52e9fb02cb9e0b8f48206278474f5a5fa167e)) * Add Async refresh to Sql Registry ([#4251](https://github.com/feast-dev/feast/issues/4251)) ([f569786](https://github.com/feast-dev/feast/commit/f5697863669a6bb9dbd491f79192e8ddd0073388)) * Add SingleStore as an OnlineStore ([#4285](https://github.com/feast-dev/feast/issues/4285)) ([2c38946](https://github.com/feast-dev/feast/commit/2c3894693e9079b8ad7873b139b30440c919e913)) * Add Tornike to maintainers.md ([#4339](https://github.com/feast-dev/feast/issues/4339)) ([8e8c1f2](https://github.com/feast-dev/feast/commit/8e8c1f2ff9a77738e71542cbaab9531f321842a4)) * Bump psycopg2 to psycopg3 for all Postgres components ([#4303](https://github.com/feast-dev/feast/issues/4303)) ([9451d9c](https://github.com/feast-dev/feast/commit/9451d9ca15f234e8e16e81351294fd63b33c1af2)) * Entity key deserialization ([#4284](https://github.com/feast-dev/feast/issues/4284)) ([83fad15](https://github.com/feast-dev/feast/commit/83fad152ffe01a3b2691095a45b90eb30044c859)) * Ignore paths feast apply ([#4276](https://github.com/feast-dev/feast/issues/4276)) ([b4d54af](https://github.com/feast-dev/feast/commit/b4d54afaa83cb3e1391d62f4243e7d63a698064c)) * Move get_online_features to OnlineStore interface ([#4319](https://github.com/feast-dev/feast/issues/4319)) ([7072fd0](https://github.com/feast-dev/feast/commit/7072fd0e2e1d2f4d9a3e8f02d04ae042b3d9c0d4)) * Port mssql contrib offline store to ibis ([#4360](https://github.com/feast-dev/feast/issues/4360)) ([7914cbd](https://github.com/feast-dev/feast/commit/7914cbdaffeade727cf3cee538cf128cbfd86e06)) ### Reverts * Revert "fix: Avoid XSS attack from Jinjin2's Environment()." ([#4357](https://github.com/feast-dev/feast/issues/4357)) ([cdeab48](https://github.com/feast-dev/feast/commit/cdeab486970ccb8c716499610f927a6e8eb14457)), closes [#4355](https://github.com/feast-dev/feast/issues/4355) --- CHANGELOG.md | 38 +++++++++++++++++++ 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, 67 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 798df5d0247..6b7c8be4b77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +# [0.40.0](https://github.com/feast-dev/feast/compare/v0.39.0...v0.40.0) (2024-07-31) + + +### Bug Fixes + +* Added missing type ([#4315](https://github.com/feast-dev/feast/issues/4315)) ([86af60a](https://github.com/feast-dev/feast/commit/86af60ad87d537b17e4ce6ec7a5eac0d637fb32d)) +* Avoid XSS attack from Jinjin2's Environment(). ([#4355](https://github.com/feast-dev/feast/issues/4355)) ([40270e7](https://github.com/feast-dev/feast/commit/40270e754660d0a8f57cc8a3bbfb1e1e346c3d86)) +* CGO Memory leak issue in GO Feature server ([#4291](https://github.com/feast-dev/feast/issues/4291)) ([43e198f](https://github.com/feast-dev/feast/commit/43e198f6945c5e868ade341309f2c5ca39ac563e)) +* Deprecated the datetime.utcfromtimestamp(). ([#4306](https://github.com/feast-dev/feast/issues/4306)) ([21deec8](https://github.com/feast-dev/feast/commit/21deec8495a101442e78cabc9a30cb5fbee5382f)) +* Fix SQLite import issue ([#4294](https://github.com/feast-dev/feast/issues/4294)) ([398ea3b](https://github.com/feast-dev/feast/commit/398ea3b86c83605963124404ff4baa95162dc1f4)) +* Increment operator to v0.39.0 ([#4368](https://github.com/feast-dev/feast/issues/4368)) ([3ddb4fb](https://github.com/feast-dev/feast/commit/3ddb4fb90d845bb3113cc51c484938579668d2c5)) +* Minor typo in the unit test. ([#4296](https://github.com/feast-dev/feast/issues/4296)) ([6c75e84](https://github.com/feast-dev/feast/commit/6c75e84b036f84910dcbd7f1733ebd0d8839ab6c)) +* OnDemandFeatureView type inference for array types ([#4310](https://github.com/feast-dev/feast/issues/4310)) ([c45ff72](https://github.com/feast-dev/feast/commit/c45ff72f821404c595477e696ab4be1b888090cc)) +* Remove redundant batching in PostgreSQLOnlineStore.online_write_batch and fix progress bar ([#4331](https://github.com/feast-dev/feast/issues/4331)) ([0d89d15](https://github.com/feast-dev/feast/commit/0d89d1519fc6b8ddd05a2588138e2e85f5a921b1)) +* Remove typo. ([#4351](https://github.com/feast-dev/feast/issues/4351)) ([92d17de](https://github.com/feast-dev/feast/commit/92d17def8cdff2bebfa622a4b3846d5bdc3e58d8)) +* Retire the datetime.utcnow(). ([#4352](https://github.com/feast-dev/feast/issues/4352)) ([a8bc696](https://github.com/feast-dev/feast/commit/a8bc696010fa94fa0be44fba2570bee0eab83ba2)) +* Update dask version to support pandas 1.x ([#4326](https://github.com/feast-dev/feast/issues/4326)) ([a639d61](https://github.com/feast-dev/feast/commit/a639d617c047030f75c6950e9bfa6e5cfe63daaa)) +* Update Feast object metadata in the registry ([#4257](https://github.com/feast-dev/feast/issues/4257)) ([8028ae0](https://github.com/feast-dev/feast/commit/8028ae0f39e706637bc2781850a3b7d8925a87f7)) +* Using one single function call for utcnow(). ([#4307](https://github.com/feast-dev/feast/issues/4307)) ([98ff63c](https://github.com/feast-dev/feast/commit/98ff63cd389207998b3452ec46e5a2f0fc70485c)) + + +### Features + +* Add async feature retrieval for Postgres Online Store ([#4327](https://github.com/feast-dev/feast/issues/4327)) ([cea52e9](https://github.com/feast-dev/feast/commit/cea52e9fb02cb9e0b8f48206278474f5a5fa167e)) +* Add Async refresh to Sql Registry ([#4251](https://github.com/feast-dev/feast/issues/4251)) ([f569786](https://github.com/feast-dev/feast/commit/f5697863669a6bb9dbd491f79192e8ddd0073388)) +* Add SingleStore as an OnlineStore ([#4285](https://github.com/feast-dev/feast/issues/4285)) ([2c38946](https://github.com/feast-dev/feast/commit/2c3894693e9079b8ad7873b139b30440c919e913)) +* Add Tornike to maintainers.md ([#4339](https://github.com/feast-dev/feast/issues/4339)) ([8e8c1f2](https://github.com/feast-dev/feast/commit/8e8c1f2ff9a77738e71542cbaab9531f321842a4)) +* Bump psycopg2 to psycopg3 for all Postgres components ([#4303](https://github.com/feast-dev/feast/issues/4303)) ([9451d9c](https://github.com/feast-dev/feast/commit/9451d9ca15f234e8e16e81351294fd63b33c1af2)) +* Entity key deserialization ([#4284](https://github.com/feast-dev/feast/issues/4284)) ([83fad15](https://github.com/feast-dev/feast/commit/83fad152ffe01a3b2691095a45b90eb30044c859)) +* Ignore paths feast apply ([#4276](https://github.com/feast-dev/feast/issues/4276)) ([b4d54af](https://github.com/feast-dev/feast/commit/b4d54afaa83cb3e1391d62f4243e7d63a698064c)) +* Move get_online_features to OnlineStore interface ([#4319](https://github.com/feast-dev/feast/issues/4319)) ([7072fd0](https://github.com/feast-dev/feast/commit/7072fd0e2e1d2f4d9a3e8f02d04ae042b3d9c0d4)) +* Port mssql contrib offline store to ibis ([#4360](https://github.com/feast-dev/feast/issues/4360)) ([7914cbd](https://github.com/feast-dev/feast/commit/7914cbdaffeade727cf3cee538cf128cbfd86e06)) + + +### Reverts + +* Revert "fix: Avoid XSS attack from Jinjin2's Environment()." ([#4357](https://github.com/feast-dev/feast/issues/4357)) ([cdeab48](https://github.com/feast-dev/feast/commit/cdeab486970ccb8c716499610f927a6e8eb14457)), closes [#4355](https://github.com/feast-dev/feast/issues/4355) + # [0.39.0](https://github.com/feast-dev/feast/compare/v0.38.0...v0.39.0) (2024-06-18) diff --git a/infra/charts/feast-feature-server/Chart.yaml b/infra/charts/feast-feature-server/Chart.yaml index aa39b158dd7..af47692f16e 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.39.0 +version: 0.40.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 121b0cc0cd9..63ff7cf61b2 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.39.0` +Current chart version is `0.40.0` ## Installation @@ -40,7 +40,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.39.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | +| image.tag | string | `"0.40.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 33430749d8d..0c46bfff854 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.39.0 + tag: 0.40.0 imagePullSecrets: [] nameOverride: "" diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml index c724a748a6b..1f030ca6172 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.39.0 +version: 0.40.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index d611b69d847..bf8e4d7b8d2 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.39.0` +Feature store for machine learning Current chart version is `0.40.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.39.0 | -| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.39.0 | +| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.40.0 | +| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.40.0 | ## Values diff --git a/infra/charts/feast/charts/feature-server/Chart.yaml b/infra/charts/feast/charts/feature-server/Chart.yaml index 5616d463015..f91185be845 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.39.0 -appVersion: v0.39.0 +version: 0.40.0 +appVersion: v0.40.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 f4a8ea8cda8..c75fc421c62 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.39.0](https://img.shields.io/badge/Version-0.39.0-informational?style=flat-square) ![AppVersion: v0.39.0](https://img.shields.io/badge/AppVersion-v0.39.0-informational?style=flat-square) +![Version: 0.40.0](https://img.shields.io/badge/Version-0.40.0-informational?style=flat-square) ![AppVersion: v0.40.0](https://img.shields.io/badge/AppVersion-v0.40.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.39.0"` | Image tag | +| image.tag | string | `"0.40.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.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index cd60eaf93f8..d9c964bbca6 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.39.0 + tag: 0.40.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 2e3211697f3..7e336e7a3e8 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.39.0 -appVersion: v0.39.0 +version: 0.40.0 +appVersion: v0.40.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 dec106617e5..f90d5bda185 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.39.0](https://img.shields.io/badge/Version-0.39.0-informational?style=flat-square) ![AppVersion: v0.39.0](https://img.shields.io/badge/AppVersion-v0.39.0-informational?style=flat-square) +![Version: 0.40.0](https://img.shields.io/badge/Version-0.40.0-informational?style=flat-square) ![AppVersion: v0.40.0](https://img.shields.io/badge/AppVersion-v0.40.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.39.0"` | Image tag | +| image.tag | string | `"0.40.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.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/infra/charts/feast/charts/transformation-service/values.yaml b/infra/charts/feast/charts/transformation-service/values.yaml index a6935a9993c..aee47048e83 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.39.0 + tag: 0.40.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index 1f579a5f3c3..7b1277fc69a 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.39.0 + version: 0.40.0 condition: feature-server.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: transformation-service alias: transformation-service - version: 0.39.0 + version: 0.40.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 492e756ba57..2c1c32792c8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -35,7 +35,7 @@ - 0.39.0 + 0.40.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 66daf7b993e..36777ca0be3 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.39.0", + "@feast-dev/feast-ui": "0.40.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 aa176269799..cd1913bbb1f 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.39.0": - version "0.39.0" - resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.39.0.tgz#9ab9bdcfd866399383b489f192e3d907590ac841" - integrity sha512-ggTyiv+D/i6sF5WZRxEFmVKMVgWmrdP3bnUzbDYnMpJ6A1UKFOdj29Ukh4F8DXDvrAskV1LjF+DZVkaD5lF4TQ== +"@feast-dev/feast-ui@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.40.0.tgz#0dc60cbbd4f63d161927321c0bbf57bbfe6b7d09" + integrity sha512-jiCtMYCBvNSfHCjemFRa0NFIIAR5y6spWBnUZyc4GXY2YxGcznw+PZSzOoi7JrOwpNzNPB0PTBUqJgBAxus20w== dependencies: "@elastic/datemath" "^5.0.3" "@elastic/eui" "^55.0.1" diff --git a/ui/package.json b/ui/package.json index a380c65cfc9..cd80859aa15 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "@feast-dev/feast-ui", - "version": "0.39.0", + "version": "0.40.0", "private": false, "files": [ "dist"