├── .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:
37 | - item_id: integer representing the id of the item to be queried.
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:
44 | - name: (optional) string representing the name of the item to be queried.
45 |
- price: (optional) float representing the price of the item to be queried.
46 |
- count: (optional) integer representing the count of the item to be queried.
47 |
- category: (optional) string representing the category of the item to be queried.
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:
55 | - item: JSON data representing an item to be added.
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:
62 | - item_id: integer representing the id of the item to be updated.
63 | Query parameters (all optional):
64 | - name: string representing the new name of the item.
65 |
- price: float representing the new price of the item.
66 |
- count: integer representing the new count of the item.
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:
74 | - item_id: integer representing the id of the item to be deleted.
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 |
--------------------------------------------------------------------------------