wedata3-feature-engineering), covering client initialization, feature database management, feature table creation, writing, reading, metadata maintenance, deletion, offline feature consumption (training set construction, model logging, batch inference), and online feature table operations.%pip install "wedata3-feature-engineering[mlflow3]>=0.2.3"
mlflow >= 3.10.0 and < 3.11.0. If an mlflow version mismatch is reported when import wedata is executed, make sure that the [mlflow3] extra is installed (that is, wedata3-feature-engineering[mlflow3]), and do not mix mlflow 2.x and 3.x in the same Notebook.export the environment variables yourself. Otherwise, initializing FeatureEngineeringClient(...) will raise EnvironmentError / KeyError.spark session that you can pass in directly. For local debugging, create a SparkSession and pass it in yourself.%pip install "wedata3-feature-engineering[mlflow3]>=0.2.3"%pip list | grep wedata3-feature-engineering
from wedata.feature_engineering.client import FeatureEngineeringClientfrom wedata.common.constants.engine_types import EngineTypes# The DataBuddy Notebook kernel has a built-in spark session. For local debugging, create one and pass it in yourself.fe = FeatureEngineeringClient(spark)
# Create a feature database idempotently (skip if it already exists). Supports a single-segment name or two-part catalog.schema.fe.create_database("<schema_name>", catalog_name="<catalog_name>", comment="<database description>")# List all databases in the specified Catalog.fe.list_databases(catalog_name="<catalog_name>")# Check whether the database exists (any internal exception is swallowed and False is returned).fe.database_exists("<schema_name>", catalog_name="<catalog_name>")# Delete a database. By default, the deletion is non-cascading to prevent accidental data loss. Use cascading deletion with caution.fe.drop_database("<schema_name>", catalog_name="<catalog_name>")
API | Function | Key parameters |
create_database | Idempotently create a feature database ( CREATE DATABASE IF NOT EXISTS). | database_name (required, supports the two-part catalog.schema format), catalog_name, comment, location |
drop_database | Delete a feature database. | database_name, catalog_name, cascade (default: False, whether to cascade table deletion), if_exists (default: True) |
list_databases | List all databases under a specified Catalog. | catalog_name (if not specified, the current Catalog is used) |
database_exists | Idempotently check whether a database exists. | database_name,catalog_name |
drop_database raises an error from the underlying layer when the database is not empty and cascade=False. Setting cascade=True cascades the deletion to all tables under it, so use it with caution in production environments.create_table to create a feature table. The table schema can be automatically inferred from the provided df or explicitly passed through schema. engine_type is required and specifies the underlying table engine:from wedata.common.constants.engine_types import EngineTypes# Method 1: Pass in a df to automatically infer the schema and write the initial data.fe.create_table(name="<catalog>.<schema>.<table_name>", # Fully qualified name of the feature tableprimary_keys=["<primary key list>"], # Pass a string for a single key or a list for a composite key.timestamp_key=["<timestamp column name>"], # Timestamp key column name (singular)df=<Spark DataFrame>, # Passing in a df automatically infers the schema and writes the initial data.engine_type=EngineTypes.ICEBERG_ENGINE, # Required: ICEBERG_ENGINE or HIVE_ENGINEdescription=["<table description>"], # Optional)# Method 2: Create an empty table by explicitly providing a schema without passing in a df.fe.create_table(name="<catalog>.<schema>.<table_name>",primary_keys=["<primary key column name>"],timestamp_key="<timestamp column name>",schema=<PySpark StructType>, # Required when df is empty.engine_type=EngineTypes.ICEBERG_ENGINE,)
primary_keys is of type Union[str, List[str]]. You can pass a string directly for a single primary key or a list for a composite primary key.schema and df are mutually exclusive: schema can be omitted when df is provided (inferred from df), and schema is required only when df=None.catalog_name / database_name (specified when name is not a fully qualified name), tags (table-level tag dictionary), partition_columns (partition columns).engine_type): EngineTypes provides two values: ICEBERG_ENGINE (Iceberg tables, recommended) and HIVE_ENGINE (Hive tables). Prefer Iceberg because it supports schema evolution and time travel, delivering better performance for point-in-time feature queries.decimal(p, s) type, which prevents model signature (signature) inference when the model is logged and leads to type mismatches between online and offline inference. Before writing to the feature table, cast all decimal columns to cast("double") and encapsulate this processing into a reusable utility function. Keep timestamp columns as a time type instead of converting them to strings, otherwise they cannot be used as timestamp keys.write_table:fe.write_table(name="<catalog>.<schema>.<table_name>", # Fully qualified name of the target feature tabledf=<Spark DataFrame>, # Feature data to be writtenmode="append", # Write mode: append (default) / overwrite)
fe.write_table(name="<catalog>.<schema>.<table_name>",df=<Streaming DataFrame>,mode="append",checkpoint_location="<checkpoint path>", # Required for streaming writestrigger={"processingTime": "10 seconds"}, # Optional, streaming trigger configuration)
write_table does not support the merge mode. For incremental writes, use the default append mode.get_table to programmatically retrieve the complete metadata of a feature table, or call read_table to read feature table data (returning a Spark DataFrame):# Obtain metadata.table_info = fe.get_table(name="<catalog_name>.<schema_name>.<table_name>")print(table_info.primary_keys)print(table_info.timestamp_keys)print(table_info.tags)# Read data.df = fe.read_table(name="<catalog_name>.<schema_name>.<table_name>")df.show(5)
read_table reads the full dataset from the offline feature table. Data in the online feature table (PostgreSQL) is used by the online inference service to automatically look up features when a request is received, so callers do not need to explicitly read it through the SDK. To verify online table data, you can view the corresponding online feature table in the data catalog.# Set or update a tag.fe.set_feature_table_tag(name="<catalog_name>.<schema_name>.<table_name>",key="<tag_key>", # For example, "business_line", "version", and so onvalue="<tag_value>", # For example, "growth", "v1.2.0", and so on)# Delete a tagfe.delete_feature_table_tag(name="<catalog_name>.<schema_name>.<table_name>",key="<tag_key>",)
Tag Key | Example Value | Purpose |
owner | ml-team | Owner / Responsible team |
purpose | test / prod | Purpose (test / production) |
domain | user / order / product | Business domain |
sla | daily / hourly | Data update frequency |
primary_keys and timestamp_keys are reserved fields of the SDK and cannot be used as keys for custom tags.version tag (such as v1.0.0 or v1.1.0) together with the data change history of the Catalog table to track business version evolution. To quickly switch between different versions, you can create separate feature tables for each version and explicitly select the desired table in your training code.fe.drop_table(name="<catalog_name>.<schema_name>.<table_name>")
drop_online_table first.feature_spec.yaml in any model artifact still references this table.create_training_set to join multiple feature tables and generate training data:from wedata.feature_engineering.client import FeatureEngineeringClientfrom wedata.common.entities.feature_lookup import FeatureLookupfe = FeatureEngineeringClient(spark)# 1. Primary key DataFrame: contains the training label + primary keys + event timestamplabels_df = spark.table("<catalog_name>.<schema_name>.<label_table_name>")# 2. Define the feature tables and fields to join. We strongly recommend using three-part fully qualified names to avoid relying on the default catalog.feature_lookups = [FeatureLookup(table_name="<catalog_name>.<schema_name>.<feature_table_name_1>",feature_names=["<feature_column_1>", "<feature_column_2>"],lookup_key=["<primary_key>"],timestamp_lookup_key="<timestamp_key>",),FeatureLookup(table_name="<catalog_name>.<schema_name>.<feature_table_name_2>",feature_names=["<feature_column_3>"],lookup_key=["<primary_key>"],timestamp_lookup_key="<timestamp_key>",),]# 3. Create a Training Datasettraining_set = fe.create_training_set(df=labels_df,feature_lookups=feature_lookups,label="<label_column>",exclude_columns=[],)training_df = training_set.load_df()
timestamp_lookup_key is used to retrieve feature values by point in time (point-in-time correctness), which means joining each sample with the most recent feature value before its event timestamp to avoid feature leakage. To limit the lookback time range, use lookback_window (a datetime.timedelta) in FeatureLookup to retrieve only feature values within that time window.decimal(p,s) to double. The SDK does not recognize the decimal(p,s) type, which may cause type parsing failures or inconsistencies between online and offline values.lookback_window must be used together with timestamp_lookup_key, and its value must be a non-negative datetime.timedelta (0 means exact match, and omitting it means no limit). Otherwise, a ValueError is raised.FeatureLookup + FeatureFunction combination: You can declare lookups from multiple feature tables at the same time and use FeatureFunction to introduce On-Demand features:import datetimefrom wedata.common.entities.feature_lookup import FeatureLookupfrom wedata.common.entities.feature_function import FeatureFunctionfeature_lookups = [FeatureLookup(table_name="<catalog_name>.<schema_name>.<feature_table_a>",lookup_key=["<primary_key_a>"],feature_names=["<feature_col_1>", "<feature_col_2>"],rename_outputs={"<feature_col_1>": "<renamed_col_1>"}, # Rename output columns to avoid name conflicts.),FeatureLookup(table_name="<catalog_name>.<schema_name>.<feature_table_b>",lookup_key=["<primary_key_b>"],timestamp_lookup_key="<timestamp_key>",lookback_window=datetime.timedelta(days=30), # Retrieve only feature values from the last 30 days.),FeatureFunction(# On-Demand Features (On-Demand UDF)udf_name="<catalog_name>.<schema_name>.<udf_name>",input_bindings={"<udf_input_param>": "<renamed_col_1>"},output_name="<udf_output_col>",),]
log_model:import mlflowfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import OneHotEncoder, StandardScalerfrom sklearn.compose import ColumnTransformerfrom sklearn.ensemble import GradientBoostingClassifierwith mlflow.start_run() as run:pdf = training_df.toPandas()X = pdf.drop(columns=["<label_column>"])y = pdf["<label_column>"]# Preprocessing (OHE / Scaler, and so on) must be encapsulated into a Pipeline. Do not perform manual preprocessing outside the Pipeline using methods such as pd.get_dummies.# Otherwise, the feature transformations from the training phase cannot be reproduced during online inference, leading to inconsistencies between online and offline results.preprocessor = ColumnTransformer(transformers=[("cat", OneHotEncoder(handle_unknown="ignore"), ["<categorical_feature>"]),("num", StandardScaler(), ["<numeric_feature_1>", "<numeric_feature_2>"]),])model = Pipeline(steps=[("preprocessor", preprocessor),("clf", GradientBoostingClassifier()),])model.fit(X, y)fe.log_model(model=model,artifact_path="<model_artifact_path>",flavor=mlflow.sklearn,training_set=training_set,registered_model_name="<catalog_name>.<schema_name>.<model_name>",infer_input_example=True,# Automatically generate input_example and model signature based on training data.)
log_model, the following rules must be followed:fe.log_model instead of mlflow.sklearn.log_model: only the former packages FeatureSpec into MLmodel, while the latter causes score_batch to fail to find the feature source.training_set: the model signature and input_example are automatically inferred by the SDK based on training_set.feature_spec, aligned with the original columns in the feature table.infer_input_example=True automatically generates input_example and the signature from training data, which is a prerequisite for deploying an online inference service and must be enabled. Do not manually call infer_signature(...) or manually pass input_example=..., otherwise the original columns of feature_spec will be misaligned and the contract will be polluted.sklearn.Pipeline, and the model passed in must be the entire Pipeline. Do not use pd.get_dummies / manually call StandardScaler.fit_transform outside the Pipeline before model.fit(...). External preprocessing steps will not be serialized into MLmodel. During inference, the SDK looks up raw string columns from the feature table and feeds them to a bare model that only recognizes post-OHE column names, which at best raises Feature names unseen at fit time and at worst silently outputs the training set mean for all samples.catalog.schema.model_name for registered_model_name to facilitate Model Registry governance.fe.log_model writes a feature_spec.yaml file into the model artifact, recording the feature tables, fields, primary keys, and timestamp relationships that the model depends on during inference. This file is automatically read in subsequent score_batch and online inference.score_batch to perform offline batch inference. At this point, you only need to provide a primary key DataFrame. The feature columns are automatically looked up from the feature table and filled in by the SDK based on the feature_spec.yaml in the model artifact:# Primary key DataFrame: contains only primary keys + event timestamp, with no feature columns required.# Note: The timestamp column must match the feature table schema type (cast to timestamp if necessary).keys_df = spark.table("<catalog_name>.<schema_name>.<scoring_table_name>")result_df = fe.score_batch(model_uri="models:/<catalog_name>.<schema_name>.<model_name>/<version>", # Align the version number with the latest version produced by training.df=keys_df,result_type="double", # Optional. The data type of the prediction result column. Defaults to "double".timestamp_key="<timestamp_key>", # Strongly recommended to explicitly specify when training includes timestamp_lookup_key to ensure point-in-time consistency.)result_df.show()
df of score_batch has the following hard constraints:prediction column, nor can it be a streaming DataFrame.lookup_key declared in feature_spec.yaml.timestamp_lookup_key is declared during training, it is strongly recommended to explicitly include a timestamp column with the same name in df to ensure point-in-time consistency. If it is not included, the SDK automatically injects current_timestamp() to fetch the latest visible features, which is equivalent to predicting historical samples with today's features and is semantically imprecise.feature_spec.yaml based on model_uri.keys_df, automatically look them up from the feature table through Feature Lookup.publish_table:fe.publish_table(catalog_name="<offline_catalog_name>", # Catalog where the offline feature table residesschema_name="<offline_schema_name>", # Schema where the offline feature table residestable_name="<offline_table_name>", # Name of the offline feature tableonline_db_name="<online_schema_name>", # Database where the online table residesonline_table_name="<online_table_name>", # Name of the online table# If trigger is not specified, a full sync is performed once a day at 00:00 (Asia/Shanghai) by default.)
publish_table requires at least the following in the workspace: ① a running data computing resource group; ② a PostgreSQL connection. Otherwise, a RuntimeError is thrown (the error message includes a request_id for easier troubleshooting).trigger parameter.TargetPath.CatalogName). If the error TargetPath.CatalogName cannot be empty is reported during the call, you can pass the additional parameter online_catalog_name="<online_catalog_name>" based on the preceding parameters. For the specific name, refer to the online Catalog configured in the workspace.fe.drop_online_table(name="<catalog_name>.<schema_name>.<table_name>")
Layer | Naming Rules | Example |
Catalog | Business domain | showcase / risk / recommend |
Schema (Feature Database) | <Team>_<Scenario>_db | growth_database / risk_churn_db |
Feature Table | <Entity>_<Purpose>_<Version> | orders_purchase_delivery_feature_v2 |
catalog.schema.table consistently when referencing feature tables. This avoids the side effects of relying on the default catalog and eliminates code changes when a migration to a different workspace is performed.order_purchase_delivery_time) to avoid duplication with feature columns.create_table Key ParametersParameter | Description | Required | Default Value |
name | Fully qualified name of the feature table, in the format catalog.schema.table | Yes | - |
primary_keys | Primary key column name, of type Union[str, List[str]]. Pass a string for a single key and a list for a composite primary key. | Yes | — |
timestamp_key | Timestamp key column name (singular) | No | None |
engine_type | Underlying table engine, EngineTypes.ICEBERG_ENGINE or EngineTypes.HIVE_ENGINE | Yes | — |
df | Initial data Spark DataFrame. After it is passed in, schema can be automatically inferred and the first batch of data written. | No | None |
schema | Table schema (PySpark StructType). Required when df=None | No | None |
catalog_name | Catalog name (specified when name does not use a fully qualified name) | No | None |
database_name | Schema/database name (specified when name does not use a fully qualified name) | No | None |
partition_columns | List of partition column names | No | None |
tags | Table-level tag dictionary | No | None |
description | Table description, displayed in the feature management list and on the Catalog details page. | No | Null |
write_table Key ParametersParameter | Description | Required | Default Value |
name | Fully qualified name of the target feature table | Yes | — |
df | Data to write, Spark DataFrame (supports batch / streaming DataFrame) | Yes | — |
mode | Write mode, append / overwrite | No | append |
checkpoint_location | checkpoint path for streaming writes | Required for streaming writes | None |
trigger | Streaming write trigger configuration | No | None |
get_table / read_table ParametersParameter | Description | Required | Default Value |
name | Feature table name (can be a fully qualified name, or used with catalog_name / database_name) | Yes | — |
database_name | Schema/database name | No | None |
catalog_name | Catalog name | No | None |
get_table and create_table return a FeatureTable entity (wedata.common.entities.feature_table.FeatureTable), which is constructed and returned by the API and should not be directly instantiated by callers. The fields include: name, table_id, description, primary_keys, partition_columns, features, timestamp_keys, creation_timestamp, online_stores, and tags.tags field is lazily loaded. Accessing it directly before it is loaded will raise a ValueError. Make sure that the table has been returned correctly before reading the tags.set_feature_table_tag / delete_feature_table_tag ParametersParameter | Description | Required | Default Value |
name | Feature table name (can be a fully qualified name, or used with catalog_name / database_name) | Yes | — |
database_name | Schema/database name | No | None |
catalog_name | Catalog name | No | None |
key | Tag Key | Yes | "" |
value | Tag value (only set_feature_table_tag) | Yes | "" |
FeatureLookup Key ParametersParameter | Description | Required | Default Value |
table_name | Feature table name. Using a three-part fully qualified name is strongly recommended catalog.schema.table | Yes | — |
lookup_key | Primary key column name used for matching (composite primary key supported) | Yes | — |
feature_names | List of feature column names to look up. If not specified, all non-primary-key columns are looked up. | No | All non-primary-key columns |
timestamp_lookup_key | Timestamp column name used for point-in-time lookback | No | None |
lookback_window | Time window for point-in-time lookback, of type Optional[datetime.timedelta], used with timestamp_lookup_key | No | None |
rename_outputs | Mapping for renaming output fields to avoid column name conflicts across multiple feature tables. | No | None |
is_online | Whether it is an online lookup scenario | No | None |
online_config | Configuration related to online lookup | No | None |
FeatureLookup has 9 parameters in total, including 2 deprecated parameters (feature_name and output_name). Use feature_names and rename_outputs instead.create_training_set Key ParametersParameter | Description | Required | Default Value |
df | Primary key DataFrame, containing primary key, timestamp, and training label | Yes | — |
feature_lookups | List of FeatureLookup objects (mutually exclusive with feature_spec) | No | — |
feature_spec | Existing feature configuration (mutually exclusive with feature_lookups; choose either one) | No | — |
label | Training label column name | Yes | — |
exclude_columns | List of column names excluded from training | No | [] |
database_name | Schema/database name | No | None |
catalog_name | Catalog name | No | None |
log_model Key ParametersParameter | Type | Description | Required | Default Value |
model | Any | Trained model object (must be trained using the DataFrame returned by training_set.load_df()) | Yes | — |
artifact_path | str | MLflow artifact path (for example, "model") | Yes | — |
flavor | ModuleType | MLflow flavor module, such as mlflow.sklearn and mlflow.xgboost | Yes | — |
training_set | Optional[TrainingSet] | Training set object; if not provided, FeatureSpec is not included (automatic lookup is not performed during inference). | No | None |
registered_model_name | Optional[str] | Model registration name; a three-part format catalog.schema.model_name is recommended. | No | None |
model_registry_uri | Optional[str] | Model Registry URI | No | None |
await_registration_for | int | Number of seconds to wait for registration to complete | No | 300 |
infer_input_example | bool | Whether to automatically generate input_example from training_set.load_df() | No | False |
**kwargs | — | Passed through to the log_model of the corresponding flavor | No | — |
signature and input_example are automatically generated from training_set.feature_spec and cannot be overridden by parameters, ensuring consistent lookup column contracts during score_batch. Do not manually call infer_signature(...) or pass input_example=... again.score_batch Key ParametersParameter | Description | Required | Default Value |
model_uri | MLflow model URI, such as runs:/<run_id>/model, models:/<name>/<version>, or models:/<name>/<stage> | Yes | — |
df | Primary key DataFrame that must contain all lookup_key entries in feature_spec.yaml; it cannot contain the prediction column and cannot be a streaming DataFrame. | Yes | — |
result_type | Data type of the prediction result column. The value is the same as result_type of mlflow.pyfunc.spark_udf. | No | "double" |
timestamp_key | Name of the timestamp column in the inference DataFrame. If not specified and the model training includes timestamp_lookup_key, the SDK injects current_timestamp() to retrieve the latest features. | No | None |
publish_table ParametersParameter | Description | Required | Default Value |
catalog_name | Catalog where the offline feature table resides | Yes | — |
schema_name | Schema or database where the offline feature table resides | Yes | — |
table_name | Offline feature table name | Yes | — |
online_db_name | Database where the online table resides | Yes | — |
online_table_name | Online table name | Yes | — |
trigger | Synchronization scheduling configuration. If not specified, a full synchronization is performed once a day at 00:00 (Asia/Shanghai) by default. | No | None |
online_catalog_name (the Catalog to which the online table belongs, corresponding to the server-side TargetPath.CatalogName). Pass this parameter only when the error TargetPath.CatalogName cannot be empty is reported. For the value, refer to the online Catalog configured in the workspace.drop_table / drop_online_table ParametersParameter | Description | Required | Default Value |
name | Feature table name (can be a fully qualified name, or used with catalog_name / database_name) | Yes | — |
database_name | Schema/database name | No | None |
catalog_name | Catalog name | No | None |
피드백