├── 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)
--------------------------------------------------------------------------------