Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sdk/python/feast/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@

# Environment variable for feature server docker image tag
DOCKER_IMAGE_TAG_ENV_NAME: str = "FEAST_SERVER_DOCKER_IMAGE_TAG"

# Default feature server registry ttl (seconds)
DEFAULT_FEATURE_SERVER_REGISTRY_TTL = 5
65 changes: 39 additions & 26 deletions sdk/python/feast/feature_server.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import json
import sys
import threading
import traceback
import warnings
from typing import List, Optional

import gunicorn.app.base
import pandas as pd
from dateutil import parser
from fastapi import FastAPI, HTTPException, Request, Response, status
Expand All @@ -15,6 +15,7 @@

import feast
from feast import proto_json, utils
from feast.constants import DEFAULT_FEATURE_SERVER_REGISTRY_TTL
from feast.data_source import PushMode
from feast.errors import PushSourceNotFoundException
from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest
Expand Down Expand Up @@ -45,7 +46,10 @@ class MaterializeIncrementalRequest(BaseModel):
feature_views: Optional[List[str]] = None


def get_app(store: "feast.FeatureStore", registry_ttl_sec: int = 5):
def get_app(
store: "feast.FeatureStore",
registry_ttl_sec: int = DEFAULT_FEATURE_SERVER_REGISTRY_TTL,
):
proto_json.patch()

app = FastAPI()
Expand Down Expand Up @@ -202,24 +206,27 @@ def materialize_incremental(body=Depends(get_body)):
return app


class FeastServeApplication(gunicorn.app.base.BaseApplication):
def __init__(self, store: "feast.FeatureStore", **options):
self._app = get_app(
store=store,
registry_ttl_sec=options.get("registry_ttl_sec", 5),
)
self._options = options
super().__init__()
if sys.platform != "win32":
import gunicorn.app.base

def load_config(self):
for key, value in self._options.items():
if key.lower() in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
class FeastServeApplication(gunicorn.app.base.BaseApplication):
def __init__(self, store: "feast.FeatureStore", **options):
self._app = get_app(
store=store,
registry_ttl_sec=options["registry_ttl_sec"],
)
self._options = options
super().__init__()

def load_config(self):
for key, value in self._options.items():
if key.lower() in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)

self.cfg.set("worker_class", "uvicorn.workers.UvicornWorker")
self.cfg.set("worker_class", "uvicorn.workers.UvicornWorker")

def load(self):
return self._app
def load(self):
return self._app


def start_server(
Expand All @@ -229,13 +236,19 @@ def start_server(
no_access_log: bool,
workers: int,
keep_alive_timeout: int,
registry_ttl_sec: int = 5,
registry_ttl_sec: int,
):
FeastServeApplication(
store=store,
bind=f"{host}:{port}",
accesslog=None if no_access_log else "-",
workers=workers,
keepalive=keep_alive_timeout,
registry_ttl_sec=registry_ttl_sec,
).run()
if sys.platform != "win32":
FeastServeApplication(
store=store,
bind=f"{host}:{port}",
accesslog=None if no_access_log else "-",
workers=workers,
keepalive=keep_alive_timeout,
registry_ttl_sec=registry_ttl_sec,
).run()
else:
import uvicorn

app = get_app(store, registry_ttl_sec)
uvicorn.run(app, host=host, port=port, access_log=(not no_access_log))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"typeguard>=4.0.0",
"fastapi>=0.68.0",
"uvicorn[standard]>=0.14.0,<1",
"gunicorn",
"gunicorn; platform_system != 'Windows'",
# https://github.com/dask/dask/issues/10996
"dask>=2021.1.0,<2024.3.0",
"bowler", # Needed for automatic repo upgrades
Expand Down