-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit dc84eab
Showing
41 changed files
with
667 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
IS_DEBUG = False | ||
API_KEY = sample_api_key | ||
DEFAULT_MODEL_PATH=./sample_model/lin_reg_california_housing_model.joblib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# Hidden files | ||
.DS_store | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
env/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
htmlcov-py36/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
.vscode/ | ||
.pytest-cache/ | ||
.pytest_cache/ | ||
.empty/ | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# dotenv | ||
.env | ||
|
||
# virtualenv | ||
.venv | ||
venv/ | ||
ENV/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
https://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# file GENERATED by distutils, do NOT edit | ||
setup.cfg | ||
setup.py | ||
fastapi_skeleton/__init__.py | ||
fastapi_skeleton/main.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# FastAPI Model Server Skeleton | ||
|
||
Serving machine learning models production-ready, fast, easy and secure powered by the great FastAPI by [Sebastián Ramírez]([)](https://github.com/tiangolo). | ||
|
||
This repository contains a skeleton app which can be used to speed-up your next machine learning project. The code is fully tested and provides a preconfigured `tox` to quickly expand this sample code. | ||
|
||
To experiment and get a feeling on how to use this skeleton, a sample regression model for house price prediction is included in this project. Follow the installation and setup instructions to run the sample model and serve it aso RESTful API. | ||
|
||
## Requirements | ||
|
||
Python 3.6+ | ||
|
||
## Installation | ||
Install the required packages in your local environment (ideally virtualenv, conda, etc.). | ||
```bash | ||
pip install -r requirements | ||
``` | ||
|
||
|
||
## Setup | ||
1. Duplicate the `.env.example` file and rename it to `.env` | ||
|
||
|
||
2. In the `.env` file configure the `API_KEY` entry. The key is used for authenticating our API. <br> | ||
A sample API key can be generated using Python REPL: | ||
```python | ||
import uuid | ||
print(str(uuid.uuid4())) | ||
``` | ||
|
||
## Run It | ||
|
||
1. Start your app with: | ||
```bash | ||
uvicorn fastapi_skeleton.main:app | ||
``` | ||
|
||
2. Go to [http://localhost:8000/docs](http://localhost:8000/docs). | ||
|
||
3. Click `Authorize` and enter the API key as created in the Setup step. | ||
![Authroization](./docs/authorize.png) | ||
|
||
4. You can use the sample payload from the `docs/sample_payload.json` file when trying out the house price prediction model using the API. | ||
![Prediction with example payload](./docs/sample_payload.png) | ||
|
||
## Run Tests | ||
|
||
If you're not using `tox`, please install with: | ||
```bash | ||
pip install tox | ||
``` | ||
|
||
Run your tests with: | ||
```bash | ||
tox | ||
``` | ||
|
||
This runs tests and coverage for Python 3.6 and Flake8, Autopep8, Bandit. |
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"median_income_in_block": 8.3252, | ||
"median_house_age_in_block": 41, | ||
"average_rooms": 6, | ||
"average_bedrooms": 1, | ||
"population_per_block": 322, | ||
"average_house_occupancy": 2.55, | ||
"block_latitude": 37.88, | ||
"block_longitude": -122.23 | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
from fastapi import APIRouter | ||
|
||
from fastapi_skeleton.models.heartbeat import HearbeatResult | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/heartbeat", response_model=HearbeatResult, name="heartbeat") | ||
def get_hearbeat() -> HearbeatResult: | ||
heartbeat = HearbeatResult(is_alive=True) | ||
return heartbeat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from fastapi import APIRouter, Depends | ||
from starlette.requests import Request | ||
|
||
from fastapi_skeleton.core import security | ||
from fastapi_skeleton.models.payload import HousePredictionPayload | ||
from fastapi_skeleton.models.prediction import HousePredictionResult | ||
from fastapi_skeleton.services.models import HousePriceModel | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.post("/predict", response_model=HousePredictionResult, name="predict") | ||
def post_predict( | ||
request: Request, | ||
authenticated: bool = Depends(security.validate_request), | ||
block_data: HousePredictionPayload = None | ||
) -> HousePredictionResult: | ||
|
||
model: HousePriceModel = request.app.state.model | ||
prediction: HousePredictionResult = model.predict(block_data) | ||
|
||
return prediction |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
|
||
from fastapi import APIRouter | ||
|
||
from fastapi_skeleton.api.routes import heartbeat, prediction | ||
|
||
api_router = APIRouter() | ||
api_router.include_router(heartbeat.router, tags=["health"], prefix="/health") | ||
api_router.include_router(prediction.router, tags=[ | ||
"prediction"], prefix="/model") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
|
||
|
||
from starlette.config import Config | ||
from starlette.datastructures import Secret | ||
|
||
APP_VERSION = "0.0.1" | ||
APP_NAME = "House Price Prediction Example" | ||
API_PREFIX = "/api" | ||
|
||
config = Config(".env") | ||
|
||
API_KEY: Secret = config("API_KEY", cast=Secret) | ||
IS_DEBUG: bool = config("IS_DEBUG", cast=bool, default=False) | ||
|
||
DEFAULT_MODEL_PATH: str = config("DEFAULT_MODEL_PATH") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
|
||
|
||
from typing import Callable | ||
|
||
from fastapi import FastAPI | ||
from loguru import logger | ||
|
||
from fastapi_skeleton.core.config import DEFAULT_MODEL_PATH | ||
from fastapi_skeleton.services.models import HousePriceModel | ||
|
||
|
||
def _startup_model(app: FastAPI) -> None: | ||
model_path = DEFAULT_MODEL_PATH | ||
model_instance = HousePriceModel(model_path) | ||
app.state.model = model_instance | ||
|
||
|
||
def _shutdown_model(app: FastAPI) -> None: | ||
app.state.model = None | ||
|
||
|
||
def start_app_handler(app: FastAPI) -> Callable: | ||
def startup() -> None: | ||
logger.info("Running app start handler.") | ||
_startup_model(app) | ||
return startup | ||
|
||
|
||
def stop_app_handler(app: FastAPI) -> Callable: | ||
def shutdown() -> None: | ||
logger.info("Running app shutdown handler.") | ||
_shutdown_model(app) | ||
return shutdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
NO_API_KEY = "No API key provided." | ||
AUTH_REQ = "Authentication required." | ||
HTTP_500_DETAIL = "Internal server error." | ||
|
||
# templates | ||
NO_VALID_PAYLOAD = "{} is not a valid payload." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
|
||
import secrets | ||
from typing import Optional | ||
|
||
from fastapi import HTTPException, Security | ||
from fastapi.security.api_key import APIKeyHeader | ||
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED | ||
|
||
from fastapi_skeleton.core import config | ||
from fastapi_skeleton.core.messages import AUTH_REQ, NO_API_KEY | ||
|
||
api_key = APIKeyHeader(name="token", auto_error=False) | ||
|
||
|
||
def validate_request(header: Optional[str] = Security(api_key)) -> bool: | ||
if header is None: | ||
raise HTTPException( | ||
status_code=HTTP_400_BAD_REQUEST, detail=NO_API_KEY, headers={} | ||
) | ||
if not secrets.compare_digest(header, str(config.API_KEY)): | ||
raise HTTPException( | ||
status_code=HTTP_401_UNAUTHORIZED, detail=AUTH_REQ, headers={} | ||
) | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
|
||
from fastapi import FastAPI | ||
|
||
from fastapi_skeleton.api.routes.router import api_router | ||
from fastapi_skeleton.core.config import (API_PREFIX, APP_NAME, APP_VERSION, | ||
IS_DEBUG) | ||
from fastapi_skeleton.core.event_handlers import (start_app_handler, | ||
stop_app_handler) | ||
|
||
|
||
def get_app() -> FastAPI: | ||
fast_app = FastAPI(title=APP_NAME, version=APP_VERSION, debug=IS_DEBUG) | ||
fast_app.include_router(api_router, prefix=API_PREFIX) | ||
|
||
fast_app.add_event_handler("startup", start_app_handler(fast_app)) | ||
fast_app.add_event_handler("shutdown", stop_app_handler(fast_app)) | ||
|
||
return fast_app | ||
|
||
|
||
app = get_app() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
|
||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class HearbeatResult(BaseModel): | ||
is_alive: bool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
from typing import List | ||
from pydantic import BaseModel | ||
|
||
|
||
class HousePredictionPayload(BaseModel): | ||
median_income_in_block: float | ||
median_house_age_in_block: int | ||
average_rooms: int | ||
average_bedrooms: int | ||
population_per_block: int | ||
average_house_occupancy: int | ||
block_latitude: float | ||
block_longitude: float | ||
|
||
|
||
def payload_to_list(hpp: HousePredictionPayload) -> List: | ||
return [ | ||
hpp.median_income_in_block, | ||
hpp.median_house_age_in_block, | ||
hpp.average_rooms, | ||
hpp.average_bedrooms, | ||
hpp.population_per_block, | ||
hpp.average_house_occupancy, | ||
hpp.block_latitude, | ||
hpp.block_longitude] |
Oops, something went wrong.