├── .pylintrc ├── Dockerfile ├── README.md ├── __pycache__ └── main.cpython-310.pyc ├── cloudbuild.yaml ├── main.py └── requirements.txt /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | extension-pkg-allow-list=pydantic -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.10 2 | 3 | WORKDIR /app 4 | 5 | RUN pip install --upgrade pip 6 | 7 | COPY requirements.txt requirements.txt 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | COPY . . 12 | 13 | EXPOSE 8080 14 | 15 | ENV PYTHONUNBUFFERED=1 16 | 17 | # CMD ["python", "-u", "main.py"] 18 | 19 | CMD ["uvicorn", "--host", "0.0.0.0", "--port", "8080", "main:app"] 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy Model 2 | 3 | Deploying an ML model may sound scary. We don't learn Tensorflow, model training, or even python as a CC participant.
4 | Through this repo, you can see the many ways you can deploy a model.
5 | 1. The `starting-point` branch covers all the basic FastAPI stuffs, how a Rest API code look like using python as its programming language 6 | 2. The `FastAPI-using-own-model` branch can be used as an example on model "deployment" using FastAPI 7 | 3. The `Express-TFJS` branch is an example of model "deployment" using nodejs. It requires extra work, you have to convert your model to json and its performance is not as good as native python 8 | 4. The `TF-Serving` and `TF-Serving-API` branches are used in tandem for TF Serving implementation. TF Serving is only used to host the model and the API can be used as an example of how to redirect request to a TF Serving backend. You can also ask your MD peer to make a direct request instead of making another API 9 | 10 | # FastAPI 11 | 12 | FastAPI is a modern web framework for building RESTful APIs in Python. It was first released in 2018 and has quickly gained popularity among developers due to its ease of use, speed and robustness. FastAPI is based on Pydantic and type hints to validate, serialize, and deserialize data. 13 | 14 | Documentation: https://fastapi.tiangolo.com 15 | 16 | Source Code: https://github.com/tiangolo/fastapi 17 | 18 | To run this file, do 19 | ```bash 20 | uvicorn main:app 21 | ``` 22 | or 23 | ```bash 24 | uvicorn main:app --reload 25 | ``` 26 | to automatically restart the kernel everytime there's a change saved inside `main.py` 27 | 28 | # Basic REST API using FastAPI 29 | 30 | ### 1. Index 31 | Endpoint: GET `/`
32 | Returns all items data as a dictionary.
33 | 34 | ### 2. Query item by id 35 | Endpoint: GET `/items/{item_id}`
36 | Parameters: 38 | Returns the item corresponding to the provided item_id parameter.
39 | If no item corresponds to the provided item_id, raises a 404 Not Found error.
40 | 41 | ### 3. Query item by parameters
42 | Endpoint: GET `/items/`
43 | Query parameters: 48 | Returns the items that match the provided query parameters.
49 | If no item matches the query parameters, returns an empty selection.
50 | If no query parameters are provided, returns all items.
51 | 52 | ### 4. Add new item
53 | Endpoint: POST `/`
54 | Parameters: 56 | Adds the provided item to the items data.
57 | If the item ID already exists in the data, raises a 400 Bad Request error.
58 | 59 | ### 5. Update item
60 | Endpoint: PUT `/update/{item_id}`
61 | Path parameter: 63 | Query parameters (all optional): 67 | Updates the attributes of the item with the provided item_id.
68 | If the item with the provided item_id does not exist in the data, raises a 404 Not Found error.
69 | If no update parameters are provided, raises a 400 Bad Request error.
70 | 71 | ### 6. Delete item
72 | Endpoint: DELETE `/delete/{item_id}` 73 | Parameters: 75 | Deletes the item with the provided item_id from the data.
76 | If the item with the provided item_id does not exist in the data, raises a 404 Not Found error. 77 | -------------------------------------------------------------------------------- /__pycache__/main.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuck1z/deploy-model/8a65b0300f5706a5f775210edbe8700d8717a1c1/__pycache__/main.cpython-310.pyc -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | # Build the container image 3 | - name: 'gcr.io/cloud-builders/docker' 4 | args: ['build', '-t', 'gcr.io/$PROJECT_ID/somethingidkkkkkkkkkkkkk:$COMMIT_SHA', '.'] 5 | # Push the container image to Container Registry 6 | - name: 'gcr.io/cloud-builders/docker' 7 | args: ['push', 'gcr.io/$PROJECT_ID/somethingidkkkkkkkkkkkkk:$COMMIT_SHA'] 8 | # Deploy container image to Cloud Run 9 | - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' 10 | entrypoint: gcloud 11 | args: 12 | - 'run' 13 | - 'deploy' 14 | - 'somethingidkkkkkkkkkkkkk' 15 | - '--image' 16 | - 'gcr.io/$PROJECT_ID/somethingidkkkkkkkkkkkkk:$COMMIT_SHA' 17 | # delete the line below if you want auth (using gcloud iap) 18 | - '--allow-unauthenticated' 19 | - '--region' 20 | - 'asia-southeast2' 21 | images: 22 | - 'gcr.io/$PROJECT_ID/somethingidkkkkkkkkkkkkk:$COMMIT_SHA' 23 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pydantic import BaseModel 3 | from fastapi import FastAPI, HTTPException, Path, Query 4 | 5 | app = FastAPI() # create a new FastAPI app instance 6 | 7 | # Define an Enum class to represent the possible categories for an item 8 | 9 | 10 | class Category(Enum): 11 | FRUITS = "fruits" 12 | VEGGIES = "vegetables" 13 | 14 | # Define a Pydantic model for an item 15 | class Item(BaseModel): 16 | name: str 17 | price: float 18 | count: int 19 | id: int 20 | category: Category 21 | 22 | 23 | # Define some initial items for the app 24 | items = { 25 | 0: Item(name="Apple", price=9.99, count=20, id=0, category=Category.FRUITS), 26 | 1: Item(name="Kale", price=5.99, count=20, id=1, category=Category.VEGGIES), 27 | 2: Item(name="Orange", price=1.99, count=100, id=2, category=Category.FRUITS), 28 | } 29 | 30 | # Define a simple endpoint to return all items as a dictionary 31 | 32 | 33 | @app.get("/") 34 | def index() -> dict[str, dict[int, Item]]: 35 | return {"items": items} 36 | 37 | # Define an endpoint to query an item by its ID 38 | 39 | 40 | @app.get("/items/{item_id}") 41 | def query_item_by_id(item_id: int) -> Item: 42 | # Raise an exception if the item is not found 43 | if item_id not in items: 44 | raise HTTPException( 45 | status_code=404, detail=f"Item with {item_id=} does not exist.") 46 | # Return the item if found 47 | return items[item_id] 48 | 49 | 50 | # Define a type alias for the selection dictionary used in the query_item_by_parameters endpoint 51 | Selection = dict[str, str | int | float | Category | None] 52 | 53 | # Define an endpoint to query items by name, price, count, and/or category 54 | 55 | 56 | @app.get("/items/") 57 | def query_item_by_parameters( 58 | name: str | None = None, 59 | price: float | None = None, 60 | count: int | None = None, 61 | category: Category | None = None, 62 | ) -> dict[str, Selection | list[Item]]: 63 | # Define a helper function to check if an item matches the query arguments 64 | def check_item(item: Item): 65 | """Check if the item matches the query arguments from the outer scope.""" 66 | return all( 67 | ( 68 | name is None or item.name == name, 69 | price is None or item.price == price, 70 | count is None or item.count != count, 71 | category is None or item.category is category, 72 | ) 73 | ) 74 | # Filter the items based on the query arguments 75 | selection = [item for item in items.values() if check_item(item)] 76 | # Return the query parameters and the selected items 77 | return { 78 | "query": {"name": name, "price": price, "count": count, "category": category}, 79 | "selection": selection, 80 | } 81 | 82 | # Define an endpoint to add a new item 83 | 84 | 85 | @app.post("/") 86 | def add_item(item: Item) -> dict[str, Item]: 87 | # Raise an exception if the item ID already exists 88 | if item.id in items: 89 | raise HTTPException( 90 | status_code=400, detail=f"Item with {item.id=} already exists.") 91 | # Add the item to the dictionary and return it 92 | items[item.id] = item 93 | return {"added": item} 94 | 95 | 96 | # This API endpoint updates an existing item with the given item_id 97 | @app.put("/update/{item_id}") 98 | def update( 99 | # Path parameter 'item_id' must be an integer greater than or equal to 0 100 | item_id: int = Path(ge=0), 101 | # Query parameter 'name' is optional, and if provided, must be a string with length between 1 and 8 102 | name: str | None = Query(defaut=None, min_length=1, max_length=8), 103 | # Query parameter 'price' is optional, and if provided, must be a float greater than 0.0 104 | price: float | None = Query(default=None, gt=0.0), 105 | # Query parameter 'count' is optional, and if provided, must be an integer greater than or equal to 0 106 | count: int | None = Query(default=None, ge=0), 107 | ): 108 | # If item_id does not exist in the 'items' dictionary, raise a 404 Not Found error 109 | if item_id not in items: 110 | HTTPException(status_code=404, 111 | detail=f"Item with {item_id=} does not exist.") 112 | 113 | # If none of the update parameters are provided, raise a 400 Bad Request error 114 | if all(info is None for info in (name, price, count)): 115 | raise HTTPException( 116 | status_code=400, detail="No parameters provided for update." 117 | ) 118 | 119 | # Get the existing item from the 'items' dictionary using the item_id 120 | item = items[item_id] 121 | 122 | # Update the item attributes if the corresponding parameter is provided 123 | if name is not None: 124 | item.name = name 125 | if price is not None: 126 | item.price = price 127 | if count is not None: 128 | item.count = count 129 | 130 | # Return a dictionary with the updated item 131 | return {"updated": item} 132 | 133 | 134 | # This API endpoint deletes an existing item with the given item_id 135 | @app.delete("/delete/{item_id}") 136 | def delete_item(item_id: int) -> dict[str, Item]: 137 | 138 | # If item_id does not exist in the 'items' dictionary, raise a 404 Not Found error 139 | if item_id not in items: 140 | raise HTTPException( 141 | status_code=404, detail=f"Item with {item_id=} does not exist." 142 | ) 143 | 144 | # Remove the item from the 'items' dictionary using the item_id 145 | item = items.pop(item_id) 146 | 147 | # Return a dictionary with the deleted item 148 | return {"deleted": item} 149 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.95.0 2 | pydantic==1.10.7 3 | uvicorn==0.21.1 4 | --------------------------------------------------------------------------------