diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml
index 278be10b890..841f5da87b3 100644
--- a/.github/workflows/build_wheels.yml
+++ b/.github/workflows/build_wheels.yml
@@ -17,7 +17,10 @@ jobs:
version_without_prefix: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }}
highest_semver_tag: ${{ steps.get_highest_semver.outputs.highest_semver_tag }}
steps:
- - uses: actions/checkout@v2
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ persist-credentials: false
- name: Get release version
id: get_release_version
run: echo ::set-output name=release_version::${GITHUB_REF#refs/*/}
@@ -38,6 +41,7 @@ jobs:
echo ::set-output name=highest_semver_tag::$(get_tag_release -m)
fi
- name: Check output
+ id: check_output
env:
RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }}
VERSION_WITHOUT_PREFIX: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 46e16657543..9587044a84d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -49,7 +49,7 @@ jobs:
needs: get-version
strategy:
matrix:
- component: [feature-server-python, feature-server-python-aws, feature-server-java, feature-transformation-server]
+ component: [feature-server, feature-server-python-aws, feature-server-java, feature-transformation-server]
env:
MAVEN_CACHE: gs://feast-templocation-kf-feast/.m2.2020-08-19.tar
REGISTRY: feastdev
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index feab7b0eef9..ec56d60e4c5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -42,40 +42,41 @@ jobs:
echo "Current version is ${CURRENT_VERSION}"
echo "Next version is ${NEXT_VERSION}"
- # publish-web-ui-npm:
- # if: github.repository == 'feast-dev/feast'
- # needs: get_dry_release_versions
- # runs-on: ubuntu-latest
- # env:
- # # This publish is working using an NPM automation token to bypass 2FA
- # NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- # CURRENT_VERSION: ${{ needs.get_dry_release_versions.outputs.current_version }}
- # NEXT_VERSION: ${{ needs.get_dry_release_versions.outputs.next_version }}
- # steps:
- # - uses: actions/checkout@v2
- # - uses: actions/setup-node@v2
- # with:
- # node-version: '17.x'
- # registry-url: 'https://registry.npmjs.org'
- # - name: Bump file versions (temporarily for Web UI publish)
- # run: python ./infra/scripts/release/bump_file_versions.py ${CURRENT_VERSION} ${NEXT_VERSION}
- # - name: Install yarn dependencies
- # working-directory: ./ui
- # run: yarn install
- # - name: Build yarn rollup
- # working-directory: ./ui
- # run: yarn build:lib
- # - name: Publish UI package
- # working-directory: ./ui
- # run: npm publish
- # env:
- # # This publish is working using an NPM automation token to bypass 2FA
- # NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ publish-web-ui-npm:
+ if: github.repository == 'feast-dev/feast'
+ needs: get_dry_release_versions
+ runs-on: ubuntu-latest
+ env:
+ # This publish is working using an NPM automation token to bypass 2FA
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ CURRENT_VERSION: ${{ needs.get_dry_release_versions.outputs.current_version }}
+ NEXT_VERSION: ${{ needs.get_dry_release_versions.outputs.next_version }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '17.x'
+ registry-url: 'https://registry.npmjs.org'
+ - name: Bump file versions (temporarily for Web UI publish)
+ run: python ./infra/scripts/release/bump_file_versions.py ${CURRENT_VERSION} ${NEXT_VERSION}
+ - name: Install yarn dependencies
+ working-directory: ./ui
+ run: yarn install
+ - name: Build yarn rollup
+ working-directory: ./ui
+ run: yarn build:lib
+ - name: Publish UI package
+ if: github.event.inputs.dry_run == 'false'
+ working-directory: ./ui
+ run: npm publish
+ env:
+ # This publish is working using an NPM automation token to bypass 2FA
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
release:
name: release
runs-on: ubuntu-latest
- #needs: publish-web-ui-npm
+ needs: publish-web-ui-npm
env:
GITHUB_TOKEN: ${{ github.event.inputs.token }}
GIT_AUTHOR_NAME: feast-ci-bot
@@ -91,6 +92,9 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: '16'
+ - name: Setup Helm-docs
+ run: |
+ brew install norwoodj/tap/helm-docs
- name: Release (Dry Run)
if: github.event.inputs.dry_run == 'true'
run: |
diff --git a/.gitignore b/.gitignore
index 1edde846ff2..6a86eb2682b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,8 +125,6 @@ instance/
# Sphinx documentation
docs/_build/
-sdk/python/docs/source
-sdk/python/docs/html
# PyBuilder
target/
@@ -186,6 +184,7 @@ dmypy.json
*.code-workspace
# Protos
+sdk/python/docs/html
sdk/python/feast/protos/
sdk/go/protos/
go/protos/
diff --git a/.releaserc.js b/.releaserc.js
index aadc4373e91..124ad8801c2 100644
--- a/.releaserc.js
+++ b/.releaserc.js
@@ -40,8 +40,8 @@ module.exports = {
// Validate the type of release we are doing
"verifyReleaseCmd": "./infra/scripts/validate-release.sh ${nextRelease.type} " + current_branch,
- // Bump all version files and build UI / update yarn.lock
- "prepareCmd": "python ./infra/scripts/release/bump_file_versions.py ${lastRelease.version} ${nextRelease.version}; make build-ui"
+ // Bump all version files and build UI / update yarn.lock / helm charts
+ "prepareCmd": "python ./infra/scripts/release/bump_file_versions.py ${lastRelease.version} ${nextRelease.version}; make build-ui; make build-helm-docs"
}],
["@semantic-release/release-notes-generator", {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cb54565778..b657e9ddd1a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+# [0.25.0](https://github.com/feast-dev/feast/compare/v0.24.0...v0.25.0) (2022-09-20)
+
+
+### Bug Fixes
+
+* Broken Feature Service Link ([#3227](https://github.com/feast-dev/feast/issues/3227)) ([e117082](https://github.com/feast-dev/feast/commit/e1170822bd3de8e1bfe803d9e2757c760fa5df2f))
+* Feature-server image is missing mysql dependency for mysql registry ([#3223](https://github.com/feast-dev/feast/issues/3223)) ([ae37b20](https://github.com/feast-dev/feast/commit/ae37b2058e59a722c45324f5b43668ae4e74657d))
+* Fix handling of TTL in Go server ([#3232](https://github.com/feast-dev/feast/issues/3232)) ([f020630](https://github.com/feast-dev/feast/commit/f020630c0144ab366f50c29dc3c97b8501687d3b))
+* Fix materialization when running on Spark cluster. ([#3166](https://github.com/feast-dev/feast/issues/3166)) ([175fd25](https://github.com/feast-dev/feast/commit/175fd256e0d21f6539f68708705bddf1caa3d975))
+* Fix push API to respect feature view's already inferred entity types ([#3172](https://github.com/feast-dev/feast/issues/3172)) ([7c50ab5](https://github.com/feast-dev/feast/commit/7c50ab510633c11646b6ff04853f3f26942ad646))
+* Fix release workflow ([#3144](https://github.com/feast-dev/feast/issues/3144)) ([20a9dd9](https://github.com/feast-dev/feast/commit/20a9dd98550ad8daf291381a771b3da798e4c1a4))
+* Fix Shopify timestamp bug and add warnings to help with debugging entity registration ([#3191](https://github.com/feast-dev/feast/issues/3191)) ([de75971](https://github.com/feast-dev/feast/commit/de75971e27357a8fb4a882bd7ec4212148256616))
+* Handle complex Spark data types in SparkSource ([#3154](https://github.com/feast-dev/feast/issues/3154)) ([5ddb83b](https://github.com/feast-dev/feast/commit/5ddb83b14817f55e51e5c89014a3439ec3ef5a59))
+* Local staging location provision ([#3195](https://github.com/feast-dev/feast/issues/3195)) ([cdf0faf](https://github.com/feast-dev/feast/commit/cdf0fafa6939f67cfb13ee3e28ff16a46c2147ae))
+* Remove bad snowflake offline store method ([#3204](https://github.com/feast-dev/feast/issues/3204)) ([dfdd0ca](https://github.com/feast-dev/feast/commit/dfdd0ca5fe54b638ac5a268501d67e5621ca8d89))
+* Remove opening file object when validating S3 parquet source ([#3217](https://github.com/feast-dev/feast/issues/3217)) ([a906018](https://github.com/feast-dev/feast/commit/a9060188713e34d07fd82cf3469061fdd2220956))
+* Snowflake config file search error ([#3193](https://github.com/feast-dev/feast/issues/3193)) ([189afb9](https://github.com/feast-dev/feast/commit/189afb9313d071c7b6492e0e8a996e6d073a2c6c))
+* Update Snowflake Online docs ([#3206](https://github.com/feast-dev/feast/issues/3206)) ([7bc1dff](https://github.com/feast-dev/feast/commit/7bc1dff5882c53c7e25f51ddb0b730bd81091a03))
+
+
+### Features
+
+* Add `to_remote_storage` functionality to `SparkOfflineStore` ([#3175](https://github.com/feast-dev/feast/issues/3175)) ([2107ce2](https://github.com/feast-dev/feast/commit/2107ce295f191eb1339c8670f963d39e66c4ccf6))
+* Add ability to give boto extra args for registry config ([#3219](https://github.com/feast-dev/feast/issues/3219)) ([fbc6a2c](https://github.com/feast-dev/feast/commit/fbc6a2c48303424ef08f9b206e406fc0448e5c6f))
+* Add health endpoint to py server ([#3202](https://github.com/feast-dev/feast/issues/3202)) ([43222f2](https://github.com/feast-dev/feast/commit/43222f21046c54a68250350c49b4cdf819d41591))
+* Add snowflake support for date & number with scale ([#3148](https://github.com/feast-dev/feast/issues/3148)) ([50e8755](https://github.com/feast-dev/feast/commit/50e8755d41ca2eacd41e31fc0be1202c69b61fdd))
+* Add tag kwarg to set Snowflake online store table path ([#3176](https://github.com/feast-dev/feast/issues/3176)) ([39aeea3](https://github.com/feast-dev/feast/commit/39aeea3fa77c3b3a789556a1e0fa22ecedcae4ea))
+* Add workgroup to athena offline store config ([#3139](https://github.com/feast-dev/feast/issues/3139)) ([a752211](https://github.com/feast-dev/feast/commit/a752211e1d0d6b44901d88f435328fc355d16c20))
+* Implement spark materialization engine ([#3184](https://github.com/feast-dev/feast/issues/3184)) ([a59c33a](https://github.com/feast-dev/feast/commit/a59c33ac10760b4029fadd8e377eb36a2c458583))
+
# [0.24.0](https://github.com/feast-dev/feast/compare/v0.23.0...v0.24.0) (2022-08-25)
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 00000000000..259c13ea3f0
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,54 @@
+# See https://help.github.com/articles/about-codeowners/
+# for more info about CODEOWNERS file
+
+# Core Interfaces
+/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers @chhabrakadabra @mavysavydav @sfc-gh-madkins
+/sdk/python/feast/infra/online_stores/online_store.py @feast-dev/maintainers @DvirDukhan
+/sdk/python/feast/infra/materialization_engine/batch_materialization_engine.py @feast-dev/maintainers @whoahbot @sfc-gh-madkins
+
+# ==== Offline Stores ====
+# Core utils
+/sdk/python/feast/infra/offline_stores/offline_utils.py @feast-dev/maintainers @chhabrakadabra @mavysavydav @sfc-gh-madkins
+
+# BigQuery
+/sdk/python/feast/infra/offline_stores/offline_store.py @feast-dev/maintainers @chhabrakadabra @mavysavydav
+
+# Snowflake
+/sdk/python/feast/infra/offline_stores/snowflake* @sfc-gh-madkins
+
+# Athena (contrib)
+/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/ @toping4445
+
+# Azure SQL (contrib)
+/sdk/python/feast/infra/offline_stores/contrib/mssql_offline_store/ @kevjumba
+
+# Spark (contrib)
+/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/ @niklasvm @kevjumba
+
+# ==== Online Stores ====
+
+# Redis
+/sdk/python/feast/infra/online_stores/redis.py @DvirDukhan
+/java/feast/serving/connectors/redis/ @DvirDukhan
+
+# Snowflake
+/sdk/python/feast/infra/online_stores/snowflake.py @sfc-gh-madkins
+
+# Cassandra (contrib)
+/sdk/python/feast/infra/online_stores/cassandra_online_store/ @hemidactylus
+
+# ==== Batch Materialization Engines ====
+
+# Snowflake
+/sdk/python/feast/infra/materialization/snowflake* @sfc-gh-madkins
+
+# Bytewax
+/sdk/python/feast/infra/materialization/contrib/bytewax/ @whoahbot
+
+# AWS Lambda
+/sdk/python/feast/infra/materialization/contrib/aws_lambda/ @achals
+
+# ==== Web UI ====
+/ui/ @adchia
+/sdk/python/feast/ui/ @adchia
+/sdk/python/feast/ui_server.py @adchia
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ae259a72fa8..2bc09150028 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,420 +1,3 @@
Development Guide: Main Feast Repository
-> Please see [Development Guide](https://docs.feast.dev/project/development-guide) for project level development instructions.
-
-Maintainer's Guide
-
-> Please see [Maintainer's Guide](https://docs.feast.dev/project/maintainers) for instructions for maintainers. Normal developers can also use this guide to setup their forks for localized integration tests.
-
-Table of Contents
-
-- [Overview](#overview)
-- [Community](#community)
-- [Making a pull request](#making-a-pull-request)
- - [Pull request checklist](#pull-request-checklist)
- - [Forking the repo](#forking-the-repo)
- - [Pre-commit Hooks](#pre-commit-hooks)
- - [Signing off commits](#signing-off-commits)
- - [Incorporating upstream changes from master](#incorporating-upstream-changes-from-master)
-- [Feast Python SDK / CLI](#feast-python-sdk--cli)
- - [Environment Setup](#environment-setup)
- - [Code Style & Linting](#code-style--linting)
- - [Unit Tests](#unit-tests)
- - [Integration Tests](#integration-tests)
- - [Local integration tests](#local-integration-tests)
- - [(Advanced) Full integration tests](#advanced-full-integration-tests)
- - [(Advanced) Running specific provider tests or running your test against specific online or offline stores](#advanced-running-specific-provider-tests-or-running-your-test-against-specific-online-or-offline-stores)
- - [(Experimental) Run full integration tests against containerized services](#experimental-run-full-integration-tests-against-containerized-services)
- - [Contrib integration tests](#contrib-integration-tests)
- - [(Contrib) Running tests for Spark offline store](#contrib-running-tests-for-spark-offline-store)
- - [(Contrib) Running tests for Trino offline store](#contrib-running-tests-for-trino-offline-store)
- - [(Contrib) Running tests for Postgres offline store](#contrib-running-tests-for-postgres-offline-store)
- - [(Contrib) Running tests for Postgres online store](#contrib-running-tests-for-postgres-online-store)
- - [(Contrib) Running tests for HBase online store](#contrib-running-tests-for-hbase-online-store)
-- [(Experimental) Feast UI](#experimental-feast-ui)
-- [Feast Java Serving](#feast-java-serving)
-- [Developing the Feast Helm charts](#developing-the-feast-helm-charts)
- - [Feast Java Feature Server Helm Chart](#feast-java-feature-server-helm-chart)
- - [Feast Python / Go Feature Server Helm Chart](#feast-python--go-feature-server-helm-chart)
-- [Feast Go Client](#feast-go-client)
- - [Environment Setup](#environment-setup-1)
- - [Building](#building)
- - [Code Style & Linting](#code-style--linting-1)
- - [Unit Tests](#unit-tests-1)
- - [Testing with Github Actions workflows](#testing-with-github-actions-workflows)
-- [Issues](#issues)
-
-## Overview
-This guide is targeted at developers looking to contribute to Feast components in
-the main Feast repository:
-- [Feast Python SDK / CLI](#feast-python-sdk--cli)
-- [Feast Java Serving](#feast-java-serving)
-- [Feast Go Client](#feast-go-client)
-
-Please see [this page](https://docs.feast.dev/reference/codebase-structure) for more details on the structure of the entire codebase.
-
-## Community
-See [Contribution process](https://docs.feast.dev/project/contributing) and [Community](https://docs.feast.dev/community) for details on how to get more involved in the community.
-
-A quick few highlights:
-- [RFCs](https://drive.google.com/drive/u/0/folders/0AAe8j7ZK3sxSUk9PVA)
-- [Community Slack](https://slack.feast.dev/)
-- [Feast Dev Mailing List](https://groups.google.com/g/feast-dev)
-- [Community Calendar](https://calendar.google.com/calendar/u/0?cid=ZTFsZHVhdGM3MDU3YTJucTBwMzNqNW5rajBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ)
- - Includes biweekly community calls at 10AM PST
-
-## Making a pull request
-We use the convention that the assignee of a PR is the person with the next action.
-
-This means that often, the assignee may be empty (if no reviewer has been found yet), the reviewer, or the PR writer if there are comments to be addressed.
-
-### Pull request checklist
-A quick list of things to keep in mind as you're making changes:
-- As you make changes
- - Make your changes in a [forked repo](#forking-the-repo) (instead of making a branch on the main Feast repo)
- - [Sign your commits](#signing-off-commits) as you go (to avoid DCO checks failing)
- - [Rebase from master](#incorporating-upstream-changes-from-master) instead of using `git pull` on your PR branch
- - Install [pre-commit hooks](#pre-commit-hooks) to ensure all the default linters / formatters are run when you push.
-- When you make the PR
- - Make a pull request from the forked repo you made
- - Ensure you add a GitHub **label** (i.e. a kind tag to the PR (e.g. `kind/bug` or `kind/housekeeping`)) or else checks will fail.
- - Ensure you leave a release note for any user facing changes in the PR. There is a field automatically generated in the PR request. You can write `NONE` in that field if there are no user facing changes.
- - Please run tests locally before submitting a PR (e.g. for Python, the [local integration tests](#local-integration-tests))
- - Try to keep PRs smaller. This makes them easier to review.
-
-### Forking the repo
-Fork the Feast Github repo and clone your fork locally. Then make changes to a local branch to the fork.
-
-See [Creating a pull request from a fork](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
-
-### Pre-commit Hooks
-Setup [`pre-commit`](https://pre-commit.com/) to automatically lint and format the codebase on commit:
-1. Ensure that you have Python (3.7 and above) with `pip`, installed.
-2. Install `pre-commit` with `pip` & install pre-push hooks
-```sh
-pip install pre-commit
-pre-commit install --hook-type pre-commit --hook-type pre-push
-```
-3. On push, the pre-commit hook will run. This runs `make format` and `make lint`.
-
-### Signing off commits
-> :warning: Warning: using the default integrations with IDEs like VSCode or IntelliJ will not sign commits.
-> When you submit a PR, you'll have to re-sign commits to pass the DCO check.
-
-Use git signoffs to sign your commits. See
-https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification for details
-
-Then, you can sign off commits with the `-s` flag:
-```
-git commit -s -m "My first commit"
-```
-
-GPG-signing commits with `-S` is optional.
-
-### Incorporating upstream changes from master
-Our preference is the use of `git rebase [master]` instead of `git merge` : `git pull -r`.
-
-Note that this means if you are midway through working through a PR and rebase, you'll have to force push:
-`git push --force-with-lease origin [branch name]`
-
-## Feast Python SDK / CLI
-### Environment Setup
-Setting up your development environment for Feast Python SDK / CLI:
-1. Ensure that you have Docker installed in your environment. Docker is used to provision service dependencies during testing, and build images for feature servers and other components.
- 1. Please note that we use [Docker with BuiltKit](https://docs.docker.com/develop/develop-images/build_enhancements/).
-2. Ensure that you have `make`, Python (3.8 and above) with `pip`, installed.
-3. _Recommended:_ Create a virtual environment to isolate development dependencies to be installed
-```sh
-# create & activate a virtual environment
-python -m venv venv/
-source venv/bin/activate
-```
-
-3. Upgrade `pip` if outdated
-```sh
-pip install --upgrade pip
-```
-
-4. (Optional): Install Node & Yarn. Then run the following to build Feast UI artifacts for use in `feast ui`
-```
-make build-ui
-```
-
-5. Install development dependencies for Feast Python SDK / CLI
-```sh
-pip install -e ".[dev]"
-```
-
-This will allow the installed feast version to automatically reflect changes to your local development version of Feast without needing to reinstall everytime you make code changes.
-
-### Code Style & Linting
-Feast Python SDK / CLI codebase:
-- Conforms to [Black code style](https://black.readthedocs.io/en/stable/the_black_code_style.html)
-- Has type annotations as enforced by `mypy`
-- Has imports sorted by `isort`
-- Is lintable by `flake8`
-
-To ensure your Python code conforms to Feast Python code standards:
-- Autoformat your code to conform to the code style:
-```sh
-make format-python
-```
-
-- Lint your Python code before submitting it for review:
-```sh
-make lint-python
-```
-
-> Setup [pre-commit hooks](#pre-commit-hooks) to automatically format and lint on commit.
-
-### Unit Tests
-Unit tests (`pytest`) for the Feast Python SDK / CLI can run as follows:
-```sh
-make test-python
-```
-
-> :warning: Local configuration can interfere with Unit tests and cause them to fail:
-> - Ensure [no AWS configuration is present](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html)
-> and [no AWS credentials can be accessed](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials) by `boto3`
-> - Ensure Feast Python SDK / CLI is not configured with configuration overrides (ie `~/.feast/config` should be empty).
-
-### Integration Tests
-There are two sets of tests you can run:
-1. Local integration tests (for faster development, tests file offline store & key online stores)
-2. Full integration tests (requires cloud environment setups)
-
-#### Local integration tests
-For this approach of running tests, you'll need to have docker set up locally: [Get Docker](https://docs.docker.com/get-docker/)
-
-It leverages a file based offline store to test against emulated versions of Datastore, DynamoDB, and Redis, using ephemeral containers.
-
-These tests create new temporary tables / datasets locally only, and they are cleaned up. when the containers are torn down.
-
-```sh
-make test-python-integration-local
-```
-
-#### (Advanced) Full integration tests
-To test across clouds, on top of setting up Redis, you also need GCP / AWS / Snowflake setup.
-
-> Note: you can manually control what tests are run today by inspecting
-> [RepoConfiguration](https://github.com/feast-dev/feast/blob/master/sdk/python/tests/integration/feature_repos/repo_configuration.py)
-> and commenting out tests that are added to `DEFAULT_FULL_REPO_CONFIGS`
-
-**GCP**
-1. You can get free credits [here](https://cloud.google.com/free/docs/free-cloud-features#free-trial).
-2. You will need to setup a service account, enable the BigQuery API, and create a staging location for a bucket.
- * Setup your service account and project using steps 1-5 [here](https://codelabs.developers.google.com/codelabs/cloud-bigquery-python#0).
- * Remember to save your `PROJECT_ID` and your `key.json`. These will be your secrets that you will need to configure in Github actions. Namely, `secrets.GCP_PROJECT_ID` and `secrets.GCP_SA_KEY`. The `GCP_SA_KEY` value is the contents of your `key.json` file.
- * Follow these [instructions](https://cloud.google.com/storage/docs/creating-buckets) in your project to create a bucket for running GCP tests and remember to save the bucket name.
- * Make sure to add the service account email that you created in the previous step to the users that can access your bucket. Then, make sure to give the account the correct access roles, namely `objectCreator`, `objectViewer`, `objectAdmin`, and `admin`, so that your tests can use the bucket.
-3. Install the [Cloud SDK](https://cloud.google.com/sdk/docs/install).
-4. Login to gcloud if you haven't already:
- ```
- gcloud auth login
- gcloud auth application-default login
- ```
- - When you run `gcloud auth application-default login`, you should see some output of the form:
- ```
- Credentials saved to file: [$HOME/.config/gcloud/application_default_credentials.json]
- ```
- - You should run `export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/application_default_credentials.json”` to add the application credentials to your .zshrc or .bashrc.
-5. Run `export GCLOUD_PROJECT=[your project id from step 2]` to your .zshrc or .bashrc.
-6. Running `gcloud config list` should give you something like this:
- ```sh
- $ gcloud config list
- [core]
- account = [your email]
- disable_usage_reporting = True
- project = [your project id]
-
- Your active configuration is: [default]
- ```
-7. Export GCP specific environment variables in your workflow. Namely,
- ```sh
- export GCS_REGION='[your gcs region e.g US]'
- export GCS_STAGING_LOCATION='[your gcs staging location]'
- ```
- **NOTE**: Your `GCS_STAGING_LOCATION` should be in the form `gs://` where the bucket name is from step 2.
-
-8. Once authenticated, you should be able to run the integration tests for BigQuery without any failures.
-
-**AWS**
-1. Setup AWS by creating an account, database, and cluster. You will need to enable Redshift and Dynamo.
- * You can get free credits [here](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&al[…]f.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all).
-2. To run the AWS Redshift and Dynamo integration tests you will have to export your own AWS credentials. Namely,
-
-```sh
-export AWS_REGION='[your aws region]'
-export AWS_CLUSTER_ID='[your aws cluster id]'
-export AWS_USER='[your aws user]'
-export AWS_DB='[your aws database]'
-export AWS_STAGING_LOCATION='[your s3 staging location uri]'
-export AWS_IAM_ROLE='[redshift and s3 access role]'
-export AWS_LAMBDA_ROLE='[your aws lambda execution role]'
-export AWS_REGISTRY_PATH='[your aws registry path]'
-```
-
-**Snowflake**
-1. See https://signup.snowflake.com/ to setup a trial.
-2. Setup your account and if you are not an `ACCOUNTADMIN` (if you created your own account, you should be), give yourself the `SYSADMIN` role.
- ```sql
- grant role accountadmin, sysadmin to user user2;
- ```
- * Also remember to save your [account name](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#:~:text=organization_name%20is%20the%20name%20of,your%20account%20within%20your%20organization), username, and role.
- * Your account name can be found under
-3. Create Dashboard and add a Tile.
-4. Create a warehouse and database named `FEAST` with the schemas `OFFLINE` and `ONLINE`.
- ```sql
- create or replace warehouse feast_tests_wh with
- warehouse_size='MEDIUM' --set your warehouse size to whatever your budget allows--
- auto_suspend = 180
- auto_resume = true
- initially_suspended=true;
-
- create or replace database FEAST;
- use database FEAST;
- create schema OFFLINE;
- create schema ONLINE;
- ```
-5. You will need to create a data unloading location(either on S3, GCP, or Azure). Detailed instructions [here](https://docs.snowflake.com/en/user-guide/data-unload-overview.html). You will need to save the storage export location and the storage export name. You will need to create a [storage integration ](https://docs.snowflake.com/en/sql-reference/sql/create-storage-integration.html) in your warehouse to make this work. Name this storage integration `FEAST_S3`.
-6. Then to run successfully, you'll need some environment variables setup:
- ```sh
- export SNOWFLAKE_CI_DEPLOYMENT='[your snowflake account name]'
- export SNOWFLAKE_CI_USER='[your snowflake username]'
- export SNOWFLAKE_CI_PASSWORD='[your snowflake pw]'
- export SNOWFLAKE_CI_ROLE='[your CI role e.g. SYSADMIN]'
- export SNOWFLAKE_CI_WAREHOUSE='[your warehouse]'
- export BLOB_EXPORT_STORAGE_NAME='[your data unloading storage name]'
- export BLOB_EXPORT_URI='[your data unloading blob uri]`
- ```
-7. Once everything is setup, running snowflake integration tests should pass without failures.
-
-Note that for Snowflake / GCP / AWS, running `make test-python-integration` will create new temporary tables / datasets in your cloud storage tables.
-
-#### (Advanced) Running specific provider tests or running your test against specific online or offline stores
-
-1. If you don't need to have your test run against all of the providers(`gcp`, `aws`, and `snowflake`) or don't need to run against all of the online stores, you can tag your test with specific providers or stores that you need(`@pytest.mark.universal_online_stores` or `@pytest.mark.universal_online_stores` with the `only` parameter). The `only` parameter selects specific offline providers and online stores that your test will test against. Example:
-
-```python
-# Only parametrizes this test with the sqlite online store
-@pytest.mark.universal_online_stores(only=["sqlite"])
-def test_feature_get_online_features_types_match():
-```
-
-2. You can also filter tests to run by using pytest's cli filtering. Instead of using the make commands to test Feast, you can filter tests by name with the `-k` parameter. The parametrized integration tests are all uniquely identified by their provider and online store so the `-k` option can select only the tests that you need to run. For example, to run only Redshift related tests, you can use the following command:
-
-```sh
-python -m pytest -n 8 --integration -k Redshift sdk/python/tests
-```
-
-#### (Experimental) Run full integration tests against containerized services
-Test across clouds requires existing accounts on GCP / AWS / Snowflake, and may incur costs when using these services.
-
-For this approach of running tests, you'll need to have docker set up locally: [Get Docker](https://docs.docker.com/get-docker/)
-
-It's possible to run some integration tests against emulated local versions of these services, using ephemeral containers.
-These tests create new temporary tables / datasets locally only, and they are cleaned up. when the containers are torn down.
-
-The services with containerized replacements currently implemented are:
-- Datastore
-- DynamoDB
-- Redis
-- Trino
-- HBase
-- Postgres
-- Cassandra
-
-You can run `make test-python-integration-container` to run tests against the containerized versions of dependencies.
-
-### Contrib integration tests
-#### (Contrib) Running tests for Spark offline store
-You can run `make test-python-universal-spark` to run all tests against the Spark offline store. (Note: you'll have to run `pip install -e ".[dev]"` first).
-
-Not all tests are passing yet
-
-#### (Contrib) Running tests for Trino offline store
-You can run `make test-python-universal-trino` to run all tests against the Trino offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
-
-#### (Contrib) Running tests for Postgres offline store
-You can run `test-python-universal-postgres-offline` to run all tests against the Postgres offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
-
-#### (Contrib) Running tests for Postgres online store
-You can run `test-python-universal-postgres-online` to run all tests against the Postgres offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
-
-#### (Contrib) Running tests for HBase online store
-TODO
-
-## (Experimental) Feast UI
-See [Feast contributing guide](ui/CONTRIBUTING.md)
-
-## Feast Java Serving
-See [Java contributing guide](java/CONTRIBUTING.md)
-
-See also development instructions related to the helm chart below at [Developing the Feast Helm charts](#developing-the-feast-helm-charts)
-
-## Developing the Feast Helm charts
-There are 3 helm charts:
-- Feast Java feature server
-- Feast Python / Go feature server
-- (deprecated) Feast Python feature server
-
-Generally, you can override the images in the helm charts with locally built Docker images, and install the local helm
-chart.
-
-All README's for helm charts are generated using [helm-docs](https://github.com/norwoodj/helm-docs). You can install it
-(e.g. with `brew install norwoodj/tap/helm-docs`) and then run `make build-helm-docs`.
-
-### Feast Java Feature Server Helm Chart
-See the Java demo example (it has development instructions too using minikube) [here](examples/java-demo/README.md)
-
-It will:
-- run `make build-java-docker-dev` to build local Java feature server binaries
-- configure the included `application-override.yaml` to override the image tag to use the locally built dev images.
-- install the local chart with `helm install feast-release ../../../infra/charts/feast --values application-override.yaml`
-
-### Feast Python / Go Feature Server Helm Chart
-See the Python demo example (it has development instructions too using minikube) [here](examples/python-helm-demo/README.md)
-
-It will:
-- run `make build-feature-server-dev` to build a local python feature server binary
-- install the local chart with `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)`
-
-## Feast Go Client
-### Environment Setup
-Setting up your development environment for Feast Go SDK:
-
-- Install Golang, [`protoc` with the Golang & grpc plugins](https://developers.google.com/protocol-buffers/docs/gotutorial#compiling-your-protocol-buffers)
-
-### Building
-Build the Feast Go Client with the `go` toolchain:
-```sh
-make compile-go-lib
-```
-
-### Code Style & Linting
-Feast Go Client codebase:
-- Conforms to the code style enforced by `go fmt`.
-- Is lintable by `go vet`.
-
-Autoformat your Go code to satisfy the Code Style standard:
-```sh
-go fmt
-```
-
-Lint your Go code:
-```sh
-go vet
-```
-
-> Setup [pre-commit hooks](#pre-commit-hooks) to automatically format and lint on commit.
-
-### Unit Tests
-Unit tests for the Feast Go Client can be run as follows:
-```sh
-make test-go
-```
-
-### Testing with Github Actions workflows
-
-Please refer to the maintainers [doc](./docs/project/maintainers.md) if you would like to locally test out the github actions workflow changes. This document will help you setup your fork to test the ci integration tests and other workflows without needing to make a pull request against feast-dev master.
+> Please see [Development Guide](https://docs.feast.dev/project/development-guide) for project level development instructions, including instructions for Maintainers.
diff --git a/Makefile b/Makefile
index 8e03ed5349f..0bf0c82669f 100644
--- a/Makefile
+++ b/Makefile
@@ -159,16 +159,19 @@ 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. https://docs.aws.amazon.com/athena/latest/ug/getting-started.html
-#Modify environment variables ATHENA_DATA_SOURCE, ATHENA_DATABASE, ATHENA_S3_BUCKET_NAME if you want to change the data source, database, and bucket name of S3 to use.
-#If tests fail with the pytest -n 8 option, change the number to 1.
+# 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.
test-python-universal-athena:
PYTHONPATH='.' \
FULL_REPO_CONFIGS_MODULE=sdk.python.feast.infra.offline_stores.contrib.athena_repo_configuration \
PYTEST_PLUGINS=feast.infra.offline_stores.contrib.athena_offline_store.tests \
FEAST_USAGE=False IS_TEST=True \
+ ATHENA_REGION=ap-northeast-2 \
ATHENA_DATA_SOURCE=AwsDataCatalog \
ATHENA_DATABASE=default \
+ ATHENA_WORKGROUP=primary \
ATHENA_S3_BUCKET_NAME=feast-integration-tests \
python -m pytest -n 8 --integration \
-k "not test_go_feature_server and \
@@ -353,19 +356,15 @@ lint-go: compile-protos-go compile-go-lib
# Docker
-build-docker: build-ci-docker build-feature-server-python-docker build-feature-server-python-aws-docker build-feature-transformation-server-docker build-feature-server-java-docker
+build-docker: build-feature-server-python-docker build-feature-server-python-aws-docker build-feature-transformation-server-docker build-feature-server-java-docker
push-ci-docker:
docker push $(REGISTRY)/feast-ci:$(VERSION)
-# TODO(adchia): consider removing. This doesn't run successfully right now
-build-ci-docker:
- docker buildx build -t $(REGISTRY)/feast-ci:$(VERSION) -f infra/docker/ci/Dockerfile --load .
-
-push-feature-server-python-docker:
+push-feature-server-docker:
docker push $(REGISTRY)/feature-server:$$VERSION
-build-feature-server-python-docker:
+build-feature-server-docker:
docker buildx build --build-arg VERSION=$$VERSION \
-t $(REGISTRY)/feature-server:$$VERSION \
-f sdk/python/feast/infra/feature_servers/multicloud/Dockerfile --load .
diff --git a/README.md b/README.md
index b663533710b..c8adfa5f22c 100644
--- a/README.md
+++ b/README.md
@@ -185,8 +185,8 @@ The list below contains the functionality that contributors are planning to deve
* [ ] Batch transformation (In progress. See [RFC](https://docs.google.com/document/d/1964OkzuBljifDvkV-0fakp2uaijnVzdwWNGdz7Vz50A/edit))
* **Streaming**
* [x] [Custom streaming ingestion job support](https://docs.feast.dev/how-to-guides/creating-a-custom-provider)
- * [x] [Push based streaming data ingestion to online store (Alpha)](https://docs.feast.dev/reference/data-sources/push)
- * [x] [Push based streaming data ingestion to offline store (Alpha)](https://docs.feast.dev/reference/data-sources/push)
+ * [x] [Push based streaming data ingestion to online store](https://docs.feast.dev/reference/data-sources/push)
+ * [x] [Push based streaming data ingestion to offline store](https://docs.feast.dev/reference/data-sources/push)
* **Deployments**
* [x] AWS Lambda (Alpha release. See [RFC](https://docs.google.com/document/d/1eZWKWzfBif66LDN32IajpaG-j82LSHCCOzY6R7Ax7MI/edit))
* [x] Kubernetes (See [guide](https://docs.feast.dev/how-to-guides/running-feast-in-production#4.3.-java-based-feature-server-deployed-on-kubernetes))
@@ -202,7 +202,7 @@ The list below contains the functionality that contributors are planning to deve
* [x] Model-centric feature tracking (feature services)
* [x] Amundsen integration (see [Feast extractor](https://github.com/amundsen-io/amundsen/blob/main/databuilder/databuilder/extractor/feast_extractor.py))
* [x] DataHub integration (see [DataHub Feast docs](https://datahubproject.io/docs/generated/ingestion/sources/feast/))
- * [x] Feast Web UI (Alpha release. See [docs](https://docs.feast.dev/reference/alpha-web-ui))
+ * [x] Feast Web UI (Beta release. See [docs](https://docs.feast.dev/reference/alpha-web-ui))
## 🎓 Important Resources
diff --git a/community/README.md b/community/README.md
new file mode 100644
index 00000000000..3ffe7f46296
--- /dev/null
+++ b/community/README.md
@@ -0,0 +1,8 @@
+# Feast Community
+
+Welcome to the Feast community!
+
+Please see the Community section on [Feast.dev](https://docs.feast.dev/) for more details on getting involved.
+
+- [Governance](governance.md): The Feast governance structure
+- [Maintainers](maintainers.md): List of members acting as maintainers
diff --git a/community/governance.excalidraw b/community/governance.excalidraw
new file mode 100644
index 00000000000..f4c8dad9a4f
--- /dev/null
+++ b/community/governance.excalidraw
@@ -0,0 +1,913 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 620,
+ "versionNonce": 853777363,
+ "isDeleted": false,
+ "id": "pr0yIJcUDXb4nFgowH9_r",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 409.5,
+ "y": 620.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 194,
+ "height": 83,
+ "seed": 1695250557,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "YfmPferxgVKoP70zGfYDK",
+ "type": "text"
+ },
+ {
+ "id": "YfmPferxgVKoP70zGfYDK",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "YfmPferxgVKoP70zGfYDK"
+ },
+ {
+ "id": "IsihlXUGDSklv2RsxX6wO",
+ "type": "arrow"
+ },
+ {
+ "id": "G5s2AUFJ730fyPsIbA8xP",
+ "type": "arrow"
+ },
+ {
+ "id": "j9ZVC3ZgHTsAGe3hJQecp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582134601,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 623,
+ "versionNonce": 328400605,
+ "isDeleted": false,
+ "id": "YfmPferxgVKoP70zGfYDK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 414.5,
+ "y": 649.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 184,
+ "height": 25,
+ "seed": 1575229907,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582134601,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CODEOWNERS",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "pr0yIJcUDXb4nFgowH9_r",
+ "originalText": "CODEOWNERS"
+ },
+ {
+ "type": "rectangle",
+ "version": 756,
+ "versionNonce": 1648798067,
+ "isDeleted": false,
+ "id": "XDy4VWWtJ9sd6hzPJDdFe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 409.5,
+ "y": 779.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 194,
+ "height": 83,
+ "seed": 1925179667,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "gUz4p_oPytb5-ejbYb81N",
+ "type": "text"
+ },
+ {
+ "id": "gUz4p_oPytb5-ejbYb81N",
+ "type": "text"
+ },
+ {
+ "id": "gUz4p_oPytb5-ejbYb81N",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "gUz4p_oPytb5-ejbYb81N"
+ },
+ {
+ "id": "G5s2AUFJ730fyPsIbA8xP",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582134601,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 781,
+ "versionNonce": 1240013629,
+ "isDeleted": false,
+ "id": "gUz4p_oPytb5-ejbYb81N",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 414.5,
+ "y": 808.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 184,
+ "height": 25,
+ "seed": 140322205,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582134601,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Contributors",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "XDy4VWWtJ9sd6hzPJDdFe",
+ "originalText": "Contributors"
+ },
+ {
+ "type": "text",
+ "version": 463,
+ "versionNonce": 2109720179,
+ "isDeleted": false,
+ "id": "AYJKq2RGJrSIpbfiJOf_4",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 526,
+ "y": 517.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 274,
+ "height": 75,
+ "seed": 1616513981,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "1. organize contributors\n2. influence roadmap\n3. own direction of an area",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "1. organize contributors\n2. influence roadmap\n3. own direction of an area"
+ },
+ {
+ "type": "rectangle",
+ "version": 776,
+ "versionNonce": 519656573,
+ "isDeleted": false,
+ "id": "z5LT5d710gSTA9DjwiL3O",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 70,
+ "angle": 0,
+ "x": 1013.7117834394903,
+ "y": 187.5000000000001,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 132,
+ "height": 682,
+ "seed": 1424710877,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "RscqyQXicYOkFsE_zvran",
+ "type": "text"
+ },
+ {
+ "id": "J7IG4T5j15pB3b_K0Cpd9",
+ "type": "arrow"
+ },
+ {
+ "id": "XEohLLmfFl0L9Wi2ew5AU",
+ "type": "arrow"
+ },
+ {
+ "id": "o3Pp-94PORhEiEauRcZW_",
+ "type": "arrow"
+ },
+ {
+ "type": "text",
+ "id": "RscqyQXicYOkFsE_zvran"
+ },
+ {
+ "id": "j9ZVC3ZgHTsAGe3hJQecp",
+ "type": "arrow"
+ },
+ {
+ "id": "Klq-VJGZiolZnGuaNJ8k9",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582138112,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 896,
+ "versionNonce": 1733426643,
+ "isDeleted": false,
+ "id": "RscqyQXicYOkFsE_zvran",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1018.7117834394903,
+ "y": 476.0000000000001,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 122,
+ "height": 105,
+ "seed": 1202400115,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582138113,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "Feast \nGitHub \nproject",
+ "baseline": 95,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "z5LT5d710gSTA9DjwiL3O",
+ "originalText": "Feast GitHub project"
+ },
+ {
+ "id": "IsihlXUGDSklv2RsxX6wO",
+ "type": "arrow",
+ "x": 506.997671158975,
+ "y": 619,
+ "width": 0,
+ "height": 132,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 345290749,
+ "version": 680,
+ "versionNonce": 787007421,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -132
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "pr0yIJcUDXb4nFgowH9_r",
+ "focus": 0.005130630504896713,
+ "gap": 1.5
+ },
+ "endBinding": {
+ "elementId": "TBYpmrW2OsKEqbpZfEeJg",
+ "focus": 0.461338833375829,
+ "gap": 1
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "id": "G5s2AUFJ730fyPsIbA8xP",
+ "type": "arrow",
+ "x": 506.9985864097345,
+ "y": 776,
+ "width": 0.9914493467237548,
+ "height": 71,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 241364467,
+ "version": 241,
+ "versionNonce": 649485971,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.9914493467237548,
+ -71
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "XDy4VWWtJ9sd6hzPJDdFe",
+ "focus": 0.011569796958606356,
+ "gap": 3.5
+ },
+ "endBinding": {
+ "elementId": "pr0yIJcUDXb4nFgowH9_r",
+ "focus": 0.011204382815075232,
+ "gap": 1.5
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "id": "TBYpmrW2OsKEqbpZfEeJg",
+ "type": "rectangle",
+ "x": 409.5,
+ "y": 188,
+ "width": 361.99999999999994,
+ "height": 298,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 30,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 1515491581,
+ "version": 231,
+ "versionNonce": 593345661,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "id": "IsihlXUGDSklv2RsxX6wO",
+ "type": "arrow"
+ },
+ {
+ "id": "Klq-VJGZiolZnGuaNJ8k9",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "YEEHpa4RXaR8G9YW55v25",
+ "type": "rectangle",
+ "x": 428.5,
+ "y": 398,
+ "width": 163.61445783132532,
+ "height": 70.00000000000001,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 932648787,
+ "version": 319,
+ "versionNonce": 398988755,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "8iyUZwSph5yMVrXehf6vg"
+ },
+ {
+ "id": "o3Pp-94PORhEiEauRcZW_",
+ "type": "arrow"
+ },
+ {
+ "id": "IsihlXUGDSklv2RsxX6wO",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "8iyUZwSph5yMVrXehf6vg",
+ "type": "text",
+ "x": 433.5,
+ "y": 422.5,
+ "width": 153.61445783132532,
+ "height": 21,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 1803538003,
+ "version": 365,
+ "versionNonce": 952837341,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "text": "Area maintainers",
+ "fontSize": 16.697223677317968,
+ "fontFamily": 1,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "baseline": 15,
+ "containerId": "YEEHpa4RXaR8G9YW55v25",
+ "originalText": "Area maintainers"
+ },
+ {
+ "type": "rectangle",
+ "version": 355,
+ "versionNonce": 1753998195,
+ "isDeleted": false,
+ "id": "Wh-PpzmGy1bWJko0akD-a",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 429.8072289156627,
+ "y": 257.1185567010309,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 161,
+ "height": 68.88144329896907,
+ "seed": 1844448573,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "OJCS1hAx71BD6u1jesJzR",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "OJCS1hAx71BD6u1jesJzR"
+ },
+ {
+ "id": "o3Pp-94PORhEiEauRcZW_",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 409,
+ "versionNonce": 556564797,
+ "isDeleted": false,
+ "id": "OJCS1hAx71BD6u1jesJzR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 434.8072289156627,
+ "y": 271.55927835051546,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 151,
+ "height": 40,
+ "seed": 852504851,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "fontSize": 16.413043478260864,
+ "fontFamily": 1,
+ "text": "Project \nmaintainers",
+ "baseline": 34,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "Wh-PpzmGy1bWJko0akD-a",
+ "originalText": "Project maintainers"
+ },
+ {
+ "id": "o3Pp-94PORhEiEauRcZW_",
+ "type": "arrow",
+ "x": 510.1952932956257,
+ "y": 396.60017389144207,
+ "width": 0.34508644012566947,
+ "height": 69.20034778288408,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 1889236627,
+ "version": 572,
+ "versionNonce": 918879507,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.34508644012566947,
+ -69.20034778288408
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "YEEHpa4RXaR8G9YW55v25",
+ "focus": -0.0035794947090358044,
+ "gap": 1.3998261085579315
+ },
+ "endBinding": {
+ "elementId": "Wh-PpzmGy1bWJko0akD-a",
+ "focus": -0.0051056226396315905,
+ "gap": 1.3998261085579884
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "id": "4CHi-UfB3oI1PAfcFm2o_",
+ "type": "text",
+ "x": 528.5,
+ "y": 354.5,
+ "width": 218,
+ "height": 20,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 2054408115,
+ "version": 238,
+ "versionNonce": 1105416605,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "text": "break ties by majority vote",
+ "fontSize": 16,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 14,
+ "containerId": null,
+ "originalText": "break ties by majority vote"
+ },
+ {
+ "id": "gHvMhIQl4S1SxPE8kzHLx",
+ "type": "text",
+ "x": 431.8072289156627,
+ "y": 201.5,
+ "width": 157,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "mcHoJ-dlfU3T8l_C93UPa"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 1597289651,
+ "version": 154,
+ "versionNonce": 1038738099,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "text": "Maintainers",
+ "fontSize": 28,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 25,
+ "containerId": null,
+ "originalText": "Maintainers"
+ },
+ {
+ "type": "text",
+ "version": 545,
+ "versionNonce": 1478563325,
+ "isDeleted": false,
+ "id": "_qJ5MtLgnvmF1-EDKX6qg",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 533,
+ "y": 732.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 106,
+ "height": 25,
+ "seed": 1870614973,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582134602,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "review PRs",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "review PRs"
+ },
+ {
+ "id": "j9ZVC3ZgHTsAGe3hJQecp",
+ "type": "arrow",
+ "x": 610.590909090909,
+ "y": 673.6931323855418,
+ "width": 394.7818338530517,
+ "height": 1.1368683772161603e-13,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 1115132605,
+ "version": 594,
+ "versionNonce": 1612334739,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582138112,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 394.7818338530517,
+ 1.1368683772161603e-13
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "pr0yIJcUDXb4nFgowH9_r",
+ "gap": 7.0909090909090455,
+ "focus": 0.2817622261576348
+ },
+ "endBinding": {
+ "elementId": "z5LT5d710gSTA9DjwiL3O",
+ "gap": 9.339040495529549,
+ "focus": -0.4257863119810608
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "id": "Klq-VJGZiolZnGuaNJ8k9",
+ "type": "arrow",
+ "x": 775.7385321100917,
+ "y": 334,
+ "width": 233.2672383568049,
+ "height": 0,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 632787667,
+ "version": 198,
+ "versionNonce": 1893334067,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1662582138112,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 233.2672383568049,
+ 0
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "TBYpmrW2OsKEqbpZfEeJg",
+ "gap": 4.238532110091741,
+ "focus": -0.020134228187919462
+ },
+ "endBinding": {
+ "elementId": "z5LT5d710gSTA9DjwiL3O",
+ "gap": 5.7060129725937765,
+ "focus": 0.5703812316715546
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "text",
+ "version": 651,
+ "versionNonce": 1375877757,
+ "isDeleted": false,
+ "id": "diazwl57WWW_7gfm7wMea",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 678,
+ "y": 637.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 262,
+ "height": 25,
+ "seed": 2077675699,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582152851,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "merge PRs if no objections",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "merge PRs if no objections"
+ },
+ {
+ "type": "text",
+ "version": 658,
+ "versionNonce": 1558756051,
+ "isDeleted": false,
+ "id": "_T1wMHFNqfA6a8Ku2OLDl",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 840,
+ "y": 296.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 102,
+ "height": 25,
+ "seed": 1987233139,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1662582148465,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "merge PRs",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "merge PRs"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/community/governance.md b/community/governance.md
new file mode 100644
index 00000000000..89cf800bc88
--- /dev/null
+++ b/community/governance.md
@@ -0,0 +1,272 @@
+ Feast Governance
+
+- [Introduction](#introduction)
+- [Feast community overview](#feast-community-overview)
+- [Feast governance model overview](#feast-governance-model-overview)
+- [Roles And Responsibilities](#roles-and-responsibilities)
+ - [Users](#users)
+ - [Contributors](#contributors)
+ - [CODEOWNERS](#codeowners)
+ - [Maintainers (project + area)](#maintainers-project--area)
+ - [Types of maintainers](#types-of-maintainers)
+ - [Optional maintainer responsibilities](#optional-maintainer-responsibilities)
+ - [Becoming a Maintainer](#becoming-a-maintainer)
+ - [Who is eligible to become a maintainer?](#who-is-eligible-to-become-a-maintainer)
+ - [Process](#process)
+ - [Earning a Nomination](#earning-a-nomination)
+ - [Losing Maintainer Status](#losing-maintainer-status)
+- [Decision Making Process](#decision-making-process)
+ - [Lazy consensus](#lazy-consensus)
+ - [Voting](#voting)
+ - [Changes to Governance](#changes-to-governance)
+- [Roadmap Creation](#roadmap-creation)
+ - [RFCs Process](#rfcs-process)
+ - [When to Use RFCs](#when-to-use-rfcs)
+- [Resources](#resources)
+
+
+# Introduction
+
+Feast is an open-source feature store for machine learning that allows teams to define, manage, store, and serve features to operational ML systems.
+
+The Feast project aims for open and transparent governance and decision-making, thus encouraging community building and contribution.
+
+A formal governance structure helps us to
+
+* Provide a structure for individuals to become involved in the project.
+* Communicate all processes for members to operate within the project.
+* Document a system for open product development, roadmapping, and planning.
+* Provide a means for making decisions if consensus cannot be reached.
+
+# Feast community overview
+
+On a high level, the key moving parts of the community are:
+- **GitHub activity** (issues + pull requests)
+- **Slack community** ([slack.feast.dev](slack.feast.dev))
+ - `#feast-development` is where design discussions happen amongst contributors
+ - Other Slack channels exist for users to ask and answer questions.
+- **RFCs** ([drive folder](https://drive.google.com/drive/u/0/folders/1msUsgmDbVBaysmhBlg9lklYLLTMk4bC3)) for detailed discussions
+- **Community calls** (biweekly) to discuss best practices, contributions, and announce changes
+- **Maintainer syncs** (monthly) for [maintainers](maintainers.md) to discuss project direction and health
+
+With this structure, users and contributors largely self-organize and contribute changes as per [lazy consensus](#lazy-consensus). If there is active opposition and unresolvable conflict, then maintainers step in to break ties or make decisions.
+
+We dive more deeply into the governance model below.
+
+# Feast governance model overview
+
+Feast is a meritocratic, consensus-based community project.
+
+Anyone interested in the project can join the community to:
+- contribute to the project design
+- participate in the decision-making process.
+
+The general decision making workflow is as follows:
+
+
+
+> **Note**: There may not always a corresponding CODEOWNER for the affected code, in which case the responsibility falls on other maintainers or contributors with write access to review + merge the PR
+
+# Roles And Responsibilities
+
+## Users
+
+Users are community members who require the operational ML functionality of Feast. They are the most important community members, and without them, the project would have no purpose. Anyone can be a user; there are no special requirements.
+
+Feast asks its users to participate in the project and community as much as possible. User contributions enable the project team to ensure that they satisfy the needs of those users. Frequently, user contributions include (but are not limited to):
+
+* Providing developers with feedback on the project (user experience)
+* Providing feature requests
+* Filing bug reports or flagging issues
+* Providing moral support
+* Evangelizing the project
+
+Users who continue to engage with the project and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section.
+
+
+## Contributors
+
+Contributors are community members who contribute in concrete ways to the project. Anyone can become a contributor, and contributions can take many forms, as detailed in the [all-contributors project](https://allcontributors.org/docs/en/emoji-key#table). There is no expectation of commitment to the project, no specific skill requirements, and no selection process.
+
+In addition to their actions as users, contributors may also find themselves doing one or more of the following:
+
+* Supporting new users (existing users are often the best people to help new users)
+* Creating, triaging or commenting on Issues
+* Doing code reviews or commenting on technical documents
+* Writing, editing, translating or reviewing the documentation
+* Organizing events or evangelizing the project
+
+Contributors engage with the project through the issue tracker and slack community, or by writing or editing documentation. They submit changes to the project itself via Pull Requests (PRs), which will be considered for inclusion in the project by existing maintainers (see next section).
+
+Contributors should follow the following guides when creating PRs:
+- [Contribution Process](https://docs.feast.dev/project/contributing)
+- [Development Guide](https://docs.feast.dev/project/development-guide).
+
+As contributors gain experience and familiarity with the project, their profile and commitment within the community will increase. At some stage, they may find themselves being nominated for being a maintainer.
+
+## CODEOWNERS
+
+On top of maintainers who will be in the CODEOWNERS file, other contributors can also be added as a lower commitment way to contribute by reviewing / responding to PRs.
+
+CODEOWNERS will generally be the first point of contacts in reviewing pull requests and will have commit privileges.
+
+## Maintainers (project + area)
+
+Maintainers are community members who have shown that they are committed to Feast’s continued development through ongoing engagement with the community. Because of this, maintainers have the right to merge PRs and have voting rights.
+
+> **Note**: maintainers, like other contributors, must make changes to Feast via pull requests (with code review). This applies to all changes to documentation, code, configuration, governance, etc.
+
+### Types of maintainers
+
+There are two kinds of maintainers
+
+1. **Project maintainers** control overall project organization and resolving disputes. They also
+ - Attend a regular maintainers sync
+ - Participate in strategic planning, approve changes to the governance model, and manage the copyrights within the project outputs.
+ - (optional) Attend community calls
+ - (optional) Planning project roadmaps and articulating vision
+ - (optional) Guide design decisions to reinforce key project values (e.g. simplicity)
+2. **Area maintainers** own a specific technical area (which may span code modules), often specifically targeting a user journey or tech stack. They
+ - are generally point people in GitHub or Slack on discussions in that area (e.g. tagged in `#feast-development`)
+ - (optional) help drive roadmap decisions
+
+> **Note:** project maintainers may also be area maintainers, but this does not give their ideas increased weight over other area maintainers.
+
+Decisions that need tie breakers may require intervention via project maintainers majority consensus.
+
+### Optional maintainer responsibilities
+Other optional activites a maintainer (project or area maintainer) may participate in:
+ * Monitor email aliases and our Slack (#feast-general, #feast-development, #feast-beginners).
+ * Perform code reviews for other maintainers and the community. The areas of specialization listed in [OWNERS.md](OWNERS.md) can be used to help with routing an issue/question to the right person.
+ * Triage GitHub issues, applying [labels]([https://github.com/feast-dev/feast/labels](https://github.com/feast-dev/feast/labels)) to each new item. Labels are extremely useful for future issue follow ups. Adding labels is somewhat subjective, so please use your best judgment.
+ * Triage build issues, filing issues for known flaky builds or bugs, fixing or finding someone to fix any master build breakages.
+ * Make sure that ongoing PRs are moving forward at the right pace or closing them.
+
+## Becoming a Maintainer
+
+### Who is eligible to become a maintainer?
+Anyone can become a maintainer. Typically, a potential maintainer will need to show that they understand the project, its objectives, and its strategy. They will also have provided valuable contributions to the project over a period of time. Maintainers must also act in the interest of the community.
+
+### Process
+Any existing maintainer can nominate new maintainers. Once they have been nominated, there will be a vote by the rest of the maintainers. Maintainer voting is one of the few activities that takes place in private. This is to allow maintainers to express their opinions about a nominee without causing embarrassment freely. The approval requires **three maintainers +1 vote** and **no -1 vote from a maintainer**.
+
+The nominee is entitled to request an explanation of any ‘no’ votes against them, regardless of the vote's outcome. This explanation will be provided by the maintainers and will be anonymous and constructive.
+
+Nominees may decline their appointment as a maintainer. Becoming a maintainer means that they will be spending a substantial time working on Feast for the foreseeable future. It is essential to recognize that being a maintainer is a privilege, not a right. That privilege must be earned, and once earned, the rest of the maintainers can remove it in extreme circumstances.
+
+Lazy consensus does not apply to becoming a maintainer. A vote must be held. Voting takes place through the [maintainer mailing list](https://groups.google.com/g/feast-maintainers). A vote must stay open for at least 7 days.
+
+### Earning a Nomination
+
+There is not a single path of earning a nomination for maintainer at Feast, however, we can give some guidance about some actions that would help:
+
+* Start by expressing interest to the maintainers that you are interested in becoming a maintainer.
+* You can start tackling issues labeled as ‘help wanted’, or if you are new to the project, some of the ‘good first issue’ tickets.
+* As you gain experience with the codebase and our standards, we will ask you to do code reviews for incoming PRs (i.e., all maintainers are expected to shoulder a proportional share of community reviews).
+* We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers.
+
+## Losing Maintainer Status
+
+If a maintainer is no longer interested and cannot perform the maintainer duties listed above, they can volunteer to be moved to emeritus status. The maintainer status is attributed for life otherwise. An emeritus maintainer may request reinstatement of commit access from the rest of maintainers. Such reinstatement is subject to lazy consensus approval of active maintainers.
+
+Emeritus status is a nominal title, and confers no special rights (like voting) or access. Emeritus members are functionally identical to normal contributors, with the exception that they can request for reinstatement of their commit access.
+
+In extreme cases, maintainers can lose their status by a vote of the maintainers per the voting process below.
+
+
+# Decision Making Process
+
+Decisions about the future of Feast are made through discussion with all community members, from the newest user to the most experienced maintainer. All non-sensitive project management discussion takes place on the project issue tracker system. Occasionally, sensitive discussion occurs on a private channel of our Slack.
+
+To ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of lazy consensus. This allows the majority of decisions to be made without resorting to a formal vote.
+
+
+## Lazy consensus
+
+Decision making typically involves the following steps:
+* Proposal *(via GitHub issue + GitHub PR)*
+* Discussion *(in Slack channels at #feast-development and GitHub)*
+* (optional) Maintainers voting (if there is active opposition + consensus is not reached through discussion)
+* Decision
+
+Any community member can make a proposal for consideration by the community. To initiate a discussion about a new idea, they should create an issue or submit a PR implementing the idea to the issue tracker. This will prompt a review and, if necessary, a discussion of the idea. The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little discussion to reach consensus.
+
+In general, as long as nobody explicitly opposes a proposal or PR, it is recognized as having the support of the community. This is called lazy consensus - that is, those who have not stated their opinion explicitly have implicitly agreed to the proposal's implementation.
+
+Lazy consensus is a fundamental concept within the project. This process allows a large group of people to reach consensus efficiently as someone with no objections to a proposal need not spend time stating their position.
+
+For lazy consensus to be effective, it is necessary to allow at least 48 hours before assuming that there are no objections to the proposal. This requirement ensures that everyone is given enough time to read, digest, and respond to the proposal. This time period is chosen to be as inclusive as possible of all participants, regardless of their location and time commitments.
+
+
+## Voting
+
+Not all decisions can be made using lazy consensus. Issues such as those affecting the strategic direction or legal standing of the project must gain explicit approval in the form of a vote. Every member of the community is encouraged to express their opinions in all discussions and all votes. However, only project maintainers have binding votes for the purposes of decision making.
+
+
+## Changes to Governance
+
+We believe governance needs to adapt in order to be effective long term. This governance document itself can be extended or modified as our community and project grows and our needs change.
+
+A change in our governance structure should be a rare occurrence and should face sufficient scrutiny and review. To this end, the rules that apply to modifications to the Feast Governance structure are more stringent:
+
+* Governance changes are made through PRs to the [feast-dev/community](https://github.com/feast-dev/community) repository.
+* Lazy consensus applies to governance changes, but the proposed changes must be public for at least 7 days instead of 48 hours before they are accepted.
+* If there is opposition to a change, a vote will be held by maintainers.
+* Voting is asynchronous. All maintainers must be notified of a vote through the [maintainer mailing list](https://groups.google.com/g/feast-maintainers).
+* Maintainers must be given at least 7 days to respond.
+* Voting requires a super-majority in order to pass a decision, and maintainers do not hold veto power for these votes. A super-majority is defined as at least 60% of votes in favor.
+* The total pool of votes does not include those who abstain from voting.
+* A quorum is required for voting. A quorum is 60% of maintainers.
+
+
+# Roadmap Creation
+
+Our [roadmap](https://docs.feast.dev/roadmap) gives an overview of what we are currently working on and what we want to tackle next. This helps potential contributors understand your project's current status and where it's going next, as well as giving a chance to be part of the planning.
+
+In this section, we describe the process we follow to create it, using request for comments documents (RFCs).
+
+
+## RFCs Process
+
+Most of the issues we see can be handled with regular GitHub issues. However, some changes are "substantial", and we ask that these go through a design process and produce a consensus among the Feast community.
+
+The "RFC" (request for comments) process is intended to provide a consistent and controlled path for new features to enter the roadmap. The high-level process looks like this:
+
+
+
+1. Contributor creates an RFC draft in the repository
+2. Users, Contributors, and Maintainers discuss and upvote the draft
+3. If confident on its success, contributor completes the RFC with more in-detail technical specifications
+4. Maintainers approve RFC when it is ready
+5. Maintainers meet every quarter and choose three or five items based on popularity and alignment with project vision and goals
+6. Those selected items become part of the Mid-term goals
+
+
+### When to Use RFCs
+
+What constitutes a "substantial" change is evolving based on the community, but may include the following:
+* New features that require configuration options to activate/deactivate
+* Remove features
+* Architecture changes
+
+Some changes do not require an RFC:
+* Reorganizing or refactoring code or documentation
+* Improvements that tackle objective quality criteria (speedup, better browser support)
+* Changes noticeable only by contributors or maintainers
+* Examples:
+ * Adding programmatic descriptions
+ * Adding support for tags at a column level
+
+If you submit a pull request to implement a new feature without going through the RFC process, it may be closed with a polite request to submit an RFC first. That said, if most of the work is done, we'd accelerate the process.
+
+We will keep our RFC documents in a separate repo on the feast-dev organization, where a detailed step by step process will be documented.
+
+
+# Resources
+
+* [Envoy’s Governance Document](https://github.com/envoyproxy/envoy/blob/master/GOVERNANCE.md)
+* [OSS Watch, Meritocratic Governance](http://oss-watch.ac.uk/resources/meritocraticgovernancemodel)
+* [The Apache Software Foundation meritocratic model](http://www.apache.org/foundation/how-it-works.html#meritocracy)
+* [Ember RFCs](https://github.com/emberjs/rfcs)
+
+Attribution: The Feast governance structure is based on the Amundsen Governance structure.
diff --git a/community/governance.png b/community/governance.png
new file mode 100644
index 00000000000..c2b00930e3a
Binary files /dev/null and b/community/governance.png differ
diff --git a/community/maintainers.md b/community/maintainers.md
new file mode 100644
index 00000000000..cdf78b150cd
--- /dev/null
+++ b/community/maintainers.md
@@ -0,0 +1,42 @@
+# Maintainers
+
+See [Governance](governance.md) for what each maintainer type is
+
+## Project maintainers
+
+In alphabetical order
+
+| Name | GitHub Username | Email | Organization |
+| -------------- | ---------------- | --------------------------- | ------------------ |
+| Abhin Chhabra | `chhabrakadabra` | chhabra.abhin@gmail.com | Shopify |
+| Achal Shah | `achals` | achals@gmail.com | Tecton |
+| Danny Chiao | `adchia` | d.chiao@gmail.com | Tecton |
+| David Liu | `mavysavydav` | davidyliuliu@gmail.com | Twitter |
+| Felix Wang | `felixwang9817` | wangfelix98@gmail.com | Tecton |
+| Kevin Zhang | `kevjumba` | kevin.zhang.13499@gmail.com | Tecton |
+| Matt Delacour | `MattDelac` | mdelacour@hey.com | (formerly) Shopify |
+| Miles Adkins | `sfc-gh-madkins` | miles.adkins@snowflake.com | Snowflake |
+| Willem Pienaar | `woop` | will.pienaar@gmail.com | Tecton |
+| Zhiling Chen | `zhilingc` | chnzhlng@gmail.com | GetGround |
+
+## Area maintainers
+
+Generally, with contribution questions here, default to `#feast-development` in the [slack.feast.dev](slack.feast.dev) Slack channel, but these may be folks for you to tag in messages
+
+| Area | Description | Name |
+| -------------------- | -------------------------------------------------------------------------- | --------------------------------------------- |
+| Data ingestion | ingesting batch + stream data into the online store (materialization) | Achal Shah,
Felix Wang,
Kevin Zhang |
+| Developer experience | tooling, testing, documentation, tutorials | Achal Shah |
+| Feature serving | optimization, caching, deployment patterns, batch retrieval, range queries | Dvir Dukhan |
+| Ops | general deployment concerns, CI/CD, versioning | Keith Adler,
Danny Chiao,
Felix Wang |
+| Web UI | i.e. `feast ui` output | Danny Chiao,
David Liu |
+
+## Emeritus Maintainers
+
+| Name | GitHub Username | Email | Organization |
+| ------------------- | --------------- | --------------------------- | ------------ |
+| Oleg Avdeev | oavdeev | oleg.v.avdeev@gmail.com | Tecton |
+| Oleksii Moskalenko | pyalex | moskalenko.alexey@gmail.com | Tecton |
+| Jay Parthasarthy | jparthasarthy | jparthasarthy@gmail.com | Tecton |
+| Pradithya Aria Pura | pradithya | pradithya.aria@gmail.com | Gojek |
+| Tsotne Tabidze | tsotnet | tsotnet@gmail.com | Tecton |
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index b838e5fe5b1..f387406c3fd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -57,7 +57,7 @@ Many companies have used Feast to power real-world ML use cases such as:
## How can I get started?
{% hint style="info" %}
-The best way to learn Feast is to use it. Head over to our [Quickstart](getting-started/quickstart.md) and try it out!
+The best way to learn Feast is to use it. Join our [Slack channel](http://slack.feast.dev) and head over to our [Quickstart](getting-started/quickstart.md) and try it out!
{% endhint %}
Explore the following resources to get started with Feast:
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 8ee48677302..430f9a745d7 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -1,7 +1,7 @@
# Table of contents
* [Introduction](README.md)
-* [Community](community.md)
+* [Community & getting help](community.md)
* [Roadmap](roadmap.md)
* [Changelog](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md)
@@ -24,7 +24,6 @@
* [Online store](getting-started/architecture-and-components/online-store.md)
* [Batch Materialization Engine](getting-started/architecture-and-components/batch-materialization-engine.md)
* [Provider](getting-started/architecture-and-components/provider.md)
-* [Learning by example](getting-started/feast-workshop.md)
* [Third party integrations](getting-started/third-party-integrations.md)
* [FAQ](getting-started/faq.md)
@@ -50,7 +49,7 @@
* [Read features from the online store](how-to-guides/feast-snowflake-gcp-aws/read-features-from-the-online-store.md)
* [Scaling Feast](how-to-guides/scaling-feast.md)
* [Structuring Feature Repos](how-to-guides/structuring-repos.md)
-* [Running Feast in production](how-to-guides/running-feast-in-production.md)
+* [Running Feast in production (e.g. on Kubernetes)](how-to-guides/running-feast-in-production.md)
* [Upgrading for Feast 0.20+](how-to-guides/automated-feast-upgrade.md)
* [Customizing Feast](how-to-guides/customizing-feast/README.md)
* [Adding a custom batch materialization engine](how-to-guides/customizing-feast/creating-a-custom-materialization-engine.md)
@@ -87,6 +86,7 @@
* [Trino (contrib)](reference/offline-stores/trino.md)
* [Azure Synapse + Azure SQL (contrib)](reference/offline-stores/mssql.md)
* [Online stores](reference/online-stores/README.md)
+ * [Overview](reference/online-stores/overview.md)
* [SQLite](reference/online-stores/sqlite.md)
* [Snowflake](reference/online-stores/snowflake.md)
* [Redis](reference/online-stores/redis.md)
@@ -120,6 +120,7 @@
* [Contribution process](project/contributing.md)
* [Development guide](project/development-guide.md)
+* [Backwards Compatibility Policy](project/compatibility.md)
* [Maintainer Docs](project/maintainers.md)
* [Versioning policy](project/versioning-policy.md)
* [Release process](project/release-process.md)
diff --git a/docs/community.md b/docs/community.md
index dc1cc8a0fe8..098b6b3f90b 100644
--- a/docs/community.md
+++ b/docs/community.md
@@ -1,17 +1,20 @@
-# Community
+# Community & Getting Help
## Links & Resources
-* [Slack](https://slack.feast.dev): Feel free to ask questions or say hello!
+* [GitHub Repository](https://github.com/feast-dev/feast/): Find the complete Feast codebase on GitHub.
+ * [Community Governance Doc](https://github.com/feast-dev/feast/blob/master/community): See the governance model of Feast, including who the maintainers are and how decisions are made.
+* [Slack](https://slack.feast.dev): Feel free to ask questions or say hello! This is the main place where maintainers and contributors brainstorm and where users ask questions or discuss best practices.
+ * Feast users should join `#feast-general` or `#feast-beginners` to ask questions
+ * Feast developers / contributors should join `#feast-development`
* [Mailing list](https://groups.google.com/d/forum/feast-dev): We have both a user and developer mailing list.
* Feast users should join [feast-discuss@googlegroups.com](mailto:feast-discuss@googlegroups.com) group by clicking [here](https://groups.google.com/g/feast-discuss).
- * Feast developers should join [feast-dev@googlegroups.com](mailto:feast-dev@googlegroups.com) group by clicking [here](https://groups.google.com/d/forum/feast-dev).
+ * Feast developers / contributors should join [feast-dev@googlegroups.com](mailto:feast-dev@googlegroups.com) group by clicking [here](https://groups.google.com/d/forum/feast-dev).
* [Community Calendar](https://calendar.google.com/calendar/u/0?cid=ZTFsZHVhdGM3MDU3YTJucTBwMzNqNW5rajBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ): Includes community calls and design meetings.
* [Google Folder](https://drive.google.com/drive/u/0/folders/1jgMHOPDT2DvBlJeO9LCM79DP4lm4eOrR): This folder is used as a central repository for all Feast resources. For example:
* Design proposals in the form of Request for Comments (RFC).
* User surveys and meeting minutes.
* Slide decks of conferences our contributors have spoken at.
-* [Feast GitHub Repository](https://github.com/feast-dev/feast/): Find the complete Feast codebase on GitHub.
* [Feast Linux Foundation Wiki](https://wiki.lfaidata.foundation/display/FEAST/Feast+Home): Our LFAI wiki page contains links to resources for contributors and maintainers.
## How can I get help?
@@ -22,17 +25,30 @@
## Community Calls
+### General community call (biweekly)
We have a user and contributor community call every two weeks (US & EU friendly).
{% hint style="info" %}
Please join the above Feast user groups in order to see calendar invites to the community calls
{% endhint %}
-### Frequency (every 2 weeks)
+#### Frequency (every 2 weeks)
* Tuesday 10:00 am to 10:30 am PST
-### Links
+#### Links
* Zoom: [https://zoom.us/j/6325193230](https://zoom.us/j/6325193230)
* Meeting notes (incl recordings): [https://bit.ly/feast-notes](https://bit.ly/feast-notes)
+
+### Developers call (biweekly)
+We also have a `#feast-development` community call every two weeks, where we discuss contributions + brainstorm best practices.
+
+#### Frequency (every 2 weeks)
+
+* Tuesday 8:00 am to 8:30 am PST
+
+#### Links
+
+* Meeting notes (incl recordings): [Feast Development Biweekly](https://docs.google.com/document/d/1zUbIWFWjaBEVlToOdupnmKQwgAtFYx41sPoEEEdd2io/edit#)
+* Zoom: [https://zoom.us/j/93657748160?pwd=K3ZpdzhqejgrcXNhc3BlSjFMdzUxdz09](https://zoom.us/j/93657748160?pwd=K3ZpdzhqejgrcXNhc3BlSjFMdzUxdz09)
diff --git a/docs/getting-started/architecture-and-components/online-store.md b/docs/getting-started/architecture-and-components/online-store.md
index 21b4dbcb9c7..980089c4fe8 100644
--- a/docs/getting-started/architecture-and-components/online-store.md
+++ b/docs/getting-started/architecture-and-components/online-store.md
@@ -1,15 +1,18 @@
# Online store
-The Feast online store is used for low-latency online feature value lookups. Feature values are loaded into the online store from data sources in feature views using the `materialize` command.
+Feast uses online stores to serve features at low latency.
+Feature values are loaded from data sources into the online store through _materialization_, which can be triggered through the `materialize` command.
-The storage schema of features within the online store mirrors that of the data source used to populate the online store. One key difference between the online store and data sources is that only the latest feature values are stored per entity key. No historical values are stored.
+The storage schema of features within the online store mirrors that of the original data source.
+One key difference is that for each [entity key](../concepts/entity.md), only the latest feature values are stored.
+No historical values are stored.
-Example batch data source
+Here is an example batch data source:

-Once the above data source is materialized into Feast \(using `feast materialize`\), the feature values will be stored as follows:
+Once the above data source is materialized into Feast (using `feast materialize`), the feature values will be stored as follows:

-Features can also be written to the online store via [push sources](../../reference/data-sources/push.md)
\ No newline at end of file
+Features can also be written directly to the online store via [push sources](../../reference/data-sources/push.md) .
\ No newline at end of file
diff --git a/docs/getting-started/concepts/feature-retrieval.md b/docs/getting-started/concepts/feature-retrieval.md
index f4462d06900..fd216fc71f5 100644
--- a/docs/getting-started/concepts/feature-retrieval.md
+++ b/docs/getting-started/concepts/feature-retrieval.md
@@ -17,41 +17,112 @@ Each of these retrieval mechanisms accept:
Before beginning, you need to instantiate a local `FeatureStore` object that knows how to parse the registry (see [more details](https://docs.feast.dev/getting-started/concepts/registry))
-
+For code examples of how the below work, inspect the generated repository from `feast init -t [YOUR TEMPLATE]` (`gcp`, `snowflake`, and `aws` are the most fully fleshed).
-How to: generate training data
+## Concepts
+Before diving into how to retrieve features, we need to understand some high level concepts in Feast.
-Feast abstracts away point-in-time join complexities with the `get_historical_features` API.
+### Feature Services
-It expects an **entity dataframe (or SQL query to retrieve a list of entities)** and a **list of feature references (or a feature service)**
+A feature service is an object that represents a logical group of features from one or more [feature views](feature-view.md#feature-view). Feature Services allows features from within a feature view to be used as needed by an ML model. Users can expect to create one feature service per model version, allowing for tracking of the features used by models.
-#### **Option 1: using feature references (to pick individual features when exploring data)**
+{% tabs %}
+{% tab title="driver_trips_feature_service.py" %}
+```python
+from driver_ratings_feature_view import driver_ratings_fv
+from driver_trips_feature_view import driver_stats_fv
+
+driver_stats_fs = FeatureService(
+ name="driver_activity",
+ features=[driver_stats_fv, driver_ratings_fv[["lifetime_rating"]]]
+)
+```
+{% endtab %}
+{% endtabs %}
+
+Feature services are used during
+
+* The generation of training datasets when querying feature views in order to find historical feature values. A single training dataset may consist of features from multiple feature views.
+* Retrieval of features for batch scoring from the offline store (e.g. with an entity dataframe where all timestamps are `now()`)
+* Retrieval of features from the online store for online inference (with smaller batch sizes). The features retrieved from the online store may also belong to multiple feature views.
+
+{% hint style="info" %}
+Applying a feature service does not result in an actual service being deployed.
+{% endhint %}
+
+Feature services enable referencing all or some features from a feature view.
+
+Retrieving from the online store with a feature service
```python
-entity_df = pd.DataFrame.from_dict(
- {
- "driver_id": [1001, 1002, 1003, 1004, 1001],
- "event_timestamp": [
- datetime(2021, 4, 12, 10, 59, 42),
- datetime(2021, 4, 12, 8, 12, 10),
- datetime(2021, 4, 12, 16, 40, 26),
- datetime(2021, 4, 12, 15, 1, 12),
- datetime.now()
- ]
- }
+from feast import FeatureStore
+feature_store = FeatureStore('.') # Initialize the feature store
+
+feature_service = feature_store.get_feature_service("driver_activity")
+features = feature_store.get_online_features(
+ features=feature_service, entity_rows=[entity_dict]
)
-training_df = store.get_historical_features(
- entity_df=entity_df,
+```
+
+Retrieving from the offline store with a feature service
+
+```python
+from feast import FeatureStore
+feature_store = FeatureStore('.') # Initialize the feature store
+
+feature_service = feature_store.get_feature_service("driver_activity")
+feature_store.get_historical_features(features=feature_service, entity_df=entity_df)
+```
+
+### Feature References
+
+This mechanism of retrieving features is only recommended as you're experimenting. Once you want to launch experiments or serve models, feature services are recommended.
+
+Feature references uniquely identify feature values in Feast. The structure of a feature reference in string form is as follows: `:`
+
+Feature references are used for the retrieval of features from Feast:
+
+```python
+online_features = fs.get_online_features(
features=[
- "driver_hourly_stats:conv_rate",
- "driver_hourly_stats:acc_rate",
- "driver_daily_features:daily_miles_driven"
+ 'driver_locations:lon',
+ 'drivers_activity:trips_today'
],
-).to_df()
-print(training_df.head())
+ entity_rows=[
+ # {join_key: entity_value}
+ {'driver': 'driver_1001'}
+ ]
+)
```
-#### Option 2: using feature services (to version models)
+It is possible to retrieve features from multiple feature views with a single request, and Feast is able to join features from multiple tables in order to build a training dataset. However, it is not possible to reference (or retrieve) features from multiple projects at the same time.
+
+{% hint style="info" %}
+Note, if you're using [Feature views without entities](feature-view.md#feature-views-without-entities), then those features can be added here without additional entity values in the `entity_rows` parameter.
+{% endhint %}
+
+### Event timestamp
+
+The timestamp on which an event occurred, as found in a feature view's data source. The event timestamp describes the event time at which a feature was observed or generated.
+
+Event timestamps are used during point-in-time joins to ensure that the latest feature values are joined from feature views onto entity rows. Event timestamps are also used to ensure that old feature values aren't served to models during online serving.
+
+### Dataset
+
+A dataset is a collection of rows that is produced by a historical retrieval from Feast in order to train a model. A dataset is produced by a join from one or more feature views onto an entity dataframe. Therefore, a dataset may consist of features from multiple feature views.
+
+**Dataset vs Feature View:** Feature views contain the schema of data and a reference to where data can be found (through its data source). Datasets are the actual data manifestation of querying those data sources.
+
+**Dataset vs Data Source:** Datasets are the output of historical retrieval, whereas data sources are the inputs. One or more data sources can be used in the creation of a dataset.
+
+## Retrieving historical features (for training data or batch scoring)
+Feast abstracts away point-in-time join complexities with the `get_historical_features` API.
+
+We go through the major steps, and also show example code. Note that the quickstart templates generally have end-to-end working examples for all these cases.
+
+
+
+Full example: generate training data
```python
entity_df = pd.DataFrame.from_dict(
@@ -77,60 +148,118 @@ print(training_df.head())
-How to: retrieve offline features for batch scoring
+Full example: retrieve offline features for batch scoring
The main difference here compared to training data generation is how to handle timestamps in the entity dataframe. You want to pass in the **current time** to get the latest feature values for all your entities.
-#### Option 1: fetching features with entity dataframe
-
```python
from feast import FeatureStore
-import pandas as pd
store = FeatureStore(repo_path=".")
# Get the latest feature values for unique entities
-entity_df = pd.DataFrame.from_dict({"driver_id": [1001, 1002, 1003, 1004, 1005],})
-entity_df["event_timestamp"] = pd.to_datetime("now", utc=True)
+entity_sql = f"""
+ SELECT
+ driver_id,
+ CURRENT_TIMESTAMP() as event_timestamp
+ FROM {store.get_data_source("driver_hourly_stats_source").get_table_query_string()}
+ WHERE event_timestamp BETWEEN '2021-01-01' and '2021-12-31'
+ GROUP BY driver_id
+"""
batch_scoring_features = store.get_historical_features(
- entity_df=entity_df, features=store.get_feature_service("model_v2"),
+ entity_df=entity_sql,
+ features=store.get_feature_service("model_v2"),
).to_df()
# predictions = model.predict(batch_scoring_features)
```
-#### Option 2: fetching features using a SQL query to generate entities
+
-```python
-from feast import FeatureStore
+### Step 1: Specifying Features
+Feast accepts either:
+- [feature services](feature-retrieval.md#feature-services), which group features needed for a model version
+- [feature references](feature-retrieval.md#feature-references)
-store = FeatureStore(repo_path=".")
+### Example: querying a feature service (recommended)
+```python
+training_df = store.get_historical_features(
+ entity_df=entity_df,
+ features=store.get_feature_service("model_v1"),
+).to_df()
+```
-# Get the latest feature values for unique entities
-batch_scoring_features = store.get_historical_features(
- entity_df="""
- SELECT
- user_id,
- CURRENT_TIME() as event_timestamp
- FROM entity_source_table
- WHERE user_last_active_time BETWEEN '2019-01-01' and '2020-12-31'
- GROUP BY user_id
- """
- ,
- features=store.get_feature_service("model_v2"),
+### Example: querying a list of feature references
+```python
+training_df = store.get_historical_features(
+ entity_df=entity_df,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_daily_features:daily_miles_driven"
+ ],
).to_df()
-# predictions = model.predict(batch_scoring_features)
```
+### Step 2: Specifying Entities
+Feast accepts either a **Pandas dataframe** as the entity dataframe (including entity keys and timestamps) or a **SQL query** to generate the entities.
-
+Both approaches must specify the full **entity key** needed as well as the **timestamps**. Feast then joins features onto this dataframe.
-
+### Example: entity dataframe for generating training data
+```python
+entity_df = pd.DataFrame.from_dict(
+ {
+ "driver_id": [1001, 1002, 1003, 1004, 1001],
+ "event_timestamp": [
+ datetime(2021, 4, 12, 10, 59, 42),
+ datetime(2021, 4, 12, 8, 12, 10),
+ datetime(2021, 4, 12, 16, 40, 26),
+ datetime(2021, 4, 12, 15, 1, 12),
+ datetime.now()
+ ]
+ }
+)
+training_df = store.get_historical_features(
+ entity_df=entity_df,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_daily_features:daily_miles_driven"
+ ],
+).to_df()
+```
-How to: retrieve online features for real-time model inference (Python SDK)
+### Example: entity SQL query for generating training data
+You can also pass a SQL string to generate the above dataframe. This is useful for getting all entities in a timeframe from some data source.
+
+```python
+entity_sql = f"""
+ SELECT
+ driver_id,
+ event_timestamp
+ FROM {store.get_data_source("driver_hourly_stats_source").get_table_query_string()}
+ WHERE event_timestamp BETWEEN '2021-01-01' and '2021-12-31'
+"""
+training_df = store.get_historical_features(
+ entity_df=entity_sql,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_daily_features:daily_miles_driven"
+ ],
+).to_df()
+```
+## Retrieving online features (for model inference)
Feast will ensure the latest feature values for registered features are available. At retrieval time, you need to supply a list of **entities** and the corresponding **features** to be retrieved. Similar to `get_historical_features`, we recommend using feature services as a mechanism for grouping features in a model version.
_Note: unlike `get_historical_features`, the `entity_rows` **do not need timestamps** since you only want one feature value per entity key._
+There are several options for retrieving online features: through the SDK, or through a feature server
+
+
+
+Full example: retrieve online features for real-time model inference (Python SDK)
+
```python
from feast import RepoConfig, FeatureStore
from feast.repo_config import RegistryConfig
@@ -160,11 +289,7 @@ features = store.get_online_features(
-How to: retrieve online features for real-time model inference (Feature Server)
-
-Feast will ensure the latest feature values for registered features are available. At retrieval time, you need to supply a list of **entities** and the corresponding **features** to be retrieved. Similar to `get_historical_features`, we recommend using feature services as a mechanism for grouping features in a model version.
-
-_Note: unlike `get_historical_features`, the `entity_rows` **do not need timestamps** since you only want one feature value per entity key._
+Full example: retrieve online features for real-time model inference (Feature Server)
This approach requires you to deploy a feature server (see [Python feature server](../../reference/feature-servers/python-feature-server)).
@@ -183,96 +308,3 @@ print(json.dumps(r.json(), indent=4, sort_keys=True))
```
-
-## Feature Services
-
-A feature service is an object that represents a logical group of features from one or more [feature views](feature-view.md#feature-view). Feature Services allows features from within a feature view to be used as needed by an ML model. Users can expect to create one feature service per model version, allowing for tracking of the features used by models.
-
-{% tabs %}
-{% tab title="driver_trips_feature_service.py" %}
-```python
-from driver_ratings_feature_view import driver_ratings_fv
-from driver_trips_feature_view import driver_stats_fv
-
-driver_stats_fs = FeatureService(
- name="driver_activity",
- features=[driver_stats_fv, driver_ratings_fv[["lifetime_rating"]]]
-)
-```
-{% endtab %}
-{% endtabs %}
-
-Feature services are used during
-
-* The generation of training datasets when querying feature views in order to find historical feature values. A single training dataset may consist of features from multiple feature views.
-* Retrieval of features for batch scoring from the offline store (e.g. with an entity dataframe where all timestamps are `now()`)
-* Retrieval of features from the online store for online inference (with smaller batch sizes). The features retrieved from the online store may also belong to multiple feature views.
-
-{% hint style="info" %}
-Applying a feature service does not result in an actual service being deployed.
-{% endhint %}
-
-Feature services enable referencing all or some features from a feature view.
-
-Retrieving from the online store with a feature service
-
-```python
-from feast import FeatureStore
-feature_store = FeatureStore('.') # Initialize the feature store
-
-feature_service = feature_store.get_feature_service("driver_activity")
-features = feature_store.get_online_features(
- features=feature_service, entity_rows=[entity_dict]
-)
-```
-
-Retrieving from the offline store with a feature service
-
-```python
-from feast import FeatureStore
-feature_store = FeatureStore('.') # Initialize the feature store
-
-feature_service = feature_store.get_feature_service("driver_activity")
-feature_store.get_historical_features(features=feature_service, entity_df=entity_df)
-```
-
-## Feature References
-
-This mechanism of retrieving features is only recommended as you're experimenting. Once you want to launch experiments or serve models, feature services are recommended.
-
-Feature references uniquely identify feature values in Feast. The structure of a feature reference in string form is as follows: `:`
-
-Feature references are used for the retrieval of features from Feast:
-
-```python
-online_features = fs.get_online_features(
- features=[
- 'driver_locations:lon',
- 'drivers_activity:trips_today'
- ],
- entity_rows=[
- # {join_key: entity_value}
- {'driver': 'driver_1001'}
- ]
-)
-```
-
-It is possible to retrieve features from multiple feature views with a single request, and Feast is able to join features from multiple tables in order to build a training dataset. However, it is not possible to reference (or retrieve) features from multiple projects at the same time.
-
-{% hint style="info" %}
-Note, if you're using [Feature views without entities](feature-view.md#feature-views-without-entities), then those features can be added here without additional entity values in the `entity_rows` parameter.
-{% endhint %}
-
-## Event timestamp
-
-The timestamp on which an event occurred, as found in a feature view's data source. The event timestamp describes the event time at which a feature was observed or generated.
-
-Event timestamps are used during point-in-time joins to ensure that the latest feature values are joined from feature views onto entity rows. Event timestamps are also used to ensure that old feature values aren't served to models during online serving.
-
-## Dataset
-
-A dataset is a collection of rows that is produced by a historical retrieval from Feast in order to train a model. A dataset is produced by a join from one or more feature views onto an entity dataframe. Therefore, a dataset may consist of features from multiple feature views.
-
-**Dataset vs Feature View:** Feature views contain the schema of data and a reference to where data can be found (through its data source). Datasets are the actual data manifestation of querying those data sources.
-
-**Dataset vs Data Source:** Datasets are the output of historical retrieval, whereas data sources are the inputs. One or more data sources can be used in the creation of a dataset.
diff --git a/docs/getting-started/feast-workshop.md b/docs/getting-started/feast-workshop.md
deleted file mode 100644
index 0d648452223..00000000000
--- a/docs/getting-started/feast-workshop.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Learning by example
-
-This workshop aims to teach users about Feast.
-
-We explain concepts & best practices by example, and also showcase how to address common use cases.
-
-### Pre-requisites
-
-This workshop assumes you have the following installed:
-
-* A local development environment that supports running Jupyter notebooks (e.g. VSCode with Jupyter plugin)
-* Python 3.7+
-* Java 11 (for Spark, e.g. `brew install java11`)
-* pip
-* Docker & Docker Compose (e.g. `brew install docker docker-compose`)
-* Terraform ([docs](https://learn.hashicorp.com/tutorials/terraform/install-cli#install-terraform))
-* AWS CLI
-* An AWS account setup with credentials via `aws configure` (e.g see [AWS credentials quickstart](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds))
-
-Since we'll be learning how to leverage Feast in CI/CD, you'll also need to fork this workshop repository.
-
-#### **Caveats**
-
-* M1 Macbook development is untested with this flow. See also [How to run / develop for Feast on M1 Macs](https://github.com/feast-dev/feast/issues/2105).
-* Windows development has only been tested with WSL. You will need to follow this [guide](https://docs.docker.com/desktop/windows/wsl/) to have Docker play nicely.
-
-### Modules
-
-_See also:_ [_Feast quickstart_](https://docs.feast.dev/getting-started/quickstart)_,_ [_Feast x Great Expectations tutorial_](https://docs.feast.dev/tutorials/validating-historical-features)
-
-These are meant mostly to be done in order, with examples building on previous concepts.
-
-See [https://github.com/feast-dev/feast-workshop](https://github.com/feast-dev/feast-workshop)
-
-| Time (min) | Description | Module |
-| :--------: | ----------------------------------------------------------------------- | -------- |
-| 30-45 | Setting up Feast projects & CI/CD + powering batch predictions | Module 0 |
-| 15-20 | Streaming ingestion & online feature retrieval with Kafka, Spark, Redis | Module 1 |
-| 10-15 | Real-time feature engineering with on demand transformations | Module 2 |
-| TBD | Feature server deployment (embed, as a service, AWS Lambda) | TBD |
-| TBD | Versioning features / models in Feast | TBD |
-| TBD | Data quality monitoring in Feast | TBD |
-| TBD | Batch transformations | TBD |
-| TBD | Stream transformations | TBD |
diff --git a/docs/how-to-guides/customizing-feast/adding-support-for-a-new-online-store.md b/docs/how-to-guides/customizing-feast/adding-support-for-a-new-online-store.md
index 52f0897138d..ab88ebaa203 100644
--- a/docs/how-to-guides/customizing-feast/adding-support-for-a-new-online-store.md
+++ b/docs/how-to-guides/customizing-feast/adding-support-for-a-new-online-store.md
@@ -154,7 +154,10 @@ def online_write_batch(
project = config.project
for entity_key, values, timestamp, created_ts in data:
- entity_key_bin = serialize_entity_key(entity_key).hex()
+ entity_key_bin = serialize_entity_key(
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
+ ).hex()
timestamp = _to_naive_utc(timestamp)
if created_ts is not None:
created_ts = _to_naive_utc(created_ts)
@@ -184,7 +187,10 @@ def online_read(
project = config.project
for entity_key in entity_keys:
- entity_key_bin = serialize_entity_key(entity_key).hex()
+ entity_key_bin = serialize_entity_key(
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
+ ).hex()
print(f"entity_key_bin: {entity_key_bin}")
cur.execute(
@@ -208,18 +214,6 @@ def online_read(
```
{% endcode %}
-### 1.3 Type Mapping
-
-Most online stores will have to perform some custom mapping of online store datatypes to feast value types.
-
-* The function to implement here are `source_datatype_to_feast_value_type` and `get_column_names_and_types` in your `DataSource` class.
-* `source_datatype_to_feast_value_type` is used to convert your DataSource's datatypes to feast value types.
-* `get_column_names_and_types` retrieves the column names and corresponding datasource types.
-
-Add any helper functions for type conversion to `sdk/python/feast/type_map.py`.
-
-* Be sure to implement correct type mapping so that Feast can process your feature columns without casting incorrectly that can potentially cause loss of information or incorrect data.
-
## 2. Defining an OnlineStoreConfig class
Additional configuration may be needed to allow the OnlineStore to talk to the backing store. For example, MySQL may need configuration information like the host at which the MySQL instance is running, credentials for connecting to the database, etc.
diff --git a/docs/how-to-guides/feast-snowflake-gcp-aws/README.md b/docs/how-to-guides/feast-snowflake-gcp-aws/README.md
index 753650080b0..0f6d099349c 100644
--- a/docs/how-to-guides/feast-snowflake-gcp-aws/README.md
+++ b/docs/how-to-guides/feast-snowflake-gcp-aws/README.md
@@ -12,3 +12,7 @@
{% page-ref page="read-features-from-the-online-store.md" %}
+{% page-ref page="../scaling-feast.md" %}
+
+{% page-ref page="../structuring-repos.md" %}
+
diff --git a/docs/how-to-guides/production-spark-bytewax.png b/docs/how-to-guides/production-spark-bytewax.png
new file mode 100644
index 00000000000..ce3f7017cdf
Binary files /dev/null and b/docs/how-to-guides/production-spark-bytewax.png differ
diff --git a/docs/how-to-guides/running-feast-in-production.md b/docs/how-to-guides/running-feast-in-production.md
index 61b7b1fe40a..ef903b68c4b 100644
--- a/docs/how-to-guides/running-feast-in-production.md
+++ b/docs/how-to-guides/running-feast-in-production.md
@@ -1,40 +1,47 @@
-# Running Feast in production
+# Running Feast in production (e.g. on Kubernetes)
## Overview
-After learning about Feast concepts and playing with Feast locally, you're now ready to use Feast in production. This guide aims to help with the transition from a sandbox project to production-grade deployment in the cloud or on-premise.
+After learning about Feast concepts and playing with Feast locally, you're now ready to use Feast in production. This guide aims to help with the transition from a sandbox project to production-grade deployment in the cloud or on-premise (e.g. on Kubernetes).
-Overview of typical production configuration is given below:
+A typical production architecture looks like:

{% hint style="success" %}
-**Important note:** Feast is highly customizable and modular. Most Feast blocks are loosely connected and can be used independently. Hence, you are free to build your own production configuration.
+**Important note:** Feast is highly customizable and modular.
+
+Most Feast blocks are loosely connected and can be used independently. Hence, you are free to build your own production configuration.
For example, you might not have a stream source and, thus, no need to write features in real-time to an online store. Or you might not need to retrieve online features. Feast also often provides multiple options to achieve the same goal. We discuss tradeoffs below.
+
+Additionally, please check the how-to guide for some specific recommendations on [how to scale Feast](./scaling-feast.md).
{% endhint %}
In this guide we will show you how to:
1. Deploy your feature store and keep your infrastructure in sync with your feature repository
-2. Keep the data in your online store up to date
+2. Keep the data in your online store up to date (from batch and stream sources)
3. Use Feast for model training and serving
-4. Ingest features from a stream source
-5. Monitor your production deployment
## 1. Automatically deploying changes to your feature definitions
-### Setting up a feature repository
+### 1.1 Setting up a feature repository
The first step to setting up a deployment of Feast is to create a Git repository that contains your feature definitions. The recommended way to version and track your feature definitions is by committing them to a repository and tracking changes through commits. If you recall, running `feast apply` commits feature definitions to a **registry**, which users can then read elsewhere.
-### Setting up CI/CD to automatically update the registry
+### 1.2 Setting up a database-backed registry
+
+Out of the box, Feast serializes all of its state into a file-based registry. When running Feast in production, we recommend using the more scalable SQL-based registry that is backed by a database. Details are available [here](./scaling-feast.md#scaling-feast-registry).
+
+### 1.3 Setting up CI/CD to automatically update the registry
We recommend typically setting up CI/CD to automatically run `feast plan` and `feast apply` when pull requests are opened / merged.
-### Setting up multiple environments
+### 1.4 Setting up multiple environments
A common scenario when using Feast in production is to want to test changes to Feast object definitions. For this, we recommend setting up a _staging_ environment for your offline and online stores, which mirrors _production_ (with potentially a smaller data set).
+
Having this separate environment allows users to test changes by first applying them to staging, and then promoting the changes to production after verifying the changes on staging.
Different options are presented in the [how-to guide](structuring-repos.md).
@@ -43,101 +50,138 @@ Different options are presented in the [how-to guide](structuring-repos.md).
To keep your online store up to date, you need to run a job that loads feature data from your feature view sources into your online store. In Feast, this loading operation is called materialization.
-### 2.1. Manual materializations
-
-The simplest way to schedule materialization is to run an **incremental** materialization using the Feast CLI:
+### 2.1 Scalable Materialization
-```
-feast materialize-incremental 2022-01-01T00:00:00
-```
+Out of the box, Feast's materialization process uses an in-process materialization engine. This engine loads all the data being materialized into memory from the offline store, and writes it into the online store.
-The above command will load all feature values from all feature view sources into the online store up to the time `2022-01-01T00:00:00`.
+This approach may not scale to large amounts of data, which users of Feast may be dealing with in production.
+In this case, we recommend using one of the more [scalable materialization engines](./scaling-feast.md#scaling-materialization), such as the [Bytewax Materialization Engine](../reference/batch-materialization/bytewax.md), or the [Snowflake Materialization Engine](../reference/batch-materialization/snowflake.md).
+Users may also need to [write a custom materialization engine](../how-to-guides/customizing-feast/creating-a-custom-materialization-engine.md) to work on their existing infrastructure.
-A timestamp is required to set the end date for materialization. If your source is fully up to date then the end date would be the current time. However, if you are querying a source where data is not yet available, then you do not want to set the timestamp to the current time. You would want to use a timestamp that ends at a date for which data is available. The next time `materialize-incremental` is run, Feast will load data that starts from the previous end date, so it is important to ensure that the materialization interval does not overlap with time periods for which data has not been made available. This is commonly the case when your source is an ETL pipeline that is scheduled on a daily basis.
+The Bytewax materialization engine can run materialization on an existing Kubernetes cluster. An example configuration of this in a `feature_store.yaml` is as follows:
-An alternative approach to incremental materialization (where Feast tracks the intervals of data that need to be ingested), is to call Feast directly from your scheduler like Airflow. In this case, Airflow is the system that tracks the intervals that have been ingested.
-
-```
-feast materialize -v driver_hourly_stats 2020-01-01T00:00:00 2020-01-02T00:00:00
+```yaml
+batch_engine:
+ type: bytewax
+ namespace: bytewax
+ image: bytewax/bytewax-feast:latest
+ env:
+ - name: AWS_ACCESS_KEY_ID
+ valueFrom:
+ secretKeyRef:
+ name: aws-credentials
+ key: aws-access-key-id
+ - name: AWS_SECRET_ACCESS_KEY
+ valueFrom:
+ secretKeyRef:
+ name: aws-credentials
+ key: aws-secret-access-key
```
-In the above example we are materializing the source data from the `driver_hourly_stats` feature view over a day. This command can be scheduled as the final operation in your Airflow ETL, which runs after you have computed your features and stored them in the source location. Feast will then load your feature data into your online store.
+### 2.2 Scheduled materialization
+
+> See also [data ingestion](../getting-started/concepts/data-ingestion.md#batch-data-ingestion) for code snippets
-The timestamps above should match the interval of data that has been computed by the data transformation system.
+It is up to you to orchestrate and schedule runs of materialization.
-### 2.2. Automate periodic materializations
+Feast keeps the history of materialization in its registry so that the choice could be as simple as a [unix cron util](https://en.wikipedia.org/wiki/Cron). Cron util should be sufficient when you have just a few materialization jobs (it's usually one materialization job per feature view) triggered infrequently.
-It is up to you which orchestration/scheduler to use to periodically run `$ feast materialize`. Feast keeps the history of materialization in its registry so that the choice could be as simple as a [unix cron util](https://en.wikipedia.org/wiki/Cron). Cron util should be sufficient when you have just a few materialization jobs (it's usually one materialization job per feature view) triggered infrequently. However, the amount of work can quickly outgrow the resources of a single machine. That happens because the materialization job needs to repackage all rows before writing them to an online store. That leads to high utilization of CPU and memory. In this case, you might want to use a job orchestrator to run multiple jobs in parallel using several workers. Kubernetes Jobs or Airflow are good choices for more comprehensive job orchestration.
+However, the amount of work can quickly outgrow the resources of a single machine. That happens because the materialization job needs to repackage all rows before writing them to an online store. That leads to high utilization of CPU and memory. In this case, you might want to use a job orchestrator to run multiple jobs in parallel using several workers. Kubernetes Jobs or Airflow are good choices for more comprehensive job orchestration.
-If you are using Airflow as a scheduler, Feast can be invoked through the [BashOperator](https://airflow.apache.org/docs/apache-airflow/stable/howto/operator/bash.html) after the [Python SDK](https://pypi.org/project/feast/) has been installed into a virtual environment and your feature repo has been synced:
+If you are using Airflow as a scheduler, Feast can be invoked through a [PythonOperator](https://airflow.apache.org/docs/apache-airflow/stable/howto/operator/python.html) after the [Python SDK](https://pypi.org/project/feast/) has been installed into a virtual environment and your feature repo has been synced:
```python
-materialize = BashOperator(
- task_id='materialize',
- bash_command=f'feast materialize-incremental {datetime.datetime.now().replace(microsecond=0).isoformat()}',
+import datetime
+from airflow.operators.python_operator import PythonOperator
+from feast import RepoConfig, FeatureStore
+from feast.infra.online_stores.dynamodb import DynamoDBOnlineStoreConfig
+from feast.repo_config import RegistryConfig
+
+# Define Python callable
+def materialize():
+ repo_config = RepoConfig(
+ registry=RegistryConfig(path="s3://[YOUR BUCKET]/registry.pb"),
+ project="feast_demo_aws",
+ provider="aws",
+ offline_store="file",
+ online_store=DynamoDBOnlineStoreConfig(region="us-west-2")
+ )
+ store = FeatureStore(config=repo_config)
+ # Option 1: materialize just one feature view
+ # store.materialize_incremental(datetime.datetime.now(), feature_views=["my_fv_name"])
+ # Option 2: materialize all feature views incrementally
+ store.materialize_incremental(datetime.datetime.now())
+
+# Use Airflow PythonOperator
+materialize_python = PythonOperator(
+ task_id='materialize_python',
+ python_callable=materialize,
)
```
{% hint style="success" %}
-Important note: Airflow worker must have read and write permissions to the registry file on GS / S3 since it pulls configuration and updates materialization history.
+Important note: Airflow worker must have read and write permissions to the registry file on GCS / S3 since it pulls configuration and updates materialization history.
{% endhint %}
-## 3. How to use Feast for model training
+### 2.3 Stream feature ingestion
+See more details at [data ingestion](../getting-started/concepts/data-ingestion.md), which shows how to ingest streaming features or 3rd party feature data via a push API.
-After we've defined our features and data sources in the repository, we can generate training datasets.
+This supports pushing feature values into Feast to both online or offline stores.
-The first thing we need to do in our training code is to create a `FeatureStore` object with a path to the registry.
-One way to ensure your production clients have access to the feature store is to provide a copy of the `feature_store.yaml` to those pipelines. This `feature_store.yaml` file will have a reference to the feature store registry, which allows clients to retrieve features from offline or online stores.
+## 3. How to use Feast for model training
-```python
-fs = FeatureStore(repo_path="production/")
-```
+### 3.1. Generating training data
+> For more details, see [feature retrieval](../getting-started/concepts/feature-retrieval.md#retrieving-historical-features-for-training-data-or-batch-scoring)
-Then, training data can be retrieved as follows:
+After we've defined our features and data sources in the repository, we can generate training datasets. We highly recommend you use a `FeatureService` to version the features that go into a specific model version.
-```python
-feature_refs = [
- 'driver_hourly_stats:conv_rate',
- 'driver_hourly_stats:acc_rate',
- 'driver_hourly_stats:avg_daily_trips'
-]
-
-training_df = fs.get_historical_features(
- entity_df=entity_df,
- features=feature_refs,
-).to_df()
+1. The first thing we need to do in our training code is to create a `FeatureStore` object with a path to the registry.
+ - One way to ensure your production clients have access to the feature store is to provide a copy of the `feature_store.yaml` to those pipelines. This `feature_store.yaml` file will have a reference to the feature store registry, which allows clients to retrieve features from offline or online stores.
-model = ml.fit(training_df)
-```
+ ```python
+ from feast import FeatureStore
-The most common way to productionize ML models is by storing and versioning models in a "model store", and then deploying these models into production. When using Feast, it is recommended that the list of feature references also be saved alongside the model. This ensures that models and the features they are trained on are paired together when being shipped into production:
+ fs = FeatureStore(repo_path="production/")
+ ```
+2. Then, you need to generate an **entity dataframe**. You have two options
+ - Create an entity dataframe manually and pass it in
+ - Use a SQL query to dynamically generate lists of entities (e.g. all entities within a time range) and timestamps to pass into Feast
+3. Then, training data can be retrieved as follows:
-```python
-# Save model
-model.save('my_model.bin')
+ ```python
+ training_retrieval_job = fs.get_historical_features(
+ entity_df=entity_df_or_sql_string,
+ features=fs.get_feature_service("driver_activity_v1"),
+ )
-# Save features
-open('feature_refs.json', 'w') as f:
- json.dump(feature_refs, f)
-```
+ # Option 1: In memory model training
+ model = ml.fit(training_retrieval_job.to_df())
-To test your model locally, you can simply create a `FeatureStore` object, fetch online features, and then make a prediction:
+ # Option 2: Unloading to blob storage. Further post-processing can occur before kicking off distributed training.
+ training_retrieval_job.to_remote_storage()
+ ```
+
+### 3.2 Versioning features that power ML models
+The most common way to productionize ML models is by storing and versioning models in a "model store", and then deploying these models into production. When using Feast, it is recommended that the feature service name and the model versions have some established convention.
+
+For example, in MLflow:
```python
-# Load model
-model = ml.load('my_model.bin')
+import mlflow.pyfunc
-# Load feature references
-with open('feature_refs.json', 'r') as f:
- feature_refs = json.load(f)
+# Load model from MLflow
+model_name = "my-model"
+model_version = 1
+model = mlflow.pyfunc.load_model(
+ model_uri=f"models:/{model_name}/{model_version}"
+)
-# Create feature store object
fs = FeatureStore(repo_path="production/")
-# Read online features
+# Read online features using the same model name and model version
feature_vector = fs.get_online_features(
- features=feature_refs,
+ features=fs.get_feature_service(f"{model_name}_v{model_version}"),
entity_rows=[{"driver_id": 1001}]
).to_dict()
@@ -151,7 +195,7 @@ It is important to note that both the training pipeline and model serving servic
## 4. Retrieving online features for prediction
-Once you have successfully loaded (or in Feast terminology materialized) your data from batch sources into the online store, you can start consuming features for model inference. There are three approaches for that purpose sorted from the most simple one (in an operational sense) to the most performant (benchmarks to be published soon):
+Once you have successfully loaded data from batch / streaming sources into the online store, you can start consuming features for model inference.
### 4.1. Use the Python SDK within an existing Python service
@@ -174,18 +218,11 @@ feature_vector = fs.get_online_features(
).to_dict()
```
-### 4.2. Consume features via HTTP API from Serverless Feature Server
-
-If you don't want to add the Feast Python SDK as a dependency, or your feature retrieval service is written in a non-Python language, Feast can deploy a simple feature server on serverless infrastructure (eg, AWS Lambda, Google Cloud Run) for you. This service will provide an HTTP API with JSON I/O, which can be easily used with any programming language.
+### 4.2. Deploy Feast feature servers on Kubernetes
-[Read more about this feature](../reference/feature-servers/alpha-aws-lambda-feature-server.md)
-
-### 4.3. Go feature server deployed on Kubernetes
-
-For users with very latency-sensitive and high QPS use-cases, Feast offers a high-performance [Go feature server](../reference/feature-servers/go-feature-server.md). It can use either HTTP or gRPC.
-
-The Go feature server can be deployed to a Kubernetes cluster via Helm charts in a few simple steps:
+To deploy a Feast feature server on Kubernetes, you can use the included [helm chart + tutorial](https://github.com/feast-dev/feast/tree/master/infra/charts/feast-feature-server) (which also has detailed instructions and an example tutorial).
+**Basic steps**
1. Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and [helm 3](https://helm.sh/)
2. Add the Feast Helm repository and download the latest charts:
@@ -194,51 +231,16 @@ helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com
helm repo update
```
-1. Run Helm Install
+3. Run Helm Install
```
helm install feast-release feast-charts/feast-feature-server \
- --set global.registry.path=s3://feast/registries/prod \
- --set global.project=
-```
-
-This chart will deploy a single service. The service must have read access to the registry file on cloud storage. It will keep a copy of the registry in their memory and periodically refresh it, so expect some delays in update propagation in exchange for better performance. In order for the Go feature server to be enabled, you should set `go_feature_serving: True` in the `feature_store.yaml`.
-
-## 5. Ingesting features from a stream source
-
-Recently Feast added functionality for [stream ingestion](../reference/data-sources/push.md). Please note that this is still in an early phase and new incompatible changes may be introduced.
-
-### 5.1. Using Python SDK in your Apache Spark / Beam pipeline
-
-The default option to write features from a stream is to add the Python SDK into your existing PySpark / Beam pipeline. Feast SDK provides writer implementation that can be called from `foreachBatch` stream writer in PySpark like this:
-
-```python
-from feast import FeatureStore
-
-store = FeatureStore(...)
-
-def feast_writer(spark_df):
- pandas_df = spark_df.to_pandas()
- store.push("driver_hourly_stats", pandas_df)
-
-streamingDF.writeStream.foreachBatch(feast_writer).start()
+ --set feature_store_yaml_base64=$(base64 feature_store.yaml)
```
-### 5.2. Push Service (Alpha)
+This will deploy a single service. The service must have read access to the registry file on cloud storage. It will keep a copy of the registry in their memory and periodically refresh it, so expect some delays in update propagation in exchange for better performance.
-Alternatively, if you want to ingest features directly from a broker (eg, Kafka or Kinesis), you can use the "push service", which will write to an online store and/or offline store. This service will expose an HTTP API or when deployed on Serverless platforms like AWS Lambda or Google Cloud Run, this service can be directly connected to Kinesis or PubSub.
-
-If you are using Kafka, [HTTP Sink](https://docs.confluent.io/kafka-connect-http/current/overview.html) could be utilized as a middleware. In this case, the "push service" can be deployed on Kubernetes or as a Serverless function.
-
-## 6. Monitoring
-
-Feast services can report their metrics to a StatsD-compatible collector. To activate this function, you'll need to provide a StatsD IP address and a port when deploying the helm chart (in future, this will be added to `feature_store.yaml`).
-
-We use an [InfluxDB-style extension](https://github.com/prometheus/statsd\_exporter#tagging-extensions) for StatsD format to be able to send tags along with metrics. Keep that in mind while selecting the collector ([telegraph](https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd) will work for sure).
-
-We chose StatsD since it's a de-facto standard with various implementations (eg, [1](https://github.com/prometheus/statsd\_exporter), [2](https://github.com/influxdata/telegraf/blob/master/plugins/inputs/statsd/README.md)) and metrics can be easily exported to Prometheus, InfluxDB, AWS CloudWatch, etc.
-
-## 7. Using environment variables in your yaml configuration
+## 5. Using environment variables in your yaml configuration
You might want to dynamically set parts of your configuration from your environment. For instance to deploy Feast to production and development with the same configuration, but a different server. Or to inject secrets without exposing them in your git repo. To do this, it is possible to use the `${ENV_VAR}` syntax in your `feature_store.yaml` file. For instance:
@@ -266,32 +268,17 @@ online_store:
## Summary
-Summarizing it all together we want to show several options of architecture that will be most frequently used in production:
+In summary, the overall architecture in production may look like:
-### Option #1 (currently preferred)
+* Feast SDK is being triggered by CI (eg, Github Actions). It applies the latest changes from the feature repo to the Feast database-backed registry
+* Data ingestion
+ * **Batch data**: Airflow manages materialization jobs to ingest batch data from DWH to the online store periodically. When working with large datasets to materialize, we recommend using a batch materialization engine
+ * If your offline and online workloads are in Snowflake, the Snowflake materialization engine is likely the best option.
+ * If your offline and online workloads are not using Snowflake, but using Kubernetes is an option, the Bytewax materialization engine is likely the best option.
+ * If none of these engines suite your needs, you may continue using the in-process engine, or write a custom engine (e.g with Spark or Ray).
+ * **Stream data**: The Feast Push API is used within existing Spark / Beam pipelines to push feature values to offline / online stores
-* Feast SDK is being triggered by CI (eg, Github Actions). It applies the latest changes from the feature repo to the Feast registry
-* Airflow manages materialization jobs to ingest data from DWH to the online store periodically
-* For the stream ingestion Feast Python SDK is used in the existing Spark / Beam pipeline
-* Online features are served via either a Python feature server or a high performance Go feature server
- * The Go feature server can be deployed on a Kubernetes cluster (via Helm charts)
+* Online features are served via the Python feature server over HTTP, or consumed using the Feast Python SDK.
* Feast Python SDK is called locally to generate a training dataset
-
-
-### Option #2 _(still in development)_
-
-Same as Option #1, except:
-
-* Push service is deployed as AWS Lambda / Google Cloud Run and is configured as a sink for Kinesis or PubSub to ingest features directly from a stream broker. Lambda / Cloud Run is being managed by Feast SDK (from CI environment)
-* Materialization jobs are managed inside Kubernetes via Kubernetes Job (currently not managed by Helm)
-
-
-
-### Option #3 _(still in development)_
-
-Same as Option #2, except:
-
-* Push service is deployed on Kubernetes cluster and exposes an HTTP API that can be used as a sink for Kafka (via kafka-http connector) or accessed directly.
-
-
+
diff --git a/docs/project/compatibility.md b/docs/project/compatibility.md
new file mode 100644
index 00000000000..9db4d3a14a4
--- /dev/null
+++ b/docs/project/compatibility.md
@@ -0,0 +1,13 @@
+# API Compatibility in Feast
+
+Feast follows [semantic versions](./versioning-policy.md). Being pre-1.0, Feast is still considered in active initial development as per the [SemVer spec](https://semver.org/#faq).
+
+That being said, Feast takes backwards compatibility seriously, to ensure that introducing new functionality across minor versions does not break current users.
+
+When possible, API changes should always be made in a backwards compatible way.
+If this is not possible, the maintainers introduce new APIs alongside existing, now-deprecated APIs, with the intention of supporting the existing APIs for at least 3 minor versions, before deprecating and removing them.
+In some cases, the deprecated APIs may be supported for longer than 3 minor versions, if necessary to give users a longer time for migrations.
+
+When deprecating existing APIs, deprecation warnings should be introduced early, with the expected version at which the deprecated API would be removed.
+
+At this point, core functionality in Feast is considered "stable" and ready for usage. However, there are still some components that are considered "Alpha". Please check the [roadmap](../roadmap.md) for a full list of all capabilities and their status.
diff --git a/docs/project/contributing.md b/docs/project/contributing.md
index 933237f204f..9a3e3e1a3ea 100644
--- a/docs/project/contributing.md
+++ b/docs/project/contributing.md
@@ -1,11 +1,36 @@
# Contribution process
-We use [RFCs](https://en.wikipedia.org/wiki/Request_for_Comments) and [GitHub issues](https://github.com/feast-dev/feast/issues) to communicate development ideas. The simplest way to contribute to Feast is to leave comments in our [RFCs](https://drive.google.com/drive/u/0/folders/1Lj1nIeRB868oZvKTPLYqAvKQ4O0BksjY) in the [Feast Google Drive](https://drive.google.com/drive/u/0/folders/0AAe8j7ZK3sxSUk9PVA) or our GitHub issues. You will need to join our [Google Group](../community.md) in order to get access.
+## Getting started
+After familiarizing yourself with the documentation, the simplest way to get started is to:
+1. Join the `#feast-development` [Slack channel](https://tectonfeast.slack.com/archives/C01NTDB88QK), where contributors discuss ideas and PRs
+2. Join our Google Groups in order to get access to RFC folders + get invites to community calls. See [community](../community.md) for more details.
+3. Setup your developer environment by following [development guide](development-guide.md).
+4. Either create a [GitHub issue](https://github.com/feast-dev/feast/issues) or make a draft PR (following [development guide](development-guide.md)) to get the ball rolling!
-We follow a process of [lazy consensus](http://community.apache.org/committers/lazyConsensus.html). If you believe you know what the project needs then just start development. If you are unsure about which direction to take with development then please communicate your ideas through a GitHub issue or through our [Slack Channel](../community.md) before starting development.
+## Decision making process
+*See [governance](../../community/governance.md) for more details here*
+
+We follow a process of [lazy consensus](http://community.apache.org/committers/lazyConsensus.html). If you believe you know what the project needs then just start development. As long as there is no active opposition and the PR has been approved by maintainers or CODEOWNERS, contributions will be merged.
+
+We use our `#feast-development` [Slack channel](https://tectonfeast.slack.com/archives/C01NTDB88QK), [GitHub issues](https://github.com/feast-dev/feast/issues), and [GitHub pull requests](https://github.com/feast-dev/feast/pulls) to communicate development ideas.
+
+The general decision making workflow is as follows:
+
+
+
+> **Note**: There may not always a corresponding CODEOWNER for the affected code, in which case the responsibility falls on other maintainers or contributors with write access to review + merge the PR
+
+## Pull requests
Please [submit a PR](https://github.com/feast-dev/feast/pulls) to the master branch of the Feast repository once you are ready to submit your contribution. Code submission to Feast \(including submission from project maintainers\) require review and approval from maintainers or code owners.
PRs that are submitted by the general public need to be identified as `ok-to-test`. Once enabled, [Prow](https://github.com/kubernetes/test-infra/tree/master/prow) will run a range of tests to verify the submission, after which community members will help to review the pull request.
-See also [Community](../community.md) for other ways to get involved with the community (e.g. joining community calls)
\ No newline at end of file
+See also [Making a pull request](development-guide.md#making-a-pull-request) for other guidelines on making pull requests in Feast.
+
+## Resources
+
+- [Community](../community.md) for other ways to get involved with the community (e.g. joining community calls)
+- [Development guide](development-guide.md) for tips on how to contribute
+- [Feast GitHub issues](https://github.com/feast-dev/feast/issues) to see what others are working on
+- [Feast RFCs](https://drive.google.com/drive/u/0/folders/1msUsgmDbVBaysmhBlg9lklYLLTMk4bC3) for a folder of previously written RFCs
\ No newline at end of file
diff --git a/docs/project/development-guide.md b/docs/project/development-guide.md
index 5aae0628f67..39c2088e854 100644
--- a/docs/project/development-guide.md
+++ b/docs/project/development-guide.md
@@ -1,72 +1,444 @@
-# Development guide
+# Development Guide: Main Feast Repository
+## Table of Contents
+
+- [Development Guide: Main Feast Repository](#development-guide-main-feast-repository)
+ - [Table of Contents](#table-of-contents)
+ - [Overview](#overview)
+ - [Compatibility](#compatibility)
+ - [Community](#community)
+ - [Making a pull request](#making-a-pull-request)
+ - [Pull request checklist](#pull-request-checklist)
+ - [Good practices to keep in mind](#good-practices-to-keep-in-mind)
+ - [Forking the repo](#forking-the-repo)
+ - [Pre-commit Hooks](#pre-commit-hooks)
+ - [Signing off commits](#signing-off-commits)
+ - [Incorporating upstream changes from master](#incorporating-upstream-changes-from-master)
+ - [Feast Python SDK / CLI](#feast-python-sdk--cli)
+ - [Environment Setup](#environment-setup)
+ - [Code Style & Linting](#code-style--linting)
+ - [Unit Tests](#unit-tests)
+ - [Integration Tests](#integration-tests)
+ - [Local integration tests](#local-integration-tests)
+ - [(Advanced) Full integration tests](#advanced-full-integration-tests)
+ - [(Advanced) Running specific provider tests or running your test against specific online or offline stores](#advanced-running-specific-provider-tests-or-running-your-test-against-specific-online-or-offline-stores)
+ - [(Experimental) Run full integration tests against containerized services](#experimental-run-full-integration-tests-against-containerized-services)
+ - [Contrib integration tests](#contrib-integration-tests)
+ - [(Contrib) Running tests for Spark offline store](#contrib-running-tests-for-spark-offline-store)
+ - [(Contrib) Running tests for Trino offline store](#contrib-running-tests-for-trino-offline-store)
+ - [(Contrib) Running tests for Postgres offline store](#contrib-running-tests-for-postgres-offline-store)
+ - [(Contrib) Running tests for Postgres online store](#contrib-running-tests-for-postgres-online-store)
+ - [(Contrib) Running tests for HBase online store](#contrib-running-tests-for-hbase-online-store)
+ - [(Experimental) Feast UI](#experimental-feast-ui)
+ - [Feast Java Serving](#feast-java-serving)
+ - [Developing the Feast Helm charts](#developing-the-feast-helm-charts)
+ - [Feast Java Feature Server Helm Chart](#feast-java-feature-server-helm-chart)
+ - [Feast Python / Go Feature Server Helm Chart](#feast-python--go-feature-server-helm-chart)
+ - [Feast Go Client](#feast-go-client)
+ - [Go Environment Setup](#go-environment-setup)
+ - [Building Go](#building-go)
+ - [Go Code Style & Linting](#go-code-style--linting)
+ - [Go Unit Tests](#go-unit-tests)
+ - [Testing with Github Actions workflows](#testing-with-github-actions-workflows)
+ - [Feast Data Storage Format](#feast-data-storage-format)
## Overview
+This guide is targeted at developers looking to contribute to Feast components in
+the main Feast repository:
+- [Feast Python SDK / CLI](#feast-python-sdk--cli)
+- [Feast Java Serving](#feast-java-serving)
+- [Feast Go Client](#feast-go-client)
-This guide is targeted at developers looking to contribute to Feast:
+Please see [this page](../reference/codebase-structure.md) for more details on the structure of the entire codebase.
-* [Project Structure](development-guide.md#repository-structure)
-* [Making a Pull Request](development-guide.md#making-a-pull-request)
-* [Feast Data Storage Format](development-guide.md#feast-data-storage-format)
-* [Feast Protobuf API](development-guide.md#feast-protobuf-api)
-* [Maintainer Guide](./maintainers.md)
+## Compatibility
-> Learn How the Feast [Contributing Process](contributing.md) works.
+The compatibility policy for Feast can be found [here](compatibility.md), and should be followed for all changes proposed, by maintainers or contributors.
-## Making a Pull Request
+## Community
+See [Contribution process](./contributing.md) and [Community](../community.md) for details on how to get more involved in the community.
-{% hint style="info" %}
-See also the CONTRIBUTING.md in the corresponding GitHub repository \(e.g. [main repo doc](https://github.com/feast-dev/feast/blob/master/CONTRIBUTING.md)\)
-{% endhint %}
+A quick few highlights:
+- [RFCs](https://drive.google.com/drive/u/0/folders/0AAe8j7ZK3sxSUk9PVA)
+- [Community Slack](https://slack.feast.dev/)
+- [Feast Dev Mailing List](https://groups.google.com/g/feast-dev)
+- [Community Calendar](https://calendar.google.com/calendar/u/0?cid=ZTFsZHVhdGM3MDU3YTJucTBwMzNqNW5rajBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ)
+ - Includes biweekly community calls at 10AM PST
-### Incorporating upstream changes from master
+## Making a pull request
+We use the convention that the assignee of a PR is the person with the next action.
-Our preference is the use of `git rebase` instead of `git merge` : `git pull -r`
+If the assignee is empty it means that no reviewer has been found yet.
+If a reviewer has been found, they should also be the assigned the PR.
+Finally, if there are comments to be addressed, the PR author should be the one assigned the PR.
-### Signing commits
+PRs that are submitted by the general public need to be identified as `ok-to-test`. Once enabled, [Prow](https://github.com/kubernetes/test-infra/tree/master/prow) will run a range of tests to verify the submission, after which community members will help to review the pull request.
-Commits have to be signed before they are allowed to be merged into the Feast codebase:
-
-```bash
-# Include -s flag to signoff
-git commit -s -m "My first commit"
-```
+### Pull request checklist
+A quick list of things to keep in mind as you're making changes:
+- As you make changes
+ - Make your changes in a [forked repo](#forking-the-repo) (instead of making a branch on the main Feast repo)
+ - [Sign your commits](#signing-off-commits) as you go (to avoid DCO checks failing)
+ - [Rebase from master](#incorporating-upstream-changes-from-master) instead of using `git pull` on your PR branch
+ - Install [pre-commit hooks](#pre-commit-hooks) to ensure all the default linters / formatters are run when you push.
+- When you make the PR
+ - Make a pull request from the forked repo you made
+ - Ensure the title of the PR matches semantic release conventions (e.g. start with `feat:` or `fix:` or `ci:` or `chore:` or `docs:`). Keep in mind that any PR with `feat:` or `fix:` will directly make it into the change log of a release, so make sure they are understandable!
+ - Ensure you add a GitHub **label** (i.e. a kind tag to the PR (e.g. `kind/bug` or `kind/housekeeping`)) or else checks will fail.
+ - Ensure you leave a release note for any user facing changes in the PR. There is a field automatically generated in the PR request. You can write `NONE` in that field if there are no user facing changes.
+ - Please run tests locally before submitting a PR (e.g. for Python, the [local integration tests](#local-integration-tests))
+ - Try to keep PRs smaller. This makes them easier to review.
### Good practices to keep in mind
-
* Fill in the description based on the default template configured when you first open the PR
* What this PR does/why we need it
* Which issue\(s\) this PR fixes
* Does this PR introduce a user-facing change
-* Include `kind` label when opening the PR
* Add `WIP:` to PR name if more work needs to be done prior to review
-* Avoid `force-pushing` as it makes reviewing difficult
-**Managing CI-test failures**
-* GitHub runner tests
- * Click `checks` tab to analyse failed tests
-* Prow tests
- * Visit [Prow status page ](http://prow.feast.ai/)to analyse failed tests
+### Forking the repo
+Fork the Feast Github repo and clone your fork locally. Then make changes to a local branch to the fork.
-## Feast Data Storage Format
+See [Creating a pull request from a fork](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
-Feast data storage contracts are documented in the following locations:
+### Pre-commit Hooks
+Setup [`pre-commit`](https://pre-commit.com/) to automatically lint and format the codebase on commit:
+1. Ensure that you have Python (3.7 and above) with `pip`, installed.
+2. Install `pre-commit` with `pip` & install pre-push hooks
+```sh
+pip install pre-commit
+pre-commit install --hook-type pre-commit --hook-type pre-push
+```
+3. On push, the pre-commit hook will run. This runs `make format` and `make lint`.
-* [Feast Offline Storage Format](https://github.com/feast-dev/feast/blob/master/docs/specs/offline_store_format.md): Used by BigQuery, Snowflake \(Future\), Redshift \(Future\).
-* [Feast Online Storage Format](https://github.com/feast-dev/feast/blob/master/docs/specs/online_store_format.md): Used by Redis, Google Datastore.
+### Signing off commits
+> :warning: Warning: using the default integrations with IDEs like VSCode or IntelliJ will not sign commits.
+> When you submit a PR, you'll have to re-sign commits to pass the DCO check.
+
+Use git signoffs to sign your commits. See
+https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification for details
+
+Then, you can sign off commits with the `-s` flag:
+```
+git commit -s -m "My first commit"
+```
+
+GPG-signing commits with `-S` is optional.
+
+### Incorporating upstream changes from master
+Our preference is the use of `git rebase [master]` instead of `git merge` : `git pull -r`.
+
+Note that this means if you are midway through working through a PR and rebase, you'll have to force push:
+`git push --force-with-lease origin [branch name]`
+
+## Feast Python SDK / CLI
+### Environment Setup
+Setting up your development environment for Feast Python SDK / CLI:
+1. Ensure that you have Docker installed in your environment. Docker is used to provision service dependencies during testing, and build images for feature servers and other components.
+ 1. Please note that we use [Docker with BuiltKit](https://docs.docker.com/develop/develop-images/build_enhancements/).
+2. Ensure that you have `make`, Python (3.8 and above) with `pip`, installed.
+3. _Recommended:_ Create a virtual environment to isolate development dependencies to be installed
+ ```sh
+ # create & activate a virtual environment
+ python -m venv venv/
+ source venv/bin/activate
+ ```
+4. Upgrade `pip` if outdated
+ ```sh
+ pip install --upgrade pip
+ ```
+
+5. (Optional): Install Node & Yarn. Then run the following to build Feast UI artifacts for use in `feast ui`
+```
+make build-ui
+```
+
+6. Install development dependencies for Feast Python SDK / CLI
+```sh
+pip install -e ".[dev]"
+```
+
+This will allow the installed feast version to automatically reflect changes to your local development version of Feast without needing to reinstall everytime you make code changes.
+
+### Code Style & Linting
+Feast Python SDK / CLI codebase:
+- Conforms to [Black code style](https://black.readthedocs.io/en/stable/the_black_code_style.html)
+- Has type annotations as enforced by `mypy`
+- Has imports sorted by `isort`
+- Is lintable by `flake8`
+
+To ensure your Python code conforms to Feast Python code standards:
+- Autoformat your code to conform to the code style:
+```sh
+make format-python
+```
+
+- Lint your Python code before submitting it for review:
+```sh
+make lint-python
+```
+
+> Setup [pre-commit hooks](#pre-commit-hooks) to automatically format and lint on commit.
+
+### Unit Tests
+Unit tests (`pytest`) for the Feast Python SDK / CLI can run as follows:
+```sh
+make test-python
+```
+
+> :warning: Local configuration can interfere with Unit tests and cause them to fail:
+> - Ensure [no AWS configuration is present](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html)
+ > and [no AWS credentials can be accessed](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials) by `boto3`
+> - Ensure Feast Python SDK / CLI is not configured with configuration overrides (ie `~/.feast/config` should be empty).
+
+### Integration Tests
+There are two sets of tests you can run:
+1. Local integration tests (for faster development, tests file offline store & key online stores)
+2. Full integration tests (requires cloud environment setups)
+
+#### Local integration tests
+For this approach of running tests, you'll need to have docker set up locally: [Get Docker](https://docs.docker.com/get-docker/)
-## Feast Protobuf API
+It leverages a file based offline store to test against emulated versions of Datastore, DynamoDB, and Redis, using ephemeral containers.
-Feast Protobuf API defines the common API used by Feast's Components:
+These tests create new temporary tables / datasets locally only, and they are cleaned up. when the containers are torn down.
+
+```sh
+make test-python-integration-local
+```
+
+#### (Advanced) Full integration tests
+To test across clouds, on top of setting up Redis, you also need GCP / AWS / Snowflake setup.
+
+> Note: you can manually control what tests are run today by inspecting
+> [RepoConfiguration](https://github.com/feast-dev/feast/blob/master/sdk/python/tests/integration/feature_repos/repo_configuration.py)
+> and commenting out tests that are added to `DEFAULT_FULL_REPO_CONFIGS`
+
+**GCP**
+1. You can get free credits [here](https://cloud.google.com/free/docs/free-cloud-features#free-trial).
+2. You will need to setup a service account, enable the BigQuery API, and create a staging location for a bucket.
+
+* Setup your service account and project using steps 1-5 [here](https://codelabs.developers.google.com/codelabs/cloud-bigquery-python#0).
+ * Remember to save your `PROJECT_ID` and your `key.json`. These will be your secrets that you will need to configure in Github actions. Namely, `secrets.GCP_PROJECT_ID` and `secrets.GCP_SA_KEY`. The `GCP_SA_KEY` value is the contents of your `key.json` file.
+* Follow these [instructions](https://cloud.google.com/storage/docs/creating-buckets) in your project to create a bucket for running GCP tests and remember to save the bucket name.
+ * Make sure to add the service account email that you created in the previous step to the users that can access your bucket. Then, make sure to give the account the correct access roles, namely `objectCreator`, `objectViewer`, `objectAdmin`, and `admin`, so that your tests can use the bucket.
+
+3. Install the [Cloud SDK](https://cloud.google.com/sdk/docs/install).
+4. Login to gcloud if you haven't already:
+ ```
+ gcloud auth login
+ gcloud auth application-default login
+ ```
+- When you run `gcloud auth application-default login`, you should see some output of the form:
+ ```
+ Credentials saved to file: [$HOME/.config/gcloud/application_default_credentials.json]
+ ```
+- You should run `export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/application_default_credentials.json”` to add the application credentials to your .zshrc or .bashrc.
+5. Run `export GCLOUD_PROJECT=[your project id from step 2]` to your .zshrc or .bashrc.
+6. Running `gcloud config list` should give you something like this:
+ ```sh
+ $ gcloud config list
+ [core]
+ account = [your email]
+ disable_usage_reporting = True
+ project = [your project id]
+
+ Your active configuration is: [default]
+ ```
+7. Export GCP specific environment variables in your workflow. Namely,
+ ```sh
+ export GCS_REGION='[your gcs region e.g US]'
+ export GCS_STAGING_LOCATION='[your gcs staging location]'
+ ```
+**NOTE**: Your `GCS_STAGING_LOCATION` should be in the form `gs://` where the bucket name is from step 2.
+
+8. Once authenticated, you should be able to run the integration tests for BigQuery without any failures.
+
+**AWS**
+1. Setup AWS by creating an account, database, and cluster. You will need to enable Redshift and Dynamo.
+* You can get free credits [here](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&al[…]f.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all).
+2. To run the AWS Redshift and Dynamo integration tests you will have to export your own AWS credentials. Namely,
+
+```sh
+export AWS_REGION='[your aws region]'
+export AWS_CLUSTER_ID='[your aws cluster id]'
+export AWS_USER='[your aws user]'
+export AWS_DB='[your aws database]'
+export AWS_STAGING_LOCATION='[your s3 staging location uri]'
+export AWS_IAM_ROLE='[redshift and s3 access role]'
+export AWS_LAMBDA_ROLE='[your aws lambda execution role]'
+export AWS_REGISTRY_PATH='[your aws registry path]'
+```
-* Feast Protobuf API specifications are written in [proto3](https://developers.google.com/protocol-buffers/docs/proto3) in the Main Feast Repository.
-* Changes to the API should be proposed via a [GitHub Issue](https://github.com/feast-dev/feast/issues/new/choose) for discussion first.
+**Snowflake**
+1. See https://signup.snowflake.com/ to setup a trial.
+2. Setup your account and if you are not an `ACCOUNTADMIN` (if you created your own account, you should be), give yourself the `SYSADMIN` role.
+ ```sql
+ grant role accountadmin, sysadmin to user user2;
+ ```
+* Also remember to save your [account name](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#:~:text=organization_name%20is%20the%20name%20of,your%20account%20within%20your%20organization), username, and role.
+* Your account name can be found under
+3. Create Dashboard and add a Tile.
+4. Create a warehouse and database named `FEAST` with the schemas `OFFLINE` and `ONLINE`.
+ ```sql
+ create or replace warehouse feast_tests_wh with
+ warehouse_size='MEDIUM' --set your warehouse size to whatever your budget allows--
+ auto_suspend = 180
+ auto_resume = true
+ initially_suspended=true;
-### Generating Language Bindings
+ create or replace database FEAST;
+ use database FEAST;
+ create schema OFFLINE;
+ create schema ONLINE;
+ ```
+5. You will need to create a data unloading location(either on S3, GCP, or Azure). Detailed instructions [here](https://docs.snowflake.com/en/user-guide/data-unload-overview.html). You will need to save the storage export location and the storage export name. You will need to create a [storage integration ](https://docs.snowflake.com/en/sql-reference/sql/create-storage-integration.html) in your warehouse to make this work. Name this storage integration `FEAST_S3`.
+6. Then to run successfully, you'll need some environment variables setup:
+ ```sh
+ export SNOWFLAKE_CI_DEPLOYMENT='[your snowflake account name]'
+ export SNOWFLAKE_CI_USER='[your snowflake username]'
+ export SNOWFLAKE_CI_PASSWORD='[your snowflake pw]'
+ export SNOWFLAKE_CI_ROLE='[your CI role e.g. SYSADMIN]'
+ export SNOWFLAKE_CI_WAREHOUSE='[your warehouse]'
+ export BLOB_EXPORT_STORAGE_NAME='[your data unloading storage name]'
+ export BLOB_EXPORT_URI='[your data unloading blob uri]`
+ ```
+7. Once everything is setup, running snowflake integration tests should pass without failures.
-The language specific bindings have to be regenerated when changes are made to the Feast Protobuf API:
+Note that for Snowflake / GCP / AWS, running `make test-python-integration` will create new temporary tables / datasets in your cloud storage tables.
+
+#### (Advanced) Running specific provider tests or running your test against specific online or offline stores
+
+1. If you don't need to have your test run against all of the providers(`gcp`, `aws`, and `snowflake`) or don't need to run against all of the online stores, you can tag your test with specific providers or stores that you need(`@pytest.mark.universal_online_stores` or `@pytest.mark.universal_online_stores` with the `only` parameter). The `only` parameter selects specific offline providers and online stores that your test will test against. Example:
+
+```python
+# Only parametrizes this test with the sqlite online store
+@pytest.mark.universal_online_stores(only=["sqlite"])
+def test_feature_get_online_features_types_match():
+```
+
+2. You can also filter tests to run by using pytest's cli filtering. Instead of using the make commands to test Feast, you can filter tests by name with the `-k` parameter. The parametrized integration tests are all uniquely identified by their provider and online store so the `-k` option can select only the tests that you need to run. For example, to run only Redshift related tests, you can use the following command:
+
+```sh
+python -m pytest -n 8 --integration -k Redshift sdk/python/tests
+```
+
+#### (Experimental) Run full integration tests against containerized services
+Test across clouds requires existing accounts on GCP / AWS / Snowflake, and may incur costs when using these services.
+
+For this approach of running tests, you'll need to have docker set up locally: [Get Docker](https://docs.docker.com/get-docker/)
+
+It's possible to run some integration tests against emulated local versions of these services, using ephemeral containers.
+These tests create new temporary tables / datasets locally only, and they are cleaned up. when the containers are torn down.
+
+The services with containerized replacements currently implemented are:
+- Datastore
+- DynamoDB
+- Redis
+- Trino
+- HBase
+- Postgres
+- Cassandra
+
+You can run `make test-python-integration-container` to run tests against the containerized versions of dependencies.
+
+### Contrib integration tests
+#### (Contrib) Running tests for Spark offline store
+You can run `make test-python-universal-spark` to run all tests against the Spark offline store. (Note: you'll have to run `pip install -e ".[dev]"` first).
+
+Not all tests are passing yet
+
+#### (Contrib) Running tests for Trino offline store
+You can run `make test-python-universal-trino` to run all tests against the Trino offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
+
+#### (Contrib) Running tests for Postgres offline store
+You can run `test-python-universal-postgres-offline` to run all tests against the Postgres offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
+
+#### (Contrib) Running tests for Postgres online store
+You can run `test-python-universal-postgres-online` to run all tests against the Postgres offline store. (Note: you'll have to run `pip install -e ".[dev]"` first)
+
+#### (Contrib) Running tests for HBase online store
+TODO
+
+## (Experimental) Feast UI
+See [Feast contributing guide](ui/CONTRIBUTING.md)
+
+## Feast Java Serving
+See [Java contributing guide](java/CONTRIBUTING.md)
+
+See also development instructions related to the helm chart below at [Developing the Feast Helm charts](#developing-the-feast-helm-charts)
+
+## Developing the Feast Helm charts
+There are 3 helm charts:
+- Feast Java feature server
+- Feast Python / Go feature server
+- (deprecated) Feast Python feature server
+
+Generally, you can override the images in the helm charts with locally built Docker images, and install the local helm
+chart.
+
+All README's for helm charts are generated using [helm-docs](https://github.com/norwoodj/helm-docs). You can install it
+(e.g. with `brew install norwoodj/tap/helm-docs`) and then run `make build-helm-docs`.
+
+### Feast Java Feature Server Helm Chart
+See the Java demo example (it has development instructions too using minikube) [here](examples/java-demo/README.md)
+
+It will:
+- run `make build-java-docker-dev` to build local Java feature server binaries
+- configure the included `application-override.yaml` to override the image tag to use the locally built dev images.
+- install the local chart with `helm install feast-release ../../../infra/charts/feast --values application-override.yaml`
+
+### Feast Python / Go Feature Server Helm Chart
+See the Python demo example (it has development instructions too using minikube) [here](examples/python-helm-demo/README.md)
+
+It will:
+- run `make build-feature-server-dev` to build a local python feature server binary
+- install the local chart with `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)`
+
+## Feast Go Client
+### Go Environment Setup
+Setting up your development environment for Feast Go SDK:
+
+- Install Golang, [`protoc` with the Golang & grpc plugins](https://developers.google.com/protocol-buffers/docs/gotutorial#compiling-your-protocol-buffers)
+
+### Building Go
+Build the Feast Go Client with the `go` toolchain:
+```sh
+make compile-go-lib
+```
+
+### Go Code Style & Linting
+Feast Go Client codebase:
+- Conforms to the code style enforced by `go fmt`.
+- Is lintable by `go vet`.
+
+Autoformat your Go code to satisfy the Code Style standard:
+```sh
+go fmt
+```
+
+Lint your Go code:
+```sh
+go vet
+```
+
+> Setup [pre-commit hooks](#pre-commit-hooks) to automatically format and lint on commit.
+
+### Go Unit Tests
+Unit tests for the Feast Go Client can be run as follows:
+```sh
+make test-go
+```
+
+### Testing with Github Actions workflows
+
+Please refer to the maintainers [doc](./docs/project/maintainers.md) if you would like to locally test out the github actions workflow changes.
+This document will help you setup your fork to test the ci integration tests and other workflows without needing to make a pull request against feast-dev master.
+
+## Feast Data Storage Format
+
+Feast data storage contracts are documented in the following locations:
+
+* [Feast Offline Storage Format](https://github.com/feast-dev/feast/blob/master/docs/specs/offline_store_format.md): Used by BigQuery, Snowflake \(Future\), Redshift \(Future\).
+* [Feast Online Storage Format](https://github.com/feast-dev/feast/blob/master/docs/specs/online_store_format.md): Used by Redis, Google Datastore.
-| Repository | Language | Regenerating Language Bindings |
-| :--- | :--- | :--- |
-| [Main Feast Repository](https://github.com/feast-dev/feast) | Python | Run `make compile-protos-python` to generate bindings |
-| [Main Feast Repository](https://github.com/feast-dev/feast) | Golang | Run `make compile-protos-go` to generate bindings |
diff --git a/docs/reference/codebase-structure.md b/docs/reference/codebase-structure.md
index b75227860bf..8eb55726793 100644
--- a/docs/reference/codebase-structure.md
+++ b/docs/reference/codebase-structure.md
@@ -125,6 +125,11 @@ Within `go/`, the `internal/feast/` directory contains most of the core logic:
Feast uses [protobuf](https://github.com/protocolbuffers/protobuf) to store serialized versions of the core Feast objects.
The protobuf definitions are stored in `protos/feast`.
+The [registry](../getting-started/concepts/registry.md) consists of the serialized representations of the Feast objects.
+
+Typically, changes being made to the Feast objects require changes to their corresponding protobuf representations.
+The usual best practices for making changes to protobufs should be followed ensure backwards and forwards compatibility.
+
## Web UI
The `ui/` directory contains the Web UI.
diff --git a/docs/reference/data-sources/push.md b/docs/reference/data-sources/push.md
index 035ee583604..6dad690c16a 100644
--- a/docs/reference/data-sources/push.md
+++ b/docs/reference/data-sources/push.md
@@ -1,7 +1,5 @@
# Push source
-**Warning**: This is an _experimental_ feature. It's intended for early testing and feedback, and could change without warnings in future releases.
-
## Description
Push sources allow feature values to be pushed to the online store and offline store in real time. This allows fresh feature values to be made available to applications. Push sources supercede the
@@ -22,7 +20,7 @@ Streaming data sources are important sources of feature values. A typical setup
Feast allows users to push features previously registered in a feature view to the online store for fresher features. It also allows users to push batches of stream data to the offline store by specifying that the push be directed to the offline store. This will push the data to the offline store declared in the repository configuration used to initialize the feature store.
-## Example
+## Example (basic)
### Defining a push source
Note that the push schema needs to also include the entity.
@@ -59,3 +57,24 @@ fs.push("push_source_name", feature_data_frame, to=PushMode.ONLINE_AND_OFFLINE)
See also [Python feature server](../feature-servers/python-feature-server.md) for instructions on how to push data to a deployed feature server.
+## Example (Spark Streaming)
+
+The default option to write features from a stream is to add the Python SDK into your existing PySpark pipeline.
+
+```python
+from feast import FeatureStore
+
+store = FeatureStore(...)
+
+spark = SparkSession.builder.getOrCreate()
+
+streamingDF = spark.readStream.format(...).load()
+
+def feast_writer(spark_df):
+ pandas_df = spark_df.to_pandas()
+ store.push("driver_hourly_stats", pandas_df)
+
+streamingDF.writeStream.foreachBatch(feast_writer).start()
+```
+
+This can also be used under the hood by a contrib stream processor (see [Tutorial: Building streaming features](../../tutorials/building-streaming-features.md))
\ No newline at end of file
diff --git a/docs/reference/online-stores/README.md b/docs/reference/online-stores/README.md
index 6d616b46f25..58c8705e9ee 100644
--- a/docs/reference/online-stores/README.md
+++ b/docs/reference/online-stores/README.md
@@ -2,6 +2,10 @@
Please see [Online Store](../../getting-started/architecture-and-components/online-store.md) for an explanation of online stores.
+{% content-ref url="overview.md" %}
+[overview.md](overview.md)
+{% endcontent-ref %}
+
{% content-ref url="sqlite.md" %}
[sqlite.md](sqlite.md)
{% endcontent-ref %}
diff --git a/docs/reference/online-stores/cassandra.md b/docs/reference/online-stores/cassandra.md
index 3355c3728ce..48b7b73f439 100644
--- a/docs/reference/online-stores/cassandra.md
+++ b/docs/reference/online-stores/cassandra.md
@@ -55,7 +55,34 @@ online_store:
```
{% endcode %}
+The full set of configuration options is available in [CassandraOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.contrib.cassandra_online_store.cassandra_online_store.CassandraOnlineStoreConfig).
For a full explanation of configuration options please look at file
`sdk/python/feast/infra/online_stores/contrib/cassandra_online_store/README.md`.
-Storage specifications can be found at `docs/specs/online_store_format.md`.
\ No newline at end of file
+Storage specifications can be found at `docs/specs/online_store_format.md`.
+
+## 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 Cassandra online store.
+
+| | Cassandra |
+| :-------------------------------------------------------- | :-- |
+| 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 | yes |
+| 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/docs/reference/online-stores/datastore.md b/docs/reference/online-stores/datastore.md
index ed1425abb68..0867853f15d 100644
--- a/docs/reference/online-stores/datastore.md
+++ b/docs/reference/online-stores/datastore.md
@@ -18,4 +18,30 @@ online_store:
```
{% endcode %}
-Configuration options are available [here](https://rtd.feast.dev/en/latest/#feast.repo_config.DatastoreOnlineStoreConfig).
+The full set of configuration options is available in [DatastoreOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.datastore.DatastoreOnlineStoreConfig).
+
+## 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 Datastore online store.
+
+| | Datastore |
+| :-------------------------------------------------------- | :-- |
+| 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/docs/reference/online-stores/dynamodb.md b/docs/reference/online-stores/dynamodb.md
index f9f8b4339d7..2f94c768199 100644
--- a/docs/reference/online-stores/dynamodb.md
+++ b/docs/reference/online-stores/dynamodb.md
@@ -17,7 +17,7 @@ online_store:
```
{% endcode %}
-Configuration options are available [here](https://github.com/feast-dev/feast/blob/17bfa6118d6658d2bff53d7de8e2ccef5681714d/sdk/python/feast/infra/online_stores/dynamodb.py#L36).
+The full set of configuration options is available in [DynamoDBOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.dynamodb.DynamoDBOnlineStoreConfig).
## Permissions
@@ -53,3 +53,29 @@ The following inline policy can be used to grant Feast the necessary permissions
```
Lastly, this IAM role needs to be associated with the desired Redshift cluster. Please follow the official AWS guide for the necessary steps [here](https://docs.aws.amazon.com/redshift/latest/dg/c-getting-started-using-spectrum-add-role.html).
+
+## 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 DynamoDB online store.
+
+| | DynamoDB |
+| :-------------------------------------------------------- | :-- |
+| 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/docs/reference/online-stores/overview.md b/docs/reference/online-stores/overview.md
new file mode 100644
index 00000000000..981a1aeeed0
--- /dev/null
+++ b/docs/reference/online-stores/overview.md
@@ -0,0 +1,54 @@
+# Overview
+
+## Functionality
+
+Here are the methods exposed by the `OnlineStore` interface, along with the core functionality supported by the method:
+* `online_write_batch`: write feature values to the online store
+* `online_read`: read feature values from the online store
+* `update`: update infrastructure (e.g. tables) in the online store
+* `teardown`: teardown infrastructure (e.g. tables) in the online store
+* `plan`: generate a plan of infrastructure changes based on feature repo changes
+
+There is also additional functionality not properly captured by these interface methods:
+* support for on-demand transforms
+* readable by Python SDK
+* readable by Java
+* readable by Go
+* support for entityless feature views
+* support for concurrent writing to the same key
+* support for ttl (time to live) at retrieval
+* support for deleting expired data
+
+Finally, there are multiple data models for storing the features in the online store. For example, features could be:
+* collocated by feature view
+* collocated by feature service
+* collocated by entity key
+
+See this [issue](https://github.com/feast-dev/feast/issues/2254) for a discussion around the tradeoffs of each of these data models.
+
+## Functionality Matrix
+
+There are currently five core online store implementations: `SqliteOnlineStore`, `RedisOnlineStore`, `DynamoDBOnlineStore`, `SnowflakeOnlineStore`, and `DatastoreOnlineStore`.
+There are several additional implementations contributed by the Feast community (`PostgreSQLOnlineStore`, `HbaseOnlineStore`, and `CassandraOnlineStore`), which are not guaranteed to be stable or to match the functionality of the core implementations.
+Details for each specific online store, such as how to configure it in a `feature_store.yaml`, can be found [here](README.md).
+
+Below is a matrix indicating which online stores support what functionality.
+
+| | Sqlite | Redis | DynamoDB | Snowflake | Datastore | Postgres | Hbase | Cassandra |
+| :-------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- |
+| write feature values to the online store | yes | yes | yes | yes | yes | yes | yes | yes |
+| read feature values from the online store | yes | yes | yes | yes | yes | yes | yes | yes |
+| update infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes |
+| teardown infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes |
+| generate a plan of infrastructure changes | yes | no | no | no | no | no | no | yes |
+| support for on-demand transforms | yes | yes | yes | yes | yes | yes | yes | yes |
+| readable by Python SDK | yes | yes | yes | yes | yes | yes | yes | yes |
+| readable by Java | no | yes | no | no | no | no | no | no |
+| readable by Go | yes | yes | no | no | no | no | no | no |
+| support for entityless feature views | yes | yes | yes | yes | yes | yes | yes | yes |
+| support for concurrent writing to the same key | no | yes | no | no | no | no | no | no |
+| support for ttl (time to live) at retrieval | no | yes | no | no | no | no | no | no |
+| support for deleting expired data | no | yes | no | no | no | no | no | no |
+| collocated by feature view | yes | no | yes | yes | yes | yes | yes | yes |
+| collocated by feature service | no | no | no | no | no | no | no | no |
+| collocated by entity key | no | yes | no | no | no | no | no | no |
diff --git a/docs/reference/online-stores/postgres.md b/docs/reference/online-stores/postgres.md
index 4f51dff6172..083c0006359 100644
--- a/docs/reference/online-stores/postgres.md
+++ b/docs/reference/online-stores/postgres.md
@@ -30,4 +30,30 @@ online_store:
```
{% endcode %}
-Configuration options are available [here](https://rtd.feast.dev/en/latest/feast.infra.utils.postgres.html#module-feast.infra.utils.postgres.postgres_config).
+The full set of configuration options is available in [PostgreSQLOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.contrib.postgres.PostgreSQLOnlineStoreConfig).
+
+## 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 Postgres online store.
+
+| | Postgres |
+| :-------------------------------------------------------- | :-- |
+| 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/docs/reference/online-stores/redis.md b/docs/reference/online-stores/redis.md
index 4388ccfa0ab..80e90348c55 100644
--- a/docs/reference/online-stores/redis.md
+++ b/docs/reference/online-stores/redis.md
@@ -4,12 +4,12 @@
The [Redis](https://redis.io) online store provides support for materializing feature values into Redis.
-* Both Redis and Redis Cluster are supported
+* Both Redis and Redis Cluster are supported.
* The data model used to store feature values in Redis is described in more detail [here](../../specs/online\_store\_format.md).
## Examples
-Connecting to a single Redis instance
+Connecting to a single Redis instance:
{% code title="feature_store.yaml" %}
```yaml
@@ -22,7 +22,7 @@ online_store:
```
{% endcode %}
-Connecting to a Redis Cluster with SSL enabled and password authentication
+Connecting to a Redis Cluster with SSL enabled and password authentication:
{% code title="feature_store.yaml" %}
```yaml
@@ -36,4 +36,30 @@ online_store:
```
{% endcode %}
-Configuration options are available [here](https://rtd.feast.dev/en/master/#feast.infra.online\_stores.redis.RedisOnlineStoreConfig).
+The full set of configuration options is available in [RedisOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.redis.RedisOnlineStoreConfig).
+
+## 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 Redis online store.
+
+| | Redis |
+| :-------------------------------------------------------- | :-- |
+| 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 | yes |
+| readable by Go | yes |
+| support for entityless feature views | yes |
+| support for concurrent writing to the same key | yes |
+| support for ttl (time to live) at retrieval | yes |
+| support for deleting expired data | yes |
+| collocated by feature view | no |
+| collocated by feature service | no |
+| collocated by entity key | yes |
+
+To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix).
diff --git a/docs/reference/online-stores/snowflake.md b/docs/reference/online-stores/snowflake.md
index bf975fa7eab..d114c87144a 100644
--- a/docs/reference/online-stores/snowflake.md
+++ b/docs/reference/online-stores/snowflake.md
@@ -17,7 +17,6 @@ The data model for using a Snowflake Transient Table as an online store follows
(This model may be subject to change when Snowflake Hybrid Tables are released)
## Example
-
{% code title="feature_store.yaml" %}
```yaml
project: my_feature_repo
@@ -33,3 +32,44 @@ online_store:
database: SNOWFLAKE_DATABASE
```
{% endcode %}
+
+## Tags KWARGs Actions:
+
+"snowflake-online-store/online_path": Adding the "snowflake-online-store/online_path" key to a FeatureView tags parameter allows you to choose the online table path for the online serving table (ex. "{database}"."{schema}").
+
+{% code title="example_config.py" %}
+```python
+driver_stats_fv = FeatureView(
+ ...
+ tags={"snowflake-online-store/online_path": '"FEAST"."ONLINE"'},
+)
+```
+{% endcode %}
+
+The full set of configuration options is available in [SnowflakeOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.snowflake.SnowflakeOnlineStoreConfig).
+
+## 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 Snowflake online store.
+
+| | Snowflake |
+| :-------------------------------------------------------- | :-- |
+| 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/docs/reference/online-stores/sqlite.md b/docs/reference/online-stores/sqlite.md
index 668e6024e3c..859702c07f1 100644
--- a/docs/reference/online-stores/sqlite.md
+++ b/docs/reference/online-stores/sqlite.md
@@ -20,4 +20,30 @@ online_store:
```
{% endcode %}
-Configuration options are available [here](https://rtd.feast.dev/en/latest/#feast.repo_config.SqliteOnlineStoreConfig).
+The full set of configuration options is available in [SqliteOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.sqlite.SqliteOnlineStoreConfig).
+
+## 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 Sqlite online store.
+
+| | Sqlite |
+| :-------------------------------------------------------- | :-- |
+| 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 | yes |
+| support for on-demand transforms | yes |
+| readable by Python SDK | yes |
+| readable by Java | no |
+| readable by Go | yes |
+| 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/docs/roadmap.md b/docs/roadmap.md
index dc1d9ae1ab8..30f4317054b 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -43,15 +43,16 @@ The list below contains the functionality that contributors are planning to deve
* [ ] Batch transformation (In progress. See [RFC](https://docs.google.com/document/d/1964OkzuBljifDvkV-0fakp2uaijnVzdwWNGdz7Vz50A/edit))
* **Streaming**
* [x] [Custom streaming ingestion job support](https://docs.feast.dev/how-to-guides/creating-a-custom-provider)
- * [x] [Push based streaming data ingestion to online store (Alpha)](https://docs.feast.dev/reference/data-sources/push)
- * [x] [Push based streaming data ingestion to offline store (Alpha)](https://docs.feast.dev/reference/data-sources/push)
+ * [x] [Push based streaming data ingestion to online store](https://docs.feast.dev/reference/data-sources/push)
+ * [x] [Push based streaming data ingestion to offline store](https://docs.feast.dev/reference/data-sources/push)
* **Deployments**
* [x] AWS Lambda (Alpha release. See [RFC](https://docs.google.com/document/d/1eZWKWzfBif66LDN32IajpaG-j82LSHCCOzY6R7Ax7MI/edit))
* [x] Kubernetes (See [guide](https://docs.feast.dev/how-to-guides/running-feast-in-production#4.3.-java-based-feature-server-deployed-on-kubernetes))
* **Feature Serving**
* [x] Python Client
* [x] [Python feature server](https://docs.feast.dev/reference/feature-servers/python-feature-server)
- * [x] [Go feature server](https://docs.feast.dev/reference/feature-servers/go-feature-server)
+ * [x] [Java feature server (alpha)](https://github.com/feast-dev/feast/blob/master/infra/charts/feast/README.md)
+ * [x] [Go feature server (alpha)](https://docs.feast.dev/reference/feature-servers/go-feature-server)
* **Data Quality Management (See [RFC](https://docs.google.com/document/d/110F72d4NTv80p35wDSONxhhPBqWRwbZXG4f9mNEMd98/edit))**
* [x] Data profiling and validation (Great Expectations)
* **Feature Discovery and Governance**
@@ -60,4 +61,4 @@ The list below contains the functionality that contributors are planning to deve
* [x] Model-centric feature tracking (feature services)
* [x] Amundsen integration (see [Feast extractor](https://github.com/amundsen-io/amundsen/blob/main/databuilder/databuilder/extractor/feast_extractor.py))
* [x] DataHub integration (see [DataHub Feast docs](https://datahubproject.io/docs/generated/ingestion/sources/feast/))
- * [x] Feast Web UI (Alpha release. See [docs](https://docs.feast.dev/reference/alpha-web-ui))
+ * [x] Feast Web UI (Beta release. See [docs](https://docs.feast.dev/reference/alpha-web-ui))
diff --git a/go/embedded/online_features.go b/go/embedded/online_features.go
index 7fd34d16e40..3c470e4b244 100644
--- a/go/embedded/online_features.go
+++ b/go/embedded/online_features.go
@@ -378,12 +378,12 @@ func (s *OnlineFeatureService) StopGrpcServer() {
}
/*
- Read Record Batch from memory managed by Python caller.
- Python part uses C ABI interface to export this record into C Data Interface,
- and then it provides pointers (dataPtr & schemaPtr) to the Go part.
- Here we import this data from given pointers and wrap the underlying values
- into Go Arrow Interface (array.Record).
- See export code here https://github.com/feast-dev/feast/blob/master/sdk/python/feast/embedded_go/online_features_service.py
+Read Record Batch from memory managed by Python caller.
+Python part uses C ABI interface to export this record into C Data Interface,
+and then it provides pointers (dataPtr & schemaPtr) to the Go part.
+Here we import this data from given pointers and wrap the underlying values
+into Go Arrow Interface (array.Record).
+See export code here https://github.com/feast-dev/feast/blob/master/sdk/python/feast/embedded_go/online_features_service.py
*/
func readArrowRecord(data DataTable) (arrow.Record, error) {
return cdata.ImportCRecordBatch(
diff --git a/go/internal/feast/onlineserving/serving.go b/go/internal/feast/onlineserving/serving.go
index 3c6f5451537..dc7124fc8b8 100644
--- a/go/internal/feast/onlineserving/serving.go
+++ b/go/internal/feast/onlineserving/serving.go
@@ -21,11 +21,11 @@ import (
)
/*
- FeatureVector type represent result of retrieving single feature for multiple rows.
- It can be imagined as a column in output dataframe / table.
- It contains of feature name, list of values (across all rows),
- list of statuses and list of timestamp. All these lists have equal length.
- And this length is also equal to number of entity rows received in request.
+FeatureVector type represent result of retrieving single feature for multiple rows.
+It can be imagined as a column in output dataframe / table.
+It contains of feature name, list of values (across all rows),
+list of statuses and list of timestamp. All these lists have equal length.
+And this length is also equal to number of entity rows received in request.
*/
type FeatureVector struct {
Name string
@@ -40,11 +40,11 @@ type FeatureViewAndRefs struct {
}
/*
- We group all features from a single request by entities they attached to.
- Thus, we will be able to call online retrieval per entity and not per each feature View.
- In this struct we collect all features and views that belongs to a group.
- We also store here projected entity keys (only ones that needed to retrieve these features)
- and indexes to map result of retrieval into output response.
+We group all features from a single request by entities they attached to.
+Thus, we will be able to call online retrieval per entity and not per each feature View.
+In this struct we collect all features and views that belongs to a group.
+We also store here projected entity keys (only ones that needed to retrieve these features)
+and indexes to map result of retrieval into output response.
*/
type GroupedFeaturesPerEntitySet struct {
// A list of requested feature references of the form featureViewName:featureName that share this entity set
@@ -59,11 +59,12 @@ type GroupedFeaturesPerEntitySet struct {
}
/*
- Return
- (1) requested feature views and features grouped per View
- (2) requested on demand feature views
- existed in the registry
+Return
+ (1) requested feature views and features grouped per View
+ (2) requested on demand feature views
+
+existed in the registry
*/
func GetFeatureViewsToUseByService(
featureService *model.FeatureService,
@@ -124,10 +125,12 @@ func GetFeatureViewsToUseByService(
}
/*
- Return
- (1) requested feature views and features grouped per View
- (2) requested on demand feature views
- existed in the registry
+Return
+
+ (1) requested feature views and features grouped per View
+ (2) requested on demand feature views
+
+existed in the registry
*/
func GetFeatureViewsToUseByFeatureRefs(
features []string,
@@ -630,6 +633,9 @@ func getUniqueEntityRows(joinKeysProto []*prototypes.EntityKey) ([]*prototypes.E
}
func checkOutsideTtl(featureTimestamp *timestamppb.Timestamp, currentTimestamp *timestamppb.Timestamp, ttl *durationpb.Duration) bool {
+ if ttl.Seconds == 0 {
+ return false
+ }
return currentTimestamp.GetSeconds()-featureTimestamp.GetSeconds() > ttl.Seconds
}
diff --git a/go/internal/feast/transformation/transformation.go b/go/internal/feast/transformation/transformation.go
index 810b4e9bcbd..1cf1dd3311b 100644
--- a/go/internal/feast/transformation/transformation.go
+++ b/go/internal/feast/transformation/transformation.go
@@ -20,10 +20,10 @@ import (
)
/*
- TransformationCallback is a Python callback function's expected signature.
- The function should accept name of the on demand feature view and pointers to input & output record batches.
- Each record batch is being passed as two pointers: pointer to array (data) and pointer to schema.
- Python function is expected to return number of rows added to the output record batch.
+TransformationCallback is a Python callback function's expected signature.
+The function should accept name of the on demand feature view and pointers to input & output record batches.
+Each record batch is being passed as two pointers: pointer to array (data) and pointer to schema.
+Python function is expected to return number of rows added to the output record batch.
*/
type TransformationCallback func(ODFVName string, inputArrPtr, inputSchemaPtr, outArrPtr, outSchemaPtr uintptr, fullFeatureNames bool) int
diff --git a/infra/charts/feast-feature-server/Chart.yaml b/infra/charts/feast-feature-server/Chart.yaml
index 81970bc1a84..7095866e4f6 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.24.0
+version: 0.25.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 1ee114d9c88..9cf5c31a631 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.24.0`
+Current chart version is `0.25.0`
## Installation
@@ -11,13 +11,16 @@ helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com
helm repo update
```
-Install Feast
+Install Feast Feature Server on Kubernetes
A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example:
```
helm install feast-feature-server feast-charts/feast-feature-server --set feature_store_yaml_base64=$(base64 feature_store.yaml)
```
+## Tutorial
+See [here](https://github.com/feast-dev/feast/tree/master/examples/python-helm-demo) for a sample tutorial on testing this helm chart with a demo feature repository and a local Redis instance.
+
## Values
| Key | Type | Default | Description |
@@ -27,7 +30,7 @@ helm install feast-feature-server feast-charts/feast-feature-server --set featur
| fullnameOverride | string | `""` | |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"feastdev/feature-server"` | Docker image for Feature Server repository |
-| image.tag | string | `"0.23.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) |
+| image.tag | string | `"0.25.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/README.md.gotmpl b/infra/charts/feast-feature-server/README.md.gotmpl
index 75f28274663..fb877208e06 100644
--- a/infra/charts/feast-feature-server/README.md.gotmpl
+++ b/infra/charts/feast-feature-server/README.md.gotmpl
@@ -11,13 +11,16 @@ helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com
helm repo update
```
-Install Feast
+Install Feast Feature Server on Kubernetes
A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example:
```
helm install feast-feature-server feast-charts/feast-feature-server --set feature_store_yaml_base64=$(base64 feature_store.yaml)
```
+## Tutorial
+See [here](https://github.com/feast-dev/feast/tree/master/examples/python-helm-demo) for a sample tutorial on testing this helm chart with a demo feature repository and a local Redis instance.
+
{{ template "chart.requirementsSection" . }}
{{ template "chart.valuesSection" . }}
\ No newline at end of file
diff --git a/infra/charts/feast-feature-server/values.yaml b/infra/charts/feast-feature-server/values.yaml
index 257cf03bfa2..90954a23cb1 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.24.0
+ tag: 0.25.0
imagePullSecrets: []
nameOverride: ""
diff --git a/infra/charts/feast-python-server/Chart.yaml b/infra/charts/feast-python-server/Chart.yaml
index d2b45ee8b60..30d90876182 100644
--- a/infra/charts/feast-python-server/Chart.yaml
+++ b/infra/charts/feast-python-server/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
name: feast-python-server
description: Feast Feature Server in Python
type: application
-version: 0.24.0
+version: 0.25.0
keywords:
- machine learning
- big data
diff --git a/infra/charts/feast-python-server/README.md b/infra/charts/feast-python-server/README.md
index acdf527531b..c5d5393e29f 100644
--- a/infra/charts/feast-python-server/README.md
+++ b/infra/charts/feast-python-server/README.md
@@ -2,7 +2,7 @@
> Note: this helm chart is deprecated in favor of [feast-feature-server](../feast-feature-server/README.md)
-Current chart version is `0.24.0`
+Current chart version is `0.25.0`
## Installation
Docker repository and tag are required. Helm install example:
diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml
index a657298b52f..02f689f5419 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.24.0
+version: 0.25.0
keywords:
- machine learning
- big data
diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md
index 7a0f5f77aa1..2afdaf645cc 100644
--- a/infra/charts/feast/README.md
+++ b/infra/charts/feast/README.md
@@ -1,6 +1,6 @@
-# Feast Helm Charts
+# Feast Java Helm Charts (alpha)
-This repo contains Helm charts for Feast components that are being installed on Kubernetes:
+This repo contains Helm charts for Feast Java components that are being installed on Kubernetes:
* Feast (root chart): The complete Helm chart containing all Feast components and dependencies. Most users will use this chart, but can selectively enable/disable subcharts using the values.yaml file.
* [Feature Server](charts/feature-server): High performant JVM-based implementation of feature server.
* [Transformation Service](charts/transformation-service): Transformation server for calculating on-demand features
@@ -8,7 +8,7 @@ This repo contains Helm charts for Feast components that are being installed on
## Chart: Feast
-Feature store for machine learning Current chart version is `0.24.0`
+Feature store for machine learning Current chart version is `0.25.0`
## Installation
@@ -43,20 +43,30 @@ feature-server:
config:
host: localhost
port: 6379
+ entityKeySerializationVersion: 2
+
+global:
+ registry:
+ path: gs://[YOUR GCS BUCKET]/demo-repo/registry.db
+ cache_ttl_seconds: 60
+ project: feast_java_demo
```
-For the default configuration, please see the [Feature Server Configuration](https://github.com/feast-dev/feast-java/blob/master/serving/src/main/resources/application.yml).
+For the default configuration, please see the [Feature Server Configuration](https://github.com/feast-dev/feast/blob/master/java/serving/src/main/resources/application.yml).
For more details, please see: https://docs.feast.dev/how-to-guides/running-feast-in-production
+## Example
+See [here](https://github.com/feast-dev/feast/tree/master/examples/java-demo) for a sample tutorial on testing this helm chart with a demo feature repository and a local Redis instance.
+
## Requirements
| Repository | Name | Version |
|------------|------|---------|
| https://charts.helm.sh/stable | redis | 10.5.6 |
-| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.24.0 |
-| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.24.0 |
+| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.25.0 |
+| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.25.0 |
## Values
diff --git a/infra/charts/feast/README.md.gotmpl b/infra/charts/feast/README.md.gotmpl
index e215858fe01..7a32d22c7f1 100644
--- a/infra/charts/feast/README.md.gotmpl
+++ b/infra/charts/feast/README.md.gotmpl
@@ -1,6 +1,6 @@
-# Feast Helm Charts
+# Feast Java Helm Charts (alpha)
-This repo contains Helm charts for Feast components that are being installed on Kubernetes:
+This repo contains Helm charts for Feast Java components that are being installed on Kubernetes:
* Feast (root chart): The complete Helm chart containing all Feast components and dependencies. Most users will use this chart, but can selectively enable/disable subcharts using the values.yaml file.
* [Feature Server](charts/feature-server): High performant JVM-based implementation of feature server.
* [Transformation Service](charts/transformation-service): Transformation server for calculating on-demand features
@@ -43,13 +43,24 @@ feature-server:
config:
host: localhost
port: 6379
+ entityKeySerializationVersion: 2
+
+global:
+ registry:
+ path: gs://[YOUR GCS BUCKET]/demo-repo/registry.db
+ cache_ttl_seconds: 60
+ project: feast_java_demo
```
-For the default configuration, please see the [Feature Server Configuration](https://github.com/feast-dev/feast-java/blob/master/serving/src/main/resources/application.yml).
+For the default configuration, please see the [Feature Server Configuration](https://github.com/feast-dev/feast/blob/master/java/serving/src/main/resources/application.yml).
For more details, please see: https://docs.feast.dev/how-to-guides/running-feast-in-production
+## Example
+See [here](https://github.com/feast-dev/feast/tree/master/examples/java-demo) for a sample tutorial on testing this helm chart with a demo feature repository and a local Redis instance.
+
+
{{ template "chart.requirementsSection" . }}
{{ template "chart.valuesSection" . }}
\ No newline at end of file
diff --git a/infra/charts/feast/charts/feature-server/Chart.yaml b/infra/charts/feast/charts/feature-server/Chart.yaml
index f238b6aee4b..bdaa9ea1fcc 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.24.0
-appVersion: v0.24.0
+version: 0.25.0
+appVersion: v0.25.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 465665fb3b0..aef8c0329a6 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
- 
+ 
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.24.0"` | Image tag |
+| image.tag | string | `"0.25.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 |
diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml
index b014d8cee79..d3b8a33a645 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.24.0
+ tag: 0.25.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 4c650544f58..104b2f24278 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.24.0
-appVersion: v0.24.0
+version: 0.25.0
+appVersion: v0.25.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 7b55e1a10c6..37be5b0f106 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
- 
+ 
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.24.0"` | Image tag |
+| image.tag | string | `"0.25.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 |
diff --git a/infra/charts/feast/charts/transformation-service/values.yaml b/infra/charts/feast/charts/transformation-service/values.yaml
index 149d613e9fb..5232ac68ca1 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.24.0
+ tag: 0.25.0
# image.pullPolicy -- Image pull policy
pullPolicy: IfNotPresent
diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml
index 5dd4a4bce1b..a599ac23a44 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.24.0
+ version: 0.25.0
condition: feature-server.enabled
repository: https://feast-helm-charts.storage.googleapis.com
- name: transformation-service
alias: transformation-service
- version: 0.24.0
+ version: 0.25.0
condition: transformation-service.enabled
repository: https://feast-helm-charts.storage.googleapis.com
- name: redis
diff --git a/infra/scripts/helm/validate-helm-chart-versions.sh b/infra/scripts/helm/validate-helm-chart-versions.sh
index aac79d93154..cd8317222bd 100755
--- a/infra/scripts/helm/validate-helm-chart-versions.sh
+++ b/infra/scripts/helm/validate-helm-chart-versions.sh
@@ -3,7 +3,7 @@
set -e
# Amount of file locations that need to be bumped in unison when versions increment
-UNIQUE_VERSIONS_COUNT=20
+UNIQUE_VERSIONS_COUNT=22 # Change in release 0.24.0
if [ $# -ne 1 ]; then
echo "Please provide a single semver version (without a \"v\" prefix) to test the repository against, e.g 0.99.0"
diff --git a/infra/scripts/release/files_to_bump.txt b/infra/scripts/release/files_to_bump.txt
index e94ec88db0a..d7588185ded 100644
--- a/infra/scripts/release/files_to_bump.txt
+++ b/infra/scripts/release/files_to_bump.txt
@@ -6,7 +6,7 @@ infra/charts/feast/charts/transformation-service/values.yaml 8
infra/charts/feast/charts/feature-server/Chart.yaml 4 5
infra/charts/feast/charts/feature-server/README.md 3 20
infra/charts/feast/charts/feature-server/values.yaml 8
-infra/charts/feast/README.md 11 58 59
+infra/charts/feast/README.md 11 68 69
infra/charts/feast-python-server/Chart.yaml 5
infra/charts/feast-python-server/README.md 5
infra/charts/feast-feature-server/Chart.yaml 5
diff --git a/java/infra/docker/feature-server/Dockerfile b/java/infra/docker/feature-server/Dockerfile
index a728340d6b4..bf4e172f763 100644
--- a/java/infra/docker/feature-server/Dockerfile
+++ b/java/infra/docker/feature-server/Dockerfile
@@ -2,7 +2,7 @@
# Build stage 1: Builder
# ============================================================
-FROM maven:3.6-jdk-11 as builder
+FROM maven:3-jdk-11 as builder
WORKDIR /build
diff --git a/java/pom.xml b/java/pom.xml
index 9cff26daa6e..874daa27984 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -35,7 +35,7 @@
- 0.24.0
+ 0.25.0
https://github.com/feast-dev/feast
UTF-8
diff --git a/java/serving/src/main/java/feast/serving/service/config/ApplicationProperties.java b/java/serving/src/main/java/feast/serving/service/config/ApplicationProperties.java
index e4c33434a10..7cef10e61a8 100644
--- a/java/serving/src/main/java/feast/serving/service/config/ApplicationProperties.java
+++ b/java/serving/src/main/java/feast/serving/service/config/ApplicationProperties.java
@@ -38,7 +38,6 @@ public class ApplicationProperties {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(ApplicationProperties.class);
private FeastProperties feast;
private GrpcServer grpc;
- private RestServer rest;
public FeastProperties getFeast() {
return feast;
@@ -331,18 +330,6 @@ public void setServer(Server server) {
}
}
- public static class RestServer {
- private Server server;
-
- public Server getServer() {
- return server;
- }
-
- public void setServer(Server server) {
- this.server = server;
- }
- }
-
/** Trace metric collection properties */
public static class TracingProperties {
diff --git a/java/serving/src/main/java/feast/serving/service/config/ApplicationPropertiesModule.java b/java/serving/src/main/java/feast/serving/service/config/ApplicationPropertiesModule.java
index 35757330736..588a17d269c 100644
--- a/java/serving/src/main/java/feast/serving/service/config/ApplicationPropertiesModule.java
+++ b/java/serving/src/main/java/feast/serving/service/config/ApplicationPropertiesModule.java
@@ -16,6 +16,7 @@
*/
package feast.serving.service.config;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
@@ -40,6 +41,7 @@ public ApplicationProperties provideApplicationProperties() throws IOException {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.findAndRegisterModules();
mapper.setDefaultMergeable(Boolean.TRUE);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ApplicationProperties properties = new ApplicationProperties();
ObjectReader objectReader = mapper.readerForUpdating(properties);
diff --git a/sdk/python/docs/index.rst b/sdk/python/docs/index.rst
index beca384137d..ca96782db8a 100644
--- a/sdk/python/docs/index.rst
+++ b/sdk/python/docs/index.rst
@@ -353,6 +353,15 @@ Redis Online Store
.. autoclass:: feast.infra.online_stores.redis.RedisOnlineStoreConfig
:members:
+Snowflake Online Store
+------------------
+
+.. autoclass:: feast.infra.online_stores.snowflake.SnowflakeOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.snowflake.SnowflakeOnlineStoreConfig
+ :members:
+
PostgreSQL Online Store
-----------------------
diff --git a/sdk/python/docs/source/conf.py b/sdk/python/docs/source/conf.py
new file mode 100644
index 00000000000..5e8fd11d161
--- /dev/null
+++ b/sdk/python/docs/source/conf.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+#
+# Feast documentation build configuration file, created by
+# sphinx-quickstart on Sat Nov 30 15:06:53 2019.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+
+import sphinx_rtd_theme
+
+sys.path.insert(0, os.path.abspath("../../feast"))
+sys.path.insert(0, os.path.abspath("../.."))
+
+
+# -- Build protos ---------------------------------------------------------
+
+# For an unknown reason, the Python protos stopped being built correctly.
+# See https://readthedocs.org/projects/feast/builds/17686555/ for an
+# example where the Python protos did not build, which subsequently broke
+# the RTD build. In order to fix this, we manually compile the protos.
+import subprocess
+
+from pathlib import Path
+
+# cwd will be feast/sdk/python/docs/source
+cwd = Path(os.getcwd())
+
+# Change to feast/
+os.chdir(cwd.parent.parent.parent.parent)
+
+# Compile Python protos
+result = subprocess.run(["python", "setup.py", "build_python_protos", "--inplace"], capture_output=True)
+stdout = result.stdout.decode("utf-8")
+stderr = result.stderr.decode("utf-8")
+print(f"Apply stdout:\n{stdout}")
+print(f"Apply stderr:\n{stderr}")
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "sphinx.ext.doctest",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.todo",
+ "sphinx.ext.coverage",
+ "sphinx.ext.mathjax",
+ "sphinx.ext.ifconfig",
+ "sphinx.ext.viewcode",
+ "sphinx.ext.githubpages",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.autodoc",
+ "sphinx_rtd_theme",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+
+# General information about the project.
+project = "Feast"
+copyright = "2021, Feast Authors"
+author = "Feast Authors"
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+
+# TODO: Add the below versions back to documentation building.
+# version = (
+# os.popen("git describe --tags $(git rev-list --tags --max-count=1)").read().strip()
+# )
+# The full version, including alpha/beta/rc tags.
+# release = (
+# os.popen("git describe --tags $(git rev-list --tags --max-count=1)").read().strip()
+# )
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_rtd_theme"
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = "Feastdoc"
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, "Feast.tex", "Feast Documentation", "Feast Authors", "manual")
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, "feast", "Feast Documentation", [author], 1)]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (
+ master_doc,
+ "Feast",
+ "Feast Documentation",
+ author,
+ "Feast",
+ "One line description of project.",
+ "Miscellaneous",
+ )
+]
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {"https://docs.python.org/": None}
diff --git a/sdk/python/docs/source/feast.diff.rst b/sdk/python/docs/source/feast.diff.rst
new file mode 100644
index 00000000000..e4142171711
--- /dev/null
+++ b/sdk/python/docs/source/feast.diff.rst
@@ -0,0 +1,37 @@
+feast.diff package
+==================
+
+Submodules
+----------
+
+feast.diff.infra\_diff module
+-----------------------------
+
+.. automodule:: feast.diff.infra_diff
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.diff.property\_diff module
+--------------------------------
+
+.. automodule:: feast.diff.property_diff
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.diff.registry\_diff module
+--------------------------------
+
+.. automodule:: feast.diff.registry_diff
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.diff
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.dqm.profilers.rst b/sdk/python/docs/source/feast.dqm.profilers.rst
new file mode 100644
index 00000000000..24f452ada8f
--- /dev/null
+++ b/sdk/python/docs/source/feast.dqm.profilers.rst
@@ -0,0 +1,29 @@
+feast.dqm.profilers package
+===========================
+
+Submodules
+----------
+
+feast.dqm.profilers.ge\_profiler module
+---------------------------------------
+
+.. automodule:: feast.dqm.profilers.ge_profiler
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.dqm.profilers.profiler module
+-----------------------------------
+
+.. automodule:: feast.dqm.profilers.profiler
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.dqm.profilers
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.dqm.rst b/sdk/python/docs/source/feast.dqm.rst
new file mode 100644
index 00000000000..0c1b82f0fa2
--- /dev/null
+++ b/sdk/python/docs/source/feast.dqm.rst
@@ -0,0 +1,29 @@
+feast.dqm package
+=================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.dqm.profilers
+
+Submodules
+----------
+
+feast.dqm.errors module
+-----------------------
+
+.. automodule:: feast.dqm.errors
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.dqm
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.materialization.contrib.bytewax.rst b/sdk/python/docs/source/feast.infra.materialization.contrib.bytewax.rst
new file mode 100644
index 00000000000..86fbaa61515
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.materialization.contrib.bytewax.rst
@@ -0,0 +1,29 @@
+feast.infra.materialization.contrib.bytewax package
+=================================================================
+
+Submodules
+----------
+
+feast.infra.materialization.contrib.bytewax.bytewax\_materialization\_engine
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.materialization.contrib.bytewax.bytewax_materialization_engine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.materialization.contrib.bytewax.bytewax\_materialization\_job
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.materialization.contrib.bytewax.bytewax_materialization_job
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.materialization.contrib.bytewax
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.materialization.contrib.rst b/sdk/python/docs/source/feast.infra.materialization.contrib.rst
new file mode 100644
index 00000000000..f9d77006610
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.materialization.contrib.rst
@@ -0,0 +1,10 @@
+feast.infra.materialization.contrib package
+==========================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.materialization.contrib.bytewax
diff --git a/sdk/python/docs/source/feast.infra.materialization.lambda.rst b/sdk/python/docs/source/feast.infra.materialization.lambda.rst
new file mode 100644
index 00000000000..7ca1d44314a
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.materialization.lambda.rst
@@ -0,0 +1,29 @@
+feast.infra.materialization.lambda package
+==========================================
+
+Submodules
+----------
+
+feast.infra.materialization.lambda.app module
+---------------------------------------------
+
+.. automodule:: feast.infra.materialization.lambda.app
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.materialization.lambda.lambda\_engine module
+--------------------------------------------------------
+
+.. automodule:: feast.infra.materialization.lambda.lambda_engine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.materialization.lambda
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.materialization.rst b/sdk/python/docs/source/feast.infra.materialization.rst
new file mode 100644
index 00000000000..6e526c367cd
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.materialization.rst
@@ -0,0 +1,45 @@
+feast.infra.materialization package
+===================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.materialization.lambda
+
+Submodules
+----------
+
+feast.infra.materialization.batch\_materialization\_engine module
+-----------------------------------------------------------------
+
+.. automodule:: feast.infra.materialization.batch_materialization_engine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.materialization.local\_engine module
+------------------------------------------------
+
+.. automodule:: feast.infra.materialization.local_engine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.materialization.snowflake\_engine module
+----------------------------------------------------
+
+.. automodule:: feast.infra.materialization.snowflake_engine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.materialization
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.rst
new file mode 100644
index 00000000000..d2275b2b393
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.rst
@@ -0,0 +1,37 @@
+feast.infra.offline\_stores.contrib.athena\_offline\_store package
+==================================================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.athena_offline_store.tests
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.athena\_offline\_store.athena module
+------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_offline_store.athena
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.athena\_offline\_store.athena\_source module
+--------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_offline_store.athena_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.tests.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.tests.rst
new file mode 100644
index 00000000000..47a8f83e2b7
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.athena_offline_store.tests.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.athena\_offline\_store.tests package
+========================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.athena\_offline\_store.tests.data\_source module
+------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_offline_store.tests.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_offline_store.tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.rst
new file mode 100644
index 00000000000..8fb0b966bf8
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.rst
@@ -0,0 +1,37 @@
+feast.infra.offline\_stores.contrib.mssql\_offline\_store package
+=================================================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.mssql_offline_store.tests
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.mssql\_offline\_store.mssql module
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_offline_store.mssql
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.mssql\_offline\_store.mssqlserver\_source module
+------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_offline_store.mssqlserver_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.tests.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.tests.rst
new file mode 100644
index 00000000000..2f01ddd091a
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.mssql_offline_store.tests.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.mssql\_offline\_store.tests package
+=======================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.mssql\_offline\_store.tests.data\_source module
+-----------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_offline_store.tests.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_offline_store.tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.rst
new file mode 100644
index 00000000000..a80690fe859
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.rst
@@ -0,0 +1,37 @@
+feast.infra.offline\_stores.contrib.postgres\_offline\_store package
+====================================================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.postgres_offline_store.tests
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.postgres\_offline\_store.postgres module
+----------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.postgres\_offline\_store.postgres\_source module
+------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.tests.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.tests.rst
new file mode 100644
index 00000000000..35e60d2998b
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.postgres_offline_store.tests.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.postgres\_offline\_store.tests package
+==========================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.postgres\_offline\_store.tests.data\_source module
+--------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_offline_store.tests.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_offline_store.tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.rst
new file mode 100644
index 00000000000..ec74ddab05c
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.rst
@@ -0,0 +1,65 @@
+feast.infra.offline\_stores.contrib package
+===========================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.athena_offline_store
+ feast.infra.offline_stores.contrib.mssql_offline_store
+ feast.infra.offline_stores.contrib.postgres_offline_store
+ feast.infra.offline_stores.contrib.spark_offline_store
+ feast.infra.offline_stores.contrib.trino_offline_store
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.athena\_repo\_configuration module
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.athena_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.mssql\_repo\_configuration module
+---------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.mssql_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.postgres\_repo\_configuration module
+------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.postgres_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.spark\_repo\_configuration module
+---------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.trino\_repo\_configuration module
+---------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.rst
new file mode 100644
index 00000000000..b8b79bb48e8
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.rst
@@ -0,0 +1,37 @@
+feast.infra.offline\_stores.contrib.spark\_offline\_store package
+=================================================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.spark_offline_store.tests
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.spark\_offline\_store.spark module
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_offline_store.spark
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.spark\_offline\_store.spark\_source module
+------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_offline_store.spark_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.tests.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.tests.rst
new file mode 100644
index 00000000000..8b0f9bd88b3
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.spark_offline_store.tests.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.spark\_offline\_store.tests package
+=======================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.spark\_offline\_store.tests.data\_source module
+-----------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_offline_store.tests.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.spark_offline_store.tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.connectors.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.connectors.rst
new file mode 100644
index 00000000000..a0ee8dceabf
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.connectors.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.trino\_offline\_store.connectors package
+============================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.connectors.upload module
+----------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.connectors.upload
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.connectors
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.rst
new file mode 100644
index 00000000000..857326003f3
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.rst
@@ -0,0 +1,55 @@
+feast.infra.offline\_stores.contrib.trino\_offline\_store package
+=================================================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib.trino_offline_store.connectors
+ feast.infra.offline_stores.contrib.trino_offline_store.test_config
+ feast.infra.offline_stores.contrib.trino_offline_store.tests
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.trino module
+----------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.trino
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.trino\_queries module
+-------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.trino_queries
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.trino\_source module
+------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.trino_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.trino\_type\_map module
+---------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.trino_type_map
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.test_config.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.test_config.rst
new file mode 100644
index 00000000000..ef43a191d0a
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.test_config.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.trino\_offline\_store.test\_config package
+==============================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.test\_config.manual\_tests module
+-------------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.test_config.manual_tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.test_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.tests.rst b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.tests.rst
new file mode 100644
index 00000000000..9102f1f8d64
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.contrib.trino_offline_store.tests.rst
@@ -0,0 +1,21 @@
+feast.infra.offline\_stores.contrib.trino\_offline\_store.tests package
+=======================================================================
+
+Submodules
+----------
+
+feast.infra.offline\_stores.contrib.trino\_offline\_store.tests.data\_source module
+-----------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.tests.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores.contrib.trino_offline_store.tests
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.offline_stores.rst b/sdk/python/docs/source/feast.infra.offline_stores.rst
new file mode 100644
index 00000000000..7949c9efb32
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.offline_stores.rst
@@ -0,0 +1,101 @@
+feast.infra.offline\_stores package
+===================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores.contrib
+
+Submodules
+----------
+
+feast.infra.offline\_stores.bigquery module
+-------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.bigquery
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.bigquery\_source module
+---------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.bigquery_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.file module
+---------------------------------------
+
+.. automodule:: feast.infra.offline_stores.file
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.file\_source module
+-----------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.file_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.offline\_store module
+-------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.offline_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.offline\_utils module
+-------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.offline_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.redshift module
+-------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.redshift
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.redshift\_source module
+---------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.redshift_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.snowflake module
+--------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.snowflake
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.offline\_stores.snowflake\_source module
+----------------------------------------------------
+
+.. automodule:: feast.infra.offline_stores.snowflake_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.offline_stores
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.online_stores.contrib.cassandra_online_store.rst b/sdk/python/docs/source/feast.infra.online_stores.contrib.cassandra_online_store.rst
new file mode 100644
index 00000000000..3770cc8af70
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.online_stores.contrib.cassandra_online_store.rst
@@ -0,0 +1,21 @@
+feast.infra.online\_stores.contrib.cassandra\_online\_store package
+===================================================================
+
+Submodules
+----------
+
+feast.infra.online\_stores.contrib.cassandra\_online\_store.cassandra\_online\_store module
+-------------------------------------------------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.cassandra_online_store.cassandra_online_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.online_stores.contrib.cassandra_online_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.online_stores.contrib.hbase_online_store.rst b/sdk/python/docs/source/feast.infra.online_stores.contrib.hbase_online_store.rst
new file mode 100644
index 00000000000..ce249023049
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.online_stores.contrib.hbase_online_store.rst
@@ -0,0 +1,21 @@
+feast.infra.online\_stores.contrib.hbase\_online\_store package
+===============================================================
+
+Submodules
+----------
+
+feast.infra.online\_stores.contrib.hbase\_online\_store.hbase module
+--------------------------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.hbase_online_store.hbase
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.online_stores.contrib.hbase_online_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.online_stores.contrib.rst b/sdk/python/docs/source/feast.infra.online_stores.contrib.rst
new file mode 100644
index 00000000000..6afe9071ace
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.online_stores.contrib.rst
@@ -0,0 +1,54 @@
+feast.infra.online\_stores.contrib package
+==========================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.online_stores.contrib.cassandra_online_store
+ feast.infra.online_stores.contrib.hbase_online_store
+
+Submodules
+----------
+
+feast.infra.online\_stores.contrib.cassandra\_repo\_configuration module
+------------------------------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.cassandra_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.contrib.hbase\_repo\_configuration module
+--------------------------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.hbase_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.contrib.postgres module
+--------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.postgres
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.contrib.postgres\_repo\_configuration module
+-----------------------------------------------------------------------
+
+.. automodule:: feast.infra.online_stores.contrib.postgres_repo_configuration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.online_stores.contrib
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.online_stores.rst b/sdk/python/docs/source/feast.infra.online_stores.rst
new file mode 100644
index 00000000000..65758c409c0
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.online_stores.rst
@@ -0,0 +1,77 @@
+feast.infra.online\_stores package
+==================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.online_stores.contrib
+
+Submodules
+----------
+
+feast.infra.online\_stores.datastore module
+-------------------------------------------
+
+.. automodule:: feast.infra.online_stores.datastore
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.dynamodb module
+------------------------------------------
+
+.. automodule:: feast.infra.online_stores.dynamodb
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.helpers module
+-----------------------------------------
+
+.. automodule:: feast.infra.online_stores.helpers
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.online\_store module
+-----------------------------------------------
+
+.. automodule:: feast.infra.online_stores.online_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.redis module
+---------------------------------------
+
+.. automodule:: feast.infra.online_stores.redis
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.snowflake module
+-------------------------------------------
+
+.. automodule:: feast.infra.online_stores.snowflake
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.online\_stores.sqlite module
+----------------------------------------
+
+.. automodule:: feast.infra.online_stores.sqlite
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.online_stores
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.registry.rst b/sdk/python/docs/source/feast.infra.registry.rst
new file mode 100644
index 00000000000..7a2d9689975
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.registry.rst
@@ -0,0 +1,69 @@
+feast.infra.registry package
+============================
+
+Submodules
+----------
+
+feast.infra.registry.base\_registry module
+------------------------------------------
+
+.. automodule:: feast.infra.registry.base_registry
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.file module
+--------------------------------
+
+.. automodule:: feast.infra.registry.file
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.gcs module
+-------------------------------
+
+.. automodule:: feast.infra.registry.gcs
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.registry module
+------------------------------------
+
+.. automodule:: feast.infra.registry.registry
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.registry\_store module
+-------------------------------------------
+
+.. automodule:: feast.infra.registry.registry_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.s3 module
+------------------------------
+
+.. automodule:: feast.infra.registry.s3
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.registry.sql module
+-------------------------------
+
+.. automodule:: feast.infra.registry.sql
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.registry
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.registry_stores.rst b/sdk/python/docs/source/feast.infra.registry_stores.rst
new file mode 100644
index 00000000000..cff02fa3380
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.registry_stores.rst
@@ -0,0 +1,21 @@
+feast.infra.registry\_stores package
+====================================
+
+Submodules
+----------
+
+feast.infra.registry\_stores.sql module
+---------------------------------------
+
+.. automodule:: feast.infra.registry_stores.sql
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.registry_stores
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.rst b/sdk/python/docs/source/feast.infra.rst
new file mode 100644
index 00000000000..50e1f37f1c6
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.rst
@@ -0,0 +1,81 @@
+feast.infra package
+===================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.offline_stores
+ feast.infra.online_stores
+ feast.infra.registry
+ feast.infra.transformation_servers
+ feast.infra.utils
+
+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
+--------------------------------
+
+.. automodule:: feast.infra.infra_object
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.key\_encoding\_utils module
+---------------------------------------
+
+.. automodule:: feast.infra.key_encoding_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.local module
+------------------------
+
+.. automodule:: feast.infra.local
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.passthrough\_provider module
+----------------------------------------
+
+.. automodule:: feast.infra.passthrough_provider
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.provider module
+---------------------------
+
+.. automodule:: feast.infra.provider
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.transformation_servers.rst b/sdk/python/docs/source/feast.infra.transformation_servers.rst
new file mode 100644
index 00000000000..7de2dc79f29
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.transformation_servers.rst
@@ -0,0 +1,21 @@
+feast.infra.transformation\_servers package
+===========================================
+
+Submodules
+----------
+
+feast.infra.transformation\_servers.app module
+----------------------------------------------
+
+.. automodule:: feast.infra.transformation_servers.app
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.transformation_servers
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.utils.postgres.rst b/sdk/python/docs/source/feast.infra.utils.postgres.rst
new file mode 100644
index 00000000000..119c8c1dee9
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.utils.postgres.rst
@@ -0,0 +1,29 @@
+feast.infra.utils.postgres package
+==================================
+
+Submodules
+----------
+
+feast.infra.utils.postgres.connection\_utils module
+---------------------------------------------------
+
+.. automodule:: feast.infra.utils.postgres.connection_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.utils.postgres.postgres\_config module
+--------------------------------------------------
+
+.. automodule:: feast.infra.utils.postgres.postgres_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.utils.postgres
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.infra.utils.rst b/sdk/python/docs/source/feast.infra.utils.rst
new file mode 100644
index 00000000000..e4116e7a172
--- /dev/null
+++ b/sdk/python/docs/source/feast.infra.utils.rst
@@ -0,0 +1,37 @@
+feast.infra.utils package
+=========================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.infra.utils.postgres
+
+Submodules
+----------
+
+feast.infra.utils.aws\_utils module
+-----------------------------------
+
+.. automodule:: feast.infra.utils.aws_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.infra.utils.hbase\_utils module
+-------------------------------------
+
+.. automodule:: feast.infra.utils.hbase_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.infra.utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.loaders.rst b/sdk/python/docs/source/feast.loaders.rst
new file mode 100644
index 00000000000..d4968a29999
--- /dev/null
+++ b/sdk/python/docs/source/feast.loaders.rst
@@ -0,0 +1,21 @@
+feast.loaders package
+=====================
+
+Submodules
+----------
+
+feast.loaders.yaml module
+-------------------------
+
+.. automodule:: feast.loaders.yaml
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.loaders
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.feast.core.rst b/sdk/python/docs/source/feast.protos.feast.core.rst
new file mode 100644
index 00000000000..aaed49cd731
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.feast.core.rst
@@ -0,0 +1,333 @@
+feast.protos.feast.core package
+===============================
+
+Submodules
+----------
+
+feast.protos.feast.core.Aggregation\_pb2 module
+-----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Aggregation_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Aggregation\_pb2\_grpc module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Aggregation_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DataFormat\_pb2 module
+----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DataFormat_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DataFormat\_pb2\_grpc module
+----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DataFormat_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DataSource\_pb2 module
+----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DataSource_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DataSource\_pb2\_grpc module
+----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DataSource_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DatastoreTable\_pb2 module
+--------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DatastoreTable_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DatastoreTable\_pb2\_grpc module
+--------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DatastoreTable_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DynamoDBTable\_pb2 module
+-------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DynamoDBTable_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.DynamoDBTable\_pb2\_grpc module
+-------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.DynamoDBTable_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Entity\_pb2 module
+------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Entity_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Entity\_pb2\_grpc module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Entity_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureService\_pb2 module
+--------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureService_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureService\_pb2\_grpc module
+--------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureService_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureTable\_pb2 module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureTable_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureTable\_pb2\_grpc module
+------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureTable_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureViewProjection\_pb2 module
+---------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureViewProjection_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureViewProjection\_pb2\_grpc module
+---------------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureViewProjection_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureView\_pb2 module
+-----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureView_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.FeatureView\_pb2\_grpc module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.FeatureView_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Feature\_pb2 module
+-------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Feature_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Feature\_pb2\_grpc module
+-------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Feature_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.InfraObject\_pb2 module
+-----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.InfraObject_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.InfraObject\_pb2\_grpc module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.InfraObject_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.OnDemandFeatureView\_pb2 module
+-------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.OnDemandFeatureView_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.OnDemandFeatureView\_pb2\_grpc module
+-------------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.OnDemandFeatureView_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Registry\_pb2 module
+--------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Registry_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Registry\_pb2\_grpc module
+--------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Registry_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.RequestFeatureView\_pb2 module
+------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.RequestFeatureView_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.RequestFeatureView\_pb2\_grpc module
+------------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.RequestFeatureView_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.SavedDataset\_pb2 module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.SavedDataset_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.SavedDataset\_pb2\_grpc module
+------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.SavedDataset_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.SqliteTable\_pb2 module
+-----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.SqliteTable_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.SqliteTable\_pb2\_grpc module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.SqliteTable_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Store\_pb2 module
+-----------------------------------------
+
+.. automodule:: feast.protos.feast.core.Store_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.Store\_pb2\_grpc module
+-----------------------------------------------
+
+.. automodule:: feast.protos.feast.core.Store_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.StreamFeatureView\_pb2 module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.StreamFeatureView_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.StreamFeatureView\_pb2\_grpc module
+-----------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.StreamFeatureView_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.ValidationProfile\_pb2 module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.ValidationProfile_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.core.ValidationProfile\_pb2\_grpc module
+-----------------------------------------------------------
+
+.. automodule:: feast.protos.feast.core.ValidationProfile_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.protos.feast.core
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.feast.rst b/sdk/python/docs/source/feast.protos.feast.rst
new file mode 100644
index 00000000000..f519165db8e
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.feast.rst
@@ -0,0 +1,21 @@
+feast.protos.feast package
+==========================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.protos.feast.core
+ feast.protos.feast.serving
+ feast.protos.feast.storage
+ feast.protos.feast.types
+
+Module contents
+---------------
+
+.. automodule:: feast.protos.feast
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.feast.serving.rst b/sdk/python/docs/source/feast.protos.feast.serving.rst
new file mode 100644
index 00000000000..792335b189d
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.feast.serving.rst
@@ -0,0 +1,61 @@
+feast.protos.feast.serving package
+==================================
+
+Submodules
+----------
+
+feast.protos.feast.serving.Connector\_pb2 module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.Connector_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.serving.Connector\_pb2\_grpc module
+------------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.Connector_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.serving.ServingService\_pb2 module
+-----------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.ServingService_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.serving.ServingService\_pb2\_grpc module
+-----------------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.ServingService_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.serving.TransformationService\_pb2 module
+------------------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.TransformationService_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.serving.TransformationService\_pb2\_grpc module
+------------------------------------------------------------------
+
+.. automodule:: feast.protos.feast.serving.TransformationService_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.protos.feast.serving
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.feast.storage.rst b/sdk/python/docs/source/feast.protos.feast.storage.rst
new file mode 100644
index 00000000000..90bc1adc9b5
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.feast.storage.rst
@@ -0,0 +1,29 @@
+feast.protos.feast.storage package
+==================================
+
+Submodules
+----------
+
+feast.protos.feast.storage.Redis\_pb2 module
+--------------------------------------------
+
+.. automodule:: feast.protos.feast.storage.Redis_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.storage.Redis\_pb2\_grpc module
+--------------------------------------------------
+
+.. automodule:: feast.protos.feast.storage.Redis_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.protos.feast.storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.feast.types.rst b/sdk/python/docs/source/feast.protos.feast.types.rst
new file mode 100644
index 00000000000..aeb31bc9ad3
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.feast.types.rst
@@ -0,0 +1,61 @@
+feast.protos.feast.types package
+================================
+
+Submodules
+----------
+
+feast.protos.feast.types.EntityKey\_pb2 module
+----------------------------------------------
+
+.. automodule:: feast.protos.feast.types.EntityKey_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.types.EntityKey\_pb2\_grpc module
+----------------------------------------------------
+
+.. automodule:: feast.protos.feast.types.EntityKey_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.types.Field\_pb2 module
+------------------------------------------
+
+.. automodule:: feast.protos.feast.types.Field_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.types.Field\_pb2\_grpc module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.types.Field_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.types.Value\_pb2 module
+------------------------------------------
+
+.. automodule:: feast.protos.feast.types.Value_pb2
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.protos.feast.types.Value\_pb2\_grpc module
+------------------------------------------------
+
+.. automodule:: feast.protos.feast.types.Value_pb2_grpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast.protos.feast.types
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.protos.rst b/sdk/python/docs/source/feast.protos.rst
new file mode 100644
index 00000000000..7bec91eb030
--- /dev/null
+++ b/sdk/python/docs/source/feast.protos.rst
@@ -0,0 +1,18 @@
+feast.protos package
+====================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.protos.feast
+
+Module contents
+---------------
+
+.. automodule:: feast.protos
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.rst b/sdk/python/docs/source/feast.rst
new file mode 100644
index 00000000000..b0ed92c4cce
--- /dev/null
+++ b/sdk/python/docs/source/feast.rst
@@ -0,0 +1,378 @@
+feast package
+=============
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 4
+
+ feast.diff
+ feast.dqm
+ feast.infra
+ feast.loaders
+ feast.protos
+ feast.ui
+
+Submodules
+----------
+
+feast.aggregation module
+------------------------
+
+.. automodule:: feast.aggregation
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.base\_feature\_view module
+--------------------------------
+
+.. automodule:: feast.base_feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.batch\_feature\_view module
+---------------------------------
+
+.. automodule:: feast.batch_feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.cli module
+----------------
+
+.. automodule:: feast.cli
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.constants module
+----------------------
+
+.. automodule:: feast.constants
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.data\_format module
+-------------------------
+
+.. automodule:: feast.data_format
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.data\_source module
+-------------------------
+
+.. automodule:: feast.data_source
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.driver\_test\_data module
+-------------------------------
+
+.. automodule:: feast.driver_test_data
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.entity module
+-------------------
+
+.. automodule:: feast.entity
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.errors module
+-------------------
+
+.. automodule:: feast.errors
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feast\_object module
+--------------------------
+
+.. automodule:: feast.feast_object
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature module
+--------------------
+
+.. automodule:: feast.feature
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_logging module
+-----------------------------
+
+.. automodule:: feast.feature_logging
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_server module
+----------------------------
+
+.. automodule:: feast.feature_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_service module
+-----------------------------
+
+.. automodule:: feast.feature_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_store module
+---------------------------
+
+.. automodule:: feast.feature_store
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_view module
+--------------------------
+
+.. automodule:: feast.feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.feature\_view\_projection module
+--------------------------------------
+
+.. automodule:: feast.feature_view_projection
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.field module
+------------------
+
+.. automodule:: feast.field
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.file\_utils module
+------------------------
+
+.. automodule:: feast.file_utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.flags\_helper module
+--------------------------
+
+.. automodule:: feast.flags_helper
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.importer module
+---------------------
+
+.. automodule:: feast.importer
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.inference module
+----------------------
+
+.. automodule:: feast.inference
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.names module
+------------------
+
+.. automodule:: feast.names
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.on\_demand\_feature\_view module
+--------------------------------------
+
+.. automodule:: feast.on_demand_feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.online\_response module
+-----------------------------
+
+.. automodule:: feast.online_response
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.project\_metadata module
+------------------------------
+
+.. automodule:: feast.project_metadata
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.proto\_json module
+------------------------
+
+.. automodule:: feast.proto_json
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.repo\_config module
+-------------------------
+
+.. automodule:: feast.repo_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.repo\_contents module
+---------------------------
+
+.. automodule:: feast.repo_contents
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.repo\_operations module
+-----------------------------
+
+.. automodule:: feast.repo_operations
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.repo\_upgrade module
+--------------------------
+
+.. automodule:: feast.repo_upgrade
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.request\_feature\_view module
+-----------------------------------
+
+.. automodule:: feast.request_feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.saved\_dataset module
+---------------------------
+
+.. automodule:: feast.saved_dataset
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.stream\_feature\_view module
+----------------------------------
+
+.. automodule:: feast.stream_feature_view
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.transformation\_server module
+-----------------------------------
+
+.. automodule:: feast.transformation_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.type\_map module
+----------------------
+
+.. automodule:: feast.type_map
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.types module
+------------------
+
+.. automodule:: feast.types
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.ui\_server module
+-----------------------
+
+.. automodule:: feast.ui_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.usage module
+------------------
+
+.. automodule:: feast.usage
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.utils module
+------------------
+
+.. automodule:: feast.utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.value\_type module
+------------------------
+
+.. automodule:: feast.value_type
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.version module
+--------------------
+
+.. automodule:: feast.version
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+feast.wait module
+-----------------
+
+.. automodule:: feast.wait
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: feast
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/feast.ui.rst b/sdk/python/docs/source/feast.ui.rst
new file mode 100644
index 00000000000..01b16cb0a6a
--- /dev/null
+++ b/sdk/python/docs/source/feast.ui.rst
@@ -0,0 +1,10 @@
+feast.ui package
+================
+
+Module contents
+---------------
+
+.. automodule:: feast.ui
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/sdk/python/docs/source/index.rst b/sdk/python/docs/source/index.rst
new file mode 100644
index 00000000000..ca96782db8a
--- /dev/null
+++ b/sdk/python/docs/source/index.rst
@@ -0,0 +1,450 @@
+Feast Python API Documentation
+==============================
+
+.. We prefer 'autoclass' instead of 'autoclass' as 'autoclass' can specify a class, whereas
+ 'autoclass' will pull in all public classes and methods from that module, which we typically
+ do not want.
+
+Feature Store
+==================
+
+.. autoclass:: feast.feature_store.FeatureStore
+ :members:
+
+Config
+==================
+
+.. autoclass:: feast.repo_config.RepoConfig
+ :members:
+
+.. autoclass:: feast.repo_config.RegistryConfig
+ :members:
+
+Data Source
+==================
+
+.. autoclass:: feast.data_source.DataSource
+ :members:
+
+File Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.file_source.FileSource
+ :members:
+
+Snowflake Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.snowflake_source.SnowflakeSource
+ :members:
+
+BigQuery Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.bigquery_source.BigQuerySource
+ :members:
+
+Redshift Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.redshift_source.RedshiftSource
+ :members:
+
+Spark Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.spark_offline_store.spark_source.SparkSource
+ :members:
+
+Trino Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.trino_offline_store.trino_source.TrinoSource
+ :members:
+
+PostgreSQL Source
+------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source.PostgreSQLSource
+ :members:
+
+Request Source
+------------------
+
+.. autoclass:: feast.data_source.RequestSource
+ :members:
+
+Push Source
+------------------
+
+.. autoclass:: feast.data_source.PushSource
+ :members:
+
+Kafka Source
+------------------
+
+.. autoclass:: feast.data_source.KafkaSource
+ :members:
+
+Kinesis Source
+------------------
+
+.. autoclass:: feast.data_source.KinesisSource
+ :members:
+
+Entity
+==================
+
+.. autoclass:: feast.entity.Entity
+ :members:
+
+Feature View
+==================
+
+.. autoclass:: feast.base_feature_view.BaseFeatureView
+ :members:
+
+Feature View
+----------------------
+
+.. autoclass:: feast.feature_view.FeatureView
+ :members:
+
+On Demand Feature View
+----------------------
+
+.. autoclass:: feast.on_demand_feature_view.OnDemandFeatureView
+ :members:
+
+Batch Feature View
+----------------------
+
+.. autoclass:: feast.batch_feature_view.BatchFeatureView
+ :members:
+
+Stream Feature View
+----------------------
+
+.. autoclass:: feast.stream_feature_view.StreamFeatureView
+ :members:
+
+Field
+==================
+
+.. autoclass:: feast.field.Field
+ :members:
+
+Feature Service
+==================
+
+.. autoclass:: feast.feature_service.FeatureService
+ :members:
+
+Registry
+==================
+
+.. autoclass:: feast.infra.registry.base_registry.BaseRegistry
+ :members:
+
+Registry
+----------------------
+
+.. autoclass:: feast.infra.registry.registry.Registry
+ :members:
+
+SQL Registry
+----------------------
+
+.. autoclass:: feast.infra.registry.sql.SqlRegistry
+ :members:
+
+Registry Store
+==================
+
+.. autoclass:: feast.infra.registry.registry_store.RegistryStore
+ :members:
+
+File Registry Store
+-----------------------
+
+.. autoclass:: feast.infra.registry.file.FileRegistryStore
+ :members:
+
+GCS Registry Store
+-----------------------
+
+.. autoclass:: feast.infra.registry.gcs.GCSRegistryStore
+ :members:
+
+S3 Registry Store
+-----------------------
+
+.. autoclass:: feast.infra.registry.s3.S3RegistryStore
+ :members:
+
+PostgreSQL Registry Store
+-----------------------
+
+.. autoclass:: feast.infra.registry.contrib.postgres.postgres_registry_store.PostgreSQLRegistryStore
+ :members:
+
+Provider
+==================
+
+.. autoclass:: feast.infra.provider.Provider
+ :members:
+
+Passthrough Provider
+--------------------
+
+.. autoclass:: feast.infra.passthrough_provider.PassthroughProvider
+ :members:
+
+Local Provider
+------------------
+
+.. autoclass:: feast.infra.local.LocalProvider
+ :members:
+
+GCP Provider
+------------------
+
+.. autoclass:: feast.infra.gcp.GcpProvider
+ :members:
+
+AWS Provider
+------------------
+
+.. autoclass:: feast.infra.aws.AwsProvider
+ :members:
+
+Offline Store
+==================
+
+.. autoclass:: feast.infra.offline_stores.offline_store.OfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.offline_store.RetrievalJob
+ :members:
+
+File Offline Store
+------------------
+
+.. autoclass:: feast.infra.offline_stores.file.FileOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.file.FileOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.file.FileRetrievalJob
+ :members:
+
+Snowflake Offline Store
+-----------------------
+
+.. autoclass:: feast.infra.offline_stores.snowflake.SnowflakeOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.snowflake.SnowflakeOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.snowflake.SnowflakeRetrievalJob
+ :members:
+
+BigQuery Offline Store
+----------------------
+
+.. autoclass:: feast.infra.offline_stores.bigquery.BigQueryOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.bigquery.BigQueryOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.bigquery.BigQueryRetrievalJob
+ :members:
+
+Redshift Offline Store
+----------------------
+
+.. autoclass:: feast.infra.offline_stores.redshift.RedshiftOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.redshift.RedshiftOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.redshift.RedshiftRetrievalJob
+ :members:
+
+Spark Offline Store
+-------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.spark_offline_store.spark.SparkOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.spark_offline_store.spark.SparkOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.spark_offline_store.spark.SparkRetrievalJob
+ :members:
+
+Trino Offline Store
+-------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.trino_offline_store.trino.TrinoOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.trino_offline_store.trino.TrinoOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.trino_offline_store.trino.TrinoRetrievalJob
+ :members:
+
+PostgreSQL Offline Store
+------------------------
+
+.. autoclass:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres.PostgreSQLOfflineStore
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres.PostgreSQLOfflineStoreConfig
+ :members:
+
+.. autoclass:: feast.infra.offline_stores.contrib.postgres_offline_store.postgres.PostgreSQLRetrievalJob
+ :members:
+
+Online Store
+==================
+
+.. autoclass:: feast.infra.online_stores.online_store.OnlineStore
+ :members:
+
+Sqlite Online Store
+-------------------
+
+.. autoclass:: feast.infra.online_stores.sqlite.SqliteOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.sqlite.SqliteOnlineStoreConfig
+ :members:
+
+Datastore Online Store
+----------------------
+
+.. autoclass:: feast.infra.online_stores.datastore.DatastoreOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.datastore.DatastoreOnlineStoreConfig
+ :members:
+
+DynamoDB Online Store
+---------------------
+
+.. autoclass:: feast.infra.online_stores.dynamodb.DynamoDBOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.dynamodb.DynamoDBOnlineStoreConfig
+ :members:
+
+Redis Online Store
+------------------
+
+.. autoclass:: feast.infra.online_stores.redis.RedisOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.redis.RedisOnlineStoreConfig
+ :members:
+
+Snowflake Online Store
+------------------
+
+.. autoclass:: feast.infra.online_stores.snowflake.SnowflakeOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.snowflake.SnowflakeOnlineStoreConfig
+ :members:
+
+PostgreSQL Online Store
+-----------------------
+
+.. autoclass:: feast.infra.online_stores.contrib.postgres.PostgreSQLOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.contrib.postgres.PostgreSQLOnlineStoreConfig
+ :members:
+
+HBase Online Store
+-----------------------
+
+.. autoclass:: feast.infra.online_stores.contrib.hbase_online_store.hbase.HbaseOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.contrib.hbase_online_store.hbase.HbaseOnlineStoreConfig
+ :members:
+
+Cassandra Online Store
+-----------------------
+
+.. autoclass:: feast.infra.online_stores.contrib.cassandra_online_store.cassandra_online_store.CassandraOnlineStore
+ :members:
+
+.. autoclass:: feast.infra.online_stores.contrib.cassandra_online_store.cassandra_online_store.CassandraOnlineStoreConfig
+ :members:
+
+Batch Materialization Engine
+============================
+
+.. autoclass:: feast.infra.materialization.batch_materialization_engine.BatchMaterializationEngine
+ :members:
+
+.. autoclass:: feast.infra.materialization.batch_materialization_engine.MaterializationJob
+ :members:
+
+.. autoclass:: feast.infra.materialization.batch_materialization_engine.MaterializationTask
+ :members:
+
+Local Engine
+------------
+
+.. autoclass:: feast.infra.materialization.local_engine.LocalMaterializationEngine
+ :members:
+
+.. autoclass:: feast.infra.materialization.local_engine.LocalMaterializationEngineConfig
+ :members:
+
+.. autoclass:: feast.infra.materialization.local_engine.LocalMaterializationJob
+ :members:
+
+Bytewax Engine
+---------------------------
+
+.. autoclass:: feast.infra.materialization.contrib.bytewax.bytewax_materialization_engine.BytewaxMaterializationEngine
+ :members:
+
+.. autoclass:: feast.infra.materialization.contrib.bytewax.bytewax_materialization_engine.BytewaxMaterializationEngineConfig
+ :members:
+
+.. autoclass:: feast.infra.materialization.contrib.bytewax.bytewax_materialization_job.BytewaxMaterializationJob
+ :members:
+
+Snowflake Engine
+---------------------------
+
+.. autoclass:: feast.infra.materialization.snowflake_engine.SnowflakeMaterializationEngine
+ :members:
+
+.. autoclass:: feast.infra.materialization.snowflake_engine.SnowflakeMaterializationEngineConfig
+ :members:
+
+.. autoclass:: feast.infra.materialization.snowflake_engine.SnowflakeMaterializationJob
+ :members:
+
+(Alpha) AWS Lambda Engine
+---------------------------
+
+.. autoclass:: feast.infra.materialization.aws_lambda.lambda_engine.LambdaMaterializationEngine
+ :members:
+
+.. autoclass:: feast.infra.materialization.aws_lambda.lambda_engine.LambdaMaterializationEngineConfig
+ :members:
+
+.. autoclass:: feast.infra.materialization.aws_lambda.lambda_engine.LambdaMaterializationJob
+ :members:
diff --git a/sdk/python/docs/source/modules.rst b/sdk/python/docs/source/modules.rst
new file mode 100644
index 00000000000..3a6f8333abd
--- /dev/null
+++ b/sdk/python/docs/source/modules.rst
@@ -0,0 +1,7 @@
+feast
+=====
+
+.. toctree::
+ :maxdepth: 4
+
+ feast
diff --git a/sdk/python/feast/entity.py b/sdk/python/feast/entity.py
index 55de68a4041..30f04e9c068 100644
--- a/sdk/python/feast/entity.py
+++ b/sdk/python/feast/entity.py
@@ -33,9 +33,6 @@ class Entity:
Attributes:
name: The unique name of the entity.
value_type: The type of the entity, such as string or float.
- join_keys: A list of properties that uniquely identifies different entities within the
- collection. This currently only supports a list of size one, but is intended to
- eventually support multiple join keys.
join_key: A property that uniquely identifies different entities within the
collection. The join_key property is typically used for joining entities
with their associated features. If not specified, defaults to the name.
@@ -48,7 +45,6 @@ class Entity:
name: str
value_type: ValueType
- join_keys: List[str]
join_key: str
description: str
tags: Dict[str, str]
@@ -62,6 +58,7 @@ def __init__(
*,
name: str,
join_keys: Optional[List[str]] = None,
+ value_type: Optional[ValueType] = None,
description: str = "",
tags: Optional[Dict[str, str]] = None,
owner: str = "",
@@ -74,6 +71,8 @@ def __init__(
join_keys (optional): A list of properties that uniquely identifies different entities
within the collection. This currently only supports a list of size one, but is
intended to eventually support multiple join keys.
+ value_type (optional): The type of the entity, such as string or float. If not specified,
+ it will be inferred from the schema of the underlying data source.
description (optional): A human-readable description.
tags (optional): A dictionary of key-value pairs to store arbitrary metadata.
owner (optional): The owner of the entity, typically the email of the primary maintainer.
@@ -82,23 +81,19 @@ def __init__(
ValueError: Parameters are specified incorrectly.
"""
self.name = name
- self.value_type = ValueType.UNKNOWN
+ self.value_type = value_type or ValueType.UNKNOWN
- # For now, both the `join_key` and `join_keys` attributes are set correctly,
- # so both are usable.
- # TODO(felixwang9817): Fully remove the usage of `join_key` throughout the codebase,
- # at which point the `join_key` attribute no longer needs to be set.
if join_keys and len(join_keys) > 1:
+ # TODO(felixwang9817): When multiple join keys are supported, add a `join_keys` attribute
+ # and deprecate the `join_key` attribute.
raise ValueError(
- "An entity may only have single join key. "
+ "An entity may only have a single join key. "
"Multiple join keys will be supported in the future."
)
elif join_keys and len(join_keys) == 1:
- self.join_keys = join_keys
self.join_key = join_keys[0]
else:
self.join_key = self.name
- self.join_keys = [self.join_key]
self.description = description
self.tags = tags if tags is not None else {}
diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py
index 15fda6ac7d0..834df0e5c48 100644
--- a/sdk/python/feast/errors.py
+++ b/sdk/python/feast/errors.py
@@ -384,3 +384,17 @@ def __init__(self, features: Any):
super().__init__(
f"Invalid `features` parameter type {type(features)}. Expected one of List[str] and FeatureService."
)
+
+
+class EntitySQLEmptyResults(Exception):
+ def __init__(self, entity_sql: str):
+ super().__init__(
+ f"No entity values found from the specified SQL query to generate the entity dataframe: {entity_sql}."
+ )
+
+
+class EntityDFNotDateTime(Exception):
+ def __init__(self):
+ super().__init__(
+ "The entity dataframe specified does not have the timestamp field as a datetime."
+ )
diff --git a/sdk/python/feast/feature_server.py b/sdk/python/feast/feature_server.py
index bef88923e18..c2596d411c6 100644
--- a/sdk/python/feast/feature_server.py
+++ b/sdk/python/feast/feature_server.py
@@ -4,7 +4,7 @@
import pandas as pd
import uvicorn
-from fastapi import FastAPI, HTTPException, Request
+from fastapi import FastAPI, HTTPException, Request, Response, status
from fastapi.logger import logger
from fastapi.params import Depends
from google.protobuf.json_format import MessageToDict, Parse
@@ -124,6 +124,10 @@ def write_to_online_store(body=Depends(get_body)):
# Raise HTTPException to return the error message to the client
raise HTTPException(status_code=500, detail=str(e))
+ @app.get("/health")
+ def health():
+ return Response(status_code=status.HTTP_200_OK)
+
return app
diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py
index 23600e7c64f..9350220c21c 100644
--- a/sdk/python/feast/feature_store.py
+++ b/sdk/python/feast/feature_store.py
@@ -1478,13 +1478,8 @@ def write_to_online_store(
feature_view = self.get_feature_view(
feature_view_name, allow_registry_cache=allow_registry_cache
)
- entities = []
- for entity_name in feature_view.entities:
- entities.append(
- self.get_entity(entity_name, allow_registry_cache=allow_registry_cache)
- )
provider = self._get_provider()
- provider.ingest_df(feature_view, entities, df)
+ provider.ingest_df(feature_view, df)
@log_exceptions_and_usage
def write_to_offline_store(
diff --git a/sdk/python/feast/feature_view.py b/sdk/python/feast/feature_view.py
index 41bad5828a0..344171745be 100644
--- a/sdk/python/feast/feature_view.py
+++ b/sdk/python/feast/feature_view.py
@@ -35,7 +35,9 @@
from feast.protos.feast.core.FeatureView_pb2 import (
MaterializationInterval as MaterializationIntervalProto,
)
+from feast.types import from_value_type
from feast.usage import log_exceptions
+from feast.value_type import ValueType
warnings.simplefilter("once", DeprecationWarning)
@@ -115,6 +117,7 @@ def __init__(
If a stream source, the source should contain a batch_source for backfills & batch materialization.
schema (optional): The schema of the feature view, including feature, timestamp,
and entity columns.
+ # TODO: clarify that schema is only useful here...
entities (optional): The list of entities with which this group of features is associated.
ttl (optional): The amount of time this group of features lives. A ttl of 0 indicates that
this group of features lives forever. Note that large ttl's or a ttl of 0
@@ -155,14 +158,34 @@ def __init__(
features: List[Field] = []
self.entity_columns = []
- join_keys = []
+ join_keys: List[str] = []
if entities:
for entity in entities:
- join_keys += entity.join_keys
+ join_keys.append(entity.join_key)
+
+ # Ensure that entities have unique join keys.
+ if len(set(join_keys)) < len(join_keys):
+ raise ValueError(
+ "A feature view should not have entities that share a join key."
+ )
for field in self.schema:
if field.name in join_keys:
self.entity_columns.append(field)
+
+ # Confirm that the inferred type matches the specified entity type, if it exists.
+ matching_entities = (
+ [e for e in entities if e.join_key == field.name]
+ if entities
+ else []
+ )
+ assert len(matching_entities) == 1
+ entity = matching_entities[0]
+ if entity.value_type != ValueType.UNKNOWN:
+ if from_value_type(entity.value_type) != field.dtype:
+ raise ValueError(
+ f"Entity {entity.name} has type {entity.value_type}, which does not match the inferred type {field.dtype}."
+ )
else:
features.append(field)
@@ -386,6 +409,12 @@ def from_proto(cls, feature_view_proto: FeatureViewProto):
for field_proto in feature_view_proto.spec.entity_columns
]
+ if len(feature_view.entities) != len(feature_view.entity_columns):
+ warnings.warn(
+ f"There are some mismatches in your feature view's registered entities. Please check if you have applied your entities correctly."
+ f"Entities: {feature_view.entities} vs Entity Columns: {feature_view.entity_columns}"
+ )
+
# FeatureViewProjections are not saved in the FeatureView proto.
# Create the default projection.
feature_view.projection = FeatureViewProjection.from_definition(feature_view)
diff --git a/sdk/python/feast/inference.py b/sdk/python/feast/inference.py
index 84e8321c122..d416763bd39 100644
--- a/sdk/python/feast/inference.py
+++ b/sdk/python/feast/inference.py
@@ -115,15 +115,11 @@ def update_feature_views_with_inferred_features_and_entities(
config: The config for the current feature store.
"""
entity_name_to_entity_map = {e.name: e for e in entities}
- entity_name_to_join_keys_map = {e.name: e.join_keys for e in entities}
+ entity_name_to_join_key_map = {e.name: e.join_key for e in entities}
for fv in fvs:
join_keys = set(
- [
- join_key
- for entity_name in fv.entities
- for join_key in entity_name_to_join_keys_map[entity_name]
- ]
+ [entity_name_to_join_key_map[entity_name] for entity_name in fv.entities]
)
# Fields whose names match a join key are considered to be entity columns; all
@@ -139,8 +135,7 @@ def update_feature_views_with_inferred_features_and_entities(
if field.name not in [feature.name for feature in fv.features]:
fv.features.append(field)
- # Since the `value_type` parameter has not yet been fully deprecated for
- # entities, we respect the `value_type` attribute if it still exists.
+ # Respect the `value_type` attribute of the entity, if it is specified.
for entity_name in fv.entities:
entity = entity_name_to_entity_map[entity_name]
if (
@@ -164,13 +159,7 @@ def update_feature_views_with_inferred_features_and_entities(
fv.entity_columns.append(Field(name=DUMMY_ENTITY_ID, dtype=String))
# Run inference for entity columns if there are fewer entity fields than expected.
- num_expected_join_keys = sum(
- [
- len(entity_name_to_join_keys_map[entity_name])
- for entity_name in fv.entities
- ]
- )
- run_inference_for_entities = len(fv.entity_columns) < num_expected_join_keys
+ run_inference_for_entities = len(fv.entity_columns) < len(join_keys)
# Run inference for feature columns if there are no feature fields.
run_inference_for_features = len(fv.features) == 0
diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile
index b853411e273..751a398ad4f 100644
--- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile
+++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile
@@ -1,9 +1,16 @@
FROM python:3.8
RUN apt update && \
- apt install -y jq
+ apt install -y \
+ jq \
+ python3-dev \
+ default-libmysqlclient-dev \
+ build-essential
+
RUN pip install pip --upgrade
-RUN pip install "feast[aws,gcp,snowflake,redis,go]"
+COPY . .
+
+RUN pip install -r requirements.txt
RUN apt update
RUN apt install -y -V ca-certificates lsb-release wget
RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb
diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev
index f1dd7cc390b..751a398ad4f 100644
--- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev
+++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev
@@ -1,11 +1,16 @@
FROM python:3.8
RUN apt update && \
- apt install -y jq
+ apt install -y \
+ jq \
+ python3-dev \
+ default-libmysqlclient-dev \
+ build-essential
+
RUN pip install pip --upgrade
COPY . .
-RUN pip install ".[aws,gcp,snowflake,redis,go]"
+RUN pip install -r requirements.txt
RUN apt update
RUN apt install -y -V ca-certificates lsb-release wget
RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb
diff --git a/sdk/python/feast/infra/feature_servers/multicloud/requirements.txt b/sdk/python/feast/infra/feature_servers/multicloud/requirements.txt
new file mode 100644
index 00000000000..01d08a4effa
--- /dev/null
+++ b/sdk/python/feast/infra/feature_servers/multicloud/requirements.txt
@@ -0,0 +1 @@
+feast[aws,gcp,snowflake,redis,go,mysql]
diff --git a/sdk/python/feast/infra/materialization/contrib/spark/spark_materialization_engine.py b/sdk/python/feast/infra/materialization/contrib/spark/spark_materialization_engine.py
new file mode 100644
index 00000000000..66eb97bca78
--- /dev/null
+++ b/sdk/python/feast/infra/materialization/contrib/spark/spark_materialization_engine.py
@@ -0,0 +1,265 @@
+import tempfile
+from dataclasses import dataclass
+from datetime import datetime
+from typing import Callable, List, Literal, Optional, Sequence, Union
+
+import dill
+import pandas as pd
+import pyarrow
+from tqdm import tqdm
+
+from feast.batch_feature_view import BatchFeatureView
+from feast.entity import Entity
+from feast.feature_view import FeatureView
+from feast.infra.materialization.batch_materialization_engine import (
+ BatchMaterializationEngine,
+ MaterializationJob,
+ MaterializationJobStatus,
+ MaterializationTask,
+)
+from feast.infra.offline_stores.contrib.spark_offline_store.spark import (
+ SparkOfflineStore,
+ SparkRetrievalJob,
+)
+from feast.infra.online_stores.online_store import OnlineStore
+from feast.infra.passthrough_provider import PassthroughProvider
+from feast.infra.registry.base_registry import BaseRegistry
+from feast.protos.feast.core.FeatureView_pb2 import FeatureView as FeatureViewProto
+from feast.repo_config import FeastConfigBaseModel, RepoConfig
+from feast.stream_feature_view import StreamFeatureView
+from feast.utils import (
+ _convert_arrow_to_proto,
+ _get_column_names,
+ _run_pyarrow_field_mapping,
+)
+
+
+class SparkMaterializationEngineConfig(FeastConfigBaseModel):
+ """Batch Materialization Engine config for spark engine"""
+
+ type: Literal["spark.engine"] = "spark.engine"
+ """ Type selector"""
+
+ partitions: int = 0
+ """Number of partitions to use when writing data to online store. If 0, no repartitioning is done"""
+
+
+@dataclass
+class SparkMaterializationJob(MaterializationJob):
+ def __init__(
+ self,
+ job_id: str,
+ status: MaterializationJobStatus,
+ error: Optional[BaseException] = None,
+ ) -> None:
+ super().__init__()
+ self._job_id: str = job_id
+ self._status: MaterializationJobStatus = status
+ self._error: Optional[BaseException] = error
+
+ def status(self) -> MaterializationJobStatus:
+ return self._status
+
+ def error(self) -> Optional[BaseException]:
+ return self._error
+
+ def should_be_retried(self) -> bool:
+ return False
+
+ def job_id(self) -> str:
+ return self._job_id
+
+ def url(self) -> Optional[str]:
+ return None
+
+
+class SparkMaterializationEngine(BatchMaterializationEngine):
+ def update(
+ self,
+ project: str,
+ views_to_delete: Sequence[
+ Union[BatchFeatureView, StreamFeatureView, FeatureView]
+ ],
+ views_to_keep: Sequence[
+ Union[BatchFeatureView, StreamFeatureView, FeatureView]
+ ],
+ entities_to_delete: Sequence[Entity],
+ entities_to_keep: Sequence[Entity],
+ ):
+ # Nothing to set up.
+ pass
+
+ def teardown_infra(
+ self,
+ project: str,
+ fvs: Sequence[Union[BatchFeatureView, StreamFeatureView, FeatureView]],
+ entities: Sequence[Entity],
+ ):
+ # Nothing to tear down.
+ pass
+
+ def __init__(
+ self,
+ *,
+ repo_config: RepoConfig,
+ offline_store: SparkOfflineStore,
+ online_store: OnlineStore,
+ **kwargs,
+ ):
+ if not isinstance(offline_store, SparkOfflineStore):
+ raise TypeError(
+ "SparkMaterializationEngine is only compatible with the SparkOfflineStore"
+ )
+ super().__init__(
+ repo_config=repo_config,
+ offline_store=offline_store,
+ online_store=online_store,
+ **kwargs,
+ )
+
+ def materialize(
+ self, registry, tasks: List[MaterializationTask]
+ ) -> List[MaterializationJob]:
+ return [
+ self._materialize_one(
+ registry,
+ task.feature_view,
+ task.start_time,
+ task.end_time,
+ task.project,
+ task.tqdm_builder,
+ )
+ for task in tasks
+ ]
+
+ def _materialize_one(
+ self,
+ registry: BaseRegistry,
+ feature_view: Union[BatchFeatureView, StreamFeatureView, FeatureView],
+ start_date: datetime,
+ end_date: datetime,
+ project: str,
+ tqdm_builder: Callable[[int], tqdm],
+ ):
+ entities = []
+ for entity_name in feature_view.entities:
+ entities.append(registry.get_entity(entity_name, project))
+
+ (
+ join_key_columns,
+ feature_name_columns,
+ timestamp_field,
+ created_timestamp_column,
+ ) = _get_column_names(feature_view, entities)
+
+ job_id = f"{feature_view.name}-{start_date}-{end_date}"
+
+ try:
+ offline_job: SparkRetrievalJob = (
+ self.offline_store.pull_latest_from_table_or_query(
+ config=self.repo_config,
+ data_source=feature_view.batch_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,
+ )
+ )
+
+ spark_serialized_artifacts = _SparkSerializedArtifacts.serialize(
+ feature_view=feature_view, repo_config=self.repo_config
+ )
+
+ spark_df = offline_job.to_spark_df()
+ if self.repo_config.batch_engine.partitions != 0:
+ spark_df = spark_df.repartition(
+ self.repo_config.batch_engine.partitions
+ )
+
+ spark_df.foreachPartition(
+ lambda x: _process_by_partition(x, spark_serialized_artifacts)
+ )
+
+ return SparkMaterializationJob(
+ job_id=job_id, status=MaterializationJobStatus.SUCCEEDED
+ )
+ except BaseException as e:
+ return SparkMaterializationJob(
+ job_id=job_id, status=MaterializationJobStatus.ERROR, error=e
+ )
+
+
+@dataclass
+class _SparkSerializedArtifacts:
+ """Class to assist with serializing unpicklable artifacts to the spark workers"""
+
+ feature_view_proto: str
+ repo_config_file: str
+
+ @classmethod
+ def serialize(cls, feature_view, repo_config):
+
+ # serialize to proto
+ feature_view_proto = feature_view.to_proto().SerializeToString()
+
+ # serialize repo_config to disk. Will be used to instantiate the online store
+ repo_config_file = tempfile.NamedTemporaryFile(delete=False).name
+ with open(repo_config_file, "wb") as f:
+ dill.dump(repo_config, f)
+
+ return _SparkSerializedArtifacts(
+ feature_view_proto=feature_view_proto, repo_config_file=repo_config_file
+ )
+
+ def unserialize(self):
+ # unserialize
+ proto = FeatureViewProto()
+ proto.ParseFromString(self.feature_view_proto)
+ feature_view = FeatureView.from_proto(proto)
+
+ # load
+ with open(self.repo_config_file, "rb") as f:
+ repo_config = dill.load(f)
+
+ provider = PassthroughProvider(repo_config)
+ online_store = provider.online_store
+ return feature_view, online_store, repo_config
+
+
+def _process_by_partition(rows, spark_serialized_artifacts: _SparkSerializedArtifacts):
+ """Load pandas df to online store"""
+
+ # convert to pyarrow table
+ dicts = []
+ for row in rows:
+ dicts.append(row.asDict())
+
+ df = pd.DataFrame.from_records(dicts)
+ if df.shape[0] == 0:
+ print("Skipping")
+ return
+
+ table = pyarrow.Table.from_pandas(df)
+
+ # unserialize artifacts
+ feature_view, online_store, repo_config = spark_serialized_artifacts.unserialize()
+
+ if feature_view.batch_source.field_mapping is not None:
+ table = _run_pyarrow_field_mapping(
+ table, feature_view.batch_source.field_mapping
+ )
+
+ join_key_to_value_type = {
+ entity.name: entity.dtype.to_value_type()
+ for entity in feature_view.entity_columns
+ }
+
+ rows_to_write = _convert_arrow_to_proto(table, feature_view, join_key_to_value_type)
+ online_store.online_write_batch(
+ repo_config,
+ feature_view,
+ rows_to_write,
+ lambda x: None,
+ )
diff --git a/sdk/python/feast/infra/materialization/snowflake_engine.py b/sdk/python/feast/infra/materialization/snowflake_engine.py
index 1663cbcbc0a..0219a7923f6 100644
--- a/sdk/python/feast/infra/materialization/snowflake_engine.py
+++ b/sdk/python/feast/infra/materialization/snowflake_engine.py
@@ -2,7 +2,6 @@
import shutil
from dataclasses import dataclass
from datetime import datetime
-from pathlib import Path
from typing import Callable, List, Literal, Optional, Sequence, Union
import click
@@ -29,6 +28,7 @@
assert_snowflake_feature_names,
execute_snowflake_statement,
get_snowflake_conn,
+ get_snowflake_online_store_path,
package_snowpark_zip,
)
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
@@ -45,9 +45,7 @@ class SnowflakeMaterializationEngineConfig(FeastConfigBaseModel):
type: Literal["snowflake.engine"] = "snowflake.engine"
""" Type selector"""
- config_path: Optional[str] = (
- Path(os.environ["HOME"]) / ".snowsql/config"
- ).__str__()
+ config_path: Optional[str] = os.path.expanduser("~/.snowsql/config")
""" Snowflake config path -- absolute path required (Cant use ~)"""
account: Optional[str] = None
@@ -334,7 +332,11 @@ def generate_snowflake_materialization_query(
)
if feature_value_type_name == "UNIX_TIMESTAMP":
- feature_sql = f'{feature_sql}(DATE_PART(EPOCH_NANOSECOND, "{feature.name}")) AS "{feature.name}"'
+ feature_sql = f'{feature_sql}(DATE_PART(EPOCH_NANOSECOND, "{feature.name}"::TIMESTAMP_LTZ)) AS "{feature.name}"'
+ elif feature_value_type_name == "DOUBLE":
+ feature_sql = (
+ f'{feature_sql}("{feature.name}"::DOUBLE) AS "{feature.name}"'
+ )
else:
feature_sql = f'{feature_sql}("{feature.name}") AS "{feature.name}"'
@@ -369,8 +371,6 @@ def materialize_to_snowflake_online_store(
) -> None:
assert_snowflake_feature_names(feature_view)
- online_table = f"""{repo_config .online_store.database}"."{repo_config.online_store.schema_}"."[online-transient] {project}_{feature_view.name}"""
-
feature_names_str = '", "'.join(
[feature.name for feature in feature_view.features]
)
@@ -380,8 +380,13 @@ def materialize_to_snowflake_online_store(
else:
fv_created_str = None
+ online_path = get_snowflake_online_store_path(repo_config, feature_view)
+ online_table = (
+ f'{online_path}."[online-transient] {project}_{feature_view.name}"'
+ )
+
query = f"""
- MERGE INTO "{online_table}" online_table
+ MERGE INTO {online_table} online_table
USING (
SELECT
"entity_key" || TO_BINARY("feature_name", 'UTF-8') AS "entity_feature_key",
diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py
index 8b2773fb657..c570b8d38ab 100644
--- a/sdk/python/feast/infra/offline_stores/bigquery.py
+++ b/sdk/python/feast/infra/offline_stores/bigquery.py
@@ -28,6 +28,8 @@
from feast.errors import (
BigQueryJobCancelled,
BigQueryJobStillRunning,
+ EntityDFNotDateTime,
+ EntitySQLEmptyResults,
FeastProviderLoginError,
InvalidEntityType,
)
@@ -665,6 +667,13 @@ def _get_entity_df_event_timestamp_range(
res.get("min"),
res.get("max"),
)
+ if (
+ entity_df_event_timestamp_range[0] is None
+ or entity_df_event_timestamp_range[1] is None
+ ):
+ raise EntitySQLEmptyResults(entity_df)
+ if type(entity_df_event_timestamp_range[0]) != datetime:
+ raise EntityDFNotDateTime()
elif isinstance(entity_df, pd.DataFrame):
entity_df_event_timestamp = entity_df.loc[
:, entity_df_event_timestamp_col
diff --git a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena.py b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena.py
index 5095a43d57c..e3bb4e8ccaa 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena.py
@@ -60,6 +60,9 @@ class AthenaOfflineStoreConfig(FeastConfigBaseModel):
database: StrictStr
""" Athena database name """
+ workgroup: StrictStr
+ """ Athena workgroup name """
+
s3_staging_location: StrictStr
""" S3 path for importing & exporting data to Athena """
@@ -243,6 +246,7 @@ def query_generator() -> Iterator[str]:
athena_client,
config.offline_store.data_source,
config.offline_store.database,
+ config.offline_store.workgroup,
f"DROP TABLE IF EXISTS {config.offline_store.database}.{table_name}",
)
@@ -293,6 +297,7 @@ def write_logged_features(
athena_client=athena_client,
data_source=config.offline_store.data_source,
database=config.offline_store.database,
+ workgroup=config.offline_store.workgroup,
s3_resource=s3_resource,
s3_path=s3_path,
table_name=destination.table_name,
@@ -378,6 +383,7 @@ def _to_df_internal(self) -> pd.DataFrame:
self._athena_client,
self._config.offline_store.data_source,
self._config.offline_store.database,
+ self._config.offline_store.workgroup,
self._s3_resource,
temp_external_location,
self.get_temp_table_dml_header(temp_table_name, temp_external_location)
@@ -394,6 +400,7 @@ def _to_arrow_internal(self) -> pa.Table:
self._athena_client,
self._config.offline_store.data_source,
self._config.offline_store.database,
+ self._config.offline_store.workgroup,
self._s3_resource,
temp_external_location,
self.get_temp_table_dml_header(temp_table_name, temp_external_location)
@@ -432,6 +439,7 @@ def to_athena(self, table_name: str) -> None:
self._athena_client,
self._config.offline_store.data_source,
self._config.offline_store.database,
+ self._config.offline_store.workgroup,
query,
)
@@ -449,6 +457,7 @@ def _upload_entity_df(
athena_client,
config.offline_store.data_source,
config.offline_store.database,
+ config.offline_store.workgroup,
s3_resource,
f"{config.offline_store.s3_staging_location}/entity_df/{table_name}/{table_name}.parquet",
table_name,
@@ -460,6 +469,7 @@ def _upload_entity_df(
athena_client,
config.offline_store.data_source,
config.offline_store.database,
+ config.offline_store.workgroup,
f"CREATE TABLE {table_name} AS ({entity_df})",
)
else:
@@ -514,6 +524,7 @@ def _get_entity_df_event_timestamp_range(
athena_client,
config.offline_store.data_source,
config.offline_store.database,
+ config.offline_store.workgroup,
f"SELECT MIN({entity_df_event_timestamp_col}) AS min, MAX({entity_df_event_timestamp_col}) AS max "
f"FROM ({entity_df})",
)
diff --git a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena_source.py b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena_source.py
index bac027ff3eb..8e9e3893f3a 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena_source.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/athena_source.py
@@ -225,6 +225,7 @@ def get_table_column_names_and_types(
client,
config.offline_store.data_source,
config.offline_store.database,
+ config.offline_store.workgroup,
f"SELECT * FROM ({self.query}) LIMIT 1",
)
columns = aws_utils.get_athena_query_result(client, statement_id)[
diff --git a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/tests/data_source.py b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/tests/data_source.py
index 92e0d6e5f60..384ab69e81f 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/tests/data_source.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/athena_offline_store/tests/data_source.py
@@ -27,27 +27,20 @@ class AthenaDataSourceCreator(DataSourceCreator):
def __init__(self, project_name: str, *args, **kwargs):
super().__init__(project_name)
- self.client = aws_utils.get_athena_data_client("ap-northeast-2")
- self.s3 = aws_utils.get_s3_resource("ap-northeast-2")
- data_source = (
- os.environ.get("ATHENA_DATA_SOURCE")
- if os.environ.get("ATHENA_DATA_SOURCE")
- else "AwsDataCatalog"
- )
- database = (
- os.environ.get("ATHENA_DATABASE")
- if os.environ.get("ATHENA_DATABASE")
- else "default"
- )
- bucket_name = (
- os.environ.get("ATHENA_S3_BUCKET_NAME")
- if os.environ.get("ATHENA_S3_BUCKET_NAME")
- else "feast-integration-tests"
- )
+
+ region = os.getenv("ATHENA_REGION", "ap-northeast-2")
+ data_source = os.getenv("ATHENA_DATA_SOURCE", "AwsDataCatalog")
+ database = os.getenv("ATHENA_DATABASE", "default")
+ workgroup = os.getenv("ATHENA_WORKGROUP", "primary")
+ bucket_name = os.getenv("ATHENA_S3_BUCKET_NAME", "feast-integration-tests")
+
+ self.client = aws_utils.get_athena_data_client(region)
+ self.s3 = aws_utils.get_s3_resource(region)
self.offline_store_config = AthenaOfflineStoreConfig(
- data_source=f"{data_source}",
- region="ap-northeast-2",
- database=f"{database}",
+ data_source=data_source,
+ region=region,
+ database=database,
+ workgroup=workgroup,
s3_staging_location=f"s3://{bucket_name}/test_dir",
)
@@ -77,6 +70,7 @@ def create_data_source(
self.client,
self.offline_store_config.data_source,
self.offline_store_config.database,
+ self.offline_store_config.workgroup,
self.s3,
s3_target,
table_name,
@@ -126,5 +120,6 @@ def teardown(self):
self.client,
self.offline_store_config.data_source,
self.offline_store_config.database,
+ self.offline_store_config.workgroup,
f"DROP TABLE IF EXISTS {table}",
)
diff --git a/sdk/python/feast/infra/offline_stores/contrib/athena_repo_configuration.py b/sdk/python/feast/infra/offline_stores/contrib/athena_repo_configuration.py
index 32376eb6527..09bc6ce961c 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/athena_repo_configuration.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/athena_repo_configuration.py
@@ -1,9 +1,9 @@
+from feast.infra.offline_stores.contrib.athena_offline_store.tests.data_source import (
+ AthenaDataSourceCreator,
+)
from tests.integration.feature_repos.integration_test_repo_config import (
IntegrationTestRepoConfig,
)
-from tests.integration.feature_repos.universal.data_sources.athena import (
- AthenaDataSourceCreator,
-)
FULL_REPO_CONFIGS = [
IntegrationTestRepoConfig(
diff --git a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py
index 58519014b44..01f89f80bb7 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py
@@ -1,4 +1,6 @@
+import os
import tempfile
+import uuid
import warnings
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
@@ -29,6 +31,7 @@
RetrievalMetadata,
)
from feast.infra.registry.registry import Registry
+from feast.infra.utils import aws_utils
from feast.repo_config import FeastConfigBaseModel, RepoConfig
from feast.saved_dataset import SavedDatasetStorage
from feast.type_map import spark_schema_to_np_dtypes
@@ -46,6 +49,12 @@ class SparkOfflineStoreConfig(FeastConfigBaseModel):
""" Configuration overlay for the spark session """
# sparksession is not serializable and we dont want to pass it around as an argument
+ staging_location: Optional[StrictStr] = None
+ """ Remote path for batch materialization jobs"""
+
+ region: Optional[StrictStr] = None
+ """ AWS Region if applicable for s3-based staging locations"""
+
class SparkOfflineStore(OfflineStore):
@staticmethod
@@ -105,6 +114,7 @@ def pull_latest_from_table_or_query(
return SparkRetrievalJob(
spark_session=spark_session,
query=query,
+ config=config,
full_feature_names=False,
on_demand_feature_views=None,
)
@@ -129,6 +139,7 @@ def get_historical_features(
"Some functionality may still be unstable so functionality can change in the future.",
RuntimeWarning,
)
+
spark_session = get_spark_session_or_start_new_with_repoconfig(
store_config=config.offline_store
)
@@ -192,6 +203,7 @@ def get_historical_features(
min_event_timestamp=entity_df_event_timestamp_range[0],
max_event_timestamp=entity_df_event_timestamp_range[1],
),
+ config=config,
)
@staticmethod
@@ -286,7 +298,10 @@ def pull_all_from_table_or_query(
"""
return SparkRetrievalJob(
- spark_session=spark_session, query=query, full_feature_names=False
+ spark_session=spark_session,
+ query=query,
+ full_feature_names=False,
+ config=config,
)
@@ -296,6 +311,7 @@ def __init__(
spark_session: SparkSession,
query: str,
full_feature_names: bool,
+ config: RepoConfig,
on_demand_feature_views: Optional[List[OnDemandFeatureView]] = None,
metadata: Optional[RetrievalMetadata] = None,
):
@@ -305,6 +321,7 @@ def __init__(
self._full_feature_names = full_feature_names
self._on_demand_feature_views = on_demand_feature_views or []
self._metadata = metadata
+ self._config = config
@property
def full_feature_names(self) -> bool:
@@ -325,11 +342,7 @@ def _to_df_internal(self) -> pd.DataFrame:
def _to_arrow_internal(self) -> pyarrow.Table:
"""Return dataset as pyarrow Table synchronously"""
-
- # write to temp parquet and then load it as pyarrow table from disk
- with tempfile.TemporaryDirectory() as temp_dir:
- self.to_spark_df().write.parquet(temp_dir, mode="overwrite")
- return pq.read_table(temp_dir)
+ return pyarrow.Table.from_pandas(self._to_df_internal())
def persist(self, storage: SavedDatasetStorage, allow_overwrite: bool = False):
"""
@@ -342,6 +355,53 @@ def persist(self, storage: SavedDatasetStorage, allow_overwrite: bool = False):
raise ValueError("Cannot persist, table_name is not defined")
self.to_spark_df().createOrReplaceTempView(table_name)
+ def supports_remote_storage_export(self) -> bool:
+ return self._config.offline_store.staging_location is not None
+
+ def to_remote_storage(self) -> List[str]:
+ """Currently only works for local and s3-based staging locations"""
+ if self.supports_remote_storage_export():
+
+ sdf: pyspark.sql.DataFrame = self.to_spark_df()
+
+ if self._config.offline_store.staging_location.startswith("/"):
+ local_file_staging_location = os.path.abspath(
+ self._config.offline_store.staging_location
+ )
+
+ # write to staging location
+ output_uri = os.path.join(
+ str(local_file_staging_location), str(uuid.uuid4())
+ )
+ sdf.write.parquet(output_uri)
+
+ return _list_files_in_folder(output_uri)
+ elif self._config.offline_store.staging_location.startswith("s3://"):
+
+ spark_compatible_s3_staging_location = (
+ self._config.offline_store.staging_location.replace(
+ "s3://", "s3a://"
+ )
+ )
+
+ # write to staging location
+ output_uri = os.path.join(
+ str(spark_compatible_s3_staging_location), str(uuid.uuid4())
+ )
+ sdf.write.parquet(output_uri)
+
+ return aws_utils.list_s3_files(
+ self._config.offline_store.region, output_uri
+ )
+
+ else:
+ raise NotImplementedError(
+ "to_remote_storage is only implemented for file:// and s3:// uri schemes"
+ )
+
+ else:
+ raise NotImplementedError()
+
@property
def metadata(self) -> Optional[RetrievalMetadata]:
"""
@@ -444,6 +504,17 @@ def _format_datetime(t: datetime) -> str:
return dt
+def _list_files_in_folder(folder):
+ """List full filenames in a folder"""
+ files = []
+ for file in os.listdir(folder):
+ filename = os.path.join(folder, file)
+ if os.path.isfile(filename):
+ files.append(filename)
+
+ return files
+
+
def _cast_data_frame(
df_new: pyspark.sql.DataFrame, df_existing: pyspark.sql.DataFrame
) -> pyspark.sql.DataFrame:
diff --git a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark_source.py b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark_source.py
index 5b9f5621813..a27065fb5ed 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark_source.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark_source.py
@@ -159,10 +159,7 @@ def get_table_column_names_and_types(
store_config=config.offline_store
)
df = spark_session.sql(f"SELECT * FROM {self.get_table_query_string()}")
- return (
- (fields["name"], fields["type"])
- for fields in df.schema.jsonValue()["fields"]
- )
+ return ((field.name, field.dataType.simpleString()) for field in df.schema)
def get_table_query_string(self) -> str:
"""Returns a string that can directly be used to reference this table in SQL"""
diff --git a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/tests/data_source.py b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/tests/data_source.py
index ab1acbef73e..71c07b20c27 100644
--- a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/tests/data_source.py
+++ b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/tests/data_source.py
@@ -58,6 +58,10 @@ def create_offline_store_config(self):
self.spark_offline_store_config = SparkOfflineStoreConfig()
self.spark_offline_store_config.type = "spark"
self.spark_offline_store_config.spark_conf = self.spark_conf
+ self.spark_offline_store_config.staging_location = (
+ tempfile.TemporaryDirectory().name
+ )
+ self.spark_offline_store_config.region = "eu-west-1"
return self.spark_offline_store_config
def create_data_source(
diff --git a/sdk/python/feast/infra/offline_stores/file_source.py b/sdk/python/feast/infra/offline_stores/file_source.py
index 135409ed04a..81a83c22457 100644
--- a/sdk/python/feast/infra/offline_stores/file_source.py
+++ b/sdk/python/feast/infra/offline_stores/file_source.py
@@ -160,9 +160,7 @@ def get_table_column_names_and_types(
if filesystem is None:
schema = ParquetDataset(path).schema.to_arrow_schema()
else:
- schema = ParquetDataset(
- filesystem.open_input_file(path), filesystem=filesystem
- ).schema
+ schema = ParquetDataset(path, filesystem=filesystem).schema
return zip(schema.names, map(str, schema.types))
diff --git a/sdk/python/feast/infra/offline_stores/snowflake.py b/sdk/python/feast/infra/offline_stores/snowflake.py
index aab68718653..2d621de50ff 100644
--- a/sdk/python/feast/infra/offline_stores/snowflake.py
+++ b/sdk/python/feast/infra/offline_stores/snowflake.py
@@ -25,7 +25,7 @@
from feast import OnDemandFeatureView
from feast.data_source import DataSource
-from feast.errors import InvalidEntityType
+from feast.errors import EntitySQLEmptyResults, InvalidEntityType
from feast.feature_logging import LoggingConfig, LoggingSource
from feast.feature_view import DUMMY_ENTITY_ID, DUMMY_ENTITY_VAL, FeatureView
from feast.infra.offline_stores import offline_utils
@@ -64,9 +64,7 @@ class SnowflakeOfflineStoreConfig(FeastConfigBaseModel):
type: Literal["snowflake.offline"] = "snowflake.offline"
""" Offline store type selector"""
- config_path: Optional[str] = (
- Path(os.environ["HOME"]) / ".snowsql/config"
- ).__str__()
+ config_path: Optional[str] = os.path.expanduser("~/.snowsql/config")
""" Snowflake config path -- absolute path required (Cant use ~)"""
account: Optional[str] = None
@@ -449,15 +447,6 @@ def to_sql(self) -> str:
with self._query_generator() as query:
return query
- def to_arrow_chunks(self, arrow_options: Optional[Dict] = None) -> Optional[List]:
- with self._query_generator() as query:
-
- arrow_batches = execute_snowflake_statement(
- self.snowflake_conn, query
- ).get_result_batches()
-
- return arrow_batches
-
def persist(self, storage: SavedDatasetStorage, allow_overwrite: bool = False):
assert isinstance(storage, SavedDatasetSnowflakeStorage)
self.to_snowflake(table_name=storage.snowflake_options.table)
@@ -585,6 +574,11 @@ def _get_entity_df_event_timestamp_range(
results = execute_snowflake_statement(snowflake_conn, query).fetchall()
entity_df_event_timestamp_range = cast(Tuple[datetime, datetime], results[0])
+ if (
+ entity_df_event_timestamp_range[0] is None
+ or entity_df_event_timestamp_range[1] is None
+ ):
+ raise EntitySQLEmptyResults(entity_df)
else:
raise InvalidEntityType(type(entity_df))
diff --git a/sdk/python/feast/infra/offline_stores/snowflake_source.py b/sdk/python/feast/infra/offline_stores/snowflake_source.py
index a25e8fd9034..40e50b3cab9 100644
--- a/sdk/python/feast/infra/offline_stores/snowflake_source.py
+++ b/sdk/python/feast/infra/offline_stores/snowflake_source.py
@@ -264,18 +264,17 @@ def get_table_column_names_and_types(
]
else:
raise NotImplementedError(
- "Numbers larger than INT64 are not supported"
+ "NaNs or Numbers larger than INT64 are not supported"
)
else:
- raise NotImplementedError(
- "The following Snowflake Data Type is not supported: DECIMAL -- Convert to DOUBLE"
- )
- elif row["type_code"] in [3, 5, 9, 10, 12]:
+ row["snowflake_type"] = "NUMBERwSCALE"
+
+ elif row["type_code"] in [5, 9, 10, 12]:
error = snowflake_unsupported_map[row["type_code"]]
raise NotImplementedError(
f"The following Snowflake Data Type is not supported: {error}"
)
- elif row["type_code"] in [1, 2, 4, 6, 7, 8, 11, 13]:
+ elif row["type_code"] in [1, 2, 3, 4, 6, 7, 8, 11, 13]:
row["snowflake_type"] = snowflake_type_code_map[row["type_code"]]
else:
raise NotImplementedError(
@@ -291,6 +290,7 @@ def get_table_column_names_and_types(
0: "NUMBER",
1: "DOUBLE",
2: "VARCHAR",
+ 3: "DATE",
4: "TIMESTAMP",
6: "TIMESTAMP_LTZ",
7: "TIMESTAMP_TZ",
@@ -300,7 +300,6 @@ def get_table_column_names_and_types(
}
snowflake_unsupported_map = {
- 3: "DATE -- Convert to TIMESTAMP",
5: "VARIANT -- Try converting to VARCHAR",
9: "OBJECT -- Try converting to VARCHAR",
10: "ARRAY -- Try converting to VARCHAR",
diff --git a/sdk/python/feast/infra/online_stores/contrib/cassandra_online_store/cassandra_online_store.py b/sdk/python/feast/infra/online_stores/contrib/cassandra_online_store/cassandra_online_store.py
index ee0cb19fef5..f89517c41eb 100644
--- a/sdk/python/feast/infra/online_stores/contrib/cassandra_online_store/cassandra_online_store.py
+++ b/sdk/python/feast/infra/online_stores/contrib/cassandra_online_store/cassandra_online_store.py
@@ -314,7 +314,8 @@ def online_write_batch(
project = config.project
for entity_key, values, timestamp, created_ts in data:
entity_key_bin = serialize_entity_key(
- entity_key, entity_key_serialization_version=2
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
).hex()
with tracing_span(name="remote_call"):
self._write_rows(
@@ -353,7 +354,8 @@ def online_read(
for entity_key in entity_keys:
entity_key_bin = serialize_entity_key(
- entity_key, entity_key_serialization_version=2
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
).hex()
with tracing_span(name="remote_call"):
diff --git a/sdk/python/feast/infra/online_stores/snowflake.py b/sdk/python/feast/infra/online_stores/snowflake.py
index a52beb73f76..c4474dff38d 100644
--- a/sdk/python/feast/infra/online_stores/snowflake.py
+++ b/sdk/python/feast/infra/online_stores/snowflake.py
@@ -2,7 +2,6 @@
import os
from binascii import hexlify
from datetime import datetime
-from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
import pandas as pd
@@ -16,6 +15,7 @@
from feast.infra.utils.snowflake.snowflake_utils import (
execute_snowflake_statement,
get_snowflake_conn,
+ get_snowflake_online_store_path,
write_pandas_binary,
)
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
@@ -31,9 +31,7 @@ class SnowflakeOnlineStoreConfig(FeastConfigBaseModel):
type: Literal["snowflake.online"] = "snowflake.online"
""" Online store type selector"""
- config_path: Optional[str] = (
- Path(os.environ["HOME"]) / ".snowsql/config"
- ).__str__()
+ config_path: Optional[str] = os.path.expanduser("~/.snowsql/config")
""" Snowflake config path -- absolute path required (Can't use ~)"""
account: Optional[str] = None
@@ -97,9 +95,13 @@ def online_write_batch(
for j, (feature_name, val) in enumerate(values.items()):
df.loc[j, "entity_feature_key"] = serialize_entity_key(
- entity_key, 2
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
) + bytes(feature_name, encoding="utf-8")
- df.loc[j, "entity_key"] = serialize_entity_key(entity_key, 2)
+ df.loc[j, "entity_key"] = serialize_entity_key(
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
+ )
df.loc[j, "feature_name"] = feature_name
df.loc[j, "value"] = val.SerializeToString()
df.loc[j, "event_ts"] = timestamp
@@ -111,9 +113,7 @@ def online_write_batch(
agg_df = pd.concat(dfs)
# This combines both the data upload plus the overwrite in the same transaction
- table_path = (
- f'"{config.online_store.database}"."{config.online_store.schema_}"'
- )
+ online_path = get_snowflake_online_store_path(config, table)
with get_snowflake_conn(config.online_store, autocommit=False) as conn:
write_pandas_binary(
conn,
@@ -124,7 +124,7 @@ def online_write_batch(
) # special function for writing binary to snowflake
query = f"""
- INSERT OVERWRITE INTO {table_path}."[online-transient] {config.project}_{table.name}"
+ INSERT OVERWRITE INTO {online_path}."[online-transient] {config.project}_{table.name}"
SELECT
"entity_feature_key",
"entity_key",
@@ -137,7 +137,7 @@ def online_write_batch(
*,
ROW_NUMBER() OVER(PARTITION BY "entity_key","feature_name" ORDER BY "event_ts" DESC, "created_ts" DESC) AS "_feast_row"
FROM
- {table_path}."[online-transient] {config.project}_{table.name}")
+ {online_path}."[online-transient] {config.project}_{table.name}")
WHERE
"_feast_row" = 1;
"""
@@ -165,7 +165,10 @@ def online_read(
(
"TO_BINARY("
+ hexlify(
- serialize_entity_key(combo[0], 2)
+ serialize_entity_key(
+ combo[0],
+ entity_key_serialization_version=config.entity_key_serialization_version,
+ )
+ bytes(combo[1], encoding="utf-8")
).__str__()[1:]
+ ")"
@@ -174,20 +177,23 @@ def online_read(
]
)
- table_path = f'"{config.online_store.database}"."{config.online_store.schema_}"'
+ online_path = get_snowflake_online_store_path(config, table)
with get_snowflake_conn(config.online_store) as conn:
query = f"""
SELECT
"entity_key", "feature_name", "value", "event_ts"
FROM
- {table_path}."[online-transient] {config.project}_{table.name}"
+ {online_path}."[online-transient] {config.project}_{table.name}"
WHERE
"entity_feature_key" IN ({entity_fetch_str})
"""
df = execute_snowflake_statement(conn, query).fetch_pandas_all()
for entity_key in entity_keys:
- entity_key_bin = serialize_entity_key(entity_key, 2)
+ entity_key_bin = serialize_entity_key(
+ entity_key,
+ entity_key_serialization_version=config.entity_key_serialization_version,
+ )
res = {}
res_ts = None
for index, row in df[df["entity_key"] == entity_key_bin].iterrows():
@@ -214,11 +220,11 @@ def update(
):
assert isinstance(config.online_store, SnowflakeOnlineStoreConfig)
- table_path = f'"{config.online_store.database}"."{config.online_store.schema_}"'
with get_snowflake_conn(config.online_store) as conn:
for table in tables_to_keep:
+ online_path = get_snowflake_online_store_path(config, table)
query = f"""
- CREATE TRANSIENT TABLE IF NOT EXISTS {table_path}."[online-transient] {config.project}_{table.name}" (
+ CREATE TRANSIENT TABLE IF NOT EXISTS {online_path}."[online-transient] {config.project}_{table.name}" (
"entity_feature_key" BINARY,
"entity_key" BINARY,
"feature_name" VARCHAR,
@@ -230,7 +236,8 @@ def update(
execute_snowflake_statement(conn, query)
for table in tables_to_delete:
- query = f'DROP TABLE IF EXISTS {table_path}."[online-transient] {config.project}_{table.name}"'
+ online_path = get_snowflake_online_store_path(config, table)
+ query = f'DROP TABLE IF EXISTS {online_path}."[online-transient] {config.project}_{table.name}"'
execute_snowflake_statement(conn, query)
def teardown(
@@ -241,8 +248,8 @@ def teardown(
):
assert isinstance(config.online_store, SnowflakeOnlineStoreConfig)
- table_path = f'"{config.online_store.database}"."{config.online_store.schema_}"'
with get_snowflake_conn(config.online_store) as conn:
for table in tables:
- query = f'DROP TABLE IF EXISTS {table_path}."[online-transient] {config.project}_{table.name}"'
+ online_path = get_snowflake_online_store_path(config, table)
+ query = f'DROP TABLE IF EXISTS {online_path}."[online-transient] {config.project}_{table.name}"'
execute_snowflake_statement(conn, query)
diff --git a/sdk/python/feast/infra/passthrough_provider.py b/sdk/python/feast/infra/passthrough_provider.py
index bb5cd38a835..28b10c12595 100644
--- a/sdk/python/feast/infra/passthrough_provider.py
+++ b/sdk/python/feast/infra/passthrough_provider.py
@@ -193,7 +193,6 @@ def online_read(
def ingest_df(
self,
feature_view: FeatureView,
- entities: List[Entity],
df: pd.DataFrame,
):
set_usage_attribute("provider", self.__class__.__name__)
@@ -204,7 +203,10 @@ def ingest_df(
table, feature_view.batch_source.field_mapping
)
- join_keys = {entity.join_key: entity.value_type for entity in entities}
+ join_keys = {
+ entity.name: entity.dtype.to_value_type()
+ for entity in feature_view.entity_columns
+ }
rows_to_write = _convert_arrow_to_proto(table, feature_view, join_keys)
self.online_write_batch(
diff --git a/sdk/python/feast/infra/provider.py b/sdk/python/feast/infra/provider.py
index 7d3c37e4c2e..82879b264af 100644
--- a/sdk/python/feast/infra/provider.py
+++ b/sdk/python/feast/infra/provider.py
@@ -123,7 +123,6 @@ def online_write_batch(
def ingest_df(
self,
feature_view: FeatureView,
- entities: List[Entity],
df: pd.DataFrame,
):
"""
@@ -131,7 +130,6 @@ def ingest_df(
Args:
feature_view: The feature view to which the dataframe corresponds.
- entities: The entities that are referenced by the dataframe.
df: The dataframe to be persisted.
"""
pass
diff --git a/sdk/python/feast/infra/registry/s3.py b/sdk/python/feast/infra/registry/s3.py
index d3772910f56..0a94c942e18 100644
--- a/sdk/python/feast/infra/registry/s3.py
+++ b/sdk/python/feast/infra/registry/s3.py
@@ -25,6 +25,7 @@ def __init__(self, registry_config: RegistryConfig, repo_path: Path):
self._uri = urlparse(uri)
self._bucket = self._uri.hostname
self._key = self._uri.path.lstrip("/")
+ self._boto_extra_args = registry_config.s3_additional_kwargs or {}
self.s3_client = boto3.resource(
"s3", endpoint_url=os.environ.get("FEAST_S3_ENDPOINT_URL")
@@ -77,4 +78,6 @@ def _write_registry(self, registry_proto: RegistryProto):
file_obj = TemporaryFile()
file_obj.write(registry_proto.SerializeToString())
file_obj.seek(0)
- self.s3_client.Bucket(self._bucket).put_object(Body=file_obj, Key=self._key)
+ self.s3_client.Bucket(self._bucket).put_object(
+ Body=file_obj, Key=self._key, **self._boto_extra_args
+ )
diff --git a/sdk/python/feast/infra/utils/aws_utils.py b/sdk/python/feast/infra/utils/aws_utils.py
index 07ce3ab17d4..7e8335ac92e 100644
--- a/sdk/python/feast/infra/utils/aws_utils.py
+++ b/sdk/python/feast/infra/utils/aws_utils.py
@@ -677,7 +677,7 @@ def list_s3_files(aws_region: str, path: str) -> List[str]:
return files
-# Athena
+# Athena utils
def get_athena_data_client(aws_region: str):
@@ -696,16 +696,17 @@ def get_athena_data_client(aws_region: str):
reraise=True,
)
def execute_athena_query_async(
- athena_data_client, data_source: str, database: str, query: str
+ athena_data_client, data_source: str, database: str, workgroup: str, query: str
) -> dict:
"""Execute Athena statement asynchronously. Does not wait for the query to finish.
Raises AthenaCredentialsError if the statement couldn't be executed due to the validation error.
Args:
- athena_data_client: athena Data API Service client
- data_source: athena Cluster Identifier
- database: athena Database Name
+ athena_data_client: Athena Data API Service client
+ data_source: Athena Data Source
+ database: Athena Database Name
+ workgroup: Athena Workgroup Name
query: The SQL query to execute
Returns: JSON response
@@ -716,7 +717,7 @@ def execute_athena_query_async(
return athena_data_client.start_query_execution(
QueryString=query,
QueryExecutionContext={"Database": database},
- WorkGroup="primary",
+ WorkGroup=workgroup,
)
except ClientError as e:
@@ -755,16 +756,19 @@ def wait_for_athena_execution(athena_data_client, execution: dict) -> None:
def drop_temp_table(
- athena_data_client, data_source: str, database: str, temp_table: str
+ athena_data_client, data_source: str, database: str, workgroup: str, temp_table: str
):
query = f"DROP TABLE `{database}.{temp_table}`"
- execute_athena_query_async(athena_data_client, data_source, database, query)
+ execute_athena_query_async(
+ athena_data_client, data_source, database, workgroup, query
+ )
def execute_athena_query(
athena_data_client,
data_source: str,
database: str,
+ workgroup: str,
query: str,
temp_table: str = None,
) -> str:
@@ -775,22 +779,25 @@ def execute_athena_query(
Args:
- athena_data_client: athena Data API Service client
- data_source: athena data source Name
- database: athena Database Name
+ athena_data_client: Athena Data API Service client
+ data_source: Athena Data Source Name
+ database: Athena Database Name
+ workgroup: Athena Workgroup Name
query: The SQL query to execute
- temp_table: temp table name to be deleted after query execution.
+ temp_table: Temp table name to be deleted after query execution.
Returns: Statement ID
"""
execution = execute_athena_query_async(
- athena_data_client, data_source, database, query
+ athena_data_client, data_source, database, workgroup, query
)
wait_for_athena_execution(athena_data_client, execution)
if temp_table is not None:
- drop_temp_table(athena_data_client, data_source, database, temp_table)
+ drop_temp_table(
+ athena_data_client, data_source, database, workgroup, temp_table
+ )
return execution["QueryExecutionId"]
@@ -822,6 +829,7 @@ def unload_athena_query_to_pa(
athena_data_client,
data_source: str,
database: str,
+ workgroup: str,
s3_resource,
s3_path: str,
query: str,
@@ -831,7 +839,7 @@ def unload_athena_query_to_pa(
bucket, key = get_bucket_and_key(s3_path)
execute_athena_query_and_unload_to_s3(
- athena_data_client, data_source, database, query, temp_table
+ athena_data_client, data_source, database, workgroup, query, temp_table
)
with tempfile.TemporaryDirectory() as temp_dir:
@@ -844,6 +852,7 @@ def unload_athena_query_to_df(
athena_data_client,
data_source: str,
database: str,
+ workgroup: str,
s3_resource,
s3_path: str,
query: str,
@@ -854,6 +863,7 @@ def unload_athena_query_to_df(
athena_data_client,
data_source,
database,
+ workgroup,
s3_resource,
s3_path,
query,
@@ -866,6 +876,7 @@ def execute_athena_query_and_unload_to_s3(
athena_data_client,
data_source: str,
database: str,
+ workgroup: str,
query: str,
temp_table: str,
) -> None:
@@ -873,20 +884,29 @@ def execute_athena_query_and_unload_to_s3(
Args:
athena_data_client: Athena Data API Service client
- data_source: Athena data source
- database: Redshift Database Name
+ data_source: Athena Data Source
+ database: Athena Database Name
+ workgroup: Athena Workgroup Name
query: The SQL query to execute
temp_table: temp table name to be deleted after query execution.
"""
- execute_athena_query(athena_data_client, data_source, database, query, temp_table)
+ execute_athena_query(
+ athena_data_client=athena_data_client,
+ data_source=data_source,
+ database=database,
+ workgroup=workgroup,
+ query=query,
+ temp_table=temp_table,
+ )
def upload_df_to_athena(
athena_client,
data_source: str,
database: str,
+ workgroup: str,
s3_resource,
s3_path: str,
table_name: str,
@@ -900,6 +920,7 @@ def upload_df_to_athena(
athena_client: Athena API Service client
data_source: Athena Data Source
database: Athena Database Name
+ workgroup: Athena Workgroup Name
s3_resource: S3 Resource object
s3_path: S3 path where the Parquet file is temporarily uploaded
table_name: The name of the new Data Catalog table where we copy the dataframe
@@ -924,6 +945,7 @@ def upload_df_to_athena(
athena_client,
data_source=data_source,
database=database,
+ workgroup=workgroup,
s3_resource=s3_resource,
s3_path=s3_path,
table_name=table_name,
@@ -935,6 +957,7 @@ def upload_arrow_table_to_athena(
athena_client,
data_source: str,
database: str,
+ workgroup: str,
s3_resource,
s3_path: str,
table_name: str,
@@ -952,8 +975,9 @@ def upload_arrow_table_to_athena(
Args:
table: The Arrow Table or Path to parquet dataset to upload
athena_client: Athena API Service client
- data_source: Athena data source
+ data_source: Athena Data Source
database: Athena Database Name
+ workgroup: Athena Workgroup Name
s3_resource: S3 Resource object
s3_path: S3 path where the Parquet file is temporarily uploaded
table_name: The name of the new Athena table where we copy the dataframe
@@ -986,7 +1010,7 @@ def upload_arrow_table_to_athena(
s3_resource.Object(bucket, key).put(Body=parquet_temp_file)
create_query = (
- f"CREATE EXTERNAL TABLE {database}.{table_name} "
+ f"CREATE EXTERNAL TABLE {database}.{table_name} {'IF NOT EXISTS' if not fail_if_exists else ''}"
f"({column_query_list}) "
f"STORED AS PARQUET "
f"LOCATION '{s3_path[:s3_path.rfind('/')]}' "
@@ -995,10 +1019,11 @@ def upload_arrow_table_to_athena(
try:
execute_athena_query(
- athena_client,
- data_source,
- database,
- f"{create_query}",
+ athena_data_client=athena_client,
+ data_source=data_source,
+ database=database,
+ workgroup=workgroup,
+ query=f"{create_query}",
)
finally:
pass
diff --git a/sdk/python/feast/infra/utils/snowflake/snowflake_utils.py b/sdk/python/feast/infra/utils/snowflake/snowflake_utils.py
index c7b27d8331c..a5d2b05d45d 100644
--- a/sdk/python/feast/infra/utils/snowflake/snowflake_utils.py
+++ b/sdk/python/feast/infra/utils/snowflake/snowflake_utils.py
@@ -22,6 +22,7 @@
import feast
from feast.errors import SnowflakeIncompleteConfig, SnowflakeQueryUnknownError
from feast.feature_view import FeatureView
+from feast.repo_config import RepoConfig
try:
import snowflake.connector
@@ -104,6 +105,21 @@ def get_snowflake_conn(config, autocommit=True) -> SnowflakeConnection:
raise SnowflakeIncompleteConfig(e)
+def get_snowflake_online_store_path(
+ config: RepoConfig,
+ feature_view: FeatureView,
+) -> str:
+ path_tag = "snowflake-online-store/online_path"
+ if path_tag in feature_view.tags:
+ online_path = feature_view.tags[path_tag]
+ else:
+ online_path = (
+ f'"{config.online_store.database}"."{config.online_store.schema_}"'
+ )
+
+ return online_path
+
+
def package_snowpark_zip(project_name) -> Tuple[str, str]:
path = os.path.dirname(feast.__file__)
copy_path = path + f"/snowflake_feast_{project_name}"
diff --git a/sdk/python/feast/infra/utils/snowflake/snowpark/snowflake_udfs.py b/sdk/python/feast/infra/utils/snowflake/snowpark/snowflake_udfs.py
index 7fde4dd3a1c..02311ca55d6 100644
--- a/sdk/python/feast/infra/utils/snowflake/snowpark/snowflake_udfs.py
+++ b/sdk/python/feast/infra/utils/snowflake/snowpark/snowflake_udfs.py
@@ -1,3 +1,4 @@
+import sys
from binascii import unhexlify
import pandas
@@ -24,6 +25,8 @@
# ValueType.BYTES = 1
@vectorized(input=pandas.DataFrame)
def feast_snowflake_binary_to_bytes_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -45,6 +48,8 @@ def feast_snowflake_binary_to_bytes_proto(df):
# ValueType.STRING = 2
@vectorized(input=pandas.DataFrame)
def feast_snowflake_varchar_to_string_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -66,6 +71,8 @@ def feast_snowflake_varchar_to_string_proto(df):
# ValueType.INT32 = 3
@vectorized(input=pandas.DataFrame)
def feast_snowflake_number_to_int32_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -87,6 +94,8 @@ def feast_snowflake_number_to_int32_proto(df):
# ValueType.INT64 = 4
@vectorized(input=pandas.DataFrame)
def feast_snowflake_number_to_int64_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -110,6 +119,8 @@ def feast_snowflake_number_to_int64_proto(df):
# ValueType.FLOAT = 5 & ValueType.DOUBLE = 6
@vectorized(input=pandas.DataFrame)
def feast_snowflake_float_to_double_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -131,6 +142,8 @@ def feast_snowflake_float_to_double_proto(df):
# ValueType.BOOL = 7
@vectorized(input=pandas.DataFrame)
def feast_snowflake_boolean_to_bool_boolean_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
df = list(
map(
ValueProto.SerializeToString,
@@ -152,6 +165,7 @@ def feast_snowflake_boolean_to_bool_boolean_proto(df):
# ValueType.UNIX_TIMESTAMP = 8
@vectorized(input=pandas.DataFrame)
def feast_snowflake_timestamp_to_unix_timestamp_proto(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
df = list(
map(
@@ -177,6 +191,8 @@ def feast_snowflake_timestamp_to_unix_timestamp_proto(df):
# converts 1 to n many entity keys to a single binary for lookups
@vectorized(input=pandas.DataFrame)
def feast_serialize_entity_keys(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
join_keys = create_entity_dict(df[0].values[0], df[2].values[0])
df = pandas.DataFrame.from_dict(
@@ -222,6 +238,8 @@ def feast_serialize_entity_keys(df):
# converts 1 to n many entity keys to a single binary for lookups
@vectorized(input=pandas.DataFrame)
def feast_entity_key_proto_to_string(df):
+ sys._xoptions["snowflake_partner_attribution"].append("feast")
+
join_keys = create_entity_dict(df[0].values[0], df[2].values[0])
df = pandas.DataFrame.from_dict(
diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py
index 47a5ae321d9..fab0f25b410 100644
--- a/sdk/python/feast/repo_config.py
+++ b/sdk/python/feast/repo_config.py
@@ -39,6 +39,7 @@
"snowflake.engine": "feast.infra.materialization.snowflake_engine.SnowflakeMaterializationEngine",
"lambda": "feast.infra.materialization.aws_lambda.lambda_engine.LambdaMaterializationEngine",
"bytewax": "feast.infra.materialization.contrib.bytewax.bytewax_materialization_engine.BytewaxMaterializationEngine",
+ "spark.engine": "feast.infra.materialization.contrib.spark.spark_materialization_engine.SparkMaterializationEngine",
}
ONLINE_STORE_CLASS_FOR_TYPE = {
@@ -112,6 +113,9 @@ class RegistryConfig(FeastBaseModel):
set to infinity by setting TTL to 0 seconds, which means the cache will only be loaded once and will never
expire. Users can manually refresh the cache by calling feature_store.refresh_registry() """
+ s3_additional_kwargs: Optional[Dict[str, str]]
+ """ Dict[str, str]: Extra arguments to pass to boto3 when writing the registry file to S3. """
+
class RepoConfig(FeastBaseModel):
"""Repo config. Typically loaded from `feature_store.yaml`"""
diff --git a/sdk/python/feast/templates/aws/feature_repo/example_repo.py b/sdk/python/feast/templates/aws/feature_repo/example_repo.py
index eaa1a1bfd4c..dd5a9c925b7 100644
--- a/sdk/python/feast/templates/aws/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/aws/feature_repo/example_repo.py
@@ -59,12 +59,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -103,3 +97,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(days=1),
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/aws/feature_repo/test_workflow.py b/sdk/python/feast/templates/aws/feature_repo/test_workflow.py
index 0d5b2714d9b..59ac1f0ee73 100644
--- a/sdk/python/feast/templates/aws/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/aws/feature_repo/test_workflow.py
@@ -1,7 +1,9 @@
+import random
import subprocess
-from datetime import datetime
+from datetime import datetime, timedelta
import pandas as pd
+from pytz import utc
from feast import FeatureStore
from feast.data_source import PushMode
@@ -18,40 +20,96 @@ def run_demo():
print("\n--- Historical features for batch scoring ---")
fetch_historical_features_entity_df(store, for_batch_scoring=True)
+ print(
+ "\n--- Historical features for training (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=False)
+
+ print(
+ "\n--- Historical features for batch scoring (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=True)
+
print("\n--- Load features into online store ---")
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
- "acc_rate": [1.0],
- "avg_daily_trips": [1000],
+ "acc_rate": [1.0 + random.random()],
+ "avg_daily_trips": [int(1000 * random.random())],
}
)
print(event_df)
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
+def fetch_historical_features_entity_sql(store: FeatureStore, for_batch_scoring):
+ end_date = (
+ datetime.now().replace(microsecond=0, second=0, minute=0).astimezone(tz=utc)
+ )
+ start_date = (end_date - timedelta(days=60)).astimezone(tz=utc)
+ # For batch scoring, we want the latest timestamps
+ if for_batch_scoring:
+ print(
+ "Generating a list of all unique entities in a time window for batch scoring"
+ )
+ # We use a group by since we want all distinct driver_ids.
+ entity_sql = f"""
+ SELECT
+ driver_id,
+ GETDATE() as event_timestamp
+ FROM {store.get_data_source("feast_driver_hourly_stats").get_table_query_string()}
+ WHERE event_timestamp BETWEEN TIMESTAMP '{start_date}' AND TIMESTAMP '{end_date}'
+ GROUP BY driver_id
+ """
+ else:
+ print("Generating training data for all entities in a time window")
+ # We don't need a group by if we want to generate training data
+ entity_sql = f"""
+ SELECT
+ driver_id,
+ event_timestamp
+ FROM {store.get_data_source("feast_driver_hourly_stats").get_table_query_string()}
+ WHERE event_timestamp BETWEEN TIMESTAMP '{start_date}' AND TIMESTAMP '{end_date}'
+ """
+
+ training_df = store.get_historical_features(
+ entity_df=entity_sql,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_hourly_stats:avg_daily_trips",
+ ],
+ ).to_df()
+ print(training_df.head())
+
+
def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):
# Note: see https://docs.feast.dev/getting-started/concepts/feature-retrieval for more details on how to retrieve
# for all entities in the offline store instead
@@ -89,7 +147,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -103,12 +161,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/cassandra/feature_repo/example_repo.py b/sdk/python/feast/templates/cassandra/feature_repo/example_repo.py
index b3c71154825..131f1bcaa61 100644
--- a/sdk/python/feast/templates/cassandra/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/cassandra/feature_repo/example_repo.py
@@ -54,12 +54,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -98,3 +92,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(days=1),
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/cassandra/feature_repo/test_workflow.py b/sdk/python/feast/templates/cassandra/feature_repo/test_workflow.py
index 2c388deea9e..eebeb113115 100644
--- a/sdk/python/feast/templates/cassandra/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/cassandra/feature_repo/test_workflow.py
@@ -22,17 +22,25 @@ def run_demo():
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
+
+ print("\n--- Online features retrieved (instead) through a feature service---")
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
"acc_rate": [1.0],
@@ -43,10 +51,7 @@ def run_demo():
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
-
- print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
@@ -89,7 +94,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -103,12 +108,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/gcp/feature_repo/example_repo.py b/sdk/python/feast/templates/gcp/feature_repo/example_repo.py
index ab2f696ef23..81e06c72018 100644
--- a/sdk/python/feast/templates/gcp/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/gcp/feature_repo/example_repo.py
@@ -63,12 +63,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -107,3 +101,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(weeks=52 * 10), # Set to be very long for example purposes only
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/gcp/feature_repo/test_workflow.py b/sdk/python/feast/templates/gcp/feature_repo/test_workflow.py
index 0f8d8894772..95ca080012b 100644
--- a/sdk/python/feast/templates/gcp/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/gcp/feature_repo/test_workflow.py
@@ -1,3 +1,4 @@
+import random
import subprocess
from datetime import datetime
@@ -18,28 +19,43 @@ def run_demo():
print("\n--- Historical features for batch scoring ---")
fetch_historical_features_entity_df(store, for_batch_scoring=True)
+ print(
+ "\n--- Historical features for training (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=False)
+
+ print(
+ "\n--- Historical features for batch scoring (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=True)
+
print("\n--- Load features into online store ---")
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
- "acc_rate": [1.0],
- "avg_daily_trips": [1000],
+ "acc_rate": [1.0 + random.random()],
+ "avg_daily_trips": [int(1000 * random.random())],
}
)
print(event_df)
@@ -48,12 +64,49 @@ def run_demo():
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
+def fetch_historical_features_entity_sql(store: FeatureStore, for_batch_scoring):
+ # For batch scoring, we want the latest timestamps
+ if for_batch_scoring:
+ print(
+ "Generating a list of all unique entities in a time window for batch scoring"
+ )
+ # We use a group by since we want all distinct driver_ids.
+ entity_sql = f"""
+ SELECT
+ driver_id,
+ CURRENT_TIMESTAMP() as event_timestamp
+ FROM {store.get_data_source("driver_hourly_stats_source").get_table_query_string()}
+ WHERE event_timestamp BETWEEN '2021-01-01' and '2021-12-31'
+ GROUP BY driver_id
+ """
+ else:
+ print("Generating training data for all entities in a time window")
+ # We don't need a group by if we want to generate training data
+ entity_sql = f"""
+ SELECT
+ driver_id,
+ event_timestamp
+ FROM {store.get_data_source("driver_hourly_stats_source").get_table_query_string()}
+ WHERE event_timestamp BETWEEN '2021-01-01' and '2021-12-31'
+ """
+
+ training_df = store.get_historical_features(
+ entity_df=entity_sql,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_hourly_stats:avg_daily_trips",
+ ],
+ ).to_df()
+ print(training_df.head())
+
+
def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):
# Note: see https://docs.feast.dev/getting-started/concepts/feature-retrieval for more details on how to retrieve
# for all entities in the offline store instead
@@ -91,7 +144,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -105,12 +158,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/hbase/feature_repo/example_repo.py b/sdk/python/feast/templates/hbase/feature_repo/example_repo.py
index b3c71154825..131f1bcaa61 100644
--- a/sdk/python/feast/templates/hbase/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/hbase/feature_repo/example_repo.py
@@ -54,12 +54,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -98,3 +92,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(days=1),
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/hbase/feature_repo/test_workflow.py b/sdk/python/feast/templates/hbase/feature_repo/test_workflow.py
index 76b8d7836c2..eebeb113115 100644
--- a/sdk/python/feast/templates/hbase/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/hbase/feature_repo/test_workflow.py
@@ -22,20 +22,25 @@ def run_demo():
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
"acc_rate": [1.0],
@@ -46,7 +51,7 @@ def run_demo():
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
@@ -89,7 +94,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -103,12 +108,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/local/README.md b/sdk/python/feast/templates/local/README.md
index 8133b6e84e3..daf3a686fbc 100644
--- a/sdk/python/feast/templates/local/README.md
+++ b/sdk/python/feast/templates/local/README.md
@@ -3,13 +3,15 @@ If you haven't already, check out the quickstart guide on Feast's website (http:
uses this repo. A quick view of what's in this repository's `feature_repo/` directory:
* `data/` contains raw demo parquet data
-* `example_repo.py` contains demo feature definitions
-* `feature_store.yaml` contains a demo setup configuring where data sources are
-* `test_workflow.py` showcases how to run all key Feast commands, including defining, retrieving, and pushing features.
+* `feature_repo/example_repo.py` contains demo feature definitions
+* `feature_repo/feature_store.yaml` contains a demo setup configuring where data sources are
+* `feature_repo/test_workflow.py` showcases how to run all key Feast commands, including defining, retrieving, and pushing features.
You can run the overall workflow with `python test_workflow.py`.
## To move from this into a more production ready workflow:
+> See more details in [Running Feast in production](https://docs.feast.dev/how-to-guides/running-feast-in-production)
+
1. First: you should start with a different Feast template, which delegates to a more scalable offline store.
- For example, running `feast init -t gcp`
or `feast init -t aws` or `feast init -t snowflake`.
diff --git a/sdk/python/feast/templates/local/feature_repo/example_repo.py b/sdk/python/feast/templates/local/feature_repo/example_repo.py
index b3c71154825..131f1bcaa61 100644
--- a/sdk/python/feast/templates/local/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/local/feature_repo/example_repo.py
@@ -54,12 +54,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -98,3 +92,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(days=1),
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/local/feature_repo/test_workflow.py b/sdk/python/feast/templates/local/feature_repo/test_workflow.py
index 76b8d7836c2..eebeb113115 100644
--- a/sdk/python/feast/templates/local/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/local/feature_repo/test_workflow.py
@@ -22,20 +22,25 @@ def run_demo():
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
"acc_rate": [1.0],
@@ -46,7 +51,7 @@ def run_demo():
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
@@ -89,7 +94,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -103,12 +108,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/postgres/feature_repo/example_repo.py b/sdk/python/feast/templates/postgres/feature_repo/example_repo.py
index a7ba9d7eace..0d1783e1e5e 100644
--- a/sdk/python/feast/templates/postgres/feature_repo/example_repo.py
+++ b/sdk/python/feast/templates/postgres/feature_repo/example_repo.py
@@ -46,12 +46,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -90,3 +84,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(days=1),
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/postgres/feature_repo/test_workflow.py b/sdk/python/feast/templates/postgres/feature_repo/test_workflow.py
index ca5c1ccf42d..f657aba15f7 100644
--- a/sdk/python/feast/templates/postgres/feature_repo/test_workflow.py
+++ b/sdk/python/feast/templates/postgres/feature_repo/test_workflow.py
@@ -22,20 +22,25 @@ def run_demo():
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
"acc_rate": [1.0],
@@ -46,7 +51,7 @@ def run_demo():
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
subprocess.run(["feast", "teardown"])
@@ -89,7 +94,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -103,12 +108,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/templates/snowflake/README.md b/sdk/python/feast/templates/snowflake/README.md
index 0c950de4358..d4f1ef6faf4 100644
--- a/sdk/python/feast/templates/snowflake/README.md
+++ b/sdk/python/feast/templates/snowflake/README.md
@@ -2,13 +2,15 @@
A quick view of what's in this repository:
* `data/` contains raw demo parquet data
-* `driver_repo.py` contains demo feature definitions
-* `feature_store.yaml` contains a demo setup configuring where data sources are
+* `feature_repo/driver_repo.py` contains demo feature definitions
+* `feature_repo/feature_store.yaml` contains a demo setup configuring where data sources are
* `test_workflow.py` showcases how to run all key Feast commands, including defining, retrieving, and pushing features.
You can run the overall workflow with `python test_workflow.py`.
## To move from this into a more production ready workflow:
+> See more details in [Running Feast in production](https://docs.feast.dev/how-to-guides/running-feast-in-production)
+
1. `feature_store.yaml` points to a local file as a registry. You'll want to setup a remote file (e.g. in S3/GCS) or a
SQL registry. See [registry docs](https://docs.feast.dev/getting-started/concepts/registry) for more details.
2. Setup CI/CD + dev vs staging vs prod environments to automatically update the registry as you change Feast feature definitions. See [docs](https://docs.feast.dev/how-to-guides/running-feast-in-production#1.-automatically-deploying-changes-to-your-feature-definitions).
diff --git a/sdk/python/feast/templates/snowflake/feature_repo/driver_repo.py b/sdk/python/feast/templates/snowflake/feature_repo/driver_repo.py
index 4befa693f99..dd05dac8455 100644
--- a/sdk/python/feast/templates/snowflake/feature_repo/driver_repo.py
+++ b/sdk/python/feast/templates/snowflake/feature_repo/driver_repo.py
@@ -66,12 +66,6 @@
tags={"team": "driver_performance"},
)
-# Defines a way to push data (to be available offline, online or both) into Feast.
-driver_stats_push_source = PushSource(
- name="driver_stats_push_source",
- batch_source=driver_stats_source,
-)
-
# Define a request data source which encodes features / information only
# available at request time (e.g. part of the user initiated HTTP request)
input_request = RequestSource(
@@ -110,3 +104,48 @@ def transformed_conv_rate(inputs: pd.DataFrame) -> pd.DataFrame:
driver_activity_v2 = FeatureService(
name="driver_activity_v2", features=[driver_stats_fv, transformed_conv_rate]
)
+
+# Defines a way to push data (to be available offline, online or both) into Feast.
+driver_stats_push_source = PushSource(
+ name="driver_stats_push_source",
+ batch_source=driver_stats_source,
+)
+
+# Defines a slightly modified version of the feature view from above, where the source
+# has been changed to the push source. This allows fresh features to be directly pushed
+# to the online store for this feature view.
+driver_stats_fresh_fv = FeatureView(
+ name="driver_hourly_stats_fresh",
+ entities=[driver],
+ ttl=timedelta(weeks=52 * 10), # Set to be very long for example purposes only
+ 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_push_source, # Changed from above
+ tags={"team": "driver_performance"},
+)
+
+
+# Define an on demand feature view which can generate new features based on
+# existing feature views and RequestSource features
+@on_demand_feature_view(
+ sources=[driver_stats_fresh_fv, input_request], # relies on fresh version of FV
+ schema=[
+ Field(name="conv_rate_plus_val1", dtype=Float64),
+ Field(name="conv_rate_plus_val2", dtype=Float64),
+ ],
+)
+def transformed_conv_rate_fresh(inputs: pd.DataFrame) -> pd.DataFrame:
+ df = pd.DataFrame()
+ df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"]
+ df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"]
+ return df
+
+
+driver_activity_v3 = FeatureService(
+ name="driver_activity_v3",
+ features=[driver_stats_fresh_fv, transformed_conv_rate_fresh],
+)
diff --git a/sdk/python/feast/templates/snowflake/test_workflow.py b/sdk/python/feast/templates/snowflake/test_workflow.py
index 6f5e33622a8..904d1e1f3e5 100644
--- a/sdk/python/feast/templates/snowflake/test_workflow.py
+++ b/sdk/python/feast/templates/snowflake/test_workflow.py
@@ -1,7 +1,9 @@
+import random
import subprocess
-from datetime import datetime
+from datetime import datetime, timedelta
import pandas as pd
+from pytz import utc
from feast import FeatureStore
from feast.data_source import PushMode
@@ -19,41 +21,97 @@ def run_demo():
print("\n--- Historical features for batch scoring ---")
fetch_historical_features_entity_df(store, for_batch_scoring=True)
+ print(
+ "\n--- Historical features for training (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=False)
+
+ print(
+ "\n--- Historical features for batch scoring (all entities in a window using SQL entity dataframe) ---"
+ )
+ fetch_historical_features_entity_sql(store, for_batch_scoring=True)
+
print("\n--- Load features into online store ---")
store.materialize_incremental(end_date=datetime.now())
print("\n--- Online features ---")
- fetch_online_features(store, use_feature_service=False)
+ fetch_online_features(store)
print("\n--- Online features retrieved (instead) through a feature service---")
- fetch_online_features(store, use_feature_service=True)
+ fetch_online_features(store, source="feature_service")
+
+ print(
+ "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
+ )
+ fetch_online_features(store, source="push")
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"created": [
- datetime(2021, 5, 13, 10, 59, 42),
+ datetime.now(),
],
"conv_rate": [1.0],
- "acc_rate": [1.0],
- "avg_daily_trips": [1000],
+ "acc_rate": [1.0 + random.random()],
+ "avg_daily_trips": [int(1000 * random.random())],
}
)
print(event_df)
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
- print("\n--- Online features again with updated values from a stream push---")
- fetch_online_features(store, use_feature_service=True)
+ print("\n--- Online features again with updated values from a stream push ---")
+ fetch_online_features(store, source="push")
print("\n--- Run feast teardown ---")
command = "cd feature_repo; feast teardown"
subprocess.run(command, shell=True)
+def fetch_historical_features_entity_sql(store: FeatureStore, for_batch_scoring):
+ end_date = (
+ datetime.now().replace(microsecond=0, second=0, minute=0).astimezone(tz=utc)
+ )
+ start_date = (end_date - timedelta(days=60)).astimezone(tz=utc)
+ # For batch scoring, we want the latest timestamps
+ if for_batch_scoring:
+ print(
+ "Generating a list of all unique entities in a time window for batch scoring"
+ )
+ # We use a group by since we want all distinct driver_ids.
+ entity_sql = f"""
+ SELECT
+ "driver_id",
+ CURRENT_TIMESTAMP() as "event_timestamp"
+ FROM {store.list_data_sources()[-1].get_table_query_string()}
+ WHERE "event_timestamp" BETWEEN '{start_date}' AND '{end_date}'
+ GROUP BY "driver_id"
+ """
+ else:
+ print("Generating training data for all entities in a time window")
+ # We don't need a group by if we want to generate training data
+ entity_sql = f"""
+ SELECT
+ "driver_id",
+ "event_timestamp"
+ FROM {store.list_data_sources()[-1].get_table_query_string()}
+ WHERE "event_timestamp" BETWEEN '{start_date}' AND '{end_date}'
+ """
+
+ training_df = store.get_historical_features(
+ entity_df=entity_sql,
+ features=[
+ "driver_hourly_stats:conv_rate",
+ "driver_hourly_stats:acc_rate",
+ "driver_hourly_stats:avg_daily_trips",
+ ],
+ ).to_df()
+ print(training_df.head())
+
+
def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):
# Note: see https://docs.feast.dev/getting-started/concepts/feature-retrieval for more details on how to retrieve
# for all entities in the offline store instead
@@ -91,7 +149,7 @@ def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring:
print(training_df.head())
-def fetch_online_features(store, use_feature_service: bool):
+def fetch_online_features(store, source: str = ""):
entity_rows = [
# {join_key: entity_value}
{
@@ -105,12 +163,13 @@ def fetch_online_features(store, use_feature_service: bool):
"val_to_add_2": 2002,
},
]
- if use_feature_service:
+ if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
+ elif source == "push":
+ features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
- "driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py
index 2cb1c4fefb0..466993bb3d7 100644
--- a/sdk/python/feast/type_map.py
+++ b/sdk/python/feast/type_map.py
@@ -320,6 +320,8 @@ def _python_datetime_to_int_timestamp(
int_timestamps.append(int(value.ToSeconds()))
elif isinstance(value, np.datetime64):
int_timestamps.append(value.astype("datetime64[s]").astype(np.int_))
+ elif isinstance(value, type(np.nan)):
+ int_timestamps.append(NULL_TIMESTAMP_INT_VALUE)
else:
int_timestamps.append(int(value))
return int_timestamps
@@ -625,8 +627,10 @@ def snowflake_type_to_feast_value_type(snowflake_type: str) -> ValueType:
"VARCHAR": ValueType.STRING,
"NUMBER32": ValueType.INT32,
"NUMBER64": ValueType.INT64,
+ "NUMBERwSCALE": ValueType.DOUBLE,
"DOUBLE": ValueType.DOUBLE,
"BOOLEAN": ValueType.BOOL,
+ "DATE": ValueType.UNIX_TIMESTAMP,
"TIMESTAMP": ValueType.UNIX_TIMESTAMP,
"TIMESTAMP_TZ": ValueType.UNIX_TIMESTAMP,
"TIMESTAMP_LTZ": ValueType.UNIX_TIMESTAMP,
diff --git a/sdk/python/tests/integration/materialization/contrib/spark/test_spark.py b/sdk/python/tests/integration/materialization/contrib/spark/test_spark.py
new file mode 100644
index 00000000000..c7028a09ef4
--- /dev/null
+++ b/sdk/python/tests/integration/materialization/contrib/spark/test_spark.py
@@ -0,0 +1,77 @@
+from datetime import timedelta
+
+import pytest
+
+from feast.entity import Entity
+from feast.feature_view import FeatureView
+from feast.field import Field
+from feast.infra.offline_stores.contrib.spark_offline_store.tests.data_source import (
+ SparkDataSourceCreator,
+)
+from feast.types import Float32
+from tests.data.data_creator import create_basic_driver_dataset
+from tests.integration.feature_repos.integration_test_repo_config import (
+ IntegrationTestRepoConfig,
+)
+from tests.integration.feature_repos.repo_configuration import (
+ construct_test_environment,
+)
+from tests.integration.feature_repos.universal.online_store.redis import (
+ RedisOnlineStoreCreator,
+)
+from tests.utils.e2e_test_validation import validate_offline_online_store_consistency
+
+
+@pytest.mark.integration
+def test_spark_materialization_consistency():
+ spark_config = IntegrationTestRepoConfig(
+ provider="local",
+ online_store_creator=RedisOnlineStoreCreator,
+ offline_store_creator=SparkDataSourceCreator,
+ batch_engine={"type": "spark.engine", "partitions": 10},
+ )
+ spark_environment = construct_test_environment(
+ spark_config, None, entity_key_serialization_version=1
+ )
+
+ df = create_basic_driver_dataset()
+
+ ds = spark_environment.data_source_creator.create_data_source(
+ df,
+ spark_environment.feature_store.project,
+ field_mapping={"ts_1": "ts"},
+ )
+
+ fs = spark_environment.feature_store
+ driver = Entity(
+ name="driver_id",
+ join_keys=["driver_id"],
+ )
+
+ driver_stats_fv = FeatureView(
+ name="driver_hourly_stats",
+ entities=[driver],
+ ttl=timedelta(weeks=52),
+ schema=[Field(name="value", dtype=Float32)],
+ source=ds,
+ )
+
+ try:
+
+ fs.apply([driver, driver_stats_fv])
+
+ print(df)
+
+ # materialization is run in two steps and
+ # we use timestamp from generated dataframe as a split point
+ split_dt = df["ts_1"][4].to_pydatetime() - timedelta(seconds=1)
+
+ print(f"Split datetime: {split_dt}")
+
+ validate_offline_online_store_consistency(fs, driver_stats_fv, split_dt)
+ finally:
+ fs.teardown()
+
+
+if __name__ == "__main__":
+ test_spark_materialization_consistency()
diff --git a/sdk/python/tests/unit/infra/test_inference_unit_tests.py b/sdk/python/tests/unit/infra/test_inference_unit_tests.py
index c5ed83c12f0..b4a0da7e47a 100644
--- a/sdk/python/tests/unit/infra/test_inference_unit_tests.py
+++ b/sdk/python/tests/unit/infra/test_inference_unit_tests.py
@@ -15,6 +15,7 @@
from feast.on_demand_feature_view import on_demand_feature_view
from feast.repo_config import RepoConfig
from feast.types import Float32, Float64, Int64, String, UnixTimestamp
+from feast.value_type import ValueType
from tests.utils.data_source_test_creator import prep_file_source
@@ -216,6 +217,78 @@ def test_feature_view_inference_respects_basic_inference():
assert len(feature_view_2.entity_columns) == 2
+def test_feature_view_inference_on_entity_value_types():
+ """
+ Tests that feature view inference correctly uses the entity `value_type` attribute.
+ """
+ entity1 = Entity(
+ name="test1", join_keys=["id_join_key"], value_type=ValueType.INT64
+ )
+ file_source = FileSource(path="some path")
+ feature_view_1 = FeatureView(
+ name="test1",
+ entities=[entity1],
+ schema=[Field(name="int64_col", dtype=Int64)],
+ source=file_source,
+ )
+
+ assert len(feature_view_1.schema) == 1
+ assert len(feature_view_1.features) == 1
+ assert len(feature_view_1.entity_columns) == 0
+
+ update_feature_views_with_inferred_features_and_entities(
+ [feature_view_1],
+ [entity1],
+ RepoConfig(
+ provider="local", project="test", entity_key_serialization_version=2
+ ),
+ )
+
+ # The schema is only used as a parameter, as is therefore not updated during inference.
+ assert len(feature_view_1.schema) == 1
+
+ # Since there is already a feature specified, additional features are not inferred.
+ assert len(feature_view_1.features) == 1
+
+ # The single entity column is inferred correctly and has the expected type.
+ assert len(feature_view_1.entity_columns) == 1
+ assert feature_view_1.entity_columns[0].dtype == Int64
+
+
+def test_conflicting_entity_value_types():
+ """
+ Tests that an error is thrown when the entity value types conflict.
+ """
+ entity1 = Entity(
+ name="test1", join_keys=["id_join_key"], value_type=ValueType.INT64
+ )
+ file_source = FileSource(path="some path")
+
+ with pytest.raises(ValueError):
+ _ = FeatureView(
+ name="test1",
+ entities=[entity1],
+ schema=[
+ Field(name="int64_col", dtype=Int64),
+ Field(
+ name="id_join_key", dtype=Float64
+ ), # Conflicts with the defined entity
+ ],
+ source=file_source,
+ )
+
+ # There should be no error here.
+ _ = FeatureView(
+ name="test1",
+ entities=[entity1],
+ schema=[
+ Field(name="int64_col", dtype=Int64),
+ Field(name="id_join_key", dtype=Int64), # Conflicts with the defined entity
+ ],
+ source=file_source,
+ )
+
+
def test_feature_view_inference_on_entity_columns(simple_dataset_1):
"""
Tests that feature view inference correctly infers entity columns.
diff --git a/sdk/python/tests/unit/test_feature_views.py b/sdk/python/tests/unit/test_feature_views.py
index 0fe3f839e1f..379396e5c63 100644
--- a/sdk/python/tests/unit/test_feature_views.py
+++ b/sdk/python/tests/unit/test_feature_views.py
@@ -15,6 +15,20 @@
from feast.types import Float32
+def test_create_feature_view_with_conflicting_entities():
+ user1 = Entity(name="user1", join_keys=["user_id"])
+ user2 = Entity(name="user2", join_keys=["user_id"])
+ batch_source = FileSource(path="some path")
+
+ with pytest.raises(ValueError):
+ _ = FeatureView(
+ name="test",
+ entities=[user1, user2],
+ ttl=timedelta(days=30),
+ source=batch_source,
+ )
+
+
def test_create_batch_feature_view():
batch_source = FileSource(path="some path")
BatchFeatureView(
diff --git a/sdk/python/tests/utils/e2e_test_validation.py b/sdk/python/tests/utils/e2e_test_validation.py
index e2b8b14eb47..43bdbefc004 100644
--- a/sdk/python/tests/utils/e2e_test_validation.py
+++ b/sdk/python/tests/utils/e2e_test_validation.py
@@ -112,8 +112,17 @@ def _check_offline_and_online_features(
full_feature_names=full_feature_names,
).to_dict()
- if full_feature_names:
+ # Wait for materialization to occur
+ if not response_dict[f"{fv.name}__value"][0]:
+ # Deal with flake with a retry
+ time.sleep(10)
+ response_dict = fs.get_online_features(
+ [f"{fv.name}:value"],
+ [{"driver_id": driver_id}],
+ full_feature_names=full_feature_names,
+ ).to_dict()
+ if full_feature_names:
if expected_value:
assert response_dict[f"{fv.name}__value"][0], f"Response: {response_dict}"
assert (
diff --git a/ui/package.json b/ui/package.json
index 7f0e7c3fbe1..73165523252 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@feast-dev/feast-ui",
- "version": "0.24.0",
+ "version": "0.25.0",
"private": false,
"files": [
"dist"
diff --git a/ui/src/pages/feature-services/FeatureServiceIndexEmptyState.tsx b/ui/src/pages/feature-services/FeatureServiceIndexEmptyState.tsx
index 40778680a3f..b5120325d08 100644
--- a/ui/src/pages/feature-services/FeatureServiceIndexEmptyState.tsx
+++ b/ui/src/pages/feature-services/FeatureServiceIndexEmptyState.tsx
@@ -17,7 +17,7 @@ const FeatureServiceIndexEmptyState = () => {
{
window.open(
- "https://docs.feast.dev/getting-started/concepts/feature-service",
+ "https://docs.feast.dev/getting-started/concepts/feature-retrieval#feature-services",
"_blank"
);
}}