├── 1_basic_app.py ├── 1_main.py ├── 2_creating_get_route_query.py ├── 2_main.py ├── 3_main.py ├── 3_more_routing.py ├── 4_main.py ├── 4_validation_with_type_hints.py ├── 5_main.py ├── 5_validation_with_Query_and_Path.py ├── 6_adding_openapi_documentation.py ├── LICENSE └── README.md /1_basic_app.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from fastapi import FastAPI 3 | from pydantic import BaseModel 4 | 5 | app = FastAPI() 6 | 7 | 8 | class Category(Enum): 9 | TOOLS = 'tools' 10 | CONSUMABLES = 'consumables' 11 | 12 | 13 | class Item(BaseModel): 14 | name: str 15 | price: float 16 | count: int 17 | id: int 18 | category: Category 19 | 20 | 21 | items = { 22 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 23 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 24 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 25 | } 26 | 27 | 28 | # FastAPI handles JSON serialization and deserialization for us. 29 | # We can simply use built-in python and Pydantic types, in this case dict[int, Item]. 30 | @app.get("/") 31 | def index() -> dict[str, dict[int, Item]]: 32 | return {"items": items} 33 | -------------------------------------------------------------------------------- /1_main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | print(requests.get("http://127.0.0.1:8000/").json()) 4 | -------------------------------------------------------------------------------- /2_creating_get_route_query.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | from fastapi import FastAPI, HTTPException 6 | 7 | app = FastAPI() 8 | 9 | 10 | class Category(Enum): 11 | TOOLS = "tools" 12 | CONSUMABLES = "consumables" 13 | 14 | 15 | class Item(BaseModel): 16 | name: str 17 | price: float 18 | count: int 19 | id: int 20 | category: Category 21 | 22 | 23 | items = { 24 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 25 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 26 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 27 | } 28 | 29 | 30 | @app.get("/") 31 | def index() -> dict[str, dict[int, Item]]: 32 | return {"items": items} 33 | 34 | 35 | # Path parameters can be specified with {} directly in the path (similar to f-string syntax) 36 | # These parameters will be forwarded to the decorated function as keyword arguments. 37 | @app.get("/items/{item_id}") 38 | def query_item_by_id(item_id: int) -> Item: 39 | if item_id not in items: 40 | raise HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 41 | 42 | return items[item_id] 43 | 44 | 45 | # Function parameters that are not path parameters can be specified as query parameters in the URL 46 | # Here we can query items like this /items?count=20 47 | Selection = dict[ 48 | str, str | int | float | Category | None 49 | ] # dictionary containing the user's query arguments 50 | 51 | 52 | @app.get("/items/") 53 | def query_item_by_parameters( 54 | name: str | None = None, 55 | price: float | None = None, 56 | count: int | None = None, 57 | category: Category | None = None, 58 | ) -> dict[str, Selection | list[Item]]: 59 | def check_item(item: Item): 60 | """Check if the item matches the query arguments from the outer scope.""" 61 | return all( 62 | ( 63 | name is None or item.name == name, 64 | price is None or item.price == price, 65 | count is None or item.count != count, 66 | category is None or item.category is category, 67 | ) 68 | ) 69 | 70 | selection = [item for item in items.values() if check_item(item)] 71 | return { 72 | "query": {"name": name, "price": price, "count": count, "category": category}, 73 | "selection": selection, 74 | } 75 | -------------------------------------------------------------------------------- /2_main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | print(requests.get("http://127.0.0.1:8000/items/1").json()) 4 | print(requests.get("http://127.0.0.1:8000/items?name=Nails").json()) 5 | -------------------------------------------------------------------------------- /3_main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | print("Adding an item:") 4 | print( 5 | requests.post( 6 | "http://127.0.0.1:8000/", 7 | json={"name": "Screwdriver", "price": 3.99, "count": 10, "id": 4, "category": "tools"}, 8 | ).json() 9 | ) 10 | print(requests.get("http://127.0.0.1:8000/").json()) 11 | print() 12 | 13 | print("Updating an item:") 14 | print(requests.put("http://127.0.0.1:8000/update/0?count=9001").json()) 15 | print(requests.get("http://127.0.0.1:8000/").json()) 16 | print() 17 | 18 | print("Deleting an item:") 19 | print(requests.delete("http://127.0.0.1:8000/delete/0").json()) 20 | print(requests.get("http://127.0.0.1:8000/").json()) 21 | -------------------------------------------------------------------------------- /3_more_routing.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | from fastapi import FastAPI, HTTPException 6 | 7 | app = FastAPI() 8 | 9 | 10 | class Category(Enum): 11 | TOOLS = "tools" 12 | CONSUMABLES = "consumables" 13 | 14 | 15 | class Item(BaseModel): 16 | name: str 17 | price: float 18 | count: int 19 | id: int 20 | category: Category 21 | 22 | 23 | items = { 24 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 25 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 26 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 27 | } 28 | 29 | 30 | @app.get("/") 31 | def index() -> dict[str, dict[int, Item]]: 32 | 33 | return {"items": items} 34 | 35 | 36 | @app.get("/items/{item_id}") 37 | def query_item_by_id(item_id: int) -> Item: 38 | 39 | if item_id not in items: 40 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 41 | 42 | return items[item_id] 43 | 44 | 45 | Selection = dict[ 46 | str, str | int | float | Category | None 47 | ] # dictionary containing the user's query arguments 48 | 49 | 50 | @app.get("/items/") 51 | def query_item_by_parameters( 52 | name: str | None = None, 53 | price: float | None = None, 54 | count: int | None = None, 55 | category: Category | None = None, 56 | ) -> dict[str, Selection | list[Item]]: 57 | def check_item(item: Item): 58 | """Check if the item matches the query arguments from the outer scope.""" 59 | return all( 60 | ( 61 | name is None or item.name == name, 62 | price is None or item.price == price, 63 | count is None or item.count != count, 64 | category is None or item.category is category, 65 | ) 66 | ) 67 | 68 | selection = [item for item in items.values() if check_item(item)] 69 | return { 70 | "query": {"name": name, "price": price, "count": count, "category": category}, 71 | "selection": selection, 72 | } 73 | 74 | 75 | @app.post("/") 76 | def add_item(item: Item) -> dict[str, Item]: 77 | 78 | if item.id in items: 79 | HTTPException(status_code=400, detail=f"Item with {item.id=} already exists.") 80 | 81 | items[item.id] = item 82 | return {"added": item} 83 | 84 | 85 | @app.put("/update/{item_id}") 86 | def update( 87 | item_id: int, 88 | name: str | None = None, 89 | price: float | None = None, 90 | count: int | None = None, 91 | ) -> dict[str, Item]: 92 | 93 | if item_id not in items: 94 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 95 | if all(info is None for info in (name, price, count)): 96 | raise HTTPException( 97 | status_code=400, detail="No parameters provided for update." 98 | ) 99 | 100 | item = items[item_id] 101 | if name is not None: 102 | item.name = name 103 | if price is not None: 104 | item.price = price 105 | if count is not None: 106 | item.count = count 107 | 108 | return {"updated": item} 109 | 110 | 111 | @app.delete("/delete/{item_id}") 112 | def delete_item(item_id: int) -> dict[str, Item]: 113 | 114 | if item_id not in items: 115 | raise HTTPException( 116 | status_code=404, detail=f"Item with {item_id=} does not exist." 117 | ) 118 | 119 | item = items.pop(item_id) 120 | return {"deleted": item} 121 | -------------------------------------------------------------------------------- /4_main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # These requests work: 4 | print(requests.get("http://127.0.0.1:8000/items?count=20").json()) 5 | print(requests.get("http://127.0.0.1:8000/items?category=tools").json()) 6 | 7 | 8 | # This request fails because 'ingredient' is not a valid category, as defined in the Category enum: 9 | print(requests.get("http://127.0.0.1:8000/items?category=ingredient").json()) 10 | 11 | 12 | # These request fail because count has to be an integer: 13 | 14 | # Here, validation occurs because of the specified type hints on the endpoint. 15 | print(requests.get("http://127.0.0.1:8000/items/?count=Hello").json()) 16 | 17 | # And here, because of Pydantic. 18 | print( 19 | requests.post( 20 | "http://127.0.0.1:8000/", 21 | json={"name": "Screwdriver", "price": 3.99, "count": 'Hello', "id": 4}, 22 | ).json() 23 | ) 24 | 25 | 26 | -------------------------------------------------------------------------------- /4_validation_with_type_hints.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | from fastapi import FastAPI, HTTPException 6 | 7 | app = FastAPI() 8 | 9 | 10 | class Category(Enum): 11 | TOOLS = "tools" 12 | CONSUMABLES = "consumables" 13 | 14 | 15 | # Inheriting from Pydantic's BaseModels gives us built-in validation. 16 | # Even if we were to use a regular @dataclass validation would still work (FastAPI converts it to Pydantic internally) 17 | class Item(BaseModel): 18 | name: str 19 | price: float 20 | count: int 21 | id: int 22 | category: Category 23 | 24 | 25 | items = { 26 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 27 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 28 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 29 | } 30 | 31 | 32 | @app.get("/") 33 | def index() -> dict[str, dict[int, Item]]: 34 | return {"items": items} 35 | 36 | 37 | @app.get("/items/{item_id}") 38 | def query_item_by_id(item_id: int) -> Item: 39 | if item_id not in items: 40 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 41 | 42 | return items[item_id] 43 | 44 | 45 | # With the type hints in place, FastAPI returns an error message when we supply arguments of an incorrect type. 46 | # Without them, we no longer have this validation. 47 | Selection = dict[ 48 | str, str | int | float | Category | None 49 | ] # dictionary containing the user's query arguments 50 | 51 | 52 | @app.get("/items/") 53 | def query_item_by_parameters( 54 | name: str | None = None, 55 | price: float | None = None, 56 | count: int | None = None, 57 | category: Category | None = None, 58 | ) -> dict[str, Selection | list[Item]]: 59 | def check_item(item: Item): 60 | """Check if the item matches the query arguments from the outer scope.""" 61 | return all( 62 | ( 63 | name is None or item.name == name, 64 | price is None or item.price == price, 65 | count is None or item.count != count, 66 | category is None or item.category is category, 67 | ) 68 | ) 69 | 70 | selection = [item for item in items.values() if check_item(item)] 71 | return { 72 | "query": {"name": name, "price": price, "count": count, "category": category}, 73 | "selection": selection, 74 | } 75 | 76 | 77 | @app.post("/") 78 | def add_item(item: Item) -> dict[str, Item]: 79 | if item.id in items: 80 | HTTPException(status_code=400, detail=f"Item with {item.id=} already exists.") 81 | 82 | items[item.id] = item 83 | return {"added": item} 84 | 85 | 86 | @app.put("/update/{item_id}") 87 | def update( 88 | item_id: int, 89 | name: str | None = None, 90 | price: float | None = None, 91 | count: int | None = None, 92 | ): 93 | if item_id not in items: 94 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 95 | if all(info is None for info in (name, price, count)): 96 | raise HTTPException( 97 | status_code=400, detail="No parameters provided for update." 98 | ) 99 | 100 | item = items[item_id] 101 | if name is not None: 102 | item.name = name 103 | if price is not None: 104 | item.price = price 105 | if count is not None: 106 | item.count = count 107 | 108 | return {"updated": item} 109 | 110 | 111 | @app.delete("/delete/{item_id}") 112 | def delete_item(item_id: int) -> dict[str, Item]: 113 | if item_id not in items: 114 | raise HTTPException( 115 | status_code=404, detail=f"Item with {item_id=} does not exist." 116 | ) 117 | 118 | item = items.pop(item_id) 119 | return {"deleted": item} 120 | -------------------------------------------------------------------------------- /5_main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # These requests will result in an error, since price and count are negative: 4 | print(requests.put("http://127.0.0.1:8000/update/0?count=-1").json()) 5 | print(requests.put("http://127.0.0.1:8000/update/0?price=-1").json()) 6 | 7 | # Similarly, an item_id must not be negative: 8 | print(requests.put("http://127.0.0.1:8000/update/-1").json()) 9 | 10 | # And name cannot exceed 8 characters: 11 | print(requests.put("http://127.0.0.1:8000/update/0?name=SuperDuperHammer").json()) 12 | -------------------------------------------------------------------------------- /5_validation_with_Query_and_Path.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | from fastapi import FastAPI, HTTPException, Path, Query 6 | 7 | app = FastAPI() 8 | 9 | 10 | class Category(Enum): 11 | TOOLS = "tools" 12 | CONSUMABLES = "consumables" 13 | 14 | 15 | class Item(BaseModel): 16 | name: str 17 | price: float 18 | count: int 19 | id: int 20 | category: Category 21 | 22 | 23 | items = { 24 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 25 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 26 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 27 | } 28 | 29 | 30 | @app.get("/") 31 | def index() -> dict[str, dict[int, Item]]: 32 | return {"items": items} 33 | 34 | 35 | @app.get("/items/{item_id}") 36 | def query_item_by_id(item_id: int) -> Item: 37 | if item_id not in items: 38 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 39 | 40 | return items[item_id] 41 | 42 | 43 | Selection = dict[ 44 | str, str | int | float | Category | None 45 | ] # dictionary containing the user's query arguments 46 | 47 | 48 | @app.get("/items/") 49 | def query_item_by_parameters( 50 | name: str | None = None, 51 | price: float | None = None, 52 | count: int | None = None, 53 | category: Category | None = None, 54 | ) -> dict[str, Selection | list[Item]]: 55 | def check_item(item: Item): 56 | """Check if the item matches the query arguments from the outer scope.""" 57 | return all( 58 | ( 59 | name is None or item.name == name, 60 | price is None or item.price == price, 61 | count is None or item.count != count, 62 | category is None or item.category is category, 63 | ) 64 | ) 65 | 66 | selection = [item for item in items.values() if check_item(item)] 67 | return { 68 | "query": {"name": name, "price": price, "count": count, "category": category}, 69 | "selection": selection, 70 | } 71 | 72 | 73 | @app.post("/") 74 | def add_item(item: Item) -> dict[str, Item]: 75 | if item.id in items: 76 | HTTPException(status_code=400, detail=f"Item with {item.id=} already exists.") 77 | 78 | items[item.id] = item 79 | return {"added": item} 80 | 81 | 82 | # We can place further restrictions on allowed arguments by using the Query and Path classes. 83 | # In this case we are setting a lower bound for valid values and a minimal and maximal length for the name. 84 | @app.put("/update/{item_id}") 85 | def update( 86 | item_id: int = Path(ge=0), 87 | name: str | None = Query(defaut=None, min_length=1, max_length=8), 88 | price: float | None = Query(default=None, gt=0.0), 89 | count: int | None = Query(default=None, ge=0), 90 | ): 91 | if item_id not in items: 92 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 93 | if all(info is None for info in (name, price, count)): 94 | raise HTTPException( 95 | status_code=400, detail="No parameters provided for update." 96 | ) 97 | 98 | item = items[item_id] 99 | if name is not None: 100 | item.name = name 101 | if price is not None: 102 | item.price = price 103 | if count is not None: 104 | item.count = count 105 | 106 | return {"updated": item} 107 | 108 | 109 | @app.delete("/delete/{item_id}") 110 | def delete_item(item_id: int) -> dict[str, Item]: 111 | 112 | if item_id not in items: 113 | raise HTTPException( 114 | status_code=404, detail=f"Item with {item_id=} does not exist." 115 | ) 116 | 117 | item = items.pop(item_id) 118 | return {"deleted": item} 119 | -------------------------------------------------------------------------------- /6_adding_openapi_documentation.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from fastapi import FastAPI, HTTPException, Path, Query 6 | 7 | # You can give your API a title and add additional metadata such as a description, version number, etc. 8 | # The description also supports markdown formatting. 9 | app = FastAPI( 10 | title="Arjan's Handyman Emporium", 11 | description="Arjan does not only code but also helps you fix things. **See what's in stock!**", 12 | version="0.1.0", 13 | ) 14 | 15 | # Docstrings of classes will be reflected in the API documentation in the 'Schemas' section 16 | class Category(Enum): 17 | """Category of an item""" 18 | 19 | TOOLS = "tools" 20 | CONSUMABLES = "consumables" 21 | 22 | 23 | # You can add metadata to attributes using the Field class. 24 | # This information will also be shown in the auto-generated documentation. 25 | class Item(BaseModel): 26 | """Representation of an item in the system.""" 27 | 28 | name: str = Field(description="Name of the item.") 29 | price: float = Field(description="Price of the item in Euro.") 30 | count: int = Field(description="Amount of instances of this item in stock.") 31 | id: int = Field(description="Unique integer that specifies this item.") 32 | category: Category = Field(description="Category this item belongs to.") 33 | 34 | 35 | items = { 36 | 0: Item(name="Hammer", price=9.99, count=20, id=0, category=Category.TOOLS), 37 | 1: Item(name="Pliers", price=5.99, count=20, id=1, category=Category.TOOLS), 38 | 2: Item(name="Nails", price=1.99, count=100, id=2, category=Category.CONSUMABLES), 39 | } 40 | 41 | 42 | @app.get("/") 43 | def index() -> dict[str, dict[int, Item]]: 44 | return {"items": items} 45 | 46 | 47 | @app.get("/items/{item_id}") 48 | def query_item_by_id(item_id: int) -> Item: 49 | if item_id not in items: 50 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 51 | 52 | return items[item_id] 53 | 54 | 55 | Selection = dict[ 56 | str, str | int | float | Category | None 57 | ] # dictionary containing the user's query arguments 58 | 59 | 60 | @app.get("/items/") 61 | def query_item_by_parameters( 62 | name: str | None = None, 63 | price: float | None = None, 64 | count: int | None = None, 65 | category: Category | None = None, 66 | ) -> dict[str, Selection | list[Item]]: 67 | def check_item(item: Item): 68 | """Check if the item matches the query arguments from the outer scope.""" 69 | return all( 70 | ( 71 | name is None or item.name == name, 72 | price is None or item.price == price, 73 | count is None or item.count != count, 74 | category is None or item.category is category, 75 | ) 76 | ) 77 | 78 | selection = [item for item in items.values() if check_item(item)] 79 | return { 80 | "query": {"name": name, "price": price, "count": count, "category": category}, 81 | "selection": selection, 82 | } 83 | 84 | 85 | @app.post("/") 86 | def add_item(item: Item) -> dict[str, Item]: 87 | if item.id in items: 88 | HTTPException(status_code=400, detail=f"Item with {item.id=} already exists.") 89 | 90 | items[item.id] = item 91 | return {"added": item} 92 | 93 | 94 | # The 'responses' keyword allows you to specify which responses a user can expect from this endpoint. 95 | @app.put( 96 | "/update/{item_id}", 97 | responses={ 98 | 404: {"description": "Item not found"}, 99 | 400: {"description": "No arguments specified"}, 100 | }, 101 | ) 102 | # The Query and Path classes also allow us to add documentation to query and path parameters. 103 | def update( 104 | item_id: int = Path( 105 | title="Item ID", description="Unique integer that specifies an item.", ge=0 106 | ), 107 | name: str 108 | | None = Query( 109 | title="Name", 110 | description="New name of the item.", 111 | default=None, 112 | min_length=1, 113 | max_length=8, 114 | ), 115 | price: float 116 | | None = Query( 117 | title="Price", 118 | description="New price of the item in Euro.", 119 | default=None, 120 | gt=0.0, 121 | ), 122 | count: int 123 | | None = Query( 124 | title="Count", 125 | description="New amount of instances of this item in stock.", 126 | default=None, 127 | ge=0, 128 | ), 129 | ): 130 | if item_id not in items: 131 | HTTPException(status_code=404, detail=f"Item with {item_id=} does not exist.") 132 | if all(info is None for info in (name, price, count)): 133 | raise HTTPException( 134 | status_code=400, detail="No parameters provided for update." 135 | ) 136 | 137 | item = items[item_id] 138 | if name is not None: 139 | item.name = name 140 | if price is not None: 141 | item.price = price 142 | if count is not None: 143 | item.count = count 144 | 145 | return {"updated": item} 146 | 147 | 148 | @app.delete("/delete/{item_id}") 149 | def delete_item(item_id: int) -> dict[str, Item]: 150 | 151 | if item_id not in items: 152 | raise HTTPException( 153 | status_code=404, detail=f"Item with {item_id=} does not exist." 154 | ) 155 | 156 | item = items.pop(item_id) 157 | return {"deleted": item} 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ArjanCodes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to Use FastAPI: A Detailed Python Tutorial 2 | 3 | In this tutorial, I'll show you the ropes to help you build Python APIs quickly and efficiently. By the end of it, you'll be a master of FastAPI and ready to start developing your own APIs! 4 | 5 | Video: https://youtu.be/SORiTsvnU28. 6 | --------------------------------------------------------------------------------