From 5becea33be082d4724c29527bc87f09546a06ac2 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Wed, 7 May 2025 19:58:55 -0400 Subject: [PATCH] Revert "feat: Add CLI, SDK, and API documentation page to Feast UI (#5337)" This reverts commit 203e8889d2d75080680491905f05fe200ba38f21. --- ui/package.json | 1 - .../docs/reference/alpha-vector-database.md | 225 ------- ui/public/docs/reference/alpha-web-ui.md | 143 ---- .../reference/batch-materialization/README.md | 11 - .../reference/batch-materialization/lambda.md | 24 - .../batch-materialization/snowflake.md | 28 - .../reference/batch-materialization/spark.md | 55 -- .../reference/beta-on-demand-feature-view.md | 281 -------- .../docs/reference/codebase-structure.md | 136 ---- .../docs/reference/compute-engine/README.md | 120 ---- .../docs/reference/data-sources/README.md | 59 -- .../docs/reference/data-sources/bigquery.md | 37 - .../docs/reference/data-sources/clickhouse.md | 36 - .../docs/reference/data-sources/couchbase.md | 37 - ui/public/docs/reference/data-sources/file.md | 25 - .../docs/reference/data-sources/kafka.md | 75 --- .../docs/reference/data-sources/kinesis.md | 74 -- .../docs/reference/data-sources/mssql.md | 29 - .../docs/reference/data-sources/overview.md | 31 - .../docs/reference/data-sources/postgres.md | 35 - ui/public/docs/reference/data-sources/push.md | 79 --- .../docs/reference/data-sources/redshift.md | 37 - .../docs/reference/data-sources/snowflake.md | 50 -- .../docs/reference/data-sources/spark.md | 59 -- .../docs/reference/data-sources/trino.md | 34 - ui/public/docs/reference/denormalized.md | 121 ---- ui/public/docs/reference/dqm.md | 77 --- .../docs/reference/feast-cli-commands.md | 377 ----------- ui/public/docs/reference/feast-ignore.md | 33 - .../docs/reference/feature-repository.md | 126 ---- .../reference/feature-repository/README.md | 130 ---- .../feature-repository/feast-ignore.md | 32 - .../feature-repository/feature-store-yaml.md | 29 - .../registration-inferencing.md | 7 - .../docs/reference/feature-servers/README.md | 19 - .../feature-servers/offline-feature-server.md | 60 -- .../feature-servers/python-feature-server.md | 240 ------- .../feature-servers/registry-server.md | 26 - .../docs/reference/feature-store-yaml.md | 132 ---- .../docs/reference/offline-stores/README.md | 47 -- .../docs/reference/offline-stores/bigquery.md | 60 -- .../reference/offline-stores/clickhouse.md | 69 -- .../reference/offline-stores/couchbase.md | 79 --- .../docs/reference/offline-stores/dask.md | 55 -- .../docs/reference/offline-stores/duckdb.md | 56 -- .../docs/reference/offline-stores/mssql.md | 62 -- .../docs/reference/offline-stores/overview.md | 58 -- .../docs/reference/offline-stores/postgres.md | 76 --- .../docs/reference/offline-stores/redshift.md | 179 ----- .../offline-stores/remote-offline-store.md | 31 - .../reference/offline-stores/snowflake.md | 82 --- .../docs/reference/offline-stores/spark.md | 72 -- .../docs/reference/offline-stores/trino.md | 108 --- .../docs/reference/online-stores/README.md | 71 -- .../docs/reference/online-stores/bigtable.md | 56 -- .../docs/reference/online-stores/cassandra.md | 92 --- .../docs/reference/online-stores/couchbase.md | 78 --- .../docs/reference/online-stores/datastore.md | 50 -- .../docs/reference/online-stores/dragonfly.md | 90 --- .../docs/reference/online-stores/dynamodb.md | 84 --- .../reference/online-stores/elasticsearch.md | 139 ---- .../docs/reference/online-stores/hazelcast.md | 58 -- ui/public/docs/reference/online-stores/ikv.md | 69 -- .../docs/reference/online-stores/milvus.md | 65 -- .../docs/reference/online-stores/mysql.md | 55 -- .../docs/reference/online-stores/overview.md | 54 -- .../docs/reference/online-stores/postgres.md | 95 --- .../docs/reference/online-stores/qdrant.md | 80 --- .../docs/reference/online-stores/redis.md | 106 --- .../docs/reference/online-stores/remote.md | 30 - .../docs/reference/online-stores/scylladb.md | 94 --- .../reference/online-stores/singlestore.md | 51 -- .../docs/reference/online-stores/snowflake.md | 78 --- .../docs/reference/online-stores/sqlite.md | 49 -- ui/public/docs/reference/providers/README.md | 11 - .../providers/amazon-web-services.md | 32 - ui/public/docs/reference/providers/azure.md | 29 - .../providers/google-cloud-platform.md | 30 - ui/public/docs/reference/providers/local.md | 16 - ui/public/docs/reference/registries/README.md | 23 - ui/public/docs/reference/registries/gcs.md | 23 - ui/public/docs/reference/registries/local.md | 23 - ui/public/docs/reference/registries/remote.md | 28 - ui/public/docs/reference/registries/s3.md | 23 - .../docs/reference/registries/snowflake.md | 30 - ui/public/docs/reference/registries/sql.md | 89 --- .../registry/registry-permissions.md | 45 -- ui/public/docs/reference/type-system.md | 41 -- ui/public/docs/reference/ui.png | Bin 174770 -> 0 bytes ui/rollup.config.js | 47 +- ui/src/FeastUISansProviders.tsx | 9 - ui/src/pages/Sidebar.tsx | 9 - .../pages/documentation/APIDocumentation.tsx | 56 -- .../pages/documentation/CLIDocumentation.tsx | 56 -- ui/src/pages/documentation/Index.tsx | 85 --- .../pages/documentation/SDKDocumentation.tsx | 56 -- ui/src/pages/documentation/styles.css | 61 -- ui/src/services/DocumentationService.ts | 223 ------ ui/yarn.lock | 633 +----------------- 99 files changed, 38 insertions(+), 7348 deletions(-) delete mode 100644 ui/public/docs/reference/alpha-vector-database.md delete mode 100644 ui/public/docs/reference/alpha-web-ui.md delete mode 100644 ui/public/docs/reference/batch-materialization/README.md delete mode 100644 ui/public/docs/reference/batch-materialization/lambda.md delete mode 100644 ui/public/docs/reference/batch-materialization/snowflake.md delete mode 100644 ui/public/docs/reference/batch-materialization/spark.md delete mode 100644 ui/public/docs/reference/beta-on-demand-feature-view.md delete mode 100644 ui/public/docs/reference/codebase-structure.md delete mode 100644 ui/public/docs/reference/compute-engine/README.md delete mode 100644 ui/public/docs/reference/data-sources/README.md delete mode 100644 ui/public/docs/reference/data-sources/bigquery.md delete mode 100644 ui/public/docs/reference/data-sources/clickhouse.md delete mode 100644 ui/public/docs/reference/data-sources/couchbase.md delete mode 100644 ui/public/docs/reference/data-sources/file.md delete mode 100644 ui/public/docs/reference/data-sources/kafka.md delete mode 100644 ui/public/docs/reference/data-sources/kinesis.md delete mode 100644 ui/public/docs/reference/data-sources/mssql.md delete mode 100644 ui/public/docs/reference/data-sources/overview.md delete mode 100644 ui/public/docs/reference/data-sources/postgres.md delete mode 100644 ui/public/docs/reference/data-sources/push.md delete mode 100644 ui/public/docs/reference/data-sources/redshift.md delete mode 100644 ui/public/docs/reference/data-sources/snowflake.md delete mode 100644 ui/public/docs/reference/data-sources/spark.md delete mode 100644 ui/public/docs/reference/data-sources/trino.md delete mode 100644 ui/public/docs/reference/denormalized.md delete mode 100644 ui/public/docs/reference/dqm.md delete mode 100644 ui/public/docs/reference/feast-cli-commands.md delete mode 100644 ui/public/docs/reference/feast-ignore.md delete mode 100644 ui/public/docs/reference/feature-repository.md delete mode 100644 ui/public/docs/reference/feature-repository/README.md delete mode 100644 ui/public/docs/reference/feature-repository/feast-ignore.md delete mode 100644 ui/public/docs/reference/feature-repository/feature-store-yaml.md delete mode 100644 ui/public/docs/reference/feature-repository/registration-inferencing.md delete mode 100644 ui/public/docs/reference/feature-servers/README.md delete mode 100644 ui/public/docs/reference/feature-servers/offline-feature-server.md delete mode 100644 ui/public/docs/reference/feature-servers/python-feature-server.md delete mode 100644 ui/public/docs/reference/feature-servers/registry-server.md delete mode 100644 ui/public/docs/reference/feature-store-yaml.md delete mode 100644 ui/public/docs/reference/offline-stores/README.md delete mode 100644 ui/public/docs/reference/offline-stores/bigquery.md delete mode 100644 ui/public/docs/reference/offline-stores/clickhouse.md delete mode 100644 ui/public/docs/reference/offline-stores/couchbase.md delete mode 100644 ui/public/docs/reference/offline-stores/dask.md delete mode 100644 ui/public/docs/reference/offline-stores/duckdb.md delete mode 100644 ui/public/docs/reference/offline-stores/mssql.md delete mode 100644 ui/public/docs/reference/offline-stores/overview.md delete mode 100644 ui/public/docs/reference/offline-stores/postgres.md delete mode 100644 ui/public/docs/reference/offline-stores/redshift.md delete mode 100644 ui/public/docs/reference/offline-stores/remote-offline-store.md delete mode 100644 ui/public/docs/reference/offline-stores/snowflake.md delete mode 100644 ui/public/docs/reference/offline-stores/spark.md delete mode 100644 ui/public/docs/reference/offline-stores/trino.md delete mode 100644 ui/public/docs/reference/online-stores/README.md delete mode 100644 ui/public/docs/reference/online-stores/bigtable.md delete mode 100644 ui/public/docs/reference/online-stores/cassandra.md delete mode 100644 ui/public/docs/reference/online-stores/couchbase.md delete mode 100644 ui/public/docs/reference/online-stores/datastore.md delete mode 100644 ui/public/docs/reference/online-stores/dragonfly.md delete mode 100644 ui/public/docs/reference/online-stores/dynamodb.md delete mode 100644 ui/public/docs/reference/online-stores/elasticsearch.md delete mode 100644 ui/public/docs/reference/online-stores/hazelcast.md delete mode 100644 ui/public/docs/reference/online-stores/ikv.md delete mode 100644 ui/public/docs/reference/online-stores/milvus.md delete mode 100644 ui/public/docs/reference/online-stores/mysql.md delete mode 100644 ui/public/docs/reference/online-stores/overview.md delete mode 100644 ui/public/docs/reference/online-stores/postgres.md delete mode 100644 ui/public/docs/reference/online-stores/qdrant.md delete mode 100644 ui/public/docs/reference/online-stores/redis.md delete mode 100644 ui/public/docs/reference/online-stores/remote.md delete mode 100644 ui/public/docs/reference/online-stores/scylladb.md delete mode 100644 ui/public/docs/reference/online-stores/singlestore.md delete mode 100644 ui/public/docs/reference/online-stores/snowflake.md delete mode 100644 ui/public/docs/reference/online-stores/sqlite.md delete mode 100644 ui/public/docs/reference/providers/README.md delete mode 100644 ui/public/docs/reference/providers/amazon-web-services.md delete mode 100644 ui/public/docs/reference/providers/azure.md delete mode 100644 ui/public/docs/reference/providers/google-cloud-platform.md delete mode 100644 ui/public/docs/reference/providers/local.md delete mode 100644 ui/public/docs/reference/registries/README.md delete mode 100644 ui/public/docs/reference/registries/gcs.md delete mode 100644 ui/public/docs/reference/registries/local.md delete mode 100644 ui/public/docs/reference/registries/remote.md delete mode 100644 ui/public/docs/reference/registries/s3.md delete mode 100644 ui/public/docs/reference/registries/snowflake.md delete mode 100644 ui/public/docs/reference/registries/sql.md delete mode 100644 ui/public/docs/reference/registry/registry-permissions.md delete mode 100644 ui/public/docs/reference/type-system.md delete mode 100644 ui/public/docs/reference/ui.png delete mode 100644 ui/src/pages/documentation/APIDocumentation.tsx delete mode 100644 ui/src/pages/documentation/CLIDocumentation.tsx delete mode 100644 ui/src/pages/documentation/Index.tsx delete mode 100644 ui/src/pages/documentation/SDKDocumentation.tsx delete mode 100644 ui/src/pages/documentation/styles.css delete mode 100644 ui/src/services/DocumentationService.ts diff --git a/ui/package.json b/ui/package.json index 0a0f129389c..94bc5f6fc6a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,7 +37,6 @@ "query-string": "^7.1.1", "react-app-polyfill": "^3.0.0", "react-code-blocks": "^0.1.6", - "react-markdown": "^10.1.0", "react-query": "^3.39.3", "react-router-dom": "^6.28.0", "reactflow": "^11.11.4", diff --git a/ui/public/docs/reference/alpha-vector-database.md b/ui/public/docs/reference/alpha-vector-database.md deleted file mode 100644 index 861c3fcb114..00000000000 --- a/ui/public/docs/reference/alpha-vector-database.md +++ /dev/null @@ -1,225 +0,0 @@ -# [Alpha] Vector Database -**Warning**: This is an _experimental_ feature. To our knowledge, this is stable, but there are still rough edges in the experience. Contributions are welcome! - -## Overview -Vector database allows user to store and retrieve embeddings. Feast provides general APIs to store and retrieve embeddings. - -## Integration -Below are supported vector databases and implemented features: - -| Vector Database | Retrieval | Indexing | V2 Support* | Online Read | -|-----------------|-----------|----------|-------------|-------------| -| Pgvector | [x] | [ ] | [] | [] | -| Elasticsearch | [x] | [x] | [] | [] | -| Milvus | [x] | [x] | [x] | [x] | -| Faiss | [ ] | [ ] | [] | [] | -| SQLite | [x] | [ ] | [x] | [x] | -| Qdrant | [x] | [x] | [] | [] | - -*Note: V2 Support means the SDK supports retrieval of features along with vector embeddings from vector similarity search. - -Note: SQLite is in limited access and only working on Python 3.10. It will be updated as [sqlite_vec](https://github.com/asg017/sqlite-vec/) progresses. - -{% hint style="danger" %} -We will be deprecating the `retrieve_online_documents` method in the SDK in the future. -We recommend using the `retrieve_online_documents_v2` method instead, which offers easier vector index configuration -directly in the Feature View and the ability to retrieve standard features alongside your vector embeddings for richer context injection. - -Long term we will collapse the two methods into one, but for now, we recommend using the `retrieve_online_documents_v2` method. -Beyond that, we will then have `retrieve_online_documents` and `retrieve_online_documents_v2` simply point to `get_online_features` for -backwards compatibility and the adopt industry standard naming conventions. -{% endhint %} - -**Note**: Milvus and SQLite implement the v2 `retrieve_online_documents_v2` method in the SDK. This will be the longer-term solution so that Data Scientists can easily enable vector similarity search by just flipping a flag. - -## Examples - -- See the v0 [Rag Demo](https://github.com/feast-dev/feast-workshop/blob/rag/module_4_rag) for an example on how to use vector database using the `retrieve_online_documents` method (planning migration and deprecation (planning migration and deprecation). -- See the v1 [Milvus Quickstart](../../examples/rag/milvus-quickstart.ipynb) for a quickstart guide on how to use Feast with Milvus using the `retrieve_online_documents_v2` method. - -### **Prepare offline embedding dataset** -Run the following commands to prepare the embedding dataset: -```shell -python pull_states.py -python batch_score_documents.py -``` -The output will be stored in `data/city_wikipedia_summaries.csv.` - -### **Initialize Feast feature store and materialize the data to the online store** -Use the feature_store.yaml file to initialize the feature store. This will use the data as offline store, and Milvus as online store. - -```yaml -project: local_rag -provider: local -registry: data/registry.db -online_store: - type: milvus - path: data/online_store.db - vector_enabled: true - embedding_dim: 384 - index_type: "IVF_FLAT" - - -offline_store: - type: file -entity_key_serialization_version: 3 -# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details. -auth: - type: no_auth -``` -Run the following command in terminal to apply the feature store configuration: - -```shell -feast apply -``` - -Note that when you run `feast apply` you are going to apply the following Feature View that we will use for retrieval later: - -```python -document_embeddings = FeatureView( - name="embedded_documents", - entities=[item, author], - schema=[ - Field( - name="vector", - dtype=Array(Float32), - # Look how easy it is to enable RAG! - vector_index=True, - vector_search_metric="COSINE", - ), - Field(name="item_id", dtype=Int64), - Field(name="author_id", dtype=String), - Field(name="created_timestamp", dtype=UnixTimestamp), - Field(name="sentence_chunks", dtype=String), - Field(name="event_timestamp", dtype=UnixTimestamp), - ], - source=rag_documents_source, - ttl=timedelta(hours=24), -) -``` - -Let's use the SDK to write a data frame of embeddings to the online store: -```python -store.write_to_online_store(feature_view_name='city_embeddings', df=df) -``` - -### **Prepare a query embedding** -During inference (e.g., during when a user submits a chat message) we need to embed the input text. This can be thought of as a feature transformation of the input data. In this example, we'll do this with a small Sentence Transformer from Hugging Face. - -```python -import torch -import torch.nn.functional as F -from feast import FeatureStore -from pymilvus import MilvusClient, DataType, FieldSchema -from transformers import AutoTokenizer, AutoModel -from example_repo import city_embeddings_feature_view, item - -TOKENIZER = "sentence-transformers/all-MiniLM-L6-v2" -MODEL = "sentence-transformers/all-MiniLM-L6-v2" - -def mean_pooling(model_output, attention_mask): - token_embeddings = model_output[ - 0 - ] # First element of model_output contains all token embeddings - input_mask_expanded = ( - attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() - ) - return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( - input_mask_expanded.sum(1), min=1e-9 - ) - -def run_model(sentences, tokenizer, model): - encoded_input = tokenizer( - sentences, padding=True, truncation=True, return_tensors="pt" - ) - # Compute token embeddings - with torch.no_grad(): - model_output = model(**encoded_input) - - sentence_embeddings = mean_pooling(model_output, encoded_input["attention_mask"]) - sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1) - return sentence_embeddings - -question = "Which city has the largest population in New York?" - -tokenizer = AutoTokenizer.from_pretrained(TOKENIZER) -model = AutoModel.from_pretrained(MODEL) -query_embedding = run_model(question, tokenizer, model).detach().cpu().numpy().tolist()[0] -``` - -### **Retrieve the top K similar documents** -First create a feature store instance, and use the `retrieve_online_documents_v2` API to retrieve the top 5 similar documents to the specified query. - -```python -context_data = store.retrieve_online_documents_v2( - features=[ - "city_embeddings:vector", - "city_embeddings:item_id", - "city_embeddings:state", - "city_embeddings:sentence_chunks", - "city_embeddings:wiki_summary", - ], - query=query_embedding, - top_k=3, - distance_metric='COSINE', -).to_df() -``` -### **Generate the Response** -Let's assume we have a base prompt and a function that formats the retrieved documents called `format_documents` that we -can then use to generate the response with OpenAI's chat completion API. -```python -FULL_PROMPT = format_documents(rag_context_data, BASE_PROMPT) - -from openai import OpenAI - -client = OpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), -) -response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": FULL_PROMPT}, - {"role": "user", "content": question} - ], -) - -# And this will print the content. Look at the examples/rag/milvus-quickstart.ipynb for an end-to-end example. -print('\n'.join([c.message.content for c in response.choices])) -``` - -### Configuration and Installation - -We offer [Milvus](https://milvus.io/), [PGVector](https://github.com/pgvector/pgvector), [SQLite](https://github.com/asg017/sqlite-vec), [Elasticsearch](https://www.elastic.co) and [Qdrant](https://qdrant.tech/) as Online Store options for Vector Databases. - -Milvus offers a convenient local implementation for vector similarity search. To use Milvus, you can install the Feast package with the Milvus extra. - -#### Installation with Milvus - -```bash -pip install feast[milvus] -``` -#### Installation with Elasticsearch - -```bash -pip install feast[elasticsearch] -``` - -#### Installation with Qdrant - -```bash -pip install feast[qdrant] -``` -#### Installation with SQLite - -If you are using `pyenv` to manage your Python versions, you can install the SQLite extension with the following command: -```bash -PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" \ - LDFLAGS="-L/opt/homebrew/opt/sqlite/lib" \ - CPPFLAGS="-I/opt/homebrew/opt/sqlite/include" \ - pyenv install 3.10.14 -``` - -And you can the Feast install package via: -```bash -pip install feast[sqlite_vec] -``` diff --git a/ui/public/docs/reference/alpha-web-ui.md b/ui/public/docs/reference/alpha-web-ui.md deleted file mode 100644 index 80c5b824c5a..00000000000 --- a/ui/public/docs/reference/alpha-web-ui.md +++ /dev/null @@ -1,143 +0,0 @@ -# \[Beta] Web UI - -**Warning**: This is an _experimental_ feature. To our knowledge, this is stable, but there are still rough edges in the experience. Contributions are welcome! - -## Overview - -The Feast Web UI allows users to explore their feature repository through a Web UI. It includes functionality such as: - -* Browsing Feast objects (feature views, entities, data sources, feature services, and saved datasets) and their relationships -* Searching and filtering for Feast objects by tags - -![Sample UI](../../ui/sample.png) - -## Usage - -There are several ways to use the Feast Web UI. - -### Feast CLI - -The easiest way to get started is to run the `feast ui` command within a feature repository: - -Output of `feast ui --help`: - -```bash -Usage: feast ui [OPTIONS] - -Shows the Feast UI over the current directory - -Options: --h, --host TEXT Specify a host for the server [default: 0.0.0.0] --p, --port INTEGER Specify a port for the server [default: 8888] --r, --registry_ttl_sec INTEGER Number of seconds after which the registry is refreshed. Default is 5 seconds. ---help Show this message and exit. -``` - -This will spin up a Web UI on localhost which automatically refreshes its view of the registry every `registry_ttl_sec` - -### Importing as a module to integrate with an existing React App - -This is the recommended way to use Feast UI for teams maintaining their own internal UI for their deployment of Feast. - -Start with bootstrapping a React app with `create-react-app` - -``` -npx create-react-app your-feast-ui -``` - -Then, in your app folder, install Feast UI and optionally its peer dependencies. Assuming you use yarn - -``` -yarn add @feast-dev/feast-ui -# For custom UI using the Elastic UI Framework (optional): -yarn add @elastic/eui -# For general custom styling (optional): -yarn add @emotion/react -``` - -Edit `index.js` in the React app to use Feast UI. - -```js -import React from "react"; -import ReactDOM from "react-dom"; -import "./index.css"; - -import FeastUI from "@feast-dev/feast-ui"; -import "@feast-dev/feast-ui/dist/feast-ui.css"; - -ReactDOM.render( - - - , - document.getElementById("root") -); -``` - -When you start the React app, it will look for `projects-list.json` to find a list of your projects. The JSON should look something like this. - -```json -{ - "projects": [ - { - "name": "Credit Score Project", - "description": "Project for credit scoring team and associated models.", - "id": "credit_score_project", - "registryPath": "/registry.json" - } - ] -} -``` - -* **Note** - `registryPath` only supports a file location or a url. - -Then start the React App - -```bash -yarn start -``` - -#### Customization - -The advantage of importing Feast UI as a module is in the ease of customization. The `` component exposes a `feastUIConfigs` prop thorough which you can customize the UI. Currently it supports a few parameters. - -##### Fetching the Project List - -By default, the Feast UI fetches the project list from the app root path. You can use `projectListPromise` to provide a promise that overrides where it's fetched from. - -```jsx - { - return res.json(); - }) - }} -/> -``` - -##### Custom Tabs - -You can add custom tabs for any of the core Feast objects through the `tabsRegistry`. - -```jsx -const tabsRegistry = { - RegularFeatureViewCustomTabs: [ - { - label: "Custom Tab Demo", // Navigation Label for the tab - path: "demo-tab", // Subpath for the tab - Component: RFVDemoCustomTab, // a React Component - }, - ] -} - - -``` - -Examples of custom tabs can be found in the `ui/custom-tabs` folder. diff --git a/ui/public/docs/reference/batch-materialization/README.md b/ui/public/docs/reference/batch-materialization/README.md deleted file mode 100644 index a05d6d75e5d..00000000000 --- a/ui/public/docs/reference/batch-materialization/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Batch materialization - -Please see [Batch Materialization Engine](../../getting-started/components/batch-materialization-engine.md) for an explanation of batch materialization engines. - -{% page-ref page="snowflake.md" %} - -{% page-ref page="bytewax.md" %} - -{% page-ref page="lambda.md" %} - -{% page-ref page="spark.md" %} diff --git a/ui/public/docs/reference/batch-materialization/lambda.md b/ui/public/docs/reference/batch-materialization/lambda.md deleted file mode 100644 index 126d07c4103..00000000000 --- a/ui/public/docs/reference/batch-materialization/lambda.md +++ /dev/null @@ -1,24 +0,0 @@ -# AWS Lambda (alpha) - -## Description - -The AWS Lambda batch materialization engine is considered alpha status. It relies on the offline store to output feature values to S3 via `to_remote_storage`, and then loads them into the online store. - -See [LambdaMaterializationEngineConfig](https://rtd.feast.dev/en/master/index.html?highlight=LambdaMaterializationEngine#feast.infra.materialization.aws_lambda.lambda_engine.LambdaMaterializationEngineConfig) for configuration options. - -See also [Dockerfile](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/infra/materialization/aws_lambda/Dockerfile) for a Dockerfile that can be used below with `materialization_image`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -... -offline_store: - type: snowflake.offline -... -batch_engine: - type: lambda - lambda_role: [your iam role] - materialization_image: [image uri of above Docker image] -``` -{% endcode %} diff --git a/ui/public/docs/reference/batch-materialization/snowflake.md b/ui/public/docs/reference/batch-materialization/snowflake.md deleted file mode 100644 index c2fa441d6d2..00000000000 --- a/ui/public/docs/reference/batch-materialization/snowflake.md +++ /dev/null @@ -1,28 +0,0 @@ -# Snowflake - -## Description - -The [Snowflake](https://trial.snowflake.com) batch materialization engine provides a highly scalable and parallel execution engine using a Snowflake Warehouse for batch materializations operations (`materialize` and `materialize-incremental`) when using a `SnowflakeSource`. - -The engine requires no additional configuration other than for you to supply Snowflake's standard login and context details. The engine leverages custom (automatically deployed for you) Python UDFs to do the proper serialization of your offline store data to your online serving tables. - -When using all three options together, `snowflake.offline`, `snowflake.engine`, and `snowflake.online`, you get the most unique experience of unlimited scale and performance + governance and data security. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -... -offline_store: - type: snowflake.offline -... -batch_engine: - type: snowflake.engine - account: snowflake_deployment.us-east-1 - user: user_login - password: user_password - role: sysadmin - warehouse: demo_wh - database: FEAST -``` -{% endcode %} diff --git a/ui/public/docs/reference/batch-materialization/spark.md b/ui/public/docs/reference/batch-materialization/spark.md deleted file mode 100644 index 27a1388c48e..00000000000 --- a/ui/public/docs/reference/batch-materialization/spark.md +++ /dev/null @@ -1,55 +0,0 @@ -# Spark (alpha) - -## Description - -The Spark batch materialization engine is considered alpha status. It relies on the offline store to output feature values to S3 via `to_remote_storage`, and then loads them into the online store. - -See [SparkMaterializationEngine](https://rtd.feast.dev/en/master/index.html?highlight=SparkMaterializationEngine#feast.infra.materialization.spark.spark_materialization_engine.SparkMaterializationEngineConfig) for configuration options. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -... -offline_store: - type: snowflake.offline -... -batch_engine: - type: spark.engine - partitions: [optional num partitions to use to write to online store] -``` -{% endcode %} - -## Example in Python - -{% code title="feature_store.py" %} -```python -from feast import FeatureStore, RepoConfig -from feast.repo_config import RegistryConfig -from feast.infra.online_stores.dynamodb import DynamoDBOnlineStoreConfig -from feast.infra.offline_stores.contrib.spark_offline_store.spark import SparkOfflineStoreConfig - -repo_config = RepoConfig( - registry="s3://[YOUR_BUCKET]/feast-registry.db", - project="feast_repo", - provider="aws", - offline_store=SparkOfflineStoreConfig( - spark_conf={ - "spark.ui.enabled": "false", - "spark.eventLog.enabled": "false", - "spark.sql.catalogImplementation": "hive", - "spark.sql.parser.quotedRegexColumnNames": "true", - "spark.sql.session.timeZone": "UTC" - } - ), - batch_engine={ - "type": "spark.engine", - "partitions": 10 - }, - online_store=DynamoDBOnlineStoreConfig(region="us-west-1"), - entity_key_serialization_version=2 -) - -store = FeatureStore(config=repo_config) -``` -{% endcode %} \ No newline at end of file diff --git a/ui/public/docs/reference/beta-on-demand-feature-view.md b/ui/public/docs/reference/beta-on-demand-feature-view.md deleted file mode 100644 index c58eab1f40a..00000000000 --- a/ui/public/docs/reference/beta-on-demand-feature-view.md +++ /dev/null @@ -1,281 +0,0 @@ -# [Beta] On Demand Feature Views - -**Warning**: This is an experimental feature. While it is stable to our knowledge, there may still be rough edges in the experience. Contributions are welcome! - -## Overview - -On Demand Feature Views (ODFVs) allow data scientists to use existing features and request-time data to transform and -create new features. Users define transformation logic that is executed during both historical and online retrieval. -Additionally, ODFVs provide flexibility in applying transformations either during data ingestion (at write time) or -during feature retrieval (at read time), controlled via the `write_to_online_store` parameter. - -By setting `write_to_online_store=True`, transformations are applied during data ingestion, and the transformed -features are stored in the online store. This can improve online feature retrieval performance by reducing computation -during reads. Conversely, if `write_to_online_store=False` (the default if omitted), transformations are applied during -feature retrieval. - -### Why Use On Demand Feature Views? - -ODFVs enable data scientists to easily impact the online feature retrieval path. For example, a data scientist could: - -1. Call `get_historical_features` to generate a training dataset. -2. Iterate in a notebook and do your feature engineering using Pandas or native Python. -3. Copy transformation logic into ODFVs and commit to a development branch of the feature repository. -4. Verify with `get_historical_features` (on a small dataset) that the transformation gives the expected output over historical data. -5. Decide whether to apply the transformation on writes or on reads by setting the `write_to_online_store` parameter accordingly. -6. Verify with `get_online_features` on the development branch that the transformation correctly outputs online features. -7. Submit a pull request to the staging or production branches, impacting production traffic. - -## Transformation Modes - -When defining an ODFV, you can specify the transformation mode using the `mode` parameter. Feast supports the following modes: - -- **Pandas Mode (`mode="pandas"`)**: The transformation function takes a Pandas DataFrame as input and returns a Pandas DataFrame as output. This mode is useful for batch transformations over multiple rows. -- **Native Python Mode (`mode="python"`)**: The transformation function uses native Python and can operate on inputs as lists of values or as single dictionaries representing a singleton (single row). - -### Singleton Transformations in Native Python Mode - -Native Python mode supports transformations on singleton dictionaries by setting `singleton=True`. This allows you to -write transformation functions that operate on a single row at a time, making the code more intuitive and aligning with -how data scientists typically think about data transformations. - -## Example -See [https://github.com/feast-dev/on-demand-feature-views-demo](https://github.com/feast-dev/on-demand-feature-views-demo) for an example on how to use on demand feature views. - - -## Registering Transformations - -When defining an ODFV, you can control when the transformation is applied using the `write_to_online_store` parameter: - -- `write_to_online_store=True`: The transformation is applied during data ingestion (on write), and the transformed features are stored in the online store. -- `write_to_online_store=False` (default): The transformation is applied during feature retrieval (on read). - -### Examples - -#### Example 1: On Demand Transformation on Read Using Pandas Mode - -```python -from feast import Field, RequestSource, on_demand_feature_view -from feast.types import Float64, Int64 -import pandas as pd - -# Define a request data source for request-time features -input_request = RequestSource( - name="vals_to_add", - schema=[ - Field(name="val_to_add", dtype=Int64), - Field(name="val_to_add_2", dtype=Int64), - ], -) - -# Use input data and feature view features to create new features in Pandas mode -@on_demand_feature_view( - sources=[driver_hourly_stats_view, input_request], - schema=[ - Field(name="conv_rate_plus_val1", dtype=Float64), - Field(name="conv_rate_plus_val2", dtype=Float64), - ], - mode="pandas", -) -def transformed_conv_rate(features_df: pd.DataFrame) -> pd.DataFrame: - df = pd.DataFrame() - df["conv_rate_plus_val1"] = features_df["conv_rate"] + features_df["val_to_add"] - df["conv_rate_plus_val2"] = features_df["conv_rate"] + features_df["val_to_add_2"] - return df -``` - -#### Example 2: On Demand Transformation on Read Using Native Python Mode (List Inputs) - -```python -from feast import Field, on_demand_feature_view -from feast.types import Float64 -from typing import Any, Dict - -# Use input data and feature view features to create new features in Native Python mode -@on_demand_feature_view( - sources=[driver_hourly_stats_view, input_request], - schema=[ - Field(name="conv_rate_plus_val1_python", dtype=Float64), - Field(name="conv_rate_plus_val2_python", dtype=Float64), - ], - mode="python", -) -def transformed_conv_rate_python(inputs: Dict[str, Any]) -> Dict[str, Any]: - output = { - "conv_rate_plus_val1_python": [ - conv_rate + val_to_add - for conv_rate, val_to_add in zip(inputs["conv_rate"], inputs["val_to_add"]) - ], - "conv_rate_plus_val2_python": [ - conv_rate + val_to_add - for conv_rate, val_to_add in zip( - inputs["conv_rate"], inputs["val_to_add_2"] - ) - ], - } - return output -``` - -#### **New** Example 3: On Demand Transformation on Read Using Native Python Mode (Singleton Input) - -```python -from feast import Field, on_demand_feature_view -from feast.types import Float64 -from typing import Any, Dict - -# Use input data and feature view features to create new features in Native Python mode with singleton input -@on_demand_feature_view( - sources=[driver_hourly_stats_view, input_request], - schema=[ - Field(name="conv_rate_plus_acc_singleton", dtype=Float64), - ], - mode="python", - singleton=True, -) -def transformed_conv_rate_singleton(inputs: Dict[str, Any]) -> Dict[str, Any]: - output = { - "conv_rate_plus_acc_singleton": inputs["conv_rate"] + inputs["acc_rate"] - } - return output -``` - -In this example, `inputs` is a dictionary representing a single row, and the transformation function returns a dictionary of transformed features for that single row. This approach is more intuitive and aligns with how data scientists typically process single data records. - -#### Example 4: On Demand Transformation on Write Using Pandas Mode - -```python -from feast import Field, on_demand_feature_view -from feast.types import Float64 -import pandas as pd - -# Existing Feature View -driver_hourly_stats_view = ... - -# Define an ODFV applying transformation during write time -@on_demand_feature_view( - sources=[driver_hourly_stats_view], - schema=[ - Field(name="conv_rate_adjusted", dtype=Float64), - ], - mode="pandas", - write_to_online_store=True, # Apply transformation during write time -) -def transformed_conv_rate(features_df: pd.DataFrame) -> pd.DataFrame: - df = pd.DataFrame() - df["conv_rate_adjusted"] = features_df["conv_rate"] * 1.1 # Adjust conv_rate by 10% - return df -``` - -To ingest data with the new feature view, include all input features required for the transformations: - -```python -from feast import FeatureStore -import pandas as pd - -store = FeatureStore(repo_path=".") - -# Data to ingest -data = pd.DataFrame({ - "driver_id": [1001], - "event_timestamp": [pd.Timestamp.now()], - "conv_rate": [0.5], - "acc_rate": [0.8], - "avg_daily_trips": [10], -}) - -# Ingest data to the online store -store.push("driver_hourly_stats_view", data) -``` - -### Feature Retrieval - -{% hint style="info" %} -**Note**: The name of the on demand feature view is the function name (e.g., `transformed_conv_rate`). -{% endhint %} - -#### Offline Features - -Retrieve historical features by referencing individual features or using a feature service: - -```python -training_df = store.get_historical_features( - entity_df=entity_df, - features=[ - "driver_hourly_stats:conv_rate", - "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", - "transformed_conv_rate_singleton:conv_rate_plus_acc_singleton", - ], -).to_df() -``` - -#### Online Features - -Retrieve online features by referencing individual features or using a feature service: - -```python -entity_rows = [ - { - "driver_id": 1001, - "val_to_add": 1, - "val_to_add_2": 2, - } -] - -online_response = store.get_online_features( - entity_rows=entity_rows, - features=[ - "driver_hourly_stats:conv_rate", - "driver_hourly_stats:acc_rate", - "transformed_conv_rate_python:conv_rate_plus_val1_python", - "transformed_conv_rate_python:conv_rate_plus_val2_python", - "transformed_conv_rate_singleton:conv_rate_plus_acc_singleton", - ], -).to_dict() -``` - -### Materializing Pre-transformed Data - -In some scenarios, you may have already transformed your data in batch (e.g., using Spark or another batch processing framework) and want to directly materialize the pre-transformed features without applying transformations during ingestion. Feast supports this through the `transform_on_write` parameter. - -When using `write_to_online_store=True` with On Demand Feature Views, you can set `transform_on_write=False` to skip transformations during the write operation. This is particularly useful for optimizing performance when working with large pre-transformed datasets. - -```python -from feast import FeatureStore -import pandas as pd - -store = FeatureStore(repo_path=".") - -# Pre-transformed data (transformations already applied) -pre_transformed_data = pd.DataFrame({ - "driver_id": [1001], - "event_timestamp": [pd.Timestamp.now()], - "conv_rate": [0.5], - # Pre-calculated values for the transformed features - "conv_rate_adjusted": [0.55], # Already contains the adjusted value -}) - -# Write to online store, skipping transformations -store.write_to_online_store( - feature_view_name="transformed_conv_rate", - df=pre_transformed_data, - transform_on_write=False # Skip transformation during write -) -``` - -This approach allows for a hybrid workflow where you can: -1. Transform data in batch using powerful distributed processing tools -2. Materialize the pre-transformed data without reapplying transformations -3. Still use the Feature Server to execute transformations during API calls when needed - -Even when features are materialized with transformations skipped (`transform_on_write=False`), the feature server can still apply transformations during API calls for any missing values or for features that require real-time computation. - -## CLI Commands -There are new CLI commands to manage on demand feature views: - -feast on-demand-feature-views list: Lists all registered on demand feature views after feast apply is run. -feast on-demand-feature-views describe [NAME]: Describes the definition of an on demand feature view. - - diff --git a/ui/public/docs/reference/codebase-structure.md b/ui/public/docs/reference/codebase-structure.md deleted file mode 100644 index 7077e48fef3..00000000000 --- a/ui/public/docs/reference/codebase-structure.md +++ /dev/null @@ -1,136 +0,0 @@ -# Codebase structure - -Let's examine the Feast codebase. -This analysis is accurate as of Feast 0.23. - -``` -$ tree -L 1 -d -. -├── docs -├── examples -├── go -├── infra -├── java -├── protos -├── sdk -└── ui -``` - -## Python SDK - -The Python SDK lives in `sdk/python/feast`. -The majority of Feast logic lives in these Python files: -* The core Feast objects ([entities](../getting-started/concepts/entity.md), [feature views](../getting-started/concepts/feature-view.md), [data sources](../getting-started/concepts/dataset.md), etc.) are defined in their respective Python files, such as `entity.py`, `feature_view.py`, and `data_source.py`. -* The `FeatureStore` class is defined in `feature_store.py` and the associated configuration object (the Python representation of the `feature_store.yaml` file) are defined in `repo_config.py`. -* The CLI and other core feature store logic are defined in `cli.py` and `repo_operations.py`. -* The type system that is used to manage conversion between Feast types and external typing systems is managed in `type_map.py`. -* The Python feature server (the server that is started through the `feast serve` command) is defined in `feature_server.py`. - -There are also several important submodules: -* `infra/` contains all the infrastructure components, such as the provider, offline store, online store, batch materialization engine, and registry. -* `dqm/` covers data quality monitoring, such as the dataset profiler. -* `diff/` covers the logic for determining how to apply infrastructure changes upon feature repo changes (e.g. the output of `feast plan` and `feast apply`). -* `embedded_go/` covers the Go feature server. -* `ui/` contains the embedded Web UI, to be launched on the `feast ui` command. - -Of these submodules, `infra/` is the most important. -It contains the interfaces for the [provider](getting-started/components/provider.md), [offline store](getting-started/components/offline-store.md), [online store](getting-started/components/online-store.md), [batch materialization engine](getting-started/components/batch-materialization-engine.md), and [registry](getting-started/components/registry.md), as well as all of their individual implementations. - -``` -$ tree --dirsfirst -L 1 infra -infra -├── contrib -├── feature_servers -├── materialization -├── offline_stores -├── online_stores -├── registry -├── transformation_servers -├── utils -├── __init__.py -├── aws.py -├── gcp.py -├── infra_object.py -├── key_encoding_utils.py -├── local.py -├── passthrough_provider.py -└── provider.py -``` - -The tests for the Python SDK are contained in `sdk/python/tests`. -For more details, see this [overview](../how-to-guides/adding-or-reusing-tests.md#test-suite-overview) of the test suite. - -### Example flow: `feast apply` - -Let's walk through how `feast apply` works by tracking its execution across the codebase. - -1. All CLI commands are in `cli.py`. - Most of these commands are backed by methods in `repo_operations.py`. - The `feast apply` command triggers `apply_total_command`, which then calls `apply_total` in `repo_operations.py`. -2. With a `FeatureStore` object (from `feature_store.py`) that is initialized based on the `feature_store.yaml` in the current working directory, `apply_total` first parses the feature repo with `parse_repo` and then calls either `FeatureStore.apply` or `FeatureStore._apply_diffs` to apply those changes to the feature store. -3. Let's examine `FeatureStore.apply`. - It splits the objects based on class (e.g. `Entity`, `FeatureView`, etc.) and then calls the appropriate registry method to apply or delete the object. - For example, it might call `self._registry.apply_entity` to apply an entity. - If the default file-based registry is used, this logic can be found in `infra/registry/registry.py`. -4. Then the feature store must update its cloud infrastructure (e.g. online store tables) to match the new feature repo, so it calls `Provider.update_infra`, which can be found in `infra/provider.py`. -5. Assuming the provider is a built-in provider (e.g. one of the local, GCP, or AWS providers), it will call `PassthroughProvider.update_infra` in `infra/passthrough_provider.py`. -6. This delegates to the online store and batch materialization engine. - For example, if the feature store is configured to use the Redis online store then the `update` method from `infra/online_stores/redis.py` will be called. - And if the local materialization engine is configured then the `update` method from `infra/materialization/local_engine.py` will be called. - -At this point, the `feast apply` command is complete. - -### Example flow: `feast materialize` - -Let's walk through how `feast materialize` works by tracking its execution across the codebase. - -1. The `feast materialize` command triggers `materialize_command` in `cli.py`, which then calls `FeatureStore.materialize` from `feature_store.py`. -2. This then calls `Provider.materialize_single_feature_view`, which can be found in `infra/provider.py`. -3. As with `feast apply`, the provider is most likely backed by the passthrough provider, in which case `PassthroughProvider.materialize_single_feature_view` will be called. -4. This delegates to the underlying batch materialization engine. - Assuming that the local engine has been configured, `LocalMaterializationEngine.materialize` from `infra/materialization/local_engine.py` will be called. -5. Since materialization involves reading features from the offline store and writing them to the online store, the local engine will delegate to both the offline store and online store. - Specifically, it will call `OfflineStore.pull_latest_from_table_or_query` and `OnlineStore.online_write_batch`. - These two calls will be routed to the offline store and online store that have been configured. - -### Example flow: `get_historical_features` - -Let's walk through how `get_historical_features` works by tracking its execution across the codebase. - -1. We start with `FeatureStore.get_historical_features` in `feature_store.py`. - This method does some internal preparation, and then delegates the actual execution to the underlying provider by calling `Provider.get_historical_features`, which can be found in `infra/provider.py`. -2. As with `feast apply`, the provider is most likely backed by the passthrough provider, in which case `PassthroughProvider.get_historical_features` will be called. -3. That call simply delegates to `OfflineStore.get_historical_features`. - So if the feature store is configured to use Snowflake as the offline store, `SnowflakeOfflineStore.get_historical_features` will be executed. - -## Java SDK - -The `java/` directory contains the Java serving component. -See [here](https://github.com/feast-dev/feast/blob/master/java/CONTRIBUTING.md) for more details on how the repo is structured. - -## Go feature server - -The `go/` directory contains the Go feature server. -Most of the files here have logic to help with reading features from the online store. -Within `go/`, the `internal/feast/` directory contains most of the core logic: -* `onlineserving/` covers the core serving logic. -* `model/` contains the implementations of the Feast objects (entity, feature view, etc.). - * For example, `entity.go` is the Go equivalent of `entity.py`. It contains a very simple Go implementation of the entity object. -* `registry/` covers the registry. - * Currently only the file-based registry supported (the sql-based registry is unsupported). Additionally, the file-based registry only supports a file-based registry store, not the GCS or S3 registry stores. -* `onlinestore/` covers the online stores (currently only Redis and SQLite are supported). - -## Protobufs - -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. -See [here](https://github.com/feast-dev/feast/blob/master/ui/CONTRIBUTING.md) for more details on the structure of the Web UI. diff --git a/ui/public/docs/reference/compute-engine/README.md b/ui/public/docs/reference/compute-engine/README.md deleted file mode 100644 index 75f29890046..00000000000 --- a/ui/public/docs/reference/compute-engine/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# 🧠 ComputeEngine (WIP) - -The `ComputeEngine` is Feast’s pluggable abstraction for executing feature pipelines — including transformations, aggregations, joins, and materializations/get_historical_features — on a backend of your choice (e.g., Spark, PyArrow, Pandas, Ray). - -It powers both: - -- `materialize()` – for batch and stream generation of features to offline/online stores -- `get_historical_features()` – for point-in-time correct training dataset retrieval - -This system builds and executes DAGs (Directed Acyclic Graphs) of typed operations, enabling modular and scalable workflows. - ---- - -## 🧠 Core Concepts - -| Component | Description | -|--------------------|----------------------------------------------------------------------| -| `ComputeEngine` | Interface for executing materialization and retrieval tasks | -| `FeatureBuilder` | Constructs a DAG from Feature View definition for a specific backend | -| `DAGNode` | Represents a logical operation (read, aggregate, join, etc.) | -| `ExecutionPlan` | Executes nodes in dependency order and stores intermediate outputs | -| `ExecutionContext` | Holds config, registry, stores, entity data, and node outputs | - ---- - -## ✨ Available Engines - -### 🔥 SparkComputeEngine - -- Distributed DAG execution via Apache Spark -- Supports point-in-time joins and large-scale materialization -- Integrates with `SparkOfflineStore` and `SparkMaterializationJob` - -### 🧪 LocalComputeEngine - -- Runs on Arrow + Specified backend (e.g., Pandas, Polars) -- Designed for local dev, testing, or lightweight feature generation -- Supports `LocalMaterializationJob` and `LocalHistoricalRetrievalJob` - ---- - -## 🛠️ Feature Builder Flow -```markdown -SourceReadNode - | - v -JoinNode (Only for get_historical_features with entity df) - | - v -FilterNode (Always included; applies TTL or user-defined filters) - | - v -AggregationNode (If aggregations are defined in FeatureView) - | - v -DeduplicationNode (If no aggregation is defined for get_historical_features) - | - v -TransformationNode (If feature_transformation is defined) - | - v -ValidationNode (If enable_validation = True) - | - v -Output - ├──> RetrievalOutput (For get_historical_features) - └──> OnlineStoreWrite / OfflineStoreWrite (For materialize) -``` - -Each step is implemented as a `DAGNode`. An `ExecutionPlan` executes these nodes in topological order, caching `DAGValue` outputs. - ---- - -## 🧩 Implementing a Custom Compute Engine - -To create your own compute engine: - -1. **Implement the interface** - -```python -from feast.infra.compute_engines.base import ComputeEngine -from feast.infra.materialization.batch_materialization_engine import MaterializationTask, MaterializationJob -from feast.infra.compute_engines.tasks import HistoricalRetrievalTask -class MyComputeEngine(ComputeEngine): - def materialize(self, task: MaterializationTask) -> MaterializationJob: - ... - - def get_historical_features(self, task: HistoricalRetrievalTask) -> RetrievalJob: - ... -``` - -2. Create a FeatureBuilder -```python -from feast.infra.compute_engines.feature_builder import FeatureBuilder - -class CustomFeatureBuilder(FeatureBuilder): - def build_source_node(self): ... - def build_aggregation_node(self, input_node): ... - def build_join_node(self, input_node): ... - def build_filter_node(self, input_node): - def build_dedup_node(self, input_node): - def build_transformation_node(self, input_node): ... - def build_output_nodes(self, input_node): ... -``` - -3. Define DAGNode subclasses - * ReadNode, AggregationNode, JoinNode, WriteNode, etc. - * Each DAGNode.execute(context) -> DAGValue - -4. Return an ExecutionPlan - * ExecutionPlan stores DAG nodes in topological order - * Automatically handles intermediate value caching - -## 🚧 Roadmap -- [x] Modular, backend-agnostic DAG execution framework -- [x] Spark engine with native support for materialization + PIT joins -- [ ] PyArrow + Pandas engine for local compute -- [ ] Native multi-feature-view DAG optimization -- [ ] DAG validation, metrics, and debug output -- [ ] Scalable distributed backend via Ray or Polars diff --git a/ui/public/docs/reference/data-sources/README.md b/ui/public/docs/reference/data-sources/README.md deleted file mode 100644 index 151a948d0af..00000000000 --- a/ui/public/docs/reference/data-sources/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Data sources - -Please see [Data Source](../../getting-started/concepts/data-ingestion.md) for a conceptual explanation of data sources. - -{% content-ref url="overview.md" %} -[overview.md](overview.md) -{% endcontent-ref %} - -{% content-ref url="file.md" %} -[file.md](file.md) -{% endcontent-ref %} - -{% content-ref url="snowflake.md" %} -[snowflake.md](snowflake.md) -{% endcontent-ref %} - -{% content-ref url="bigquery.md" %} -[bigquery.md](bigquery.md) -{% endcontent-ref %} - -{% content-ref url="redshift.md" %} -[redshift.md](redshift.md) -{% endcontent-ref %} - -{% content-ref url="push.md" %} -[push.md](push.md) -{% endcontent-ref %} - -{% content-ref url="kafka.md" %} -[kafka.md](kafka.md) -{% endcontent-ref %} - -{% content-ref url="kinesis.md" %} -[kinesis.md](kinesis.md) -{% endcontent-ref %} - -{% content-ref url="couchbase.md" %} -[couchbase.md](couchbase.md) -{% endcontent-ref %} - -{% content-ref url="spark.md" %} -[spark.md](spark.md) -{% endcontent-ref %} - -{% content-ref url="postgres.md" %} -[postgres.md](postgres.md) -{% endcontent-ref %} - -{% content-ref url="trino.md" %} -[trino.md](trino.md) -{% endcontent-ref %} - -{% content-ref url="mssql.md" %} -[mssql.md](mssql.md) -{% endcontent-ref %} - -{% content-ref url="clickhouse.md" %} -[clickhouse.md](clickhouse.md) -{% endcontent-ref %} diff --git a/ui/public/docs/reference/data-sources/bigquery.md b/ui/public/docs/reference/data-sources/bigquery.md deleted file mode 100644 index 51c9b19ecdb..00000000000 --- a/ui/public/docs/reference/data-sources/bigquery.md +++ /dev/null @@ -1,37 +0,0 @@ -# BigQuery source - -## Description - -BigQuery data sources are BigQuery tables or views. -These can be specified either by a table reference or a SQL query. -However, no performance guarantees can be provided for SQL query-based sources, so table references are recommended. - -## Examples - -Using a table reference: - -```python -from feast import BigQuerySource - -my_bigquery_source = BigQuerySource( - table_ref="gcp_project:bq_dataset.bq_table", -) -``` - -Using a query: - -```python -from feast import BigQuerySource - -BigQuerySource( - query="SELECT timestamp as ts, created, f1, f2 " - "FROM `my_project.my_dataset.my_features`", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/latest/index.html#feast.infra.offline_stores.bigquery_source.BigQuerySource). - -## Supported Types - -BigQuery data sources support all eight primitive types and their corresponding array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/clickhouse.md b/ui/public/docs/reference/data-sources/clickhouse.md deleted file mode 100644 index 7630d5dd14a..00000000000 --- a/ui/public/docs/reference/data-sources/clickhouse.md +++ /dev/null @@ -1,36 +0,0 @@ -# Clickhouse source (contrib) - -## Description - -Clickhouse data sources are Clickhouse tables or views. -These can be specified either by a table reference or a SQL query. - -## Disclaimer - -The Clickhouse data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Defining a Clickhouse source: - -```python -from feast.infra.offline_stores.contrib.clickhouse_offline_store.clickhouse_source import ( - ClickhouseSource, -) - -driver_stats_source = ClickhouseSource( - name="feast_driver_hourly_stats", - query="SELECT * FROM feast_driver_hourly_stats", - timestamp_field="event_timestamp", - created_timestamp_column="created", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.clickhouse_offline_store.clickhouse_source.ClickhouseSource). - -## Supported Types - -Clickhouse data sources support all eight primitive types and their corresponding array types. -The support for Clickhouse Decimal type is achieved by converting it to double. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/couchbase.md b/ui/public/docs/reference/data-sources/couchbase.md deleted file mode 100644 index 596e33cf50d..00000000000 --- a/ui/public/docs/reference/data-sources/couchbase.md +++ /dev/null @@ -1,37 +0,0 @@ -# Couchbase Columnar source (contrib) - -## Description - -Couchbase Columnar data sources are [Couchbase Capella Columnar](https://docs.couchbase.com/columnar/intro/intro.html) collections that can be used as a source for feature data. **Note that Couchbase Columnar is available through [Couchbase Capella](https://cloud.couchbase.com/).** - -## Disclaimer - -The Couchbase Columnar data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Defining a Couchbase Columnar source: - -```python -from feast.infra.offline_stores.contrib.couchbase_offline_store.couchbase_source import ( - CouchbaseColumnarSource, -) - -driver_stats_source = CouchbaseColumnarSource( - name="driver_hourly_stats_source", - query="SELECT * FROM Default.Default.`feast_driver_hourly_stats`", - database="Default", - scope="Default", - collection="feast_driver_hourly_stats", - timestamp_field="event_timestamp", - created_timestamp_column="created", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.couchbase_offline_store.couchbase_source.CouchbaseColumnarSource). - -## Supported Types - -Couchbase Capella Columnar data sources support `BOOLEAN`, `STRING`, `BIGINT`, and `DOUBLE` primitive types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/file.md b/ui/public/docs/reference/data-sources/file.md deleted file mode 100644 index d3fd09deca6..00000000000 --- a/ui/public/docs/reference/data-sources/file.md +++ /dev/null @@ -1,25 +0,0 @@ -# File source - -## Description - -File data sources are files on disk or on S3. -Currently only Parquet and Delta formats are supported. - -## Example - -```python -from feast import FileSource -from feast.data_format import ParquetFormat - -parquet_file_source = FileSource( - file_format=ParquetFormat(), - path="file:///feast/customer.parquet", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/latest/index.html#feast.infra.offline_stores.file_source.FileSource). - -## Supported Types - -File data sources support all eight primitive types and their corresponding array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/kafka.md b/ui/public/docs/reference/data-sources/kafka.md deleted file mode 100644 index 8794c7a1e81..00000000000 --- a/ui/public/docs/reference/data-sources/kafka.md +++ /dev/null @@ -1,75 +0,0 @@ -# Kafka source - -**Warning**: This is an _experimental_ feature. It's intended for early testing and feedback, and could change without warnings in future releases. - -## Description - -Kafka sources allow users to register Kafka streams as data sources. Feast currently does not launch or monitor jobs to ingest data from Kafka. Users are responsible for launching and monitoring their own ingestion jobs, which should write feature values to the online store through [FeatureStore.write_to_online_store](https://rtd.feast.dev/en/latest/index.html#feast.feature_store.FeatureStore.write_to_online_store). An example of how to launch such a job with Spark can be found [here](https://github.com/feast-dev/feast/tree/master/sdk/python/feast/infra/contrib). Feast also provides functionality to write to the offline store using the `write_to_offline_store` functionality. - -Kafka sources must have a batch source specified. The batch source will be used for retrieving historical features. Thus users are also responsible for writing data from their Kafka streams to a batch data source such as a data warehouse table. When using a Kafka source as a stream source in the definition of a feature view, a batch source doesn't need to be specified in the feature view definition explicitly. - -## Stream sources -Streaming data sources are important sources of feature values. A typical setup with streaming data looks like: - -1. Raw events come in (stream 1) -2. Streaming transformations applied (e.g. generating features like `last_N_purchased_categories`) (stream 2) -3. Write stream 2 values to an offline store as a historical log for training (optional) -4. Write stream 2 values to an online store for low latency feature serving -5. Periodically materialize feature values from the offline store into the online store for decreased training-serving skew and improved model performance - -## Example -### Defining a Kafka source -Note that the Kafka source has a batch source. -```python -from datetime import timedelta - -from feast import Field, FileSource, KafkaSource, stream_feature_view -from feast.data_format import JsonFormat -from feast.types import Float32 - -driver_stats_batch_source = FileSource( - name="driver_stats_source", - path="data/driver_stats.parquet", - timestamp_field="event_timestamp", -) - -driver_stats_stream_source = KafkaSource( - name="driver_stats_stream", - kafka_bootstrap_servers="localhost:9092", - topic="drivers", - timestamp_field="event_timestamp", - batch_source=driver_stats_batch_source, - message_format=JsonFormat( - schema_json="driver_id integer, event_timestamp timestamp, conv_rate double, acc_rate double, created timestamp" - ), - watermark_delay_threshold=timedelta(minutes=5), -) -``` - -### Using the Kafka source in a stream feature view -The Kafka source can be used in a stream feature view. -```python -@stream_feature_view( - entities=[driver], - ttl=timedelta(seconds=8640000000), - mode="spark", - schema=[ - Field(name="conv_percentage", dtype=Float32), - Field(name="acc_percentage", dtype=Float32), - ], - timestamp_field="event_timestamp", - online=True, - source=driver_stats_stream_source, -) -def driver_hourly_stats_stream(df: DataFrame): - from pyspark.sql.functions import col - - return ( - df.withColumn("conv_percentage", col("conv_rate") * 100.0) - .withColumn("acc_percentage", col("acc_rate") * 100.0) - .drop("conv_rate", "acc_rate") - ) -``` - -### Ingesting data -See [here](https://github.com/feast-dev/streaming-tutorial) for a example of how to ingest data from a Kafka source into Feast. diff --git a/ui/public/docs/reference/data-sources/kinesis.md b/ui/public/docs/reference/data-sources/kinesis.md deleted file mode 100644 index f2adadfec03..00000000000 --- a/ui/public/docs/reference/data-sources/kinesis.md +++ /dev/null @@ -1,74 +0,0 @@ -# Kinesis source - -**Warning**: This is an _experimental_ feature. It's intended for early testing and feedback, and could change without warnings in future releases. - -## Description - -Kinesis sources allow users to register Kinesis streams as data sources. Feast currently does not launch or monitor jobs to ingest data from Kinesis. Users are responsible for launching and monitoring their own ingestion jobs, which should write feature values to the online store through [FeatureStore.write_to_online_store](https://rtd.feast.dev/en/latest/index.html#feast.feature_store.FeatureStore.write_to_online_store). An example of how to launch such a job with Spark to ingest from Kafka can be found [here](https://github.com/feast-dev/feast/tree/master/sdk/python/feast/infra/contrib); by using a different plugin, the example can be adapted to Kinesis. Feast also provides functionality to write to the offline store using the `write_to_offline_store` functionality. - -Kinesis sources must have a batch source specified. The batch source will be used for retrieving historical features. Thus users are also responsible for writing data from their Kinesis streams to a batch data source such as a data warehouse table. When using a Kinesis source as a stream source in the definition of a feature view, a batch source doesn't need to be specified in the feature view definition explicitly. - -## Stream sources -Streaming data sources are important sources of feature values. A typical setup with streaming data looks like: - -1. Raw events come in (stream 1) -2. Streaming transformations applied (e.g. generating features like `last_N_purchased_categories`) (stream 2) -3. Write stream 2 values to an offline store as a historical log for training (optional) -4. Write stream 2 values to an online store for low latency feature serving -5. Periodically materialize feature values from the offline store into the online store for decreased training-serving skew and improved model performance - -## Example -### Defining a Kinesis source -Note that the Kinesis source has a batch source. -```python -from datetime import timedelta - -from feast import Field, FileSource, KinesisSource, stream_feature_view -from feast.data_format import JsonFormat -from feast.types import Float32 - -driver_stats_batch_source = FileSource( - name="driver_stats_source", - path="data/driver_stats.parquet", - timestamp_field="event_timestamp", -) - -driver_stats_stream_source = KinesisSource( - name="driver_stats_stream", - stream_name="drivers", - timestamp_field="event_timestamp", - batch_source=driver_stats_batch_source, - record_format=JsonFormat( - schema_json="driver_id integer, event_timestamp timestamp, conv_rate double, acc_rate double, created timestamp" - ), - watermark_delay_threshold=timedelta(minutes=5), -) -``` - -### Using the Kinesis source in a stream feature view -The Kinesis source can be used in a stream feature view. -```python -@stream_feature_view( - entities=[driver], - ttl=timedelta(seconds=8640000000), - mode="spark", - schema=[ - Field(name="conv_percentage", dtype=Float32), - Field(name="acc_percentage", dtype=Float32), - ], - timestamp_field="event_timestamp", - online=True, - source=driver_stats_stream_source, -) -def driver_hourly_stats_stream(df: DataFrame): - from pyspark.sql.functions import col - - return ( - df.withColumn("conv_percentage", col("conv_rate") * 100.0) - .withColumn("acc_percentage", col("acc_rate") * 100.0) - .drop("conv_rate", "acc_rate") - ) -``` - -### Ingesting data -See [here](https://github.com/feast-dev/streaming-tutorial) for a example of how to ingest data from a Kafka source into Feast. The approach used in the tutorial can be easily adapted to work for Kinesis as well. diff --git a/ui/public/docs/reference/data-sources/mssql.md b/ui/public/docs/reference/data-sources/mssql.md deleted file mode 100644 index 8bf1ede6aa8..00000000000 --- a/ui/public/docs/reference/data-sources/mssql.md +++ /dev/null @@ -1,29 +0,0 @@ -# MsSQL source (contrib) - -## Description - -MsSQL data sources are Microsoft sql table sources. -These can be specified either by a table reference or a SQL query. - -## Disclaimer - -The MsSQL data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Defining a MsSQL source: - -```python -from feast.infra.offline_stores.contrib.mssql_offline_store.mssqlserver_source import ( - MsSqlServerSource, -) - -driver_hourly_table = "driver_hourly" - -driver_source = MsSqlServerSource( - table_ref=driver_hourly_table, - event_timestamp_column="datetime", - created_timestamp_column="created", -) -``` diff --git a/ui/public/docs/reference/data-sources/overview.md b/ui/public/docs/reference/data-sources/overview.md deleted file mode 100644 index 9880d388dde..00000000000 --- a/ui/public/docs/reference/data-sources/overview.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -## Functionality - -In Feast, each batch data source is associated with corresponding offline stores. -For example, a `SnowflakeSource` can only be processed by the Snowflake offline store, while a `FileSource` can be processed by both File and DuckDB offline stores. -Otherwise, the primary difference between batch data sources is the set of supported types. -Feast has an internal type system, and aims to support eight primitive types (`bytes`, `string`, `int32`, `int64`, `float32`, `float64`, `bool`, and `timestamp`) along with the corresponding array types. -However, not every batch data source supports all of these types. - -For more details on the Feast type system, see [here](../type-system.md). - -## Functionality Matrix - -There are currently four core batch data source implementations: `FileSource`, `BigQuerySource`, `SnowflakeSource`, and `RedshiftSource`. -There are several additional implementations contributed by the Feast community (`PostgreSQLSource`, `SparkSource`, and `TrinoSource`), which are not guaranteed to be stable or to match the functionality of the core implementations. -Details for each specific data source can be found [here](README.md). - -Below is a matrix indicating which data sources support which types. - -| | File | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | Couchbase | -| :-------------------------------- | :-- | :-- |:----------| :-- | :-- | :-- | :-- |:----------| -| `bytes` | yes | yes | yes | yes | yes | yes | yes | yes | -| `string` | yes | yes | yes | yes | yes | yes | yes | yes | -| `int32` | yes | yes | yes | yes | yes | yes | yes | yes | -| `int64` | yes | yes | yes | yes | yes | yes | yes | yes | -| `float32` | yes | yes | yes | yes | yes | yes | yes | yes | -| `float64` | yes | yes | yes | yes | yes | yes | yes | yes | -| `bool` | yes | yes | yes | yes | yes | yes | yes | yes | -| `timestamp` | yes | yes | yes | yes | yes | yes | yes | yes | -| array types | yes | yes | yes | no | yes | yes | no | no | diff --git a/ui/public/docs/reference/data-sources/postgres.md b/ui/public/docs/reference/data-sources/postgres.md deleted file mode 100644 index 23d7818a04f..00000000000 --- a/ui/public/docs/reference/data-sources/postgres.md +++ /dev/null @@ -1,35 +0,0 @@ -# PostgreSQL source (contrib) - -## Description - -PostgreSQL data sources are PostgreSQL tables or views. -These can be specified either by a table reference or a SQL query. - -## Disclaimer - -The PostgreSQL data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Defining a Postgres source: - -```python -from feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source import ( - PostgreSQLSource, -) - -driver_stats_source = PostgreSQLSource( - name="feast_driver_hourly_stats", - query="SELECT * FROM feast_driver_hourly_stats", - timestamp_field="event_timestamp", - created_timestamp_column="created", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.postgres_offline_store.postgres_source.PostgreSQLSource). - -## Supported Types - -PostgreSQL data sources support all eight primitive types and their corresponding array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/push.md b/ui/public/docs/reference/data-sources/push.md deleted file mode 100644 index 7a7ef96c7ca..00000000000 --- a/ui/public/docs/reference/data-sources/push.md +++ /dev/null @@ -1,79 +0,0 @@ -# Push source - -## 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 [FeatureStore.write_to_online_store](https://rtd.feast.dev/en/latest/index.html#feast.feature_store.FeatureStore.write_to_online_store). - -Push sources can be used by multiple feature views. When data is pushed to a push source, Feast propagates the feature values to all the consuming feature views. - -Push sources must have a batch source specified. The batch source will be used for retrieving historical features. Thus users are also responsible for pushing data to a batch data source such as a data warehouse table. When using a push source as a stream source in the definition of a feature view, a batch source doesn't need to be specified in the feature view definition explicitly. - -## Stream sources -Streaming data sources are important sources of feature values. A typical setup with streaming data looks like: - -1. Raw events come in (stream 1) -2. Streaming transformations applied (e.g. generating features like `last_N_purchased_categories`) (stream 2) -3. Write stream 2 values to an offline store as a historical log for training (optional) -4. Write stream 2 values to an online store for low latency feature serving -5. Periodically materialize feature values from the offline store into the online store for decreased training-serving skew and improved model performance - -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 (basic) -### Defining a push source -Note that the push schema needs to also include the entity. - -```python -from feast import Entity, PushSource, ValueType, BigQuerySource, FeatureView, Feature, Field -from feast.types import Int64 - -push_source = PushSource( - name="push_source", - batch_source=BigQuerySource(table="test.test"), -) - -user = Entity(name="user", join_keys=["user_id"]) - -fv = FeatureView( - name="feature view", - entities=[user], - schema=[Field(name="life_time_value", dtype=Int64)], - source=push_source, -) -``` - -### Pushing data -Note that the `to` parameter is optional and defaults to online but we can specify these options: `PushMode.ONLINE`, `PushMode.OFFLINE`, or `PushMode.ONLINE_AND_OFFLINE`. -```python -from feast import FeatureStore -import pandas as pd -from feast.data_source import PushMode - -fs = FeatureStore(...) -feature_data_frame = pd.DataFrame() -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)) diff --git a/ui/public/docs/reference/data-sources/redshift.md b/ui/public/docs/reference/data-sources/redshift.md deleted file mode 100644 index 2c3c65cc701..00000000000 --- a/ui/public/docs/reference/data-sources/redshift.md +++ /dev/null @@ -1,37 +0,0 @@ -# Redshift source - -## Description - -Redshift data sources are Redshift tables or views. -These can be specified either by a table reference or a SQL query. -However, no performance guarantees can be provided for SQL query-based sources, so table references are recommended. - -## Examples - -Using a table name: - -```python -from feast import RedshiftSource - -my_redshift_source = RedshiftSource( - table="redshift_table", -) -``` - -Using a query: - -```python -from feast import RedshiftSource - -my_redshift_source = RedshiftSource( - query="SELECT timestamp as ts, created, f1, f2 " - "FROM redshift_table", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.redshift_source.RedshiftSource). - -## Supported Types - -Redshift data sources support all eight primitive types, but currently do not support array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/snowflake.md b/ui/public/docs/reference/data-sources/snowflake.md deleted file mode 100644 index 98a56e09f87..00000000000 --- a/ui/public/docs/reference/data-sources/snowflake.md +++ /dev/null @@ -1,50 +0,0 @@ -# Snowflake source - -## Description - -Snowflake data sources are Snowflake tables or views. -These can be specified either by a table reference or a SQL query. - -## Examples - -Using a table reference: - -```python -from feast import SnowflakeSource - -my_snowflake_source = SnowflakeSource( - database="FEAST", - schema="PUBLIC", - table="FEATURE_TABLE", -) -``` - -Using a query: - -```python -from feast import SnowflakeSource - -my_snowflake_source = SnowflakeSource( - query=""" - SELECT - timestamp_column AS "ts", - "created", - "f1", - "f2" - FROM - `FEAST.PUBLIC.FEATURE_TABLE` - """, -) -``` - -{% hint style="warning" %} -Be careful about how Snowflake handles table and column name conventions. -In particular, you can read more about quote identifiers [here](https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html). -{% endhint %} - -The full set of configuration options is available [here](https://rtd.feast.dev/en/latest/index.html#feast.infra.offline_stores.snowflake_source.SnowflakeSource). - -## Supported Types - -Snowflake data sources support all eight primitive types. Array types are also supported but not with type inference. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/spark.md b/ui/public/docs/reference/data-sources/spark.md deleted file mode 100644 index 99d5902667a..00000000000 --- a/ui/public/docs/reference/data-sources/spark.md +++ /dev/null @@ -1,59 +0,0 @@ -# Spark source (contrib) - -## Description - -Spark data sources are tables or files that can be loaded from some Spark store (e.g. Hive or in-memory). They can also be specified by a SQL query. - -## Disclaimer - -The Spark data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Using a table reference from SparkSession (for example, either in-memory or a Hive Metastore): - -```python -from feast.infra.offline_stores.contrib.spark_offline_store.spark_source import ( - SparkSource, -) - -my_spark_source = SparkSource( - table="FEATURE_TABLE", -) -``` - -Using a query: - -```python -from feast.infra.offline_stores.contrib.spark_offline_store.spark_source import ( - SparkSource, -) - -my_spark_source = SparkSource( - query="SELECT timestamp as ts, created, f1, f2 " - "FROM spark_table", -) -``` - -Using a file reference: - -```python -from feast.infra.offline_stores.contrib.spark_offline_store.spark_source import ( - SparkSource, -) - -my_spark_source = SparkSource( - path=f"{CURRENT_DIR}/data/driver_hourly_stats", - file_format="parquet", - timestamp_field="event_timestamp", - created_timestamp_column="created", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.spark_offline_store.spark_source.SparkSource). - -## Supported Types - -Spark data sources support all eight primitive types and their corresponding array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/data-sources/trino.md b/ui/public/docs/reference/data-sources/trino.md deleted file mode 100644 index c74981f47e0..00000000000 --- a/ui/public/docs/reference/data-sources/trino.md +++ /dev/null @@ -1,34 +0,0 @@ -# Trino source (contrib) - -## Description - -Trino data sources are Trino tables or views. -These can be specified either by a table reference or a SQL query. - -## Disclaimer - -The Trino data source does not achieve full test coverage. -Please do not assume complete stability. - -## Examples - -Defining a Trino source: - -```python -from feast.infra.offline_stores.contrib.trino_offline_store.trino_source import ( - TrinoSource, -) - -driver_hourly_stats = TrinoSource( - event_timestamp_column="event_timestamp", - table_ref="feast.driver_stats", - created_timestamp_column="created", -) -``` - -The full set of configuration options is available [here](https://rtd.feast.dev/en/master/#trino-source). - -## Supported Types - -Trino data sources support all eight primitive types, but currently do not support array types. -For a comparison against other batch data sources, please see [here](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/denormalized.md b/ui/public/docs/reference/denormalized.md deleted file mode 100644 index 9ac39947f05..00000000000 --- a/ui/public/docs/reference/denormalized.md +++ /dev/null @@ -1,121 +0,0 @@ -# Streaming feature computation with Denormalized - -Denormalized makes it easy to compute real-time features and write them directly to your Feast online store. This guide will walk you through setting up a streaming pipeline that computes feature aggregations and pushes them to Feast in real-time. - -![Denormalized/Feast integration diagram](../assets/feast-denormalized.png) - -## Prerequisites - -- Python 3.12+ -- Kafka cluster (local or remote) OR docker installed - -For a full working demo, check out the [feast-example](https://github.com/probably-nothing-labs/feast-example) repo. - -## Quick Start - -1. First, create a new Python project or use our template: -```bash -mkdir my-feature-project -cd my-feature-project -python -m venv .venv -source .venv/bin/activate # or `.venv\Scripts\activate` on Windows -pip install denormalized[feast] feast -``` - -2. Set up your Feast feature repository: -```bash -feast init feature_repo -``` - -## Project Structure - -Your project should look something like this: -``` -my-feature-project/ -├── feature_repo/ -│ ├── feature_store.yaml -│ └── sensor_data.py # Feature definitions -├── stream_job.py # Denormalized pipeline -└── main.py # Pipeline runner -``` - -3. Run a test Kafka instance in docker - -`docker run --rm -p 9092:9092 emgeee/kafka_emit_measurements:latest` - -This will spin up a docker container that runs a kafka instance and run a simple script to emit fake data to two topics. - - -## Define Your Features - -In `feature_repo/sensor_data.py`, define your feature view and entity: - -```python -from feast import Entity, FeatureView, PushSource, Field -from feast.types import Float64, String - -# Define your entity -sensor = Entity( - name="sensor", - join_keys=["sensor_name"], -) - -# Create a push source for real-time features -source = PushSource( - name="push_sensor_statistics", - batch_source=your_batch_source # Define your batch source -) - -# Define your feature view -stats_view = FeatureView( - name="sensor_statistics", - entities=[sensor], - schema=ds.get_feast_schema(), # Denormalized handles this for you! - source=source, - online=True, -) -``` - -## Create Your Streaming Pipeline - -In `stream_job.py`, define your streaming computations: - -```python -from denormalized import Context, FeastDataStream -from denormalized.datafusion import col, functions as f -from feast import FeatureStore - -sample_event = { - "occurred_at_ms": 100, - "sensor_name": "foo", - "reading": 0.0, -} - -# Create a stream from your Kafka topic -ds = FeastDataStream(Context().from_topic("temperature", json.dumps(sample_event), "localhost:9092", "occurred_at_ms")) - -# Define your feature computations -ds = ds.window( - [col("sensor_name")], # Group by sensor - [ - f.count(col("reading")).alias("count"), - f.min(col("reading")).alias("min"), - f.max(col("reading")).alias("max"), - f.avg(col("reading")).alias("average"), - ], - 1000, # Window size in ms - None # Slide interval (None = tumbling window) -) - -feature_store = FeatureStore(repo_path="feature_repo/") - -# This single line connects Denormalized to Feast! -ds.write_feast_feature(feature_store, "push_sensor_statistics") -``` - - - -## Need Help? - -- Email us at hello@denormalized.io -- Check out more examples on our [GitHub](https://github.com/probably-nothing-labs/denormalized/tree/main/py-denormalized/python/examples) diff --git a/ui/public/docs/reference/dqm.md b/ui/public/docs/reference/dqm.md deleted file mode 100644 index 5a02413e534..00000000000 --- a/ui/public/docs/reference/dqm.md +++ /dev/null @@ -1,77 +0,0 @@ -# Data Quality Monitoring - -Data Quality Monitoring (DQM) is a Feast module aimed to help users to validate their data with the user-curated set of rules. -Validation could be applied during: -* Historical retrieval (training dataset generation) -* [planned] Writing features into an online store -* [planned] Reading features from an online store - -Its goal is to address several complex data problems, namely: -* Data consistency - new training datasets can be significantly different from previous datasets. This might require a change in model architecture. -* Issues/bugs in the upstream pipeline - bugs in upstream pipelines can cause invalid values to overwrite existing valid values in an online store. -* Training/serving skew - distribution shift could significantly decrease the performance of the model. - -> To monitor data quality, we check that the characteristics of the tested dataset (aka the tested dataset's profile) are "equivalent" to the characteristics of the reference dataset. -> How exactly profile equivalency should be measured is up to the user. - -### Overview - -The validation process consists of the following steps: -1. User prepares reference dataset (currently only [saved datasets](../getting-started/concepts/dataset.md) from historical retrieval are supported). -2. User defines profiler function, which should produce profile by given dataset (currently only profilers based on [Great Expectations](https://docs.greatexpectations.io) are allowed). -3. Validation of tested dataset is performed with reference dataset and profiler provided as parameters. - -### Preparations -Feast with Great Expectations support can be installed via -```shell -pip install 'feast[ge]' -``` - -### Dataset profile -Currently, Feast supports only [Great Expectation's](https://greatexpectations.io/) [ExpectationSuite](https://legacy.docs.greatexpectations.io/en/latest/autoapi/great_expectations/core/expectation_suite/index.html#great_expectations.core.expectation_suite.ExpectationSuite) -as dataset's profile. Hence, the user needs to define a function (profiler) that would receive a dataset and return an [ExpectationSuite](https://legacy.docs.greatexpectations.io/en/latest/autoapi/great_expectations/core/expectation_suite/index.html#great_expectations.core.expectation_suite.ExpectationSuite). - -Great Expectations supports automatic profiling as well as manually specifying expectations: -```python -from great_expectations.dataset import Dataset -from great_expectations.core.expectation_suite import ExpectationSuite - -from feast.dqm.profilers.ge_profiler import ge_profiler - -@ge_profiler -def automatic_profiler(dataset: Dataset) -> ExpectationSuite: - from great_expectations.profile.user_configurable_profiler import UserConfigurableProfiler - - return UserConfigurableProfiler( - profile_dataset=dataset, - ignored_columns=['conv_rate'], - value_set_threshold='few' - ).build_suite() -``` -However, from our experience capabilities of automatic profiler are quite limited. So we would recommend crafting your own expectations: -```python -@ge_profiler -def manual_profiler(dataset: Dataset) -> ExpectationSuite: - dataset.expect_column_max_to_be_between("column", 1, 2) - return dataset.get_expectation_suite() -``` - - - -### Validating Training Dataset -During retrieval of historical features, `validation_reference` can be passed as a parameter to methods `.to_df(validation_reference=...)` or `.to_arrow(validation_reference=...)` of RetrievalJob. -If parameter is provided Feast will run validation once dataset is materialized. In case if validation successful materialized dataset is returned. -Otherwise, `feast.dqm.errors.ValidationFailed` exception would be raised. It will consist of all details for expectations that didn't pass. - -```python -from feast import FeatureStore - -fs = FeatureStore(".") - -job = fs.get_historical_features(...) -job.to_df( - validation_reference=fs - .get_saved_dataset("my_reference_dataset") - .as_reference(profiler=manual_profiler) -) -``` diff --git a/ui/public/docs/reference/feast-cli-commands.md b/ui/public/docs/reference/feast-cli-commands.md deleted file mode 100644 index 712df18a6b6..00000000000 --- a/ui/public/docs/reference/feast-cli-commands.md +++ /dev/null @@ -1,377 +0,0 @@ -# Feast CLI reference - -## Overview - -The Feast CLI comes bundled with the Feast Python package. It is immediately available after [installing Feast](../how-to-guides/feast-snowflake-gcp-aws/install-feast.md). - -```text -Usage: feast [OPTIONS] COMMAND [ARGS]... - - Feast CLI - - For more information, see our public docs at https://docs.feast.dev/ - -Options: - -c, --chdir TEXT Switch to a different feature repository directory before - executing the given subcommand. - - --help Show this message and exit. - -Commands: - apply Create or update a feature store deployment - configuration Display Feast configuration - entities Access entities - feature-views Access feature views - init Create a new Feast repository - materialize Run a (non-incremental) materialization job to... - materialize-incremental Run an incremental materialization job to ingest... - permissions Access permissions - registry-dump Print contents of the metadata registry - teardown Tear down deployed feature store infrastructure - version Display Feast SDK version -``` - -## Global Options - -The Feast CLI provides one global top-level option that can be used with other commands - -**chdir \(-c, --chdir\)** - -This command allows users to run Feast CLI commands in a different folder from the current working directory. - -```text -feast -c path/to/my/feature/repo apply -``` - -## Apply - -Creates or updates a feature store deployment - -```bash -feast apply -``` - -**What does Feast apply do?** - -1. Feast will scan Python files in your feature repository and find all Feast object definitions, such as feature views, entities, and data sources. -2. Feast will validate your feature definitions (e.g. for uniqueness of features) -3. Feast will sync the metadata about Feast objects to the registry. If a registry does not exist, then it will be instantiated. The standard registry is a simple protobuf binary file that is stored on disk \(locally or in an object store\). -4. Feast CLI will create all necessary feature store infrastructure. The exact infrastructure that is deployed or configured depends on the `provider` configuration that you have set in `feature_store.yaml`. For example, setting `local` as your provider will result in a `sqlite` online store being created. - -{% hint style="warning" %} -`feast apply` \(when configured to use cloud provider like `gcp` or `aws`\) will create cloud infrastructure. This may incur costs. -{% endhint %} - -## Configuration - -Display the actual configuration being used by Feast, including both user-provided configurations and default configurations applied by Feast. - -```bash -feast configuration -``` - -```yaml -project: foo -registry: data/registry.db -provider: local -online_store: - type: sqlite - path: data/online_store.db -offline_store: - type: dask -entity_key_serialization_version: 2 -auth: - type: no_auth -``` - -## Entities - -List all registered entities - -```text -feast entities list - -Options: - --tags TEXT Filter by tags (e.g. --tags 'key:value' --tags 'key:value, - key:value, ...'). Items return when ALL tags match. -``` - -```text -NAME DESCRIPTION TYPE -driver_id driver id ValueType.INT64 -``` - -## Feature views - -List all registered feature views - -```text -feast feature-views list - -Options: - --tags TEXT Filter by tags (e.g. --tags 'key:value' --tags 'key:value, - key:value, ...'). Items return when ALL tags match. -``` - -```text -NAME ENTITIES TYPE -driver_hourly_stats {'driver'} FeatureView -``` - -## Init - -Creates a new feature repository - -```text -feast init my_repo_name -``` - -```text -Creating a new Feast repository in /projects/my_repo_name. -``` - -```text -. -├── data -│ └── driver_stats.parquet -├── example.py -└── feature_store.yaml -``` - -It's also possible to use other templates - -```text -feast init -t gcp my_feature_repo -``` - -or to set the name of the new project - -```text -feast init -t gcp my_feature_repo -``` - -## Materialize - -Load data from feature views into the online store between two dates - -```bash -feast materialize 2020-01-01T00:00:00 2022-01-01T00:00:00 -``` - -Load data for specific feature views into the online store between two dates - -```text -feast materialize -v driver_hourly_stats 2020-01-01T00:00:00 2022-01-01T00:00:00 -``` - -```text -Materializing 1 feature views from 2020-01-01 to 2022-01-01 - -driver_hourly_stats: -100%|██████████████████████████| 5/5 [00:00<00:00, 5949.37it/s] -``` - -## Materialize incremental - -Load data from feature views into the online store, beginning from either the previous `materialize` or `materialize-incremental` end date, or the beginning of time. - -```text -feast materialize-incremental 2022-01-01T00:00:00 -``` - -## Permissions - -### List permissions -List all registered permission - -```text -feast permissions list - -Options: - --tags TEXT Filter by tags (e.g. --tags 'key:value' --tags 'key:value, - key:value, ...'). Items return when ALL tags match. - -v, --verbose Print the resources matching each configured permission -``` - -```text -+-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ -| NAME | TYPES | NAME_PATTERNS | ACTIONS | ROLES | REQUIRED_TAGS | -+=======================+=============+=======================+===========+================+================+========+ -| reader_permission1234 | FeatureView | transformed_conv_rate | DESCRIBE | reader | - | -| | | driver_hourly_stats | DESCRIBE | reader | - | -+-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ -| writer_permission1234 | FeatureView | transformed_conv_rate | CREATE | writer | - | -+-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ -| special | FeatureView | special.* | DESCRIBE | admin | test-key2 : test-value2 | -| | | | UPDATE | special-reader | test-key : test-value | -+-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ -``` - -`verbose` option describes the resources matching each configured permission: - -```text -feast permissions list -v -``` - -```text -Permissions: - -permissions -├── reader_permission1234 ['reader'] -│ └── FeatureView: none -└── writer_permission1234 ['writer'] - ├── FeatureView: none - │── OnDemandFeatureView: ['transformed_conv_rate_fresh', 'transformed_conv_rate'] - └── BatchFeatureView: ['driver_hourly_stats', 'driver_hourly_stats_fresh'] -``` - -### Describe a permission -Describes the provided permission - -```text -feast permissions describe permission-name -name: permission-name -types: -- FEATURE_VIEW -namePattern: transformed_conv_rate -requiredTags: - required1: required-value1 - required2: required-value2 -actions: -- DESCRIBE -policy: - roleBasedPolicy: - roles: - - reader -tags: - key1: value1 - key2: value2 - -``` -### Permission check -The `permissions check` command is used to identify resources that lack the appropriate permissions based on their type, name, or tags. - -This command is particularly useful for administrators when roles, actions, or permissions have been modified or newly configured. By running this command, administrators can easily verify which resources and actions are not protected by any permission configuration, ensuring that proper security measures are in place. - -```text -> feast permissions check - - -The following resources are not secured by any permission configuration: -NAME TYPE -driver Entity -driver_hourly_stats_fresh FeatureView -The following actions are not secured by any permission configuration (Note: this might not be a security concern, depending on the used APIs): -NAME TYPE UNSECURED ACTIONS -driver Entity CREATE - DESCRIBE - UPDATE - DELETE - READ_ONLINE - READ_OFFLINE - WRITE_ONLINE - WRITE_OFFLINE -driver_hourly_stats_fresh FeatureView CREATE - DESCRIBE - UPDATE - DELETE - READ_ONLINE - READ_OFFLINE - WRITE_ONLINE - WRITE_OFFLINE - -Based on the above results, the administrator can reassess the permissions configuration and make any necessary adjustments to meet their security requirements. - -If no resources are accessible publicly, the permissions check command will return the following response: -> feast permissions check -The following resources are not secured by any permission configuration: -NAME TYPE -The following actions are not secured by any permission configuration (Note: this might not be a security concern, depending on the used APIs): -NAME TYPE UNSECURED ACTIONS -``` - - -### List of the configured roles -List all the configured roles - -```text -feast permissions list-roles - -Options: - --verbose Print the resources and actions permitted to each configured - role -``` - -```text -ROLE NAME -admin -reader -writer -``` - -`verbose` option describes the resources and actions permitted to each managed role: - -```text -feast permissions list-roles -v -``` - -```text -ROLE NAME RESOURCE NAME RESOURCE TYPE PERMITTED ACTIONS -admin driver_hourly_stats_source FileSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -admin vals_to_add RequestSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -admin driver_stats_push_source PushSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -admin driver_hourly_stats_source FileSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -admin vals_to_add RequestSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -admin driver_stats_push_source PushSource CREATE - DELETE - QUERY_OFFLINE - QUERY_ONLINE - DESCRIBE - UPDATE -reader driver_hourly_stats FeatureView DESCRIBE -reader driver_hourly_stats_fresh FeatureView DESCRIBE -... -``` - - -## Teardown - -Tear down deployed feature store infrastructure - -```text -feast teardown -``` - -## Version - -Print the current Feast version - -```text -feast version -``` - diff --git a/ui/public/docs/reference/feast-ignore.md b/ui/public/docs/reference/feast-ignore.md deleted file mode 100644 index 23b2f24afe6..00000000000 --- a/ui/public/docs/reference/feast-ignore.md +++ /dev/null @@ -1,33 +0,0 @@ -# .feastignore - -## Overview - -`.feastignore` is a file that is placed at the root of the [Feature Repository](feature-repository.md). This file contains paths that should be ignored when running `feast apply`. An example `.feastignore` is shown below: - -{% code title=".feastignore" %} -```text -# Ignore virtual environment -venv - -# Ignore a specific Python file -scripts/foo.py - -# Ignore all Python files directly under scripts directory -scripts/*.py - -# Ignore all "foo.py" anywhere under scripts directory -scripts/**/foo.py -``` -{% endcode %} - -`.feastignore` file is optional. If the file can not be found, every Python in the feature repo directory will be parsed by `feast apply`. - -## Feast Ignore Patterns - -| Pattern | Example matches | Explanation | -| :--- | :--- | :--- | -| venv | venv/foo.py venv/a/foo.py | You can specify a path to a specific directory. Everything in that directory will be ignored. | -| scripts/foo.py | scripts/foo.py | You can specify a path to a specific file. Only that file will be ignored. | -| scripts/\*.py | scripts/foo.py scripts/bar.py | You can specify asterisk \(\*\) anywhere in the expression. An asterisk matches zero or more characters, except "/". | -| scripts/\*\*/foo.py | scripts/foo.py scripts/a/foo.py scripts/a/b/foo.py | You can specify double asterisk \(\*\*\) anywhere in the expression. A double asterisk matches zero or more directories. | - diff --git a/ui/public/docs/reference/feature-repository.md b/ui/public/docs/reference/feature-repository.md deleted file mode 100644 index f9c91de3505..00000000000 --- a/ui/public/docs/reference/feature-repository.md +++ /dev/null @@ -1,126 +0,0 @@ -# Feature repository - -Feast manages two important sets of configuration: feature definitions, and configuration about how to run the feature store. With Feast, this configuration can be written declaratively and stored as code in a central location. This central location is called a feature repository, and it's essentially just a directory that contains some code files. - -The feature repository is the declarative source of truth for what the desired state of a feature store should be. The Feast CLI uses the feature repository to configure your infrastructure, e.g., migrate tables. - -## What is a feature repository? - -A feature repository consists of: - -* A collection of Python files containing feature declarations. -* A `feature_store.yaml` file containing infrastructural configuration. -* A `.feastignore` file containing paths in the feature repository to ignore. - -{% hint style="info" %} -Typically, users store their feature repositories in a Git repository, especially when working in teams. However, using Git is not a requirement. -{% endhint %} - -## Structure of a feature repository - -The structure of a feature repository is as follows: - -* The root of the repository should contain a `feature_store.yaml` file and may contain a `.feastignore` file. -* The repository should contain Python files that contain feature definitions. -* The repository can contain other files as well, including documentation and potentially data files. - -An example structure of a feature repository is shown below: - -```text -$ tree -a -. -├── data -│ └── driver_stats.parquet -├── driver_features.py -├── feature_store.yaml -└── .feastignore - -1 directory, 4 files -``` - -A couple of things to note about the feature repository: - -* Feast reads _all_ Python files recursively when `feast apply` is ran, including subdirectories, even if they don't contain feature definitions. -* It's recommended to add `.feastignore` and add paths to all imperative scripts if you need to store them inside the feature registry. - -## The feature\_store.yaml configuration file - -The configuration for a feature store is stored in a file named `feature_store.yaml` , which must be located at the root of a feature repository. An example `feature_store.yaml` file is shown below: - -{% code title="feature\_store.yaml" %} -```yaml -project: my_feature_repo_1 -registry: data/metadata.db -provider: local -online_store: - path: data/online_store.db -``` -{% endcode %} - -The `feature_store.yaml` file configures how the feature store should run. See [feature\_store.yaml](feature-store-yaml.md) for more details. - -## The .feastignore file - -This file contains paths that should be ignored when running `feast apply`. An example `.feastignore` is shown below: - -{% code title=".feastignore" %} -```text -# Ignore virtual environment -venv - -# Ignore a specific Python file -scripts/foo.py - -# Ignore all Python files directly under scripts directory -scripts/*.py - -# Ignore all "foo.py" anywhere under scripts directory -scripts/**/foo.py -``` -{% endcode %} - -See [.feastignore](feast-ignore.md) for more details. - -## Feature definitions - -A feature repository can also contain one or more Python files that contain feature definitions. An example feature definition file is shown below: - -{% code title="driver\_features.py" %} -```python -from datetime import timedelta - -from feast import BigQuerySource, Entity, Feature, FeatureView, Field -from feast.types import Float32, Int64, String - -driver_locations_source = BigQuerySource( - table="rh_prod.ride_hailing_co.drivers", - timestamp_field="event_timestamp", - created_timestamp_column="created_timestamp", -) - -driver = Entity( - name="driver", - description="driver id", -) - -driver_locations = FeatureView( - name="driver_locations", - entities=[driver], - ttl=timedelta(days=1), - schema=[ - Field(name="lat", dtype=Float32), - Field(name="lon", dtype=String), - Field(name="driver", dtype=Int64), - ], - source=driver_locations_source, -) -``` -{% endcode %} - -To declare new feature definitions, just add code to the feature repository, either in existing files or in a new file. For more information on how to define features, see [Feature Views](../getting-started/concepts/feature-view.md). - -### Next steps - -* See [Create a feature repository](../how-to-guides/feast-snowflake-gcp-aws/README.md) to get started with an example feature repository. -* See [feature\_store.yaml](feature-store-yaml.md), [.feastignore](feast-ignore.md) or [Feature Views](../getting-started/concepts/feature-view.md) for more information on the configuration files that live in a feature registry. - diff --git a/ui/public/docs/reference/feature-repository/README.md b/ui/public/docs/reference/feature-repository/README.md deleted file mode 100644 index 2c1b112a783..00000000000 --- a/ui/public/docs/reference/feature-repository/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# Feature repository - -Feast users use Feast to manage two important sets of configuration: - -* Configuration about how to run Feast on your infrastructure -* Feature definitions - -With Feast, the above configuration can be written declaratively and stored as code in a central location. This central location is called a feature repository. The feature repository is the declarative source of truth for what the desired state of a feature store should be. - -The Feast CLI uses the feature repository to configure, deploy, and manage your feature store. - -## What is a feature repository? - -A feature repository consists of: - -* A collection of Python files containing feature declarations. -* A `feature_store.yaml` file containing infrastructural configuration. -* A `.feastignore` file containing paths in the feature repository to ignore. - -{% hint style="info" %} -Typically, users store their feature repositories in a Git repository, especially when working in teams. However, using Git is not a requirement. -{% endhint %} - -## Structure of a feature repository - -The structure of a feature repository is as follows: - -* The root of the repository should contain a `feature_store.yaml` file and may contain a `.feastignore` file. -* The repository should contain Python files that contain feature definitions. -* The repository can contain other files as well, including documentation and potentially data files. - -An example structure of a feature repository is shown below: - -``` -$ tree -a -. -├── data -│ └── driver_stats.parquet -├── driver_features.py -├── feature_store.yaml -└── .feastignore - -1 directory, 4 files -``` - -A couple of things to note about the feature repository: - -* Feast reads _all_ Python files recursively when `feast apply` is ran, including subdirectories, even if they don't contain feature definitions. -* It's recommended to add `.feastignore` and add paths to all imperative scripts if you need to store them inside the feature registry. - -## The feature_store.yaml configuration file - -The configuration for a feature store is stored in a file named `feature_store.yaml` , which must be located at the root of a feature repository. An example `feature_store.yaml` file is shown below: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo_1 -registry: data/metadata.db -provider: local -online_store: - path: data/online_store.db -``` -{% endcode %} - -The `feature_store.yaml` file configures how the feature store should run. See [feature_store.yaml](feature-store-yaml.md) for more details. - -## The .feastignore file - -This file contains paths that should be ignored when running `feast apply`. An example `.feastignore` is shown below: - -{% code title=".feastignore" %} -``` -# Ignore virtual environment -venv - -# Ignore a specific Python file -scripts/foo.py - -# Ignore all Python files directly under scripts directory -scripts/*.py - -# Ignore all "foo.py" anywhere under scripts directory -scripts/**/foo.py -``` -{% endcode %} - -See [.feastignore](feast-ignore.md) for more details. - -## Feature definitions - -A feature repository can also contain one or more Python files that contain feature definitions. An example feature definition file is shown below: - -{% code title="driver_features.py" %} -```python -from datetime import timedelta - -from feast import BigQuerySource, Entity, Feature, FeatureView, Field -from feast.types import Float32, Int64, String - -driver_locations_source = BigQuerySource( - table_ref="rh_prod.ride_hailing_co.drivers", - timestamp_field="event_timestamp", - created_timestamp_column="created_timestamp", -) - -driver = Entity( - name="driver", - description="driver id", -) - -driver_locations = FeatureView( - name="driver_locations", - entities=[driver], - ttl=timedelta(days=1), - schema=[ - Field(name="lat", dtype=Float32), - Field(name="lon", dtype=String), - Field(name="driver", dtype=Int64), - ], - source=driver_locations_source, -) -``` -{% endcode %} - -To declare new feature definitions, just add code to the feature repository, either in existing files or in a new file. For more information on how to define features, see [Feature Views](../../getting-started/concepts/feature-view.md). - -### Next steps - -* See [Create a feature repository](../../how-to-guides/feast-snowflake-gcp-aws/create-a-feature-repository.md) to get started with an example feature repository. -* See [feature_store.yaml](feature-store-yaml.md), [.feastignore](feast-ignore.md), or [Feature Views](../../getting-started/concepts/feature-view.md) for more information on the configuration files that live in a feature registry. diff --git a/ui/public/docs/reference/feature-repository/feast-ignore.md b/ui/public/docs/reference/feature-repository/feast-ignore.md deleted file mode 100644 index 3c23ebe88bc..00000000000 --- a/ui/public/docs/reference/feature-repository/feast-ignore.md +++ /dev/null @@ -1,32 +0,0 @@ -# .feastignore - -## Overview - -`.feastignore` is a file that is placed at the root of the [Feature Repository](./). This file contains paths that should be ignored when running `feast apply`. An example `.feastignore` is shown below: - -{% code title=".feastignore" %} -``` -# Ignore virtual environment -venv - -# Ignore a specific Python file -scripts/foo.py - -# Ignore all Python files directly under scripts directory -scripts/*.py - -# Ignore all "foo.py" anywhere under scripts directory -scripts/**/foo.py -``` -{% endcode %} - -`.feastignore` file is optional. If the file can not be found, every Python file in the feature repo directory will be parsed by `feast apply`. - -## Feast Ignore Patterns - -| Pattern | Example matches | Explanation | -| ------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| venv |

