├── requirements.txt ├── LICENSE ├── main_template.py ├── book_review.py ├── README.md └── app.py /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flasgger 3 | flask_restful 4 | pyairtable 5 | gunicorn -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Keith Galli 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 | -------------------------------------------------------------------------------- /main_template.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request 2 | from flask_restful import Api, Resource 3 | from flasgger import Swagger 4 | 5 | app = Flask(__name__) 6 | api = Api(app) 7 | swagger = Swagger(app) 8 | 9 | class UppercaseText(Resource): 10 | 11 | def get(self): 12 | """ 13 | This method responds to the GET request for this endpoint and returns the data in uppercase. 14 | --- 15 | tags: 16 | - Text Processing 17 | parameters: 18 | - name: text 19 | in: query 20 | type: string 21 | required: true 22 | description: The text to be converted to uppercase 23 | responses: 24 | 200: 25 | description: A successful GET request 26 | content: 27 | application/json: 28 | schema: 29 | type: object 30 | properties: 31 | text: 32 | type: string 33 | description: The text in uppercase 34 | """ 35 | text = request.args.get('text') 36 | 37 | return jsonify({"text": text.upper()}) 38 | 39 | api.add_resource(UppercaseText, "/uppercase") 40 | 41 | if __name__ == "__main__": 42 | app.run(debug=True) -------------------------------------------------------------------------------- /book_review.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pyairtable import Api 3 | 4 | API_TOKEN = os.environ.get('AIRTABLE_TOKEN') 5 | 6 | BASE_ID = 'appi1uzlLKn1TEKSw' 7 | TABLE_ID = 'tblvMMAVHo901m2Ra' 8 | 9 | api = Api(API_TOKEN) 10 | 11 | table = api.table(BASE_ID, TABLE_ID) 12 | 13 | def get_all_records(count=None, sort=None): 14 | sort_param = [] 15 | if sort and sort.upper()=='DESC': 16 | sort_param = ['-Rating'] 17 | elif sort and sort.upper()=='ASC': 18 | sort_param = ['Rating'] 19 | 20 | return table.all(max_records=count, sort=sort_param) 21 | 22 | def get_record_id(name): 23 | return table.first(formula=f"Book='{name}'")['id'] 24 | 25 | def update_record(record_id, data): 26 | table.update(record_id, data) 27 | 28 | return True 29 | 30 | def add_record(data): 31 | # require data contains a "Book" key and a "Rating" key (data is a dict) 32 | if 'Book' not in data or 'Rating' not in data: 33 | return False 34 | 35 | table.create(data) 36 | return True 37 | 38 | if __name__ == '__main__': 39 | ## Show getting certain records 40 | print("Show getting certain records") 41 | print(table.all(formula="Rating < 5", sort=['-Rating'])) 42 | 43 | ## Show getting a single record 44 | print("Show getting a single record") 45 | 46 | # Replace a record 47 | print("Replace a record") 48 | name = "Test Message" 49 | record_id = table.first(formula=f"Book='{name}'")['id'] 50 | table.update(record_id, {"Rating": 5}) 51 | 52 | ## Show all records 53 | print("All records!") 54 | print(table.all()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building and deploying a Python API 2 | 3 | ## About 4 | 5 | In this session we will build some API endpoints, documentation for those endpoints, and the ability to test on a live site deploying on render.com. This is a simple example to get started, but hopefully it will provide the foundation for you all to build more complex APIs in the future. If you have any questions, I recommend leaving them in the YouTube comments. 6 | 7 | ## Setup 8 | 1. **Fork the repository**: 9 | Click on the 'Fork' button at the top right corner of the repository's GitHub page. This will create a copy of the repository in your own GitHub account. 10 | 11 | 2. **Clone your forked repository**: 12 | Make sure to replace YOUR_GITHUB_USERNAME with your username in this command 13 | ```bash 14 | git clone https://github.com/YOUR_GITHUB_USERNAME/python-api-example.git 15 | 16 | 3. **Navigate to directory** 17 | ```bash 18 | cd python-api-example 19 | ``` 20 | 4. **Install Python libraries** 21 | ```bash 22 | pip install -r requirements.txt 23 | 24 | 5. **Run Flask Endpoint** 25 | ```bash 26 | python3 app.py 27 | ``` 28 | 29 | 6. **Navigate to server**
30 | Open up URL app is running on (should say when you run above command -- http://127.0.0.1:5000 for example). Navigate to http://127.0.0.1:5000/apidocs to see Swagger endpoint specs. 31 | 32 | 7. **Making changes and pushing to your fork**
33 | After making your changes, you can push them to your forked GitHub repository. 34 | ```bash 35 | git add -u 36 | git commit -m "Your commit message" 37 | git push origin lunch-and-learn 38 | ``` 39 | 40 | 41 | ## Render.com 42 | 43 | We will be using [render.com](https://render.com) to deploy our app. Create an account there and connect your Github using the [following instructions](https://render.com/docs/github) 44 | 45 | ## Airtable 46 | 47 | As a very simple and easy to set-up DB, we will be using airtable. I'll show in the live stream how to create an API token to use in Python API requests. 48 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request 2 | from flask_restful import Api, Resource 3 | from flasgger import Swagger 4 | 5 | import book_review 6 | 7 | app = Flask(__name__) 8 | api = Api(app) 9 | swagger = Swagger(app) 10 | 11 | class UppercaseText(Resource): 12 | def get(self): 13 | """ 14 | This method responds to the GET request for this endpoint and returns the data in uppercase. 15 | --- 16 | tags: 17 | - Text Processing 18 | parameters: 19 | - name: text 20 | in: query 21 | type: string 22 | required: true 23 | description: The text to be converted to uppercase 24 | responses: 25 | 200: 26 | description: A successful GET request 27 | content: 28 | application/json: 29 | schema: 30 | type: object 31 | properties: 32 | text: 33 | type: string 34 | description: The text in uppercase 35 | """ 36 | text = request.args.get('text') 37 | 38 | return jsonify({"text": text.upper()}) 39 | 40 | class Records(Resource): 41 | def get(self): 42 | """ 43 | This method responds to the GET request for returning a number of books. 44 | --- 45 | tags: 46 | - Records 47 | parameters: 48 | - name: count 49 | in: query 50 | type: integer 51 | required: false 52 | description: The number of books to return 53 | - name: sort 54 | in: query 55 | type: string 56 | enum: ['ASC', 'DESC'] 57 | required: false 58 | description: Sort order for the books 59 | responses: 60 | 200: 61 | description: A successful GET request 62 | schema: 63 | type: object 64 | properties: 65 | books: 66 | type: array 67 | items: 68 | type: object 69 | properties: 70 | title: 71 | type: string 72 | description: The title of the book 73 | author: 74 | type: string 75 | description: The author of the book 76 | """ 77 | 78 | count = request.args.get('count') # Default to returning 10 books if count is not provided 79 | sort = request.args.get('sort') 80 | 81 | # Get all the books 82 | books = book_review.get_all_records(count=count, sort=sort) 83 | 84 | return {"books": books}, 200 85 | 86 | class AddRecord(Resource): 87 | def post(self): 88 | """ 89 | This method responds to the POST request for adding a new record to the DB table. 90 | --- 91 | tags: 92 | - Records 93 | parameters: 94 | - in: body 95 | name: body 96 | required: true 97 | schema: 98 | id: BookReview 99 | required: 100 | - Book 101 | - Rating 102 | properties: 103 | Book: 104 | type: string 105 | description: the name of the book 106 | Rating: 107 | type: integer 108 | description: the rating of the book (1-10) 109 | responses: 110 | 200: 111 | description: A successful POST request 112 | 400: 113 | description: Bad request, missing 'Book' or 'Rating' in the request body 114 | """ 115 | 116 | data = request.json 117 | print(data) 118 | 119 | # Check if 'Book' and 'Rating' are present in the request body 120 | if 'Book' not in data or 'Rating' not in data: 121 | return {"message": "Bad request, missing 'Book' or 'Rating' in the request body"}, 400 122 | # Call the add_record function to add the record to the DB table 123 | success = book_review.add_record(data) 124 | 125 | if success: 126 | return {"message": "Record added successfully"}, 200 127 | else: 128 | return {"message": "Failed to add record"}, 500 129 | 130 | 131 | 132 | api.add_resource(AddRecord, "/add-record") 133 | api.add_resource(Records, "/records") 134 | api.add_resource(UppercaseText, "/uppercase") 135 | 136 | if __name__ == "__main__": 137 | app.run(debug=True) --------------------------------------------------------------------------------