venv/foo.py
venv/a/foo.py

| You can specify a path to a specific directory. Everything in that directory will be ignored. | -| scripts/foo.py | scripts/foo.py | You can specify a path to a specific file. Only that file will be ignored. | -| scripts/\*.py |

scripts/foo.py
scripts/bar.py

| You can specify an asterisk (\*) anywhere in the expression. An asterisk matches zero or more characters, except "/". | -| scripts/\*\*/foo.py |

scripts/foo.py
scripts/a/foo.py
scripts/a/b/foo.py

| You can specify a double asterisk (\*\*) anywhere in the expression. A double asterisk matches zero or more directories. | diff --git a/ui/public/docs/reference/feature-repository/feature-store-yaml.md b/ui/public/docs/reference/feature-repository/feature-store-yaml.md deleted file mode 100644 index a87e09ba43e..00000000000 --- a/ui/public/docs/reference/feature-repository/feature-store-yaml.md +++ /dev/null @@ -1,29 +0,0 @@ -# feature_store.yaml - -## Overview - -`feature_store.yaml` is used to configure a feature store. The file must be located at the root of a [feature repository](./). An example `feature_store.yaml` is shown below: - -{% code title="feature_store.yaml" %} -```yaml -project: loyal_spider -registry: data/registry.db -provider: local -online_store: - type: sqlite - path: data/online_store.db -``` -{% endcode %} - -## Options - -The following top-level configuration options exist in the `feature_store.yaml` file. - -* **provider** — Configures the environment in which Feast will deploy and operate. -* **registry** — Configures the location of the feature registry. -* **online_store** — Configures the online store. -* **offline_store** — Configures the offline store. -* **project** — Defines a namespace for the entire feature store. Can be used to isolate multiple deployments in a single installation of Feast. Should only contain letters, numbers, and underscores. -* **engine** - Configures the batch materialization engine. - -Please see the [RepoConfig](https://rtd.feast.dev/en/latest/#feast.repo_config.RepoConfig) API reference for the full list of configuration options. diff --git a/ui/public/docs/reference/feature-repository/registration-inferencing.md b/ui/public/docs/reference/feature-repository/registration-inferencing.md deleted file mode 100644 index 84faf949e11..00000000000 --- a/ui/public/docs/reference/feature-repository/registration-inferencing.md +++ /dev/null @@ -1,7 +0,0 @@ -# Registration Inferencing - -## Overview - -* FeatureView - When the `features` parameter is left out of the feature view definition, upon a `feast apply` call, Feast will automatically consider every column in the data source as a feature to be registered other than the specific timestamp columns associated with the underlying data source definition (e.g. timestamp_field) and the columns associated with the feature view's entities. -* DataSource - When the `timestamp_field` parameter is left out of the data source definition, upon a 'feast apply' call, Feast will automatically find the sole timestamp column in the table underlying the data source and use that as the `timestamp_field`. If there are no columns of timestamp type or multiple columns of timestamp type, `feast apply` will throw an exception. -* Entity - When the `value_type` parameter is left out of the entity definition, upon a `feast apply` call, Feast will automatically find the column corresponding with the entity's `join_key` and take that column's data type to be the `value_type`. If the column doesn't exist, `feast apply` will throw an exception. diff --git a/ui/public/docs/reference/feature-servers/README.md b/ui/public/docs/reference/feature-servers/README.md deleted file mode 100644 index 156e60c7431..00000000000 --- a/ui/public/docs/reference/feature-servers/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Feast servers - -Feast users can choose to retrieve features from a feature server, as opposed to through the Python SDK. - -{% content-ref url="python-feature-server.md" %} -[python-feature-server.md](python-feature-server.md) -{% endcontent-ref %} - -{% content-ref url="go-feature-server.md" %} -[go-feature-server.md](go-feature-server.md) -{% endcontent-ref %} - -{% content-ref url="offline-feature-server.md" %} -[offline-feature-server.md](offline-feature-server.md) -{% endcontent-ref %} - -{% content-ref url="registry-server.md" %} -[registry-server.md](registry-server.md) -{% endcontent-ref %} \ No newline at end of file diff --git a/ui/public/docs/reference/feature-servers/offline-feature-server.md b/ui/public/docs/reference/feature-servers/offline-feature-server.md deleted file mode 100644 index 9fd429794f7..00000000000 --- a/ui/public/docs/reference/feature-servers/offline-feature-server.md +++ /dev/null @@ -1,60 +0,0 @@ -# Offline feature server - -## Description - -The Offline feature server is an Apache Arrow Flight Server that uses the gRPC communication protocol to exchange data. -This server wraps calls to existing offline store implementations and exposes interfaces as Arrow Flight endpoints. - -## How to configure the server - -## CLI - -There is a CLI command that starts the Offline feature server: `feast serve_offline`. By default, remote offline server uses port 8815, the port can be overridden with a `--port` flag. - -## Deploying as a service on Kubernetes - -See [this](../../how-to-guides/running-feast-in-production.md#id-4.2.-deploy-feast-feature-servers-on-kubernetes) for an example on how to run Feast on Kubernetes using the Operator. - -The Offline feature server can be deployed with a slight modification of the FeatureStore CR - -```yaml -apiVersion: feast.dev/v1alpha1 -kind: FeatureStore -metadata: - name: sample-offline-server -spec: - feastProject: my_project - services: - offlineStore: - server: {} -``` -> _More advanced FeatureStore CR examples can be found in the feast-operator [samples directory](../../../infra/feast-operator/config/samples)._ - -## Server Example - -The complete example can be found under [remote-offline-store-example](../../../examples/remote-offline-store) - -## How to configure the client - -Please see the detail how to configure offline store client [remote-offline-store.md](../offline-stores/remote-offline-store.md) - -## Functionality Matrix - -The set of functionalities supported by remote offline stores is the same as those supported by offline stores with the SDK, which are described in detail [here](../offline-stores/overview.md#functionality). - -# Offline Feature Server Permissions and Access Control - -## API Endpoints and Permissions - -| Endpoint | Resource Type | Permission | Description | -| ------------------------------------- |------------------|---------------|---------------------------------------------------| -| offline_write_batch | FeatureView | Write Offline | Write a batch of data to the offline store | -| write_logged_features | FeatureService | Write Offline | Write logged features to the offline store | -| persist | DataSource | Write Offline | Persist the result of a read in the offline store | -| get_historical_features | FeatureView | Read Offline | Retrieve historical features | -| pull_all_from_table_or_query | DataSource | Read Offline | Pull all data from a table or read it | -| pull_latest_from_table_or_query | DataSource | Read Offline | Pull the latest data from a table or read it | - - -## How to configure Authentication and Authorization ? - -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. \ No newline at end of file diff --git a/ui/public/docs/reference/feature-servers/python-feature-server.md b/ui/public/docs/reference/feature-servers/python-feature-server.md deleted file mode 100644 index 7d251671636..00000000000 --- a/ui/public/docs/reference/feature-servers/python-feature-server.md +++ /dev/null @@ -1,240 +0,0 @@ -# Python feature server - -## Overview - -The Python feature server is an HTTP endpoint that serves features with JSON I/O. This enables users to write and read features from the online store using any programming language that can make HTTP requests. - -## CLI - -There is a CLI command that starts the server: `feast serve`. By default, Feast uses port 6566; the port be overridden with a `--port` flag. - -## Deploying as a service - -See [this](../../how-to-guides/running-feast-in-production.md#id-4.2.-deploy-feast-feature-servers-on-kubernetes) for an example on how to run Feast on Kubernetes using the Operator. - -## Example - -### Initializing a feature server - -Here's an example of how to start the Python feature server with a local feature repo: - -```bash -$ feast init feature_repo -Creating a new Feast repository in /home/tsotne/feast/feature_repo. - -$ cd feature_repo - -$ feast apply -Created entity driver -Created feature view driver_hourly_stats -Created feature service driver_activity - -Created sqlite table feature_repo_driver_hourly_stats - -$ feast materialize-incremental $(date +%Y-%m-%d) -Materializing 1 feature views to 2021-09-09 17:00:00-07:00 into the sqlite online store. - -driver_hourly_stats from 2021-09-09 16:51:08-07:00 to 2021-09-09 17:00:00-07:00: -100%|████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 295.24it/s] - -$ feast serve -09/10/2021 10:42:11 AM INFO:Started server process [8889] -INFO: Waiting for application startup. -09/10/2021 10:42:11 AM INFO:Waiting for application startup. -INFO: Application startup complete. -09/10/2021 10:42:11 AM INFO:Application startup complete. -INFO: Uvicorn running on http://127.0.0.1:6566 (Press CTRL+C to quit) -09/10/2021 10:42:11 AM INFO:Uvicorn running on http://127.0.0.1:6566 (Press CTRL+C to quit) -``` - -### Retrieving features - -After the server starts, we can execute cURL commands from another terminal tab: - -```bash -$ curl -X POST \ - "http://localhost:6566/get-online-features" \ - -d '{ - "features": [ - "driver_hourly_stats:conv_rate", - "driver_hourly_stats:acc_rate", - "driver_hourly_stats:avg_daily_trips" - ], - "entities": { - "driver_id": [1001, 1002, 1003] - } - }' | jq -{ - "metadata": { - "feature_names": [ - "driver_id", - "conv_rate", - "avg_daily_trips", - "acc_rate" - ] - }, - "results": [ - { - "values": [ - 1001, - 0.7037263512611389, - 308, - 0.8724706768989563 - ], - "statuses": [ - "PRESENT", - "PRESENT", - "PRESENT", - "PRESENT" - ], - "event_timestamps": [ - "1970-01-01T00:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z" - ] - }, - { - "values": [ - 1002, - 0.038169607520103455, - 332, - 0.48534533381462097 - ], - "statuses": [ - "PRESENT", - "PRESENT", - "PRESENT", - "PRESENT" - ], - "event_timestamps": [ - "1970-01-01T00:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z" - ] - }, - { - "values": [ - 1003, - 0.9665873050689697, - 779, - 0.7793770432472229 - ], - "statuses": [ - "PRESENT", - "PRESENT", - "PRESENT", - "PRESENT" - ], - "event_timestamps": [ - "1970-01-01T00:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z", - "2021-12-31T23:00:00Z" - ] - } - ] -} -``` - -It's also possible to specify a feature service name instead of the list of features: - -``` -curl -X POST \ - "http://localhost:6566/get-online-features" \ - -d '{ - "feature_service": , - "entities": { - "driver_id": [1001, 1002, 1003] - } - }' | jq -``` - -### Pushing features to the online and offline stores - -The Python feature server also exposes an endpoint for [push sources](../data-sources/push.md). This endpoint allows you to push data to the online and/or offline store. - -The request definition for `PushMode` is a string parameter `to` where the options are: \[`"online"`, `"offline"`, `"online_and_offline"`]. - -**Note:** timestamps need to be strings, and might need to be timezone aware (matching the schema of the offline store) - -``` -curl -X POST "http://localhost:6566/push" -d '{ - "push_source_name": "driver_stats_push_source", - "df": { - "driver_id": [1001], - "event_timestamp": ["2022-05-13 10:59:42+00:00"], - "created": ["2022-05-13 10:59:42"], - "conv_rate": [1.0], - "acc_rate": [1.0], - "avg_daily_trips": [1000] - }, - "to": "online_and_offline" - }' | jq -``` - -or equivalently from Python: - -```python -import json -import requests -from datetime import datetime - -event_dict = { - "driver_id": [1001], - "event_timestamp": [str(datetime(2021, 5, 13, 10, 59, 42))], - "created": [str(datetime(2021, 5, 13, 10, 59, 42))], - "conv_rate": [1.0], - "acc_rate": [1.0], - "avg_daily_trips": [1000], - "string_feature": "test2", -} -push_data = { - "push_source_name":"driver_stats_push_source", - "df":event_dict, - "to":"online", -} -requests.post( - "http://localhost:6566/push", - data=json.dumps(push_data)) -``` - -## Starting the feature server in TLS(SSL) mode - -Enabling TLS mode ensures that data between the Feast client and server is transmitted securely. For an ideal production environment, it is recommended to start the feature server in TLS mode. - -### Obtaining a self-signed TLS certificate and key -In development mode we can generate a self-signed certificate for testing. In an actual production environment it is always recommended to get it from a trusted TLS certificate provider. - -```shell -openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -``` - -The above command will generate two files -* `key.pem` : certificate private key -* `cert.pem`: certificate public key - -### Starting the Online Server in TLS(SSL) Mode -To start the feature server in TLS mode, you need to provide the private and public keys using the `--key` and `--cert` arguments with the `feast serve` command. - -```shell -feast serve --key /path/to/key.pem --cert /path/to/cert.pem -``` - -# Online Feature Server Permissions and Access Control - -## API Endpoints and Permissions - -| Endpoint | Resource Type | Permission | Description | -|----------------------------|---------------------------------|-------------------------------------------------------|----------------------------------------------------------------| -| /get-online-features | FeatureView,OnDemandFeatureView | Read Online | Get online features from the feature store | -| /retrieve-online-documents | FeatureView | Read Online | Retrieve online documents from the feature store for RAG | -| /push | FeatureView | Write Online, Write Offline, Write Online and Offline | Push features to the feature store (online, offline, or both) | -| /write-to-online-store | FeatureView | Write Online | Write features to the online store | -| /materialize | FeatureView | Write Online | Materialize features within a specified time range | -| /materialize-incremental | FeatureView | Write Online | Incrementally materialize features up to a specified timestamp | - -## How to configure Authentication and Authorization ? - -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. \ No newline at end of file diff --git a/ui/public/docs/reference/feature-servers/registry-server.md b/ui/public/docs/reference/feature-servers/registry-server.md deleted file mode 100644 index 9707a597035..00000000000 --- a/ui/public/docs/reference/feature-servers/registry-server.md +++ /dev/null @@ -1,26 +0,0 @@ -# Registry server - -## Description - -The Registry server uses the gRPC communication protocol to exchange data. -This enables users to communicate with the server using any programming language that can make gRPC requests. - -## How to configure the server - -## CLI - -There is a CLI command that starts the Registry server: `feast serve_registry`. By default, remote Registry Server uses port 6570, the port can be overridden with a `--port` flag. -To start the Registry Server in TLS mode, you need to provide the private and public keys using the `--key` and `--cert` arguments. -More info about TLS mode can be found in [feast-client-connecting-to-remote-registry-sever-started-in-tls-mode](../../how-to-guides/starting-feast-servers-tls-mode.md#starting-feast-registry-server-in-tls-mode) - -## How to configure the client - -Please see the detail how to configure Remote Registry client [remote.md](../registries/remote.md) - -# Registry Server Permissions and Access Control - -Please refer the [page](./../registry/registry-permissions.md) for more details on API Endpoints and Permissions. - -## How to configure Authentication and Authorization ? - -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. \ No newline at end of file diff --git a/ui/public/docs/reference/feature-store-yaml.md b/ui/public/docs/reference/feature-store-yaml.md deleted file mode 100644 index 01f586c047b..00000000000 --- a/ui/public/docs/reference/feature-store-yaml.md +++ /dev/null @@ -1,132 +0,0 @@ -# feature\_store.yaml - -## Overview - -`feature_store.yaml` is a file that is placed at the root of the [Feature Repository](feature-repository.md). This file contains configuration about how the feature store runs. An example `feature_store.yaml` is shown below: - -{% code title="feature\_store.yaml" %} -```yaml -project: loyal_spider -registry: data/registry.db -provider: local -online_store: - type: sqlite - path: data/online_store.db -``` -{% endcode %} - -## Fields in feature\_store.yaml - -* **provider** \("local" or "gcp"\) — Defines the environment in which Feast will execute data flows. -* **registry** \(a local or GCS filepath\) — Defines the location of the feature registry. -* **online\_store** — Configures the online store. This field will have various subfields depending on the type of online store: - * **type** \("sqlite" or "datastore"\) — Defines the type of online store. - * **path** \(a local filepath\) — Parameter for the sqlite online store. Defines the path to the SQLite database file. - * **project\_id** — Optional parameter for the datastore online store. Sets the GCP project id used by Feast, if not set Feast will use the default GCP project id in the local environment. -* **project** — Defines a namespace for the entire feature store. Can be used to isolate multiple deployments in a single installation of Feast. - -## Providers - -The `provider` field defines the environment in which Feast will execute data flows. As a result, it also determines the default values for other fields. - -### Local - -When using the local provider: - -* Feast can read from **local Parquet data sources.** -* Feast performs historical feature retrieval \(point-in-time joins\) using **pandas.** -* Feast performs online feature serving from a **SQLite database.** - -### **GCP** - -When using the GCP provider: - -* Feast can read data from **BigQuery data sources.** -* Feast performs historical feature retrieval \(point-in-time joins\) in **BigQuery.** -* Feast performs online feature serving from **Google Cloud Datastore.** - -**Permissions** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Command - ComponentPermissionsRecommended Role
Apply - BigQuery (source) -

bigquery.jobs.create

-

bigquery.readsessions.create

-

bigquery.readsessions.getData

-
roles/bigquery.user
Apply - Datastore (destination) -

datastore.entities.allocateIds

-

datastore.entities.create

-

datastore.entities.delete

-

datastore.entities.get

-

datastore.entities.list

-

datastore.entities.update

-
roles/datastore.owner
Materialize - BigQuery (source)bigquery.jobs.createroles/bigquery.user
Materialize - Datastore (destination) -

datastore.entities.allocateIds

-

datastore.entities.create

-

datastore.entities.delete

-

datastore.entities.get

-

datastore.entities.list

-

datastore.entities.update

-

datastore.databases.get

-
roles/datastore.owner
Get Online Features - Datastoredatastore.entities.getroles/datastore.user
Get Historical Features - BigQuery (source) -

bigquery.datasets.get

-

bigquery.tables.get

-

bigquery.tables.create

-

bigquery.tables.updateData

-

bigquery.tables.update

-

bigquery.tables.delete

-

bigquery.tables.getData

-
roles/bigquery.dataEditor
- diff --git a/ui/public/docs/reference/offline-stores/README.md b/ui/public/docs/reference/offline-stores/README.md deleted file mode 100644 index ab25fe9a276..00000000000 --- a/ui/public/docs/reference/offline-stores/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Offline stores - -Please see [Offline Store](../../getting-started/components/offline-store.md) for a conceptual explanation of offline stores. - -{% content-ref url="overview.md" %} -[overview.md](overview.md) -{% endcontent-ref %} - -{% content-ref url="dask.md" %} -[dask.md](dask.md) -{% endcontent-ref %} - -{% content-ref url="snowflake.md" %} -[snowflake.md](snowflake.md) -{% endcontent-ref %} - -{% content-ref url="bigquery.md" %} -[bigquery.md](bigquery.md) -{% endcontent-ref %} - -{% content-ref url="redshift.md" %} -[redshift.md](redshift.md) -{% endcontent-ref %} - -{% content-ref url="duckdb.md" %} -[duckdb.md](duckdb.md) -{% endcontent-ref %} - -{% content-ref url="couchbase.md" %} -[couchbase.md](couchbase.md) -{% endcontent-ref %} - -{% content-ref url="spark.md" %} -[spark.md](spark.md) -{% endcontent-ref %} - -{% content-ref url="postgres.md" %} -[postgres.md](postgres.md) -{% endcontent-ref %} - -{% content-ref url="trino.md" %} -[trino.md](trino.md) -{% endcontent-ref %} - -{% content-ref url="mssql.md" %} -[mssql.md](mssql.md) -{% endcontent-ref %} diff --git a/ui/public/docs/reference/offline-stores/bigquery.md b/ui/public/docs/reference/offline-stores/bigquery.md deleted file mode 100644 index b7607abf595..00000000000 --- a/ui/public/docs/reference/offline-stores/bigquery.md +++ /dev/null @@ -1,60 +0,0 @@ -# BigQuery offline store - -## Description - -The BigQuery offline store provides support for reading [BigQuerySources](../data-sources/bigquery.md). - -* All joins happen within BigQuery. -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to BigQuery as a table (marked for expiration) in order to complete join operations. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[gcp]'`. You can get started by then running `feast init -t gcp`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: gs://my-bucket/data/registry.db -provider: gcp -offline_store: - type: bigquery - dataset: feast_bq_dataset -``` -{% endcode %} - -The full set of configuration options is available in [BigQueryOfflineStoreConfig](https://rtd.feast.dev/en/latest/index.html#feast.infra.offline_stores.bigquery.BigQueryOfflineStoreConfig). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the BigQuery offline store. - -| | BigQuery | -| :----------------------------------------------------------------- | :------- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | yes | -| `write_logged_features` (persist logged features to offline store) | yes | - -Below is a matrix indicating which functionality is supported by `BigQueryRetrievalJob`. - -| | BigQuery | -| ----------------------------------------------------- | -------- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | yes | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data* | partial | - -*See [GitHub issue](https://github.com/feast-dev/feast/issues/2530) for details on proposed solutions for enabling the BigQuery offline store to understand tables that use `_PARTITIONTIME` as the partition column. - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/clickhouse.md b/ui/public/docs/reference/offline-stores/clickhouse.md deleted file mode 100644 index 317d6e23e1e..00000000000 --- a/ui/public/docs/reference/offline-stores/clickhouse.md +++ /dev/null @@ -1,69 +0,0 @@ -# Clickhouse offline store (contrib) - -## Description - -The Clickhouse offline store provides support for reading [ClickhouseSource](../data-sources/clickhouse.md). -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to Clickhouse as a table (temporary table by default) in order to complete join operations. - -## Disclaimer - -The Clickhouse offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[clickhouse]'`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_project -registry: data/registry.db -provider: local -offline_store: - type: feast.infra.offline_stores.contrib.clickhouse_offline_store.clickhouse.ClickhouseOfflineStore - host: DB_HOST - port: DB_PORT - database: DB_NAME - user: DB_USERNAME - password: DB_PASSWORD - use_temporary_tables_for_entity_df: true -online_store: - path: data/online_store.db -``` -{% endcode %} - -Note that `use_temporary_tables_for_entity_df` is an optional parameter. -The full set of configuration options is available in [ClickhouseOfflineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.clickhouse_offline_store.clickhouse.ClickhouseOfflineStore). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Clickhouse offline store. - -| | Clickhouse | -| :----------------------------------------------------------------- |:-----------| -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | no | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `ClickhouseRetrievalJob`. - -| | Clickhouse | -| ----------------------------------------------------- |------------| -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | yes | -| export to data warehouse | yes | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/couchbase.md b/ui/public/docs/reference/offline-stores/couchbase.md deleted file mode 100644 index 3ae0f68d4c2..00000000000 --- a/ui/public/docs/reference/offline-stores/couchbase.md +++ /dev/null @@ -1,79 +0,0 @@ -# Couchbase Columnar offline store (contrib) - -## Description - -The Couchbase Columnar offline store provides support for reading [CouchbaseColumnarSources](../data-sources/couchbase.md). **Note that Couchbase Columnar is available through [Couchbase Capella](https://cloud.couchbase.com/).** -* Entity dataframes can be provided as a SQL++ query or can be provided as a Pandas dataframe. A Pandas dataframe will be uploaded to Couchbase Capella Columnar as a collection. - -## Disclaimer - -The Couchbase Columnar offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started - -In order to use this offline store, you'll need to run `pip install 'feast[couchbase]'`. You can get started by then running `feast init -t couchbase`. - -To get started with Couchbase Capella Columnar: -1. Sign up for a [Couchbase Capella](https://cloud.couchbase.com/) account -2. [Deploy a Columnar cluster](https://docs.couchbase.com/columnar/admin/prepare-project.html) -3. [Create an Access Control Account](https://docs.couchbase.com/columnar/admin/auth/auth-data.html) - - This account should be able to read and write. - - For testing purposes, it is recommended to assign all roles to avoid any permission issues. -4. [Configure allowed IP addresses](https://docs.couchbase.com/columnar/admin/ip-allowed-list.html) - - You must allow the IP address of the machine running Feast. - - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_project -registry: data/registry.db -provider: local -offline_store: - type: couchbase.offline - connection_string: COUCHBASE_COLUMNAR_CONNECTION_STRING # Copied from Settings > Connection String page in Capella Columnar console, starts with couchbases:// - user: COUCHBASE_COLUMNAR_USER # Couchbase cluster access name from Settings > Access Control page in Capella Columnar console - password: COUCHBASE_COLUMNAR_PASSWORD # Couchbase password from Settings > Access Control page in Capella Columnar console - timeout: 120 # Timeout in seconds for Columnar operations, optional -online_store: - path: data/online_store.db -``` -{% endcode %} - -Note that `timeout`is an optional parameter. -The full set of configuration options is available in [CouchbaseColumnarOfflineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.couchbase_offline_store.couchbase.CouchbaseColumnarOfflineStoreConfig). - - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Couchbase Columnar offline store. - -| | Couchbase Columnar | -| :----------------------------------------------------------------- |:-------------------| -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `CouchbaseColumnarRetrievalJob`. - -| | Couchbase Columnar | -| ----------------------------------------------------- |--------------------| -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | yes | -| export to data warehouse | yes | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/dask.md b/ui/public/docs/reference/offline-stores/dask.md deleted file mode 100644 index d8698ba544b..00000000000 --- a/ui/public/docs/reference/offline-stores/dask.md +++ /dev/null @@ -1,55 +0,0 @@ -# Dask offline store - -## Description - -The Dask offline store provides support for reading [FileSources](../data-sources/file.md). - -{% hint style="warning" %} -All data is downloaded and joined using Python and therefore may not scale to production workloads. -{% endhint %} - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -offline_store: - type: dask -``` -{% endcode %} - -The full set of configuration options is available in [DaskOfflineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.offline_stores.dask.DaskOfflineStoreConfig). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the dask offline store. - -| | Dask | -| :-------------------------------- | :-- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | yes | -| `write_logged_features` (persist logged features to offline store) | yes | - -Below is a matrix indicating which functionality is supported by `DaskRetrievalJob`. - -| | Dask | -| --------------------------------- | --- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | no | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | no | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/duckdb.md b/ui/public/docs/reference/offline-stores/duckdb.md deleted file mode 100644 index da3c3cd0c77..00000000000 --- a/ui/public/docs/reference/offline-stores/duckdb.md +++ /dev/null @@ -1,56 +0,0 @@ -# DuckDB offline store - -## Description - -The duckdb offline store provides support for reading [FileSources](../data-sources/file.md). It can read both Parquet and Delta formats. DuckDB offline store uses [ibis](https://ibis-project.org/) under the hood to convert offline store operations to DuckDB queries. - -* Entity dataframes can be provided as a Pandas dataframe. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[duckdb]'`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_project -registry: data/registry.db -provider: local -offline_store: - type: duckdb -online_store: - path: data/online_store.db -``` -{% endcode %} - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the DuckDB offline store. - -| | DuckdDB | -| :----------------------------------------------------------------- | :---- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | yes | -| `write_logged_features` (persist logged features to offline store) | yes | - -Below is a matrix indicating which functionality is supported by `IbisRetrievalJob`. - -| | DuckDB| -| ----------------------------------------------------- | ----- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | no | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | no | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | no | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/mssql.md b/ui/public/docs/reference/offline-stores/mssql.md deleted file mode 100644 index e352b3dd2aa..00000000000 --- a/ui/public/docs/reference/offline-stores/mssql.md +++ /dev/null @@ -1,62 +0,0 @@ -# MsSQL/Synapse offline store (contrib) - -## Description - -The MsSQL offline store provides support for reading [MsSQL Sources](../data-sources/mssql.md). Specifically, it is developed to read from [Synapse SQL](https://docs.microsoft.com/en-us/azure/synapse-analytics/sql/overview-features) on Microsoft Azure - -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[azure]'`. You can get started by then following this [tutorial](https://github.com/feast-dev/feast/blob/master/docs/tutorials/azure/README.md). - -## Disclaimer - -The MsSQL offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -registry: - registry_store_type: AzureRegistryStore - path: ${REGISTRY_PATH} # Environment Variable -project: production -provider: azure -online_store: - type: redis - connection_string: ${REDIS_CONN} # Environment Variable -offline_store: - type: mssql - connection_string: ${SQL_CONN} # Environment Variable -``` -{% endcode %} - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Spark offline store. - -| | MsSql | -| :----------------------------------------------------------------- | :---- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `MsSqlServerRetrievalJob`. - -| | MsSql | -| ----------------------------------------------------- | ----- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | no | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | no | -| local execution of Python-based on-demand transforms | no | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/overview.md b/ui/public/docs/reference/offline-stores/overview.md deleted file mode 100644 index 191ccd21a64..00000000000 --- a/ui/public/docs/reference/offline-stores/overview.md +++ /dev/null @@ -1,58 +0,0 @@ -# Overview - -## Functionality - -Here are the methods exposed by the `OfflineStore` interface, along with the core functionality supported by the method: -* `get_historical_features`: point-in-time correct join to retrieve historical features -* `pull_latest_from_table_or_query`: retrieve latest feature values for materialization into the online store -* `pull_all_from_table_or_query`: retrieve a saved dataset -* `offline_write_batch`: persist dataframes to the offline store, primarily for push sources -* `write_logged_features`: persist logged features to the offline store, for feature logging - -The first three of these methods all return a `RetrievalJob` specific to an offline store, such as a `SnowflakeRetrievalJob`. Here is a list of functionality supported by `RetrievalJob`s: -* export to dataframe -* export to arrow table -* export to arrow batches (to handle large datasets in memory) -* export to SQL -* export to data lake (S3, GCS, etc.) -* export to data warehouse -* export as Spark dataframe -* local execution of Python-based on-demand transforms -* remote execution of Python-based on-demand transforms -* persist results in the offline store -* preview the query plan before execution (`RetrievalJob`s are lazily executed) -* read partitioned data - -## Functionality Matrix - -There are currently four core offline store implementations: `DaskOfflineStore`, `BigQueryOfflineStore`, `SnowflakeOfflineStore`, and `RedshiftOfflineStore`. -There are several additional implementations contributed by the Feast community (`PostgreSQLOfflineStore`, `SparkOfflineStore`, and `TrinoOfflineStore`), which are not guaranteed to be stable or to match the functionality of the core implementations. -Details for each specific offline store, such as how to configure it in a `feature_store.yaml`, can be found [here](README.md). - -Below is a matrix indicating which offline stores support which methods. - -| | Dask | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | Couchbase | -| :-------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | -| `get_historical_features` | yes | yes | yes | yes | yes | yes | yes | yes | -| `pull_latest_from_table_or_query` | yes | yes | yes | yes | yes | yes | yes | yes | -| `pull_all_from_table_or_query` | yes | yes | yes | yes | yes | yes | yes | yes | -| `offline_write_batch` | yes | yes | yes | yes | no | no | no | no | -| `write_logged_features` | yes | yes | yes | yes | no | no | no | no | - - -Below is a matrix indicating which `RetrievalJob`s support what functionality. - -| | Dask | BigQuery | Snowflake | Redshift | Postgres | Spark | Trino | DuckDB | Couchbase | -| --------------------------------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| export to dataframe | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| export to arrow table | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| export to arrow batches | no | no | no | yes | no | no | no | no | no | -| export to SQL | no | yes | yes | yes | yes | no | yes | no | yes | -| export to data lake (S3, GCS, etc.) | no | no | yes | no | yes | no | no | no | yes | -| export to data warehouse | no | yes | yes | yes | yes | no | no | no | yes | -| export as Spark dataframe | no | no | yes | no | no | yes | no | no | no | -| local execution of Python-based on-demand transforms | yes | yes | yes | yes | yes | no | yes | yes | yes | -| remote execution of Python-based on-demand transforms | no | no | no | no | no | no | no | no | no | -| persist results in the offline store | yes | yes | yes | yes | yes | yes | no | yes | yes | -| preview the query plan before execution | yes | yes | yes | yes | yes | yes | yes | no | yes | -| read partitioned data | yes | yes | yes | yes | yes | yes | yes | yes | yes | diff --git a/ui/public/docs/reference/offline-stores/postgres.md b/ui/public/docs/reference/offline-stores/postgres.md deleted file mode 100644 index 321ddcf25e7..00000000000 --- a/ui/public/docs/reference/offline-stores/postgres.md +++ /dev/null @@ -1,76 +0,0 @@ -# PostgreSQL offline store (contrib) - -## Description - -The PostgreSQL offline store provides support for reading [PostgreSQLSources](../data-sources/postgres.md). -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to Postgres as a table in order to complete join operations. - -## Disclaimer - -The PostgreSQL offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[postgres]'`. You can get started by then running `feast init -t postgres`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_project -registry: data/registry.db -provider: local -offline_store: - type: postgres - host: DB_HOST - port: DB_PORT - database: DB_NAME - db_schema: DB_SCHEMA - user: DB_USERNAME - password: DB_PASSWORD - sslmode: verify-ca - sslkey_path: /path/to/client-key.pem - sslcert_path: /path/to/client-cert.pem - sslrootcert_path: /path/to/server-ca.pem - entity_select_mode: temp_table -online_store: - path: data/online_store.db -``` -{% endcode %} - -Note that `sslmode`, `sslkey_path`, `sslcert_path`, and `sslrootcert_path` are optional parameters. -The full set of configuration options is available in [PostgreSQLOfflineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.postgres_offline_store.postgres.PostgreSQLOfflineStoreConfig). - -Additionally, a new optional parameter `entity_select_mode` was added to tell how Postgres should load the entity data. By default(`temp_table`), a temporary table is created and the entity data frame or sql is loaded into that table. A new value of `embed_query` was added to allow directly loading the SQL query into a CTE, providing improved performance and skipping the need to CREATE and DROP the temporary table. - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the PostgreSQL offline store. - -| | Postgres | -| :----------------------------------------------------------------- | :------- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `PostgreSQLRetrievalJob`. - -| | Postgres | -| ----------------------------------------------------- | -------- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | yes | -| export to data warehouse | yes | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/redshift.md b/ui/public/docs/reference/offline-stores/redshift.md deleted file mode 100644 index e33a1856cb2..00000000000 --- a/ui/public/docs/reference/offline-stores/redshift.md +++ /dev/null @@ -1,179 +0,0 @@ -# Redshift offline store - -## Description - -The Redshift offline store provides support for reading [RedshiftSources](../data-sources/redshift.md). - -* All joins happen within Redshift. -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to Redshift temporarily in order to complete join operations. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[aws]'`. You can get started by then running `feast init -t aws`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: aws -offline_store: - type: redshift - region: us-west-2 - cluster_id: feast-cluster - database: feast-database - user: redshift-user - s3_staging_location: s3://feast-bucket/redshift - iam_role: arn:aws:iam::123456789012:role/redshift_s3_access_role -``` -{% endcode %} - -The full set of configuration options is available in [RedshiftOfflineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.redshift.RedshiftOfflineStoreConfig). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Redshift offline store. - -| | Redshift | -| :----------------------------------------------------------------- | :------- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | yes | -| `write_logged_features` (persist logged features to offline store) | yes | - -Below is a matrix indicating which functionality is supported by `RedshiftRetrievalJob`. - -| | Redshift | -| ----------------------------------------------------- | -------- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | yes | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | yes | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). - -## Permissions - -Feast requires the following permissions in order to execute commands for Redshift offline store: - -| **Command** | Permissions | Resources | -| --------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Apply** |

redshift-data:DescribeTable

redshift:GetClusterCredentials

|

arn:aws:redshift:<region>:<account_id>:dbuser:<redshift_cluster_id>/<redshift_username>

arn:aws:redshift:<region>:<account_id>:dbname:<redshift_cluster_id>/<redshift_database_name>

arn:aws:redshift:<region>:<account_id>:cluster:<redshift_cluster_id>

| -| **Materialize** | redshift-data:ExecuteStatement | arn:aws:redshift:\:\:cluster:\ | -| **Materialize** | redshift-data:DescribeStatement | \* | -| **Materialize** |

s3:ListBucket

s3:GetObject

s3:DeleteObject

|

arn:aws:s3:::<bucket_name>

arn:aws:s3:::<bucket_name>/*

| -| **Get Historical Features** |

redshift-data:ExecuteStatement

redshift:GetClusterCredentials

|

arn:aws:redshift:<region>:<account_id>:dbuser:<redshift_cluster_id>/<redshift_username>

arn:aws:redshift:<region>:<account_id>:dbname:<redshift_cluster_id>/<redshift_database_name>

arn:aws:redshift:<region>:<account_id>:cluster:<redshift_cluster_id>

| -| **Get Historical Features** | redshift-data:DescribeStatement | \* | -| **Get Historical Features** |

s3:ListBucket

s3:GetObject

s3:PutObject

s3:DeleteObject

|

arn:aws:s3:::<bucket_name>

arn:aws:s3:::<bucket_name>/*

| - -The following inline policy can be used to grant Feast the necessary permissions: - -```javascript -{ - "Statement": [ - { - "Action": [ - "s3:ListBucket", - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::/*", - "arn:aws:s3:::" - ] - }, - { - "Action": [ - "redshift-data:DescribeTable", - "redshift:GetClusterCredentials", - "redshift-data:ExecuteStatement" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:redshift:::dbuser:/", - "arn:aws:redshift:::dbname:/", - "arn:aws:redshift:::cluster:" - ] - }, - { - "Action": [ - "redshift-data:DescribeStatement" - ], - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" -} -``` - -In addition to this, Redshift offline store requires an IAM role that will be used by Redshift itself to interact with S3. More concretely, Redshift has to use this IAM role to run [UNLOAD](https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html) and [COPY](https://docs.aws.amazon.com/redshift/latest/dg/r_COPY.html) commands. Once created, this IAM role needs to be configured in `feature_store.yaml` file as `offline_store: iam_role`. - -The following inline policy can be used to grant Redshift necessary permissions to access S3: - -```javascript -{ - "Statement": [ - { - "Action": "s3:*", - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::feast-int-bucket", - "arn:aws:s3:::feast-int-bucket/*" - ] - } - ], - "Version": "2012-10-17" -} -``` - -While the following trust relationship is necessary to make sure that Redshift, and only Redshift can assume this role: - -```javascript -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "redshift.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] -} -``` - - -## Redshift Serverless - -In order to use [AWS Redshift Serverless](https://aws.amazon.com/redshift/redshift-serverless/), specify a workgroup instead of a cluster_id and user. - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: aws -offline_store: - type: redshift - region: us-west-2 - workgroup: feast-workgroup - database: feast-database - s3_staging_location: s3://feast-bucket/redshift - iam_role: arn:aws:iam::123456789012:role/redshift_s3_access_role -``` -{% endcode %} - -Please note that the IAM policies above will need the [redshift-serverless](https://aws.permissions.cloud/iam/redshift-serverless) version, rather than the standard [redshift](https://aws.permissions.cloud/iam/redshift). \ No newline at end of file diff --git a/ui/public/docs/reference/offline-stores/remote-offline-store.md b/ui/public/docs/reference/offline-stores/remote-offline-store.md deleted file mode 100644 index 8057ae32849..00000000000 --- a/ui/public/docs/reference/offline-stores/remote-offline-store.md +++ /dev/null @@ -1,31 +0,0 @@ -# Remote Offline Store - -## Description - -The Remote Offline Store is an Arrow Flight client for the offline store that implements the `RemoteOfflineStore` class using the existing `OfflineStore` interface. -The client implements various methods, including `get_historical_features`, `pull_latest_from_table_or_query`, `write_logged_features`, and `offline_write_batch`. - -## How to configure the client - -User needs to create client side `feature_store.yaml` file and set the `offline_store` type `remote` and provide the server connection configuration -including adding the host and specifying the port (default is 8815) required by the Arrow Flight client to connect with the Arrow Flight server. - -{% code title="feature_store.yaml" %} -```yaml -offline_store: - type: remote - host: localhost - port: 8815 -``` -{% endcode %} - -## Client Example - -The complete example can be find under [remote-offline-store-example](../../../examples/remote-offline-store) - -## How to configure the server - -Please see the detail how to configure offline feature server [offline-feature-server.md](../feature-servers/offline-feature-server.md) - -## How to configure Authentication and Authorization -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. diff --git a/ui/public/docs/reference/offline-stores/snowflake.md b/ui/public/docs/reference/offline-stores/snowflake.md deleted file mode 100644 index 39bbe3f8a03..00000000000 --- a/ui/public/docs/reference/offline-stores/snowflake.md +++ /dev/null @@ -1,82 +0,0 @@ -# Snowflake offline store - -## Description - -The [Snowflake](https://trial.snowflake.com) offline store provides support for reading [SnowflakeSources](../data-sources/snowflake.md). -* All joins happen within Snowflake. -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to Snowflake as a temporary table in order to complete join operations. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[snowflake]'`. - -If you're using a file based registry, then you'll also need to install the relevant cloud extra (`pip install 'feast[snowflake, CLOUD]'` where `CLOUD` is one of `aws`, `gcp`, `azure`) - -You can get started by then running `feast init -t snowflake`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -offline_store: - type: snowflake.offline - account: snowflake_deployment.us-east-1 - user: user_login - password: user_password - role: SYSADMIN - warehouse: COMPUTE_WH - database: FEAST - schema: PUBLIC -``` -{% endcode %} - -The full set of configuration options is available in [SnowflakeOfflineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.offline_stores.snowflake.SnowflakeOfflineStoreConfig). - - -## Limitation -Please be aware that here is a restriction/limitation for using SQL query string in Feast with Snowflake. Try to avoid the usage of single quote in SQL query string. For example, the following query string will fail: -``` -SELECT - some_column -FROM - some_table -WHERE - other_column = 'value' -``` -That 'value' will fail in Snowflake. Instead, please use pairs of dollar signs like `$$value$$` as [mentioned in Snowflake document](https://docs.snowflake.com/en/sql-reference/data-types-text#label-dollar-quoted-string-constants). - - - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Snowflake offline store. - -| | Snowflake | -| :----------------------------------------------------------------- | :-------- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | yes | -| `write_logged_features` (persist logged features to offline store) | yes | - -Below is a matrix indicating which functionality is supported by `SnowflakeRetrievalJob`. - -| | Snowflake | -| ----------------------------------------------------- | --------- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | yes | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | yes | -| export to data warehouse | yes | -| export as Spark dataframe | yes | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/spark.md b/ui/public/docs/reference/offline-stores/spark.md deleted file mode 100644 index 2e2facba64a..00000000000 --- a/ui/public/docs/reference/offline-stores/spark.md +++ /dev/null @@ -1,72 +0,0 @@ -# Spark offline store (contrib) - -## Description - -The Spark offline store provides support for reading [SparkSources](../data-sources/spark.md). - -* Entity dataframes can be provided as a SQL query, Pandas dataframe or can be provided as a Pyspark dataframe. A Pandas dataframes will be converted to a Spark dataframe and processed as a temporary view. - -## Disclaimer - -The Spark offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[spark]'`. You can get started by then running `feast init -t spark`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_project -registry: data/registry.db -provider: local -offline_store: - type: spark - spark_conf: - spark.master: "local[*]" - spark.ui.enabled: "false" - spark.eventLog.enabled: "false" - spark.sql.catalogImplementation: "hive" - spark.sql.parser.quotedRegexColumnNames: "true" - spark.sql.session.timeZone: "UTC" - spark.sql.execution.arrow.fallback.enabled: "true" - spark.sql.execution.arrow.pyspark.enabled: "true" -online_store: - path: data/online_store.db -``` -{% endcode %} - -The full set of configuration options is available in [SparkOfflineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.offline_stores.contrib.spark_offline_store.spark.SparkOfflineStoreConfig). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Spark offline store. - -| | Spark | -| :----------------------------------------------------------------- | :---- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `SparkRetrievalJob`. - -| | Spark | -| ----------------------------------------------------- | ----- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | no | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | no | -| export as Spark dataframe | yes | -| local execution of Python-based on-demand transforms | no | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | yes | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/offline-stores/trino.md b/ui/public/docs/reference/offline-stores/trino.md deleted file mode 100644 index fd437a7aa67..00000000000 --- a/ui/public/docs/reference/offline-stores/trino.md +++ /dev/null @@ -1,108 +0,0 @@ -# Trino offline store (contrib) - -## Description - -The Trino offline store provides support for reading [TrinoSources](../data-sources/trino.md). -* Entity dataframes can be provided as a SQL query or can be provided as a Pandas dataframe. A Pandas dataframes will be uploaded to Trino as a table in order to complete join operations. - -## Disclaimer - -The Trino offline store does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[trino]'`. You can then run `feast init`, then swap out `feature_store.yaml` with the below example to connect to Trino. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: feature_repo -registry: data/registry.db -provider: local -offline_store: - type: feast_trino.trino.TrinoOfflineStore - host: localhost - port: 8080 - catalog: memory - connector: - type: memory - user: trino - source: feast-trino-offline-store - http-scheme: https - ssl-verify: false - x-trino-extra-credential-header: foo=bar, baz=qux - - # enables authentication in Trino connections, pick the one you need - # if you don't need authentication, you can safely remove the whole auth block - auth: - # Basic Auth - type: basic - config: - username: foo - password: $FOO - - # Certificate - type: certificate - config: - cert-file: /path/to/cert/file - key-file: /path/to/key/file - - # JWT - type: jwt - config: - token: $JWT_TOKEN - - # OAuth2 (no config required) - type: oauth2 - - # Kerberos - type: kerberos - config: - config-file: /path/to/kerberos/config/file - service-name: foo - mutual-authentication: true - force-preemptive: true - hostname-override: custom-hostname - sanitize-mutual-error-response: true - principal: principal-name - delegate: true - ca_bundle: /path/to/ca/bundle/file -online_store: - path: data/online_store.db -``` -{% endcode %} - -The full set of configuration options is available in [TrinoOfflineStoreConfig](https://rtd.feast.dev/en/master/#trino-offline-store). - -## Functionality Matrix - -The set of functionality supported by offline stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the Trino offline store. - -| | Trino | -| :----------------------------------------------------------------- | :---- | -| `get_historical_features` (point-in-time correct join) | yes | -| `pull_latest_from_table_or_query` (retrieve latest feature values) | yes | -| `pull_all_from_table_or_query` (retrieve a saved dataset) | yes | -| `offline_write_batch` (persist dataframes to offline store) | no | -| `write_logged_features` (persist logged features to offline store) | no | - -Below is a matrix indicating which functionality is supported by `TrinoRetrievalJob`. - -| | Trino | -| ----------------------------------------------------- | ----- | -| export to dataframe | yes | -| export to arrow table | yes | -| export to arrow batches | no | -| export to SQL | yes | -| export to data lake (S3, GCS, etc.) | no | -| export to data warehouse | no | -| export as Spark dataframe | no | -| local execution of Python-based on-demand transforms | yes | -| remote execution of Python-based on-demand transforms | no | -| persist results in the offline store | no | -| preview the query plan before execution | yes | -| read partitioned data | yes | - -To compare this set of functionality against other offline stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/online-stores/README.md b/ui/public/docs/reference/online-stores/README.md deleted file mode 100644 index 5df4710434c..00000000000 --- a/ui/public/docs/reference/online-stores/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Online stores - -Please see [Online Store](../../getting-started/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 %} - -{% content-ref url="snowflake.md" %} -[snowflake.md](snowflake.md) -{% endcontent-ref %} - -{% content-ref url="redis.md" %} -[redis.md](redis.md) -{% endcontent-ref %} - -{% content-ref url="dragonfly.md" %} -[dragonfly.md](dragonfly.md) -{% endcontent-ref %} - -{% content-ref url="ikv.md" %} -[ikv.md](ikv.md) -{% endcontent-ref %} - -{% content-ref url="datastore.md" %} -[datastore.md](datastore.md) -{% endcontent-ref %} - -{% content-ref url="dynamodb.md" %} -[dynamodb.md](dynamodb.md) -{% endcontent-ref %} - -{% content-ref url="bigtable.md" %} -[bigtable.md](mysql.md) -{% endcontent-ref %} - -{% content-ref url="postgres.md" %} -[postgres.md](postgres.md) -{% endcontent-ref %} - -{% content-ref url="cassandra.md" %} -[cassandra.md](cassandra.md) -{% endcontent-ref %} - -{% content-ref url="couchbase.md" %} -[couchbase.md](couchbase.md) -{% endcontent-ref %} - -{% content-ref url="mysql.md" %} -[mysql.md](mysql.md) -{% endcontent-ref %} - -{% content-ref url="hazelcast.md" %} -[hazelcast.md](hazelcast.md) -{% endcontent-ref %} - -{% content-ref url="scylladb.md" %} -[scylladb.md](scylladb.md) -{% endcontent-ref %} - -{% content-ref url="remote.md" %} -[remote.md](remote.md) -{% endcontent-ref %} - -{% content-ref url="singlestore.md" %} -[singlestore.md](singlestore.md) -{% endcontent-ref %} diff --git a/ui/public/docs/reference/online-stores/bigtable.md b/ui/public/docs/reference/online-stores/bigtable.md deleted file mode 100644 index 0d6e7cfb13b..00000000000 --- a/ui/public/docs/reference/online-stores/bigtable.md +++ /dev/null @@ -1,56 +0,0 @@ -# Bigtable online store - -## Description - -The [Bigtable](https://cloud.google.com/bigtable) online store provides support for -materializing feature values into Cloud Bigtable. The data model used to store feature -values in Bigtable is described in more detail -[here](../../specs/online_store_format.md#google-bigtable-online-store-format). - -## Getting started - -In order to use this online store, you'll need to run `pip install 'feast[gcp]'`. You -can then get started with the command `feast init REPO_NAME -t gcp`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: gcp -online_store: - type: bigtable - project_id: my_gcp_project - instance: my_bigtable_instance -``` -{% endcode %} - -The full set of configuration options is available in -[BigtableOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.bigtable.BigtableOnlineStoreConfig). - -## 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 Bigtable online store. - -| | Bigtable | -|-----------------------------------------------------------|----------| -| 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 | yes | -| 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 | yes | - -To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/online-stores/cassandra.md b/ui/public/docs/reference/online-stores/cassandra.md deleted file mode 100644 index 198f15ca47f..00000000000 --- a/ui/public/docs/reference/online-stores/cassandra.md +++ /dev/null @@ -1,92 +0,0 @@ -# Cassandra + Astra DB online store - -## Description - -The [[Cassandra](https://cassandra.apache.org/_/index.html) / [Astra DB](https://www.datastax.com/products/datastax-astra?utm_source=feast)] online store provides support for materializing feature values into an Apache Cassandra / Astra DB database for online features. - -* The whole project is contained within a Cassandra keyspace -* Each feature view is mapped one-to-one to a specific Cassandra table -* This implementation inherits all strengths of Cassandra such as high availability, fault-tolerance, and data distribution - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[cassandra]'`. You can then get started with the command `feast init REPO_NAME -t cassandra`. - -### Example (Cassandra) - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: cassandra - hosts: - - 192.168.1.1 - - 192.168.1.2 - - 192.168.1.3 - keyspace: KeyspaceName - port: 9042 # optional - username: user # optional - password: secret # optional - protocol_version: 5 # optional - load_balancing: # optional - local_dc: 'datacenter1' # optional - load_balancing_policy: 'TokenAwarePolicy(DCAwareRoundRobinPolicy)' # optional - read_concurrency: 100 # optional - write_concurrency: 100 # optional -``` -{% endcode %} - -### Example (Astra DB) - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: cassandra - secure_bundle_path: /path/to/secure/bundle.zip - keyspace: KeyspaceName - username: Client_ID - password: Client_Secret - protocol_version: 4 # optional - load_balancing: # optional - local_dc: 'eu-central-1' # optional - load_balancing_policy: 'TokenAwarePolicy(DCAwareRoundRobinPolicy)' # optional - read_concurrency: 100 # optional - write_concurrency: 100 # optional -``` -{% endcode %} - -The full set of configuration options is available in [CassandraOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.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`. - -## 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/ui/public/docs/reference/online-stores/couchbase.md b/ui/public/docs/reference/online-stores/couchbase.md deleted file mode 100644 index 2878deb97ee..00000000000 --- a/ui/public/docs/reference/online-stores/couchbase.md +++ /dev/null @@ -1,78 +0,0 @@ -# Couchbase Online Store -> NOTE: -> This is a community-contributed online store that is in alpha development. It is not officially supported by the Feast project. - -## Description -The [Couchbase](https://www.couchbase.com/) online store provides support for materializing feature values into a Couchbase Operational cluster for serving online features in real-time. - -* Only the latest feature values are persisted -* Features are stored in a document-oriented format - -The data model for using Couchbase as an online store follows a document format: -* Document ID: `{project}:{table_name}:{entity_key_hex}:{feature_name}` -* Document Content: - * `metadata`: - * `event_ts` (ISO formatted timestamp) - * `created_ts` (ISO formatted timestamp) - * `feature_name` (String) - * `value` (Base64 encoded protobuf binary) - - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[couchbase]'`. You can then get started with the command `feast init REPO_NAME -t couchbase`. - -To get started with Couchbase Capella Operational: -1. [Sign up for a Couchbase Capella account](https://docs.couchbase.com/cloud/get-started/create-account.html#sign-up-free-tier) -2. [Deploy an Operational cluster](https://docs.couchbase.com/cloud/get-started/create-account.html#getting-started) -3. [Create a bucket](https://docs.couchbase.com/cloud/clusters/data-service/manage-buckets.html#add-bucket) - - This can be named anything, but must correspond to the bucket described in the `feature_store.yaml` configuration file. -4. [Create cluster access credentials](https://docs.couchbase.com/cloud/clusters/manage-database-users.html#create-database-credentials) - - These credentials should have full access to the bucket created in step 3. -5. [Configure allowed IP addresses](https://docs.couchbase.com/cloud/clusters/allow-ip-address.html) - - You must allow the IP address of the machine running Feast. - -## Example -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: couchbase.online - connection_string: couchbase://127.0.0.1 # Couchbase connection string, copied from 'Connect' page in Couchbase Capella console - user: Administrator # Couchbase username from access credentials - password: password # Couchbase password from access credentials - bucket_name: feast # Couchbase bucket name, defaults to feast - kv_port: 11210 # Couchbase key-value port, defaults to 11210. Required if custom ports are used. -entity_key_serialization_version: 2 -``` -{% endcode %} - -The full set of configuration options is available in `CouchbaseOnlineStoreConfig`. - - -## 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 Couchbase online store. - -| | Couchbase | -| :-------------------------------------------------------- | :-------- | -| 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 | yes | -| 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/ui/public/docs/reference/online-stores/datastore.md b/ui/public/docs/reference/online-stores/datastore.md deleted file mode 100644 index 761d246ba7e..00000000000 --- a/ui/public/docs/reference/online-stores/datastore.md +++ /dev/null @@ -1,50 +0,0 @@ -# Datastore online store - -## Description - -The [Datastore](https://cloud.google.com/datastore) online store provides support for materializing feature values into Cloud Datastore. The data model used to store feature values in Datastore is described in more detail [here](../../specs/online_store_format.md#google-datastore-online-store-format). - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[gcp]'`. You can then get started with the command `feast init REPO_NAME -t gcp`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: gcp -online_store: - type: datastore - project_id: my_gcp_project - namespace: my_datastore_namespace -``` -{% endcode %} - -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/ui/public/docs/reference/online-stores/dragonfly.md b/ui/public/docs/reference/online-stores/dragonfly.md deleted file mode 100644 index bcd814ecc45..00000000000 --- a/ui/public/docs/reference/online-stores/dragonfly.md +++ /dev/null @@ -1,90 +0,0 @@ -# Dragonfly online store - -## Description - -[Dragonfly](https://github.com/dragonflydb/dragonfly) is a modern in-memory datastore that implements novel algorithms and data structures on top of a multi-threaded, share-nothing architecture. Thanks to its API compatibility, Dragonfly can act as a drop-in replacement for Redis. Due to Dragonfly's hardware efficiency, you can run a single node instance on a small 8GB instance or scale vertically to large 768GB machines with 64 cores. This greatly reduces infrastructure costs as well as architectural complexity. - -Similar to Redis, Dragonfly can be used as an online feature store for Feast. - -## Using Dragonfly as a drop-in Feast online store instead of Redis - -Make sure you have Python and `pip` installed. - -Install the Feast SDK and CLI - -`pip install feast` - -In order to use Dragonfly as the online store, you'll need to install the redis extra: - -`pip install 'feast[redis]'` - -### 1. Create a feature repository - -Bootstrap a new feature repository: - -``` -feast init feast_dragonfly -cd feast_dragonfly/feature_repo -``` - -Update `feature_repo/feature_store.yaml` with the below contents: - -``` -project: feast_dragonfly -registry: data/registry.db -provider: local -online_store: -type: redis -connection_string: "localhost:6379" -``` - -### 2. Start Dragonfly - -There are several options available to get Dragonfly up and running quickly. We will be using Docker for this tutorial. - -`docker run --network=host --ulimit memlock=-1 docker.dragonflydb.io/dragonflydb/dragonfly` - -### 3. Register feature definitions and deploy your feature store - -`feast apply` - -The `apply` command scans python files in the current directory (`example_repo.py` in this case) for feature view/entity definitions, registers the objects, and deploys infrastructure. -You should see the following output: - -``` -.... -Created entity driver -Created feature view driver_hourly_stats_fresh -Created feature view driver_hourly_stats -Created on demand feature view transformed_conv_rate -Created on demand feature view transformed_conv_rate_fresh -Created feature service driver_activity_v1 -Created feature service driver_activity_v3 -Created feature service driver_activity_v2 -``` - -## 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/ui/public/docs/reference/online-stores/dynamodb.md b/ui/public/docs/reference/online-stores/dynamodb.md deleted file mode 100644 index 344caccac1d..00000000000 --- a/ui/public/docs/reference/online-stores/dynamodb.md +++ /dev/null @@ -1,84 +0,0 @@ -# DynamoDB online store - -## Description - -The [DynamoDB](https://aws.amazon.com/dynamodb/) online store provides support for materializing feature values into AWS DynamoDB. - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[aws]'`. You can then get started with the command `feast init REPO_NAME -t aws`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: aws -online_store: - type: dynamodb - region: us-west-2 -``` -{% endcode %} - -The full set of configuration options is available in [DynamoDBOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.dynamodb.DynamoDBOnlineStoreConfig). - -## Permissions - -Feast requires the following permissions in order to execute commands for DynamoDB online store: - -| **Command** | Permissions | Resources | -| ----------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------- | -| **Apply** |

dynamodb:CreateTable

dynamodb:DescribeTable

dynamodb:DeleteTable

| arn:aws:dynamodb:\:\:table/\* | -| **Materialize** | dynamodb.BatchWriteItem | arn:aws:dynamodb:\:\:table/\* | -| **Get Online Features** | dynamodb.BatchGetItem | arn:aws:dynamodb:\:\:table/\* | - -The following inline policy can be used to grant Feast the necessary permissions: - -```javascript -{ - "Statement": [ - { - "Action": [ - "dynamodb:CreateTable", - "dynamodb:DescribeTable", - "dynamodb:DeleteTable", - "dynamodb:BatchWriteItem", - "dynamodb:BatchGetItem" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:dynamodb:::table/*" - ] - } - ], - "Version": "2012-10-17" -} -``` - -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/ui/public/docs/reference/online-stores/elasticsearch.md b/ui/public/docs/reference/online-stores/elasticsearch.md deleted file mode 100644 index 683fbf6ae00..00000000000 --- a/ui/public/docs/reference/online-stores/elasticsearch.md +++ /dev/null @@ -1,139 +0,0 @@ -# ElasticSearch online store - -## Description - -The ElasticSearch online store provides support for materializing tabular feature values, as well as embedding feature vectors, into an ElasticSearch index for serving online features. \ -The embedding feature vectors are stored as dense vectors, and can be used for similarity search. More information on dense vectors can be found [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html). - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[elasticsearch]'`. You can get started by then running `feast init -t elasticsearch`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: elasticsearch - host: ES_HOST - port: ES_PORT - user: ES_USERNAME - password: ES_PASSWORD - write_batch_size: 1000 -``` -{% endcode %} - -The full set of configuration options is available in [ElasticsearchOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.elasticsearch_online_store.ElasticsearchOnlineStoreConfig). - -## Functionality Matrix - - -| | 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). - -## Retrieving online document vectors - -The ElasticSearch online store supports retrieving document vectors for a given list of entity keys. The document vectors are returned as a dictionary where the key is the entity key and the value is the document vector. The document vector is a dense vector of floats. - -{% code title="python" %} -```python -from feast import FeatureStore - -feature_store = FeatureStore(repo_path="feature_store.yaml") - -query_vector = [1.0, 2.0, 3.0, 4.0, 5.0] -top_k = 5 - -# Retrieve the top k closest features to the query vector - -feature_values = feature_store.retrieve_online_documents_v2( - features=["my_feature"], - query=query_vector, - top_k=top_k, -) -``` -{% endcode %} - -## Indexing -Currently, the indexing mapping in the ElasticSearch online store is configured as: - -{% code title="indexing_mapping" %} -```json -{ - "dynamic_templates": [ - { - "feature_objects": { - "match_mapping_type": "object", - "match": "*", - "mapping": { - "type": "object", - "properties": { - "feature_value": {"type": "binary"}, - "value_text": {"type": "text"}, - "vector_value": { - "type": "dense_vector", - "dims": vector_field_length, - "index": True, - "similarity": config.online_store.similarity, - }, - }, - }, - } - } - ], - "properties": { - "entity_key": {"type": "keyword"}, - "timestamp": {"type": "date"}, - "created_ts": {"type": "date"}, - }, -} -``` -{% endcode %} -And the online_read API mapping is configured as: - -{% code title="online_read_mapping" %} -```json -"query": { - "bool": { - "must": [ - {"terms": {"entity_key": entity_keys}}, - {"terms": {"feature_name": requested_features}}, - ] - } -}, -``` -{% endcode %} - -And the similarity search API mapping is configured as: - -{% code title="similarity_search_mapping" %} -```json -{ - "field": "vector_value", - "query_vector": embedding_vector, - "k": top_k, -} -``` -{% endcode %} - -These APIs are subject to change in future versions of Feast to improve performance and usability. \ No newline at end of file diff --git a/ui/public/docs/reference/online-stores/hazelcast.md b/ui/public/docs/reference/online-stores/hazelcast.md deleted file mode 100644 index c2fb2d898a8..00000000000 --- a/ui/public/docs/reference/online-stores/hazelcast.md +++ /dev/null @@ -1,58 +0,0 @@ -# Hazelcast online store - -## Description - -The [Hazelcast](htpps://hazelcast.com) online store provides support for materializing feature values into a Hazelcast cluster for serving online features in real-time. -In order to use Hazelcast as an online store, you need to have a running Hazelcast cluster. See this [getting started](https://hazelcast.com/get-started/) page for more details. - -* Each feature view is mapped one-to-one to a specific Hazelcast IMap -* This implementation inherits all strengths of Hazelcast such as high availability, fault-tolerance, and data distribution. -* Secure TSL/SSL connection is supported by Hazelcast online store. -* You can set TTL (Time-To-Live) setting for your features in Hazelcast cluster. - -Each feature view corresponds to an IMap in Hazelcast cluster and the entries in that IMap correspond to features of entities. -Each feature value stored separately and can be retrieved individually. - -## Getting started - -In order to use Hazelcast online store, you'll need to run `pip install 'feast[hazelcast]'`. You can then get started with the command `feast init REPO_NAME -t hazelcast`. - - -## Examples - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: hazelcast - cluster_name: dev - cluster_members: ["localhost:5701"] - key_ttl_seconds: 36000 -``` -{% endcode %} - -## Functionality Matrix - -| | Hazelcast | -| :-------------------------------------------------------- |:----------| -| 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 | 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/ui/public/docs/reference/online-stores/ikv.md b/ui/public/docs/reference/online-stores/ikv.md deleted file mode 100644 index 79f21d17797..00000000000 --- a/ui/public/docs/reference/online-stores/ikv.md +++ /dev/null @@ -1,69 +0,0 @@ -# IKV (Inlined Key-Value Store) online store - -## Description - -[IKV](https://github.com/inlinedio/ikv-store) is a fully-managed embedded key-value store, primarily designed for storing ML features. Most key-value stores (think Redis or Cassandra) need a remote database cluster, whereas IKV allows you to utilize your existing application infrastructure to store data (cost efficient) and access it without any network calls (better performance). See detailed performance benchmarks and cost comparison with Redis on [https://inlined.io](https://inlined.io). IKV can be used as an online-store in Feast, the rest of this guide goes over the setup. - -## Getting started -Make sure you have Python and `pip` installed. - -Install the Feast SDK and CLI: `pip install feast` - -In order to use this online store, you'll need to install the IKV extra (along with the dependency needed for the offline store of choice). E.g. -- `pip install 'feast[gcp, ikv]'` -- `pip install 'feast[snowflake, ikv]'` -- `pip install 'feast[aws, ikv]'` -- `pip install 'feast[azure, ikv]'` - -You can get started by using any of the other templates (e.g. `feast init -t gcp` or `feast init -t snowflake` or `feast init -t aws`), and then swapping in IKV as the online store as seen below in the examples. - -### 1. Provision an IKV store -Go to [https://inlined.io](https://inlined.io) or email onboarding[at]inlined.io - -### 2. Configure - -Update `my_feature_repo/feature_store.yaml` with the below contents: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: ikv - account_id: secret - account_passkey: secret - store_name: your-store-name - mount_directory: /absolute/path/on/disk/for/ikv/embedded/index -``` -{% endcode %} - -After provisioning an IKV account/store, you should have an account id, passkey and store-name. Additionally you must specify a mount-directory - where IKV will pull/update (maintain) a copy of the index for online reads (IKV is an embedded database). It can be skipped only if you don't plan to read any data from this container. The mount directory path usually points to a location on local/remote disk. - -The full set of configuration options is available in IKVOnlineStoreConfig at `sdk/python/feast/infra/online_stores/contrib/ikv_online_store/ikv.py` - -## 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 IKV online store. - -| | IKV | -| :-------------------------------------------------------- | :---- | -| 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 | yes | -| support for ttl (time to live) at retrieval | no | -| support for deleting expired data | no | -| 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/ui/public/docs/reference/online-stores/milvus.md b/ui/public/docs/reference/online-stores/milvus.md deleted file mode 100644 index 014c7bd68a5..00000000000 --- a/ui/public/docs/reference/online-stores/milvus.md +++ /dev/null @@ -1,65 +0,0 @@ -# Redis online store - -## Description - -The [Milvus](https://milvus.io/) online store provides support for materializing feature values into Milvus. - -* The data model used to store feature values in Milvus is described in more detail [here](../../specs/online\_store\_format.md). - -## Getting started -In order to use this online store, you'll need to install the Milvus extra (along with the dependency needed for the offline store of choice). E.g. - -`pip install 'feast[milvus]'` - -You can get started by using any of the other templates (e.g. `feast init -t gcp` or `feast init -t snowflake` or `feast init -t aws`), and then swapping in Redis as the online store as seen below in the examples. - -## Examples - -Connecting to a local MilvusDB instance: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: milvus - path: "data/online_store.db" - connection_string: "localhost:6379" - embedding_dim: 128 - index_type: "FLAT" - metric_type: "COSINE" - username: "username" - password: "password" -``` -{% endcode %} - - -The full set of configuration options is available in [MilvusOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.milvus.MilvusOnlineStoreConfig). - -## 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 Milvus online store. - -| | Milvus | -|:----------------------------------------------------------|:-------| -| 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 | 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 | no | -| vector similarity search | yes | - -To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/online-stores/mysql.md b/ui/public/docs/reference/online-stores/mysql.md deleted file mode 100644 index 8868e64279d..00000000000 --- a/ui/public/docs/reference/online-stores/mysql.md +++ /dev/null @@ -1,55 +0,0 @@ -# MySQL online store - -## Description - -The MySQL online store provides support for materializing feature values into a MySQL database for serving online features. - -* Only the latest feature values are persisted - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[mysql]'`. You can get started by then running `feast init` and then setting the `feature_store.yaml` as described below. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: mysql - host: DB_HOST - port: DB_PORT - database: DB_NAME - user: DB_USERNAME - password: DB_PASSWORD -``` -{% endcode %} - -The full set of configuration options is available in [MySQLOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.mysql_online_store.MySQLOnlineStoreConfig). - -## 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 Mys online store. - -| | Mys | -| :-------------------------------------------------------- | :--- | -| 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/ui/public/docs/reference/online-stores/overview.md b/ui/public/docs/reference/online-stores/overview.md deleted file mode 100644 index b54329ad613..00000000000 --- a/ui/public/docs/reference/online-stores/overview.md +++ /dev/null @@ -1,54 +0,0 @@ -# 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`, `CassandraOnlineStore` and `IKVOnlineStore`), 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](https://cassandra.apache.org/_/index.html) / [Astra DB](https://www.datastax.com/products/datastax-astra?utm_source=feast)] | [IKV](https://inlined.io) | Milvus | -| :-------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- |:-------| -| write feature values to the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| read feature values from the online store | yes | yes | 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 | yes | yes | -| teardown infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| generate a plan of infrastructure changes | yes | no | no | no | no | no | no | yes | no | no | -| support for on-demand transforms | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| readable by Python SDK | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| readable by Java | no | yes | no | no | no | no | no | no | no | no | -| readable by Go | yes | yes | no | no | no | no | no | no | no | no | -| support for entityless feature views | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | -| support for concurrent writing to the same key | no | yes | no | no | no | no | no | no | yes | no | -| support for ttl (time to live) at retrieval | no | yes | no | no | no | no | no | no | no | no | -| support for deleting expired data | no | yes | no | no | no | no | no | no | no | no | -| collocated by feature view | yes | no | yes | yes | yes | yes | yes | yes | no | no | -| collocated by feature service | no | no | no | no | no | no | no | no | no | no | -| collocated by entity key | no | yes | no | no | no | no | no | no | yes | no | diff --git a/ui/public/docs/reference/online-stores/postgres.md b/ui/public/docs/reference/online-stores/postgres.md deleted file mode 100644 index fb2253d043e..00000000000 --- a/ui/public/docs/reference/online-stores/postgres.md +++ /dev/null @@ -1,95 +0,0 @@ -# PostgreSQL online store - -## Description - -The PostgreSQL online store provides support for materializing feature values into a PostgreSQL database for serving online features. - -* Only the latest feature values are persisted - -* sslmode, sslkey_path, sslcert_path, and sslrootcert_path are optional - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[postgres]'`. You can get started by then running `feast init -t postgres`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: postgres - host: DB_HOST - port: DB_PORT - database: DB_NAME - db_schema: DB_SCHEMA - user: DB_USERNAME - password: DB_PASSWORD - sslmode: verify-ca - sslkey_path: /path/to/client-key.pem - sslcert_path: /path/to/client-cert.pem - sslrootcert_path: /path/to/server-ca.pem - vector_enabled: false -``` -{% endcode %} - -The full set of configuration options is available in [PostgreSQLOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.postgres_online_store.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). - -## PGVector -The Postgres online store supports the use of [PGVector](https://github.com/pgvector/pgvector) for storing feature values. -To enable PGVector, set `vector_enabled: true` in the online store configuration. - -The `vector_length` parameter can be used to specify the length of the vector in the Field. - -Please make sure to follow the instructions in the repository, which, as the time of this writing, requires you to -run `CREATE EXTENSION vector;` in the database. - - -Then you can use `retrieve_online_documents` to retrieve the top k closest vectors to a query vector. -For the Retrieval Augmented Generation (RAG) use-case, you have to embed the query prior to passing the query vector. - -{% code title="python" %} -```python -from feast import FeatureStore -from feast.infra.online_stores.postgres_online_store import retrieve_online_documents - -feature_store = FeatureStore(repo_path=".") - -query_vector = [0.1, 0.2, 0.3, 0.4, 0.5] -top_k = 5 - -feature_values = retrieve_online_documents( - feature_store=feature_store, - feature_view_name="document_fv:embedding_float", - query_vector=query_vector, - top_k=top_k, -) -``` -{% endcode %} diff --git a/ui/public/docs/reference/online-stores/qdrant.md b/ui/public/docs/reference/online-stores/qdrant.md deleted file mode 100644 index 54c4c80517c..00000000000 --- a/ui/public/docs/reference/online-stores/qdrant.md +++ /dev/null @@ -1,80 +0,0 @@ -# Qdrant online store - -## Description - -[Qdrant](http://qdrant.tech) is a vector similarity search engine. It provides a production-ready service with a convenient API to store, search, and manage vectors with additional payload and extended filtering support. It makes it useful for all sorts of neural network or semantic-based matching, faceted search, and other applications. - -## Getting started - -In order to use this online store, you'll need to run `pip install 'feast[qdrant]'`. - -## Example - -{% code title="feature_store.yaml" %} - -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: qdrant - host: localhost - port: 6333 - write_batch_size: 100 -``` - -{% endcode %} - -The full set of configuration options is available in [QdrantOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.qdrant_online_store.QdrantOnlineStoreConfig). - -## Functionality Matrix - -| | Qdrant | -| :-------------------------------------------------------- | :------- | -| 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). - -## Retrieving online document vectors - -The Qdrant online store supports retrieving document vectors for a given list of entity keys. The document vectors are returned as a dictionary where the key is the entity key and the value is the document vector. The document vector is a dense vector of floats. - -{% code title="python" %} - -```python -from feast import FeatureStore - -feature_store = FeatureStore(repo_path="feature_store.yaml") - -query_vector = [1.0, 2.0, 3.0, 4.0, 5.0] -top_k = 5 - -# Retrieve the top k closest features to the query vector -# Since Qdrant supports multiple vectors per entry, -# the vector to use can be specified in the repo config. -# Reference: https://qdrant.tech/documentation/concepts/vectors/#named-vectors -feature_values = feature_store.retrieve_online_documents( - features=["my_feature"], - query=query_vector, - top_k=top_k -) -``` - -{% endcode %} - -These APIs are subject to change in future versions of Feast to improve performance and usability. diff --git a/ui/public/docs/reference/online-stores/redis.md b/ui/public/docs/reference/online-stores/redis.md deleted file mode 100644 index ae7f8b4c5ca..00000000000 --- a/ui/public/docs/reference/online-stores/redis.md +++ /dev/null @@ -1,106 +0,0 @@ -# Redis online store - -## Description - -The [Redis](https://redis.io) online store provides support for materializing feature values into Redis. - -* 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). - -## Getting started -In order to use this online store, you'll need to install the redis extra (along with the dependency needed for the offline store of choice). E.g. -- `pip install 'feast[gcp, redis]'` -- `pip install 'feast[snowflake, redis]'` -- `pip install 'feast[aws, redis]'` -- `pip install 'feast[azure, redis]'` - -You can get started by using any of the other templates (e.g. `feast init -t gcp` or `feast init -t snowflake` or `feast init -t aws`), and then swapping in Redis as the online store as seen below in the examples. - -## Examples - -Connecting to a single Redis instance: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: redis - connection_string: "localhost:6379" -``` -{% endcode %} - -Connecting to a Redis Cluster with SSL enabled and password authentication: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: redis - redis_type: redis_cluster - connection_string: "redis1:6379,redis2:6379,ssl=true,password=my_password" -``` -{% endcode %} - -Connecting to a Redis Sentinel with SSL enabled and password authentication: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: redis - redis_type: redis_sentinel - sentinel_master: mymaster - connection_string: "redis1:26379,ssl=true,password=my_password" -``` -{% endcode %} - -Additionally, the redis online store also supports automatically deleting data via a TTL mechanism. -The TTL is applied at the entity level, so feature values from any associated feature views for an entity are removed together. -This TTL can be set in the `feature_store.yaml`, using the `key_ttl_seconds` field in the online store. For example: - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: redis - key_ttl_seconds: 604800 - connection_string: "localhost:6379" -``` -{% endcode %} - - -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/ui/public/docs/reference/online-stores/remote.md b/ui/public/docs/reference/online-stores/remote.md deleted file mode 100644 index aa97a495baa..00000000000 --- a/ui/public/docs/reference/online-stores/remote.md +++ /dev/null @@ -1,30 +0,0 @@ -# Remote online store - -## Description - -This remote online store will let you interact with remote feature server. At this moment this only supports the read operation. You can use this online store and able retrieve online features `store.get_online_features` from remote feature server. - -## Examples - -The registry is pointing to registry of remote feature store. If it is not accessible then should be configured to use remote registry. - -{% code title="feature_store.yaml" %} -```yaml -project: my-local-project -registry: /remote/data/registry.db -provider: local -online_store: - path: http://localhost:6566 - type: remote - cert: /path/to/cert.pem -entity_key_serialization_version: 2 -auth: - type: no_auth -``` -{% endcode %} - -`cert` is an optional configuration to the public certificate path when the online server starts in TLS(SSL) mode. This may be needed if the online server is started with a self-signed certificate, typically this file ends with `*.crt`, `*.cer`, or `*.pem`. - -## How to configure Authentication and Authorization -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. - diff --git a/ui/public/docs/reference/online-stores/scylladb.md b/ui/public/docs/reference/online-stores/scylladb.md deleted file mode 100644 index c8583ac101a..00000000000 --- a/ui/public/docs/reference/online-stores/scylladb.md +++ /dev/null @@ -1,94 +0,0 @@ -# ScyllaDB Cloud online store - -## Description - -ScyllaDB is a low-latency and high-performance Cassandra-compatible (uses CQL) database. You can use the existing Cassandra connector to use ScyllaDB as an online store in Feast. - -The [ScyllaDB](https://www.scylladb.com/) online store provides support for materializing feature values into a ScyllaDB or [ScyllaDB Cloud](https://www.scylladb.com/product/scylla-cloud/) cluster for serving online features real-time. - -## Getting started - -Install Feast with Cassandra support: -```bash -pip install "feast[cassandra]" -``` - -Create a new Feast project: -```bash -feast init REPO_NAME -t cassandra -``` - -### Example (ScyllaDB) - -{% code title="feature_store.yaml" %} -```yaml -project: scylla_feature_repo -registry: data/registry.db -provider: local -online_store: - type: cassandra - hosts: - - 172.17.0.2 - keyspace: feast - username: scylla - password: password -``` -{% endcode %} - -### Example (ScyllaDB Cloud) - -{% code title="feature_store.yaml" %} -```yaml -project: scylla_feature_repo -registry: data/registry.db -provider: local -online_store: - type: cassandra - hosts: - - node-0.aws_us_east_1.xxxxxxxx.clusters.scylla.cloud - - node-1.aws_us_east_1.xxxxxxxx.clusters.scylla.cloud - - node-2.aws_us_east_1.xxxxxxxx.clusters.scylla.cloud - keyspace: feast - username: scylla - password: password -``` -{% endcode %} - - -The full set of configuration options is available in [CassandraOnlineStoreConfig](https://rtd.feast.dev/en/master/#feast.infra.online_stores.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`. - -## 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 plugin. - -| | 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). - -## Resources - -* [Sample application with ScyllaDB](https://feature-store.scylladb.com/stable/) -* [ScyllaDB website](https://www.scylladb.com/) -* [ScyllaDB Cloud documentation](https://cloud.docs.scylladb.com/stable/) diff --git a/ui/public/docs/reference/online-stores/singlestore.md b/ui/public/docs/reference/online-stores/singlestore.md deleted file mode 100644 index 7272233b9dc..00000000000 --- a/ui/public/docs/reference/online-stores/singlestore.md +++ /dev/null @@ -1,51 +0,0 @@ -# SingleStore online store - -## Description - -The SingleStore online store provides support for materializing feature values into a SingleStore database for serving online features. - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[singlestore]'`. You can get started by then running `feast init` and then setting the `feature_store.yaml` as described below. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: singlestore - host: DB_HOST - port: DB_PORT - database: DB_NAME - user: DB_USERNAME - password: DB_PASSWORD -``` -{% endcode %} - -## Functionality Matrix - -The set of functionality supported by online stores is described in detail [here](overview.md#functionality). -Below is a matrix indicating which functionality is supported by the SingleStore online store. - -| | SingleStore | -| :-------------------------------------------------------- | :----------- | -| write feature values to the online store | yes | -| read feature values from the online store | yes | -| update infrastructure (e.g. tables) in the online store | yes | -| teardown infrastructure (e.g. tables) in the online store | yes | -| generate a plan of infrastructure changes | no | -| support for on-demand transforms | yes | -| readable by Python SDK | yes | -| readable by Java | no | -| readable by Go | no | -| support for entityless feature views | yes | -| support for concurrent writing to the same key | no | -| support for ttl (time to live) at retrieval | no | -| support for deleting expired data | no | -| collocated by feature view | yes | -| collocated by feature service | no | -| collocated by entity key | no | - -To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/ui/public/docs/reference/online-stores/snowflake.md b/ui/public/docs/reference/online-stores/snowflake.md deleted file mode 100644 index 6b6d107285c..00000000000 --- a/ui/public/docs/reference/online-stores/snowflake.md +++ /dev/null @@ -1,78 +0,0 @@ -# Snowflake online store - -## Description - -The [Snowflake](https://trial.snowflake.com) online store provides support for materializing feature values into a Snowflake Transient Table for serving online features. - -* Only the latest feature values are persisted - -The data model for using a Snowflake Transient Table as an online store follows a tall format (one row per feature)): -* "entity_feature_key" (BINARY) -- unique key used when reading specific feature_view x entity combination -* "entity_key" (BINARY) -- repeated key currently unused for reading entity_combination -* "feature_name" (VARCHAR) -* "value" (BINARY) -* "event_ts" (TIMESTAMP) -* "created_ts" (TIMESTAMP) - - (This model may be subject to change when Snowflake Hybrid Tables are released) - -## Getting started -In order to use this online store, you'll need to run `pip install 'feast[snowflake]'`. You can then get started with the command `feast init REPO_NAME -t snowflake`. - -## Example -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: snowflake.online - account: SNOWFLAKE_DEPLOYMENT_URL - user: SNOWFLAKE_USER - password: SNOWFLAKE_PASSWORD - role: SNOWFLAKE_ROLE - warehouse: SNOWFLAKE_WAREHOUSE - 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/ui/public/docs/reference/online-stores/sqlite.md b/ui/public/docs/reference/online-stores/sqlite.md deleted file mode 100644 index 859702c07f1..00000000000 --- a/ui/public/docs/reference/online-stores/sqlite.md +++ /dev/null @@ -1,49 +0,0 @@ -# SQLite online store - -## Description - -The [SQLite](https://www.sqlite.org/index.html) online store provides support for materializing feature values into an SQLite database for serving online features. - -* All feature values are stored in an on-disk SQLite database -* Only the latest feature values are persisted - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -online_store: - type: sqlite - path: data/online_store.db -``` -{% endcode %} - -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/ui/public/docs/reference/providers/README.md b/ui/public/docs/reference/providers/README.md deleted file mode 100644 index 925ae8ebc1f..00000000000 --- a/ui/public/docs/reference/providers/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Providers - -Please see [Provider](../../getting-started/components/provider.md) for an explanation of providers. - -{% page-ref page="local.md" %} - -{% page-ref page="google-cloud-platform.md" %} - -{% page-ref page="amazon-web-services.md" %} - -{% page-ref page="azure.md" %} diff --git a/ui/public/docs/reference/providers/amazon-web-services.md b/ui/public/docs/reference/providers/amazon-web-services.md deleted file mode 100644 index 68956a1be93..00000000000 --- a/ui/public/docs/reference/providers/amazon-web-services.md +++ /dev/null @@ -1,32 +0,0 @@ -# Amazon Web Services - -## Description - -* Offline Store: Uses the **Redshift** offline store by default. Also supports File as the offline store. -* Online Store: Uses the **DynamoDB** online store by default. Also supports Sqlite as an online store. - -## Getting started -In order to use this offline store, you'll need to run (Snowflake) `pip install 'feast[aws, snowflake]'` or (Redshift) `pip install 'feast[aws]'`. - -You can get started by then running `feast init -t snowflake` or `feast init -t aws`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: aws -online_store: - type: dynamodb - region: us-west-2 -offline_store: - type: redshift - region: us-west-2 - cluster_id: feast-cluster - database: feast-database - user: redshift-user - s3_staging_location: s3://feast-bucket/redshift - iam_role: arn:aws:iam::123456789012:role/redshift_s3_access_role -``` -{% endcode %} diff --git a/ui/public/docs/reference/providers/azure.md b/ui/public/docs/reference/providers/azure.md deleted file mode 100644 index 0e7206f076e..00000000000 --- a/ui/public/docs/reference/providers/azure.md +++ /dev/null @@ -1,29 +0,0 @@ -# Azure (contrib) - -## Description - -* Offline Store: Uses the **MsSql** offline store by default. Also supports File as the offline store. -* Online Store: Uses the **Redis** online store by default. Also supports Sqlite as an online store. - -## Disclaimer - -The Azure provider does not achieve full test coverage. -Please do not assume complete stability. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[azure]'`. You can get started by then following this [tutorial](https://github.com/feast-dev/feast/blob/master/docs/tutorials/azure/README.md). - -## Example - -{% code title="feature_store.yaml" %} -```yaml -registry: - registry_store_type: AzureRegistryStore - path: ${REGISTRY_PATH} # Environment Variable -project: production -provider: azure -online_store: - type: redis - connection_string: ${REDIS_CONN} # Environment Variable -``` -{% endcode %} \ No newline at end of file diff --git a/ui/public/docs/reference/providers/google-cloud-platform.md b/ui/public/docs/reference/providers/google-cloud-platform.md deleted file mode 100644 index 96af3b6b2ff..00000000000 --- a/ui/public/docs/reference/providers/google-cloud-platform.md +++ /dev/null @@ -1,30 +0,0 @@ -# Google Cloud Platform - -## Description - -* Offline Store: Uses the **BigQuery** offline store by default. Also supports File as the offline store. -* Online Store: Uses the **Datastore** online store by default. Also supports Sqlite as an online store. - -## Getting started -In order to use this offline store, you'll need to run `pip install 'feast[gcp]'`. You can get started by then running `feast init -t gcp`. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: gs://my-bucket/data/registry.db -provider: gcp -``` -{% endcode %} - -## **Permissions** - -| **Command** | Component | Permissions | Recommended Role | -| --------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | -| **Apply** | BigQuery (source) |

bigquery.jobs.create

bigquery.readsessions.create

bigquery.readsessions.getData

| roles/bigquery.user | -| **Apply** | Datastore (destination) |

datastore.entities.allocateIds

datastore.entities.create

datastore.entities.delete

datastore.entities.get

datastore.entities.list

datastore.entities.update

| roles/datastore.owner | -| **Materialize** | BigQuery (source) | bigquery.jobs.create | roles/bigquery.user | -| **Materialize** | Datastore (destination) |

datastore.entities.allocateIds

datastore.entities.create

datastore.entities.delete

datastore.entities.get

datastore.entities.list

datastore.entities.update

datastore.databases.get

| roles/datastore.owner | -| **Get Online Features** | Datastore | datastore.entities.get | roles/datastore.user | -| **Get Historical Features** | BigQuery (source) |

bigquery.datasets.get

bigquery.tables.get

bigquery.tables.create

bigquery.tables.updateData

bigquery.tables.update

bigquery.tables.delete

bigquery.tables.getData

| roles/bigquery.dataEditor | diff --git a/ui/public/docs/reference/providers/local.md b/ui/public/docs/reference/providers/local.md deleted file mode 100644 index a93a3b8b2d1..00000000000 --- a/ui/public/docs/reference/providers/local.md +++ /dev/null @@ -1,16 +0,0 @@ -# Local - -## Description - -* Offline Store: Uses the File offline store by default. Also supports BigQuery as the offline store. -* Online Store: Uses the Sqlite online store by default. Also supports Redis and Datastore as online stores. - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -registry: data/registry.db -provider: local -``` -{% endcode %} diff --git a/ui/public/docs/reference/registries/README.md b/ui/public/docs/reference/registries/README.md deleted file mode 100644 index 8068c0ff150..00000000000 --- a/ui/public/docs/reference/registries/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Registies - -Please see [Registry](../../getting-started/components/registry.md) for a conceptual explanation of registries. - -{% content-ref url="local.md" %} -[local.md](local.md) -{% endcontent-ref %} - -{% content-ref url="s3.md" %} -[s3.md](s3.md) -{% endcontent-ref %} - -{% content-ref url="gcs.md" %} -[gcs.md](gcs.md) -{% endcontent-ref %} - -{% content-ref url="sql.md" %} -[sql.md](sql.md) -{% endcontent-ref %} - -{% content-ref url="snowflake.md" %} -[snowflake.md](snowflake.md) -{% endcontent-ref %} diff --git a/ui/public/docs/reference/registries/gcs.md b/ui/public/docs/reference/registries/gcs.md deleted file mode 100644 index 13c9657aa13..00000000000 --- a/ui/public/docs/reference/registries/gcs.md +++ /dev/null @@ -1,23 +0,0 @@ -# GCS Registry - -## Description - -GCS registry provides support for storing the protobuf representation of your feature store objects (data sources, feature views, feature services, etc.) uing Google Cloud Storage. - -While it can be used in production, there are still inherent limitations with a file-based registries, since changing a single field in the registry requires re-writing the whole registry file. With multiple concurrent writers, this presents a risk of data loss, or bottlenecks writes to the registry since all changes have to be serialized (e.g. when running materialization for multiple feature views or time ranges concurrently). - -An example of how to configure this would be: - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: feast_gcp -registry: - path: gs://[YOUR BUCKET YOU CREATED]/registry.pb - cache_ttl_seconds: 60 -online_store: null -offline_store: - type: dask -``` -{% endcode %} \ No newline at end of file diff --git a/ui/public/docs/reference/registries/local.md b/ui/public/docs/reference/registries/local.md deleted file mode 100644 index ad1d98cea99..00000000000 --- a/ui/public/docs/reference/registries/local.md +++ /dev/null @@ -1,23 +0,0 @@ -# Local Registry - -## Description - -Local registry provides support for storing the protobuf representation of your feature store objects (data sources, feature views, feature services, etc.) in local file system. It is only intended to be used for experimentation with Feast and should not be used in production. - -There are inherent limitations with a file-based registries, since changing a single field in the registry requires re-writing the whole registry file. With multiple concurrent writers, this presents a risk of data loss, or bottlenecks writes to the registry since all changes have to be serialized (e.g. when running materialization for multiple feature views or time ranges concurrently). - -An example of how to configure this would be: - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: feast_local -registry: - path: registry.pb - cache_ttl_seconds: 60 -online_store: null -offline_store: - type: dask -``` -{% endcode %} \ No newline at end of file diff --git a/ui/public/docs/reference/registries/remote.md b/ui/public/docs/reference/registries/remote.md deleted file mode 100644 index a03e30ac85f..00000000000 --- a/ui/public/docs/reference/registries/remote.md +++ /dev/null @@ -1,28 +0,0 @@ -# Remote Registry - -## Description - -The Remote Registry is a gRPC client for the registry that implements the `RemoteRegistry` class using the existing `BaseRegistry` interface. - -## How to configure the client - -User needs to create a client side `feature_store.yaml` file, set the `registry_type` to `remote` and provide the server connection configuration. -The `path` parameter is a URL with a port (default is 6570) used by the client to connect with the Remote Registry server. - -{% code title="feature_store.yaml" %} -```yaml -registry: - registry_type: remote - path: localhost:6570 -``` -{% endcode %} - -The optional `cert` parameter can be configured as well, it should point to the public certificate path when the Registry Server starts in SSL mode. This may be needed if the Registry Server is started with a self-signed certificate, typically this file ends with *.crt, *.cer, or *.pem. -More info about the `cert` parameter can be found in [feast-client-connecting-to-remote-registry-sever-started-in-tls-mode](../../how-to-guides/starting-feast-servers-tls-mode.md#feast-client-connecting-to-remote-registry-sever-started-in-tls-mode) - -## How to configure the server - -Please see the detail how to configure registry server [registry-server.md](../feature-servers/registry-server.md) - -## How to configure Authentication and Authorization -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. diff --git a/ui/public/docs/reference/registries/s3.md b/ui/public/docs/reference/registries/s3.md deleted file mode 100644 index 65069c415c5..00000000000 --- a/ui/public/docs/reference/registries/s3.md +++ /dev/null @@ -1,23 +0,0 @@ -# S3 Registry - -## Description - -S3 registry provides support for storing the protobuf representation of your feature store objects (data sources, feature views, feature services, etc.) in S3 file system. - -While it can be used in production, there are still inherent limitations with a file-based registries, since changing a single field in the registry requires re-writing the whole registry file. With multiple concurrent writers, this presents a risk of data loss, or bottlenecks writes to the registry since all changes have to be serialized (e.g. when running materialization for multiple feature views or time ranges concurrently). - -An example of how to configure this would be: - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: feast_aws_s3 -registry: - path: s3://[YOUR BUCKET YOU CREATED]/registry.pb - cache_ttl_seconds: 60 -online_store: null -offline_store: - type: dask -``` -{% endcode %} \ No newline at end of file diff --git a/ui/public/docs/reference/registries/snowflake.md b/ui/public/docs/reference/registries/snowflake.md deleted file mode 100644 index 00d87b19775..00000000000 --- a/ui/public/docs/reference/registries/snowflake.md +++ /dev/null @@ -1,30 +0,0 @@ -# Snowflake Registry - -## Description - -The [Snowflake](https://trial.snowflake.com) registry provides support for storing the protobuf representation of your feature store objects (data sources, feature views, feature services, etc.) Because Snowflake is an ACID compliant database, this allows for changes to individual objects atomically. - -An example of how to configure this would be: - -## Example - -{% code title="feature_store.yaml" %} -```yaml -project: my_feature_repo -provider: local -registry: - registry_type: snowflake.registry - account: snowflake_deployment.us-east-1 - user: user_login - password: user_password - role: SYSADMIN - warehouse: COMPUTE_WH - database: FEAST - schema: PUBLIC - cache_ttl_seconds: 60 -offline_store: - ... -``` -{% endcode %} - -The full set of configuration options is available in [SnowflakeRegistryConfig](https://rtd.feast.dev/en/latest/#feast.infra.registry.snowflake.SnowflakeRegistryConfig). diff --git a/ui/public/docs/reference/registries/sql.md b/ui/public/docs/reference/registries/sql.md deleted file mode 100644 index ef9993c8753..00000000000 --- a/ui/public/docs/reference/registries/sql.md +++ /dev/null @@ -1,89 +0,0 @@ -# SQL Registry - -## Overview - -By default, the registry Feast uses a file-based registry implementation, which stores the protobuf representation of the registry as a serialized file. This registry file can be stored in a local file system, or in cloud storage (in, say, S3 or GCS). - -However, there's inherent limitations with a file-based registry, since changing a single field in the registry requires re-writing the whole registry file. With multiple concurrent writers, this presents a risk of data loss, or bottlenecks writes to the registry since all changes have to be serialized (e.g. when running materialization for multiple feature views or time ranges concurrently). - -An alternative to the file-based registry is the [SQLRegistry](https://rtd.feast.dev/en/latest/feast.infra.registry_stores.html#feast.infra.registry_stores.sql.SqlRegistry) which ships with Feast. This implementation stores the registry in a relational database, and allows for changes to individual objects atomically. -Under the hood, the SQL Registry implementation uses [SQLAlchemy](https://docs.sqlalchemy.org/en/14/) to abstract over the different databases. Consequently, any [database supported](https://docs.sqlalchemy.org/en/14/core/engines.html#supported-databases) by SQLAlchemy can be used by the SQL Registry. -The following databases are supported and tested out of the box: -- PostgreSQL -- MySQL -- Sqlite - -Feast can use the SQL Registry via a config change in the feature_store.yaml file. An example of how to configure this would be: - -```yaml -project: -provider: -online_store: redis -offline_store: file -registry: - registry_type: sql - path: postgresql://postgres:mysecretpassword@127.0.0.1:55001/feast - cache_ttl_seconds: 60 - sqlalchemy_config_kwargs: - echo: false - pool_pre_ping: true -``` - -Specifically, the registry_type needs to be set to sql in the registry config block. On doing so, the path should refer to the [Database URL](https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls) for the database to be used, as expected by SQLAlchemy. No other additional commands are currently needed to configure this registry. - -Should you choose to use a database technology that is compatible with one of -Feast's supported registry backends, but which speaks a different dialect (e.g. -`cockroachdb`, which is compatible with `postgres`) then some further -intervention may be required on your part. - -`SQLAlchemy`, used by the registry, may not be able to detect your database -version without first updating your DSN scheme to the appropriate -[DBAPI/dialect combination](https://docs.sqlalchemy.org/en/14/glossary.html#term-DBAPI). -When this happens, your database is likely using what is referred to as an -[external dialect](https://docs.sqlalchemy.org/en/14/dialects/#external-dialects) -in `SQLAlchemy` terminology. See your database's documentation for examples on -how to set its scheme in the Database URL. - -`Psycopg`, which is the database library leveraged by the online and offline -stores, is not impacted by the need to speak a particular dialect, and so the -following only applies to the registry. - -If you are not running Feast in a container, to accomodate `SQLAlchemy`'s need -to speak an external dialect, install additional Python modules like we do as -follows using `cockroachdb` for example: - -```shell -pip install sqlalchemy-cockroachdb -``` - -If you are running Feast in a container, you will need to create a custom image -like we do as follows, again using `cockroachdb` as an example: - -```shell -cat <<'EOF' >Dockerfile -ARG QUAY_IO_FEASTDEV_FEATURE_SERVER -FROM quay.io/feastdev/feature-server:${QUAY_IO_FEASTDEV_FEATURE_SERVER} -ARG PYPI_ORG_PROJECT_SQLALCHEMY_COCKROACHDB -RUN pip install -I --no-cache-dir \ - sqlalchemy-cockroachdb==${PYPI_ORG_PROJECT_SQLALCHEMY_COCKROACHDB} -EOF - -export QUAY_IO_FEASTDEV_FEATURE_SERVER=0.27.1 -export PYPI_ORG_PROJECT_SQLALCHEMY_COCKROACHDB=1.4.4 - -docker build \ - --build-arg QUAY_IO_FEASTDEV_FEATURE_SERVER \ - --build-arg PYPI_ORG_PROJECT_SQLALCHEMY_COCKROACHDB \ - --tag ${MY_REGISTRY}/feastdev/feature-server:${QUAY_IO_FEASTDEV_FEATURE_SERVER} . -``` - -If you are running Feast in Kubernetes, set the `image.repository` and -`imagePullSecrets` Helm values accordingly to utilize your custom image. - -There are some things to note about how the SQL registry works: -- Once instantiated, the Registry ensures the tables needed to store data exist, and creates them if they do not. -- Upon tearing down the feast project, the registry ensures that the tables are dropped from the database. -- The schema for how data is laid out in tables can be found . It is intentionally simple, storing the serialized protobuf versions of each Feast object keyed by its name. - -## Example Usage: Concurrent materialization -The SQL Registry should be used when materializing feature views concurrently to ensure correctness of data in the registry. This can be achieved by simply running feast materialize or feature_store.materialize multiple times using a correctly configured feature_store.yaml. This will make each materialization process talk to the registry database concurrently, and ensure the metadata updates are serialized. diff --git a/ui/public/docs/reference/registry/registry-permissions.md b/ui/public/docs/reference/registry/registry-permissions.md deleted file mode 100644 index 65508ef5b25..00000000000 --- a/ui/public/docs/reference/registry/registry-permissions.md +++ /dev/null @@ -1,45 +0,0 @@ -# Registry Permissions and Access Control - - -## API Endpoints and Permissions - -| Endpoint | Resource Type | Permission | Description | -| ------------------------ |---------------------|------------------------| -------------------------------------------------------------- | -| ApplyEntity | Entity | Create, Update, Delete | Apply an entity to the registry | -| GetEntity | Entity | Read | Get an entity from the registry | -| ListEntities | Entity | Read | List entities in the registry | -| DeleteEntity | Entity | Delete | Delete an entity from the registry | -| ApplyDataSource | DataSource | Create, Update, Delete | Apply a data source to the registry | -| GetDataSource | DataSource | Read | Get a data source from the registry | -| ListDataSources | DataSource | Read | List data sources in the registry | -| DeleteDataSource | DataSource | Delete | Delete a data source from the registry | -| ApplyFeatureView | FeatureView | Create, Update, Delete | Apply a feature view to the registry | -| GetFeatureView | FeatureView | Read | Get a feature view from the registry | -| ListFeatureViews | FeatureView | Read | List feature views in the registry | -| DeleteFeatureView | FeatureView | Delete | Delete a feature view from the registry | -| GetStreamFeatureView | StreamFeatureView | Read | Get a stream feature view from the registry | -| ListStreamFeatureViews | StreamFeatureView | Read | List stream feature views in the registry | -| GetOnDemandFeatureView | OnDemandFeatureView | Read | Get an on-demand feature view from the registry | -| ListOnDemandFeatureViews | OnDemandFeatureView | Read | List on-demand feature views in the registry | -| ApplyFeatureService | FeatureService | Create, Update, Delete | Apply a feature service to the registry | -| GetFeatureService | FeatureService | Read | Get a feature service from the registry | -| ListFeatureServices | FeatureService | Read | List feature services in the registry | -| DeleteFeatureService | FeatureService | Delete | Delete a feature service from the registry | -| ApplySavedDataset | SavedDataset | Create, Update, Delete | Apply a saved dataset to the registry | -| GetSavedDataset | SavedDataset | Read | Get a saved dataset from the registry | -| ListSavedDatasets | SavedDataset | Read | List saved datasets in the registry | -| DeleteSavedDataset | SavedDataset | Delete | Delete a saved dataset from the registry | -| ApplyValidationReference | ValidationReference | Create, Update, Delete | Apply a validation reference to the registry | -| GetValidationReference | ValidationReference | Read | Get a validation reference from the registry | -| ListValidationReferences | ValidationReference | Read | List validation references in the registry | -| DeleteValidationReference| ValidationReference | Delete | Delete a validation reference from the registry | -| ApplyPermission | Permission | Create, Update, Delete | Apply a permission to the registry | -| GetPermission | Permission | Read | Get a permission from the registry | -| ListPermissions | Permission | Read | List permissions in the registry | -| DeletePermission | Permission | Delete | Delete a permission from the registry | -| Commit | | None | Commit changes to the registry | -| Refresh | | None | Refresh the registry | -| Proto | | None | Get the proto representation of the registry | - -## How to configure Authentication and Authorization -Please refer the [page](./../../../docs/getting-started/concepts/permission.md) for more details on how to configure authentication and authorization. diff --git a/ui/public/docs/reference/type-system.md b/ui/public/docs/reference/type-system.md deleted file mode 100644 index affe394f570..00000000000 --- a/ui/public/docs/reference/type-system.md +++ /dev/null @@ -1,41 +0,0 @@ -# Type System - -## Motivation - -Feast uses an internal type system to provide guarantees on training and serving data. -Feast currently supports eight primitive types - `INT32`, `INT64`, `FLOAT32`, `FLOAT64`, `STRING`, `BYTES`, `BOOL`, and `UNIX_TIMESTAMP` - and the corresponding array types. -Null types are not supported, although the `UNIX_TIMESTAMP` type is nullable. -The type system is controlled by [`Value.proto`](https://github.com/feast-dev/feast/blob/master/protos/feast/types/Value.proto) in protobuf and by [`types.py`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/types.py) in Python. -Type conversion logic can be found in [`type_map.py`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/type_map.py). - -## Examples - -### Feature inference - -During `feast apply`, Feast runs schema inference on the data sources underlying feature views. -For example, if the `schema` parameter is not specified for a feature view, Feast will examine the schema of the underlying data source to determine the event timestamp column, feature columns, and entity columns. -Each of these columns must be associated with a Feast type, which requires conversion from the data source type system to the Feast type system. -* The feature inference logic calls `_infer_features_and_entities`. -* `_infer_features_and_entities` calls `source_datatype_to_feast_value_type`. -* `source_datatype_to_feast_value_type` cals the appropriate method in `type_map.py`. For example, if a `SnowflakeSource` is being examined, `snowflake_python_type_to_feast_value_type` from `type_map.py` will be called. - -### Materialization - -Feast serves feature values as [`Value`](https://github.com/feast-dev/feast/blob/master/protos/feast/types/Value.proto) proto objects, which have a type corresponding to Feast types. -Thus Feast must materialize feature values into the online store as `Value` proto objects. -* The local materialization engine first pulls the latest historical features and converts it to pyarrow. -* Then it calls `_convert_arrow_to_proto` to convert the pyarrow table to proto format. -* This calls `python_values_to_proto_values` in `type_map.py` to perform the type conversion. - -### Historical feature retrieval - -The Feast type system is typically not necessary when retrieving historical features. -A call to `get_historical_features` will return a `RetrievalJob` object, which allows the user to export the results to one of several possible locations: a Pandas dataframe, a pyarrow table, a data lake (e.g. S3 or GCS), or the offline store (e.g. a Snowflake table). -In all of these cases, the type conversion is handled natively by the offline store. -For example, a BigQuery query exposes a `to_dataframe` method that will automatically convert the result to a dataframe, without requiring any conversions within Feast. - -### Feature serving - -As mentioned above in the section on [materialization](#materialization), Feast persists feature values into the online store as `Value` proto objects. -A call to `get_online_features` will return an `OnlineResponse` object, which essentially wraps a bunch of `Value` protos with some metadata. -The `OnlineResponse` object can then be converted into a Python dictionary, which calls `feast_value_type_to_python_type` from `type_map.py`, a utility that converts the Feast internal types to Python native types. diff --git a/ui/public/docs/reference/ui.png b/ui/public/docs/reference/ui.png deleted file mode 100644 index 360d57186d331939e2c266d30961a8fa38e17cb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174770 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
    )%c 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 G_5T6mhH7pA diff --git a/ui/rollup.config.js b/ui/rollup.config.js index c0a435389b0..93e17345fe6 100644 --- a/ui/rollup.config.js +++ b/ui/rollup.config.js @@ -23,27 +23,55 @@ const banner = ` `; const rollupConfig = [ + // ES { input: inputFileName, output: [ { - dir: "dist", - entryFileNames: path.basename(pkg.module), + file: pkg.module, format: "es", sourcemap: "inline", banner, exports: "named", - inlineDynamicImports: true, }, - // CommonJS + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.devDependencies || {}), + ], + plugins: [ + pluginTypescript(), + pluginCommonjs({ + extensions: [".js", ".ts"], + }), + babel({ + babelHelpers: "bundled", + configFile: path.resolve(__dirname, ".babelrc.js"), + }), + pluginNodeResolve({ + browser: false, + }), + css({ + output: "feast-ui.css", + }), + svg(), + json(), + copy({ + targets: [{ src: "src/assets/**/*", dest: "dist/assets/" }], + }), + ], + }, + + // CommonJS + { + input: inputFileName, + output: [ { - dir: "dist", - entryFileNames: path.basename(pkg.main), + file: pkg.main, format: "cjs", sourcemap: "inline", banner, exports: "default", - inlineDynamicImports: true, }, ], external: [ @@ -64,14 +92,9 @@ const rollupConfig = [ }), css({ output: "feast-ui.css", - minify: true, - inject: false, }), svg(), json(), - copy({ - targets: [{ src: "src/assets/**/*", dest: "dist/assets/" }], - }), ], }, ]; diff --git a/ui/src/FeastUISansProviders.tsx b/ui/src/FeastUISansProviders.tsx index 907917081af..3a15d9bf083 100644 --- a/ui/src/FeastUISansProviders.tsx +++ b/ui/src/FeastUISansProviders.tsx @@ -24,7 +24,6 @@ import RootProjectSelectionPage from "./pages/RootProjectSelectionPage"; import DatasetInstance from "./pages/saved-data-sets/DatasetInstance"; import PermissionsIndex from "./pages/permissions/Index"; import LineageIndex from "./pages/lineage/Index"; -import DocumentationIndex from "./pages/documentation/Index"; import NoProjectGuard from "./components/NoProjectGuard"; import TabsRegistryContext, { @@ -148,14 +147,6 @@ const FeastUISansProvidersInner = ({ /> } /> } /> - } - /> - } - /> } /> diff --git a/ui/src/pages/Sidebar.tsx b/ui/src/pages/Sidebar.tsx index 777487a5fad..57599139a44 100644 --- a/ui/src/pages/Sidebar.tsx +++ b/ui/src/pages/Sidebar.tsx @@ -140,15 +140,6 @@ const SideNav = () => { ), isSelected: useMatchSubpath(`${baseUrl}/permissions`), }, - { - name: "Documentation", - id: htmlIdGenerator("documentation")(), - icon: , - renderItem: (props) => ( - - ), - isSelected: useMatchSubpath(`${baseUrl}/documentation`), - }, ], }, ]; diff --git a/ui/src/pages/documentation/APIDocumentation.tsx b/ui/src/pages/documentation/APIDocumentation.tsx deleted file mode 100644 index 69287c5288b..00000000000 --- a/ui/src/pages/documentation/APIDocumentation.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import { EuiPanel, EuiLoadingSpinner, EuiText } from "@elastic/eui"; -import DocumentationService from "../../services/DocumentationService"; - -const APIDocumentation = () => { - const [content, setContent] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const loadDocumentation = async () => { - try { - setIsLoading(true); - const markdown = await DocumentationService.fetchAPIDocumentation(); - setContent(markdown); - setError(null); - } catch (err) { - setError("Failed to load API documentation"); - console.error(err); - } finally { - setIsLoading(false); - } - }; - - loadDocumentation(); - }, []); - - if (isLoading) { - return ( - - - - ); - } - - if (error) { - return ( - - -

    {error}

    -
    -
    - ); - } - - return ( - - - {content} - - - ); -}; - -export default APIDocumentation; diff --git a/ui/src/pages/documentation/CLIDocumentation.tsx b/ui/src/pages/documentation/CLIDocumentation.tsx deleted file mode 100644 index 55cfdfbe222..00000000000 --- a/ui/src/pages/documentation/CLIDocumentation.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import { EuiPanel, EuiLoadingSpinner, EuiText } from "@elastic/eui"; -import DocumentationService from "../../services/DocumentationService"; - -const CLIDocumentation = () => { - const [content, setContent] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const loadDocumentation = async () => { - try { - setIsLoading(true); - const markdown = await DocumentationService.fetchCLIDocumentation(); - setContent(markdown); - setError(null); - } catch (err) { - setError("Failed to load CLI documentation"); - console.error(err); - } finally { - setIsLoading(false); - } - }; - - loadDocumentation(); - }, []); - - if (isLoading) { - return ( - - - - ); - } - - if (error) { - return ( - - -

    {error}

    -
    -
    - ); - } - - return ( - - - {content} - - - ); -}; - -export default CLIDocumentation; diff --git a/ui/src/pages/documentation/Index.tsx b/ui/src/pages/documentation/Index.tsx deleted file mode 100644 index 875c4bc87d0..00000000000 --- a/ui/src/pages/documentation/Index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - EuiPageTemplate, - EuiTabs, - EuiTab, - EuiSpacer, - EuiTitle, - EuiText, - EuiSkeletonText, -} from "@elastic/eui"; -import { useParams, useNavigate } from "react-router-dom"; -import { useDocumentTitle } from "../../hooks/useDocumentTitle"; -import "./styles.css"; - -const DocumentationIndex = () => { - useDocumentTitle("Feast Documentation"); - const { projectName, tab } = useParams(); - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState("cli"); - - useEffect(() => { - if (tab && ["cli", "sdk", "api"].includes(tab)) { - setActiveTab(tab); - } - }, [tab]); - - const tabs = [ - { - id: "cli", - name: "CLI Reference", - content: React.lazy(() => import("./CLIDocumentation")), - }, - { - id: "sdk", - name: "SDK Reference", - content: React.lazy(() => import("./SDKDocumentation")), - }, - { - id: "api", - name: "API Reference", - content: React.lazy(() => import("./APIDocumentation")), - }, - ]; - - const selectedTabConfig = tabs.find((t) => t.id === activeTab) || tabs[0]; - const TabContent = selectedTabConfig.content; - - const onSelectedTabChanged = (id: string) => { - setActiveTab(id); - navigate(`/p/${projectName}/documentation/${id}`); - }; - - const renderTabs = () => { - return tabs.map((tabItem, index) => ( - onSelectedTabChanged(tabItem.id)} - isSelected={tabItem.id === activeTab} - > - {tabItem.name} - - )); - }; - - return ( - - - -

    Feast Documentation

    -
    - -

    Documentation for the Feast SDK, REST API, and CLI.

    -
    - - {renderTabs()} - - }> - - -
    -
    - ); -}; - -export default DocumentationIndex; diff --git a/ui/src/pages/documentation/SDKDocumentation.tsx b/ui/src/pages/documentation/SDKDocumentation.tsx deleted file mode 100644 index cff8a2ca44b..00000000000 --- a/ui/src/pages/documentation/SDKDocumentation.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import { EuiPanel, EuiLoadingSpinner, EuiText } from "@elastic/eui"; -import DocumentationService from "../../services/DocumentationService"; - -const SDKDocumentation = () => { - const [content, setContent] = useState(""); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const loadDocumentation = async () => { - try { - setIsLoading(true); - const markdown = await DocumentationService.fetchSDKDocumentation(); - setContent(markdown); - setError(null); - } catch (err) { - setError("Failed to load SDK documentation"); - console.error(err); - } finally { - setIsLoading(false); - } - }; - - loadDocumentation(); - }, []); - - if (isLoading) { - return ( - - - - ); - } - - if (error) { - return ( - - -

    {error}

    -
    -
    - ); - } - - return ( - - - {content} - - - ); -}; - -export default SDKDocumentation; diff --git a/ui/src/pages/documentation/styles.css b/ui/src/pages/documentation/styles.css deleted file mode 100644 index b5387a94efb..00000000000 --- a/ui/src/pages/documentation/styles.css +++ /dev/null @@ -1,61 +0,0 @@ -.documentation-content { - line-height: 1.6; - font-size: 16px; -} - -.documentation-content h1, -.documentation-content h2, -.documentation-content h3, -.documentation-content h4, -.documentation-content h5, -.documentation-content h6 { - margin-top: 24px; - margin-bottom: 16px; - font-weight: 600; -} - -.documentation-content h1 { - font-size: 32px; -} - -.documentation-content h2 { - font-size: 24px; -} - -.documentation-content h3 { - font-size: 20px; -} - -.documentation-content code { - padding: 0.2em 0.4em; - background-color: rgba(0, 0, 0, 0.05); - border-radius: 3px; -} - -.documentation-content pre { - padding: 16px; - background-color: rgba(0, 0, 0, 0.05); - border-radius: 3px; - overflow: auto; -} - -.documentation-content pre code { - background-color: transparent; - padding: 0; -} - -.documentation-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; -} - -.documentation-content th, -.documentation-content td { - border: 1px solid #ddd; - padding: 8px; -} - -.documentation-content th { - background-color: rgba(0, 0, 0, 0.05); -} diff --git a/ui/src/services/DocumentationService.ts b/ui/src/services/DocumentationService.ts deleted file mode 100644 index 164f20cbb66..00000000000 --- a/ui/src/services/DocumentationService.ts +++ /dev/null @@ -1,223 +0,0 @@ -const DocumentationService = { - async fetchCLIDocumentation(): Promise { - try { - return `# Feast CLI Reference - -## feast apply - -Apply changes to a feature store. This command should be run after modifying -feature definitions. - -**Usage:** - -\`\`\` -feast apply -\`\`\` - -**Options:** - -- \`--skip-source-validation\`: Skip validation of data sources. - ---- - -## feast materialize - -Materialize features from an offline store into an online store. - -**Usage:** - -\`\`\` -feast materialize START_TS END_TS -\`\`\` - -**Options:** - -- \`--views\`: Feature views to materialize. - ---- - -## feast registry-dump - -Dump registry contents to local file. - -**Usage:** - -\`\`\` -feast registry-dump REGISTRY_PATH -\`\`\` - ---- - -## feast serve - -Start a feature server. - -**Usage:** - -\`\`\` -feast serve -\`\`\` - -**Options:** - -- \`--host\`: Specify host for the server. -- \`--port\`: Specify port for the server. -`; - } catch (error) { - console.error("Error fetching CLI documentation:", error); - return "# Error\nFailed to load CLI documentation."; - } - }, - - async fetchSDKDocumentation(): Promise { - try { - return `# Feast SDK Reference - -## FeatureStore - -The main entry point for interacting with Feast. - -\`\`\`python -from feast import FeatureStore - -fs = FeatureStore(repo_path="path/to/feature_repo") -\`\`\` - -### Methods - -- \`apply()\`: Register feature definitions to the feature store. -- \`get_historical_features()\`: Retrieve historical feature values. -- \`get_online_features()\`: Retrieve the latest feature values. -- \`materialize()\`: Materialize features from offline to online store. - -## FeatureView - -Define a group of features that share the same data source and entities. - -\`\`\`python -from feast import FeatureView, Feature, Entity, ValueType - -driver = Entity(name="driver_id", value_type=ValueType.INT64) - -driver_stats_view = FeatureView( - name="driver_stats", - entities=[driver], - ttl=timedelta(days=1), - features=[ - Feature(name="conv_rate", dtype=ValueType.FLOAT), - Feature(name="acc_rate", dtype=ValueType.FLOAT), - ], - batch_source=FileSource(path="path/to/data.parquet"), -) -\`\`\` - -## Entity - -Define an entity to join feature values with. - -\`\`\`python -from feast import Entity, ValueType - -driver = Entity( - name="driver_id", - value_type=ValueType.INT64, - description="Driver ID", -) -\`\`\` -`; - } catch (error) { - console.error("Error fetching SDK documentation:", error); - return "# Error\nFailed to load SDK documentation."; - } - }, - - async fetchAPIDocumentation(): Promise { - try { - return `# Feast REST API Reference - -## Feature Server Endpoints - -### GET /health - -Health check endpoint. - -**Response:** - -\`\`\`json -{ - "status": "ok" -} -\`\`\` - -### POST /get-online-features - -Retrieve the latest feature values. - -**Request:** - -\`\`\`json -{ - "features": [ - "driver_stats:conv_rate", - "driver_stats:acc_rate" - ], - "entities": { - "driver_id": [1001, 1002] - } -} -\`\`\` - -**Response:** - -\`\`\`json -{ - "metadata": { - "feature_names": ["driver_stats:conv_rate", "driver_stats:acc_rate"] - }, - "results": [ - { - "values": [0.95, 0.79], - "statuses": ["PRESENT", "PRESENT"] - }, - { - "values": [0.83, 0.85], - "statuses": ["PRESENT", "PRESENT"] - } - ] -} -\`\`\` - -### POST /push - -Push feature values to the online store. - -**Request:** - -\`\`\`json -{ - "push_source_name": "driver_stats_push_source", - "df": { - "driver_id": [1001, 1002], - "conv_rate": [0.95, 0.83], - "acc_rate": [0.79, 0.85], - "event_timestamp": ["2022-01-01T00:00:00Z", "2022-01-01T00:00:00Z"] - } -} -\`\`\` - -**Response:** - -\`\`\`json -{ - "status": "success" -} -\`\`\` -`; - } catch (error) { - console.error("Error fetching API documentation:", error); - return "# Error\nFailed to load API documentation."; - } - }, -}; - -export default DocumentationService; diff --git a/ui/yarn.lock b/ui/yarn.lock index b3d1266d194..640dd5a0c05 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2566,13 +2566,6 @@ resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.52.tgz#edbf0bca6922cd0ad1936a7486f9d03523d7565a" integrity sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw== -"@types/debug@^4.0.0": - version "4.1.12" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" - integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== - dependencies: - "@types/ms" "*" - "@types/eslint-scope@^3.7.7": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -2597,13 +2590,6 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree-jsx@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" - integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== - dependencies: - "@types/estree" "*" - "@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.7" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" @@ -2687,13 +2673,6 @@ dependencies: "@types/unist" "^2" -"@types/hast@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" - integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== - dependencies: - "@types/unist" "*" - "@types/hoist-non-react-statics@^3.3.1": version "3.3.6" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz#6bba74383cdab98e8db4e20ce5b4a6b98caed010" @@ -2790,13 +2769,6 @@ dependencies: "@types/unist" "^2" -"@types/mdast@^4.0.0": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" - integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== - dependencies: - "@types/unist" "*" - "@types/mdurl@^2": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" @@ -2812,11 +2784,6 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== -"@types/ms@*": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" - integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== - "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2973,11 +2940,6 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== -"@types/unist@*", "@types/unist@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" - integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== - "@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.11" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" @@ -3105,7 +3067,7 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": +"@ungap/structured-clone@^1.2.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== @@ -3760,11 +3722,6 @@ bail@^1.0.0: resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== -bail@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" - integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3991,11 +3948,6 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -ccount@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" - integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -4032,41 +3984,21 @@ character-entities-html4@^1.0.0: resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== -character-entities-html4@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" - integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== - character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== -character-entities-legacy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" - integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== - character-entities@^1.0.0: version "1.2.4" resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== -character-entities@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" - integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== - character-reference-invalid@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== -character-reference-invalid@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" - integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== - check-types@^11.2.3: version "11.2.3" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71" @@ -4192,11 +4124,6 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== -comma-separated-tokens@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" - integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4715,7 +4642,7 @@ debug@2.6.9, debug@^2.6.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -4734,13 +4661,6 @@ decimal.js@^10.4.2: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== -decode-named-character-reference@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz#5d6ce68792808901210dac42a8e9853511e2b8bf" - integrity sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w== - dependencies: - character-entities "^2.0.0" - decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -4806,7 +4726,7 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -dequal@^2.0.0, dequal@^2.0.3: +dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -4839,13 +4759,6 @@ detect-port-alt@^1.1.6: address "^1.0.1" debug "^2.6.0" -devlop@^1.0.0, devlop@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" - integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== - dependencies: - dequal "^2.0.0" - diff-sequences@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" @@ -5511,11 +5424,6 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-util-is-identifier-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" - integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== - estree-walker@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e" @@ -6256,27 +6164,6 @@ hast-util-to-html@^7.1.1: unist-util-is "^4.0.0" xtend "^4.0.0" -hast-util-to-jsx-runtime@^2.0.0: - version "2.3.6" - resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" - integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== - dependencies: - "@types/estree" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/unist" "^3.0.0" - comma-separated-tokens "^2.0.0" - devlop "^1.0.0" - estree-util-is-identifier-name "^3.0.0" - hast-util-whitespace "^3.0.0" - mdast-util-mdx-expression "^2.0.0" - mdast-util-mdx-jsx "^3.0.0" - mdast-util-mdxjs-esm "^2.0.0" - property-information "^7.0.0" - space-separated-tokens "^2.0.0" - style-to-js "^1.0.0" - unist-util-position "^5.0.0" - vfile-message "^4.0.0" - hast-util-to-parse5@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" @@ -6293,13 +6180,6 @@ hast-util-whitespace@^1.0.0: resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== -hast-util-whitespace@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" - integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== - dependencies: - "@types/hast" "^3.0.0" - hastscript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" @@ -6383,11 +6263,6 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-url-attributes@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" - integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== - html-void-elements@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" @@ -6582,11 +6457,6 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== -inline-style-parser@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" - integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== - inter-ui@^3.19.3: version "3.19.3" resolved "https://registry.yarnpkg.com/inter-ui/-/inter-ui-3.19.3.tgz#cf4b4b6d30de8d5463e2462588654b325206488c" @@ -6616,11 +6486,6 @@ is-alphabetical@^1.0.0: resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== -is-alphabetical@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" - integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== - is-alphanumerical@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" @@ -6629,14 +6494,6 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" -is-alphanumerical@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" - integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== - dependencies: - is-alphabetical "^2.0.0" - is-decimal "^2.0.0" - is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -6730,11 +6587,6 @@ is-decimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== -is-decimal@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" - integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== - is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -6784,11 +6636,6 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== -is-hexadecimal@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" - integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== - is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" @@ -6837,11 +6684,6 @@ is-plain-obj@^3.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== -is-plain-obj@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" - integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== - is-plain-object@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" @@ -7879,11 +7721,6 @@ long@^5.0.0, long@^5.2.3: resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== -longest-streak@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" - integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -7993,74 +7830,6 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" -mdast-util-from-markdown@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" - integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== - dependencies: - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - mdast-util-to-string "^4.0.0" - micromark "^4.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-decode-string "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - unist-util-stringify-position "^4.0.0" - -mdast-util-mdx-expression@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" - integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - -mdast-util-mdx-jsx@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" - integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - ccount "^2.0.0" - devlop "^1.1.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - parse-entities "^4.0.0" - stringify-entities "^4.0.0" - unist-util-stringify-position "^4.0.0" - vfile-message "^4.0.0" - -mdast-util-mdxjs-esm@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" - integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - -mdast-util-phrasing@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" - integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== - dependencies: - "@types/mdast" "^4.0.0" - unist-util-is "^6.0.0" - mdast-util-to-hast@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604" @@ -8075,43 +7844,6 @@ mdast-util-to-hast@^10.2.0: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" -mdast-util-to-hast@^13.0.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" - integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - "@ungap/structured-clone" "^1.0.0" - devlop "^1.0.0" - micromark-util-sanitize-uri "^2.0.0" - trim-lines "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - -mdast-util-to-markdown@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" - integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== - dependencies: - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - longest-streak "^3.0.0" - mdast-util-phrasing "^4.0.0" - mdast-util-to-string "^4.0.0" - micromark-util-classify-character "^2.0.0" - micromark-util-decode-string "^2.0.0" - unist-util-visit "^5.0.0" - zwitch "^2.0.0" - -mdast-util-to-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" - integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== - dependencies: - "@types/mdast" "^4.0.0" - mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -8179,200 +7911,6 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromark-core-commonmark@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" - integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== - dependencies: - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - micromark-factory-destination "^2.0.0" - micromark-factory-label "^2.0.0" - micromark-factory-space "^2.0.0" - micromark-factory-title "^2.0.0" - micromark-factory-whitespace "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-classify-character "^2.0.0" - micromark-util-html-tag-name "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-resolve-all "^2.0.0" - micromark-util-subtokenize "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-destination@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" - integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-label@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" - integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== - dependencies: - devlop "^1.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-space@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" - integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-title@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" - integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== - dependencies: - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-whitespace@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" - integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== - dependencies: - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-character@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" - integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== - dependencies: - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-chunked@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" - integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-classify-character@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" - integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-combine-extensions@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" - integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== - dependencies: - micromark-util-chunked "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-decode-numeric-character-reference@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" - integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-decode-string@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" - integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== - dependencies: - decode-named-character-reference "^1.0.0" - micromark-util-character "^2.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-symbol "^2.0.0" - -micromark-util-encode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" - integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== - -micromark-util-html-tag-name@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" - integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== - -micromark-util-normalize-identifier@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" - integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-resolve-all@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" - integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== - dependencies: - micromark-util-types "^2.0.0" - -micromark-util-sanitize-uri@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" - integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-encode "^2.0.0" - micromark-util-symbol "^2.0.0" - -micromark-util-subtokenize@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" - integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== - dependencies: - devlop "^1.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-symbol@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" - integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== - -micromark-util-types@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" - integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== - -micromark@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" - integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== - dependencies: - "@types/debug" "^4.0.0" - debug "^4.0.0" - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - micromark-core-commonmark "^2.0.0" - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-combine-extensions "^2.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-encode "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-resolve-all "^2.0.0" - micromark-util-sanitize-uri "^2.0.0" - micromark-util-subtokenize "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" @@ -8837,19 +8375,6 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-entities@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" - integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== - dependencies: - "@types/unist" "^2.0.0" - character-entities-legacy "^3.0.0" - character-reference-invalid "^2.0.0" - decode-named-character-reference "^1.0.0" - is-alphanumerical "^2.0.0" - is-decimal "^2.0.0" - is-hexadecimal "^2.0.0" - parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -9591,11 +9116,6 @@ property-information@^5.0.0, property-information@^5.3.0: dependencies: xtend "^4.0.0" -property-information@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112" - integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== - protobufjs-cli@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/protobufjs-cli/-/protobufjs-cli-1.2.0.tgz#653f53ad1866e81d16b9e3adf564bf59985af1bf" @@ -9855,23 +9375,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-markdown@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" - integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - hast-util-to-jsx-runtime "^2.0.0" - html-url-attributes "^3.0.0" - mdast-util-to-hast "^13.0.0" - remark-parse "^11.0.0" - remark-rehype "^11.0.0" - unified "^11.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - react-query@^3.39.3: version "3.39.3" resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" @@ -10192,27 +9695,6 @@ remark-parse-no-trim@^8.0.4: vfile-location "^3.0.0" xtend "^4.0.1" -remark-parse@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" - integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-from-markdown "^2.0.0" - micromark-util-types "^2.0.0" - unified "^11.0.0" - -remark-rehype@^11.0.0: - version "11.1.2" - resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" - integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - mdast-util-to-hast "^13.0.0" - unified "^11.0.0" - vfile "^6.0.0" - remark-rehype@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.1.0.tgz#610509a043484c1e697437fa5eb3fd992617c945" @@ -10798,11 +10280,6 @@ space-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== -space-separated-tokens@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" - integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== - spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -11006,14 +10483,6 @@ stringify-entities@^3.0.1: character-entities-legacy "^1.0.0" xtend "^4.0.0" -stringify-entities@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" - integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== - dependencies: - character-entities-html4 "^2.0.0" - character-entities-legacy "^3.0.0" - stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" @@ -11074,20 +10543,6 @@ style-loader@^3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== -style-to-js@^1.0.0: - version "1.1.16" - resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a" - integrity sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw== - dependencies: - style-to-object "1.0.8" - -style-to-object@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292" - integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== - dependencies: - inline-style-parser "0.2.4" - style-to-object@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" @@ -11309,11 +10764,6 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" -trim-lines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" - integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== - trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -11324,11 +10774,6 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -trough@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" - integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== - tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -11536,19 +10981,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -unified@^11.0.0: - version "11.0.5" - resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" - integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== - dependencies: - "@types/unist" "^3.0.0" - bail "^2.0.0" - devlop "^1.0.0" - extend "^3.0.0" - is-plain-obj "^4.0.0" - trough "^2.0.0" - vfile "^6.0.0" - unified@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" @@ -11588,25 +11020,11 @@ unist-util-is@^4.0.0: resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== -unist-util-is@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" - integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== - dependencies: - "@types/unist" "^3.0.0" - unist-util-position@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== -unist-util-position@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" - integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== - dependencies: - "@types/unist" "^3.0.0" - unist-util-remove-position@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" @@ -11621,13 +11039,6 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -unist-util-stringify-position@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" - integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== - dependencies: - "@types/unist" "^3.0.0" - unist-util-visit-parents@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" @@ -11643,14 +11054,6 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit-parents@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" - integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== - dependencies: - "@types/unist" "^3.0.0" - unist-util-is "^6.0.0" - unist-util-visit@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" @@ -11667,15 +11070,6 @@ unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" -unist-util-visit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" - integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== - dependencies: - "@types/unist" "^3.0.0" - unist-util-is "^6.0.0" - unist-util-visit-parents "^6.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -11811,14 +11205,6 @@ vfile-message@^2.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^2.0.0" -vfile-message@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" - integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== - dependencies: - "@types/unist" "^3.0.0" - unist-util-stringify-position "^4.0.0" - vfile@^4.0.0, vfile@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" @@ -11829,14 +11215,6 @@ vfile@^4.0.0, vfile@^4.2.1: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -vfile@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" - integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== - dependencies: - "@types/unist" "^3.0.0" - vfile-message "^4.0.0" - w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" @@ -12401,8 +11779,3 @@ zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== - -zwitch@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